-
-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Union[..., NoneType]
injection by get_type_hints
if a None
default value is used.
#482
base: main
Are you sure you want to change the base?
Conversation
src/typing_extensions.py
Outdated
# Values was not modified or original is already Optional | ||
if original_value == value or _could_be_inserted_optional(original_value): | ||
continue | ||
# NoneType was added to value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively hints[name] = original_value
which should be equivalent. I wonder which would be the safer alternative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using original_value
is incorrect as we may have modified the internals of the hint. For example, get_type_hints()
turns List["int"]
into List[int]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the feedback. Yes, it should have been piped trough _eval_type
as well. Can you take a look again?
@@ -1645,6 +1645,37 @@ def test_final_forward_ref(self): | |||
self.assertNotEqual(gth(Loop, globals())['attr'], Final[int]) | |||
self.assertNotEqual(gth(Loop, globals())['attr'], Final) | |||
|
|||
def test_annotation_and_optional_default(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think that more tests are necessary? Do you have ideas where something could go wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More tests have been added, covering the mentioned cases.
_NoneType = type(None) | ||
|
||
def _could_be_inserted_optional(t): | ||
"""detects Union[..., None] pattern""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_UnionGenericAlias
does not exist in the whole version range. Is this sufficient to assure that it's a Union? I am not 100% sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to get_origin
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is incorrect for this example:
>>> def f(x: Union[str, None, "str"] = None): pass
...
>>> typing_extensions.get_type_hints(f)
{'x': <class 'str'>}
I am not sure this approach is viable.
# when a None default value is used. | ||
# see https://github.com/python/typing_extensions/issues/310 | ||
original_hints = getattr(obj, '__annotations__', None) | ||
defaults = typing._get_defaults(obj) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should exit early if there are no defaults. The current code risks accessing .__annotations__
directly on classes, which could have unwanted effects.
src/typing_extensions.py
Outdated
# Values was not modified or original is already Optional | ||
if original_value == value or _could_be_inserted_optional(original_value): | ||
continue | ||
# NoneType was added to value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using original_value
is incorrect as we may have modified the internals of the hint. For example, get_type_hints()
turns List["int"]
into List[int]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are your thoughts on the "viability"? For that one example or in general?
I tried to improve the recreation of the typing.get_type_hints
path that is taken before the injection.
src/typing_extensions.py
Outdated
# Values was not modified or original is already Optional | ||
if original_value == value or _could_be_inserted_optional(original_value): | ||
continue | ||
# NoneType was added to value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the feedback. Yes, it should have been piped trough _eval_type
as well. Can you take a look again?
@@ -1645,6 +1645,37 @@ def test_final_forward_ref(self): | |||
self.assertNotEqual(gth(Loop, globals())['attr'], Final[int]) | |||
self.assertNotEqual(gth(Loop, globals())['attr'], Final) | |||
|
|||
def test_annotation_and_optional_default(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More tests have been added, covering the mentioned cases.
_NoneType = type(None) | ||
|
||
def _could_be_inserted_optional(t): | ||
"""detects Union[..., None] pattern""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to get_origin
.
I haven't thought too hard about examples that might break things, but I'm concerned about using |
Fixes #310
This PR reverts injection of
Union[..., NoneType]
bytyping.get_type_hints
in Python <3.11 if a function uses aNone
default value.