Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 132
Fix Union[..., NoneType] injection by get_type_hints if a None default value is used.#482
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
src/typing_extensions.py Outdated
| # Values was not modified or original is already Optional | ||
| iforiginal_value==valueor_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?
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
JelleZijlstra left a comment
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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
src/typing_extensions.py Outdated
| # Values was not modified or original is already Optional | ||
| iforiginal_value==valueor_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].
Daraan left a comment
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 | ||
| iforiginal_value==valueor_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?
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
JelleZijlstra commented Oct 9, 2024
I haven't thought too hard about examples that might break things, but I'm concerned about using |
Daraan commented Oct 10, 2024 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Do you refer to
importtypingfromtypingimportLista=List["str"] print(typing._eval_type(a, None, None) istyping._eval_type(a, None, None)) # False
|
fixed wrong variable and format
src/test_typing_extensions.py Outdated
| withself.subTest("Check str and repr"): | ||
| ifskip_reason=="UnionType not preserved in 3.10": | ||
| self.skipTest(skip_reason) | ||
| self.assertEqual(str(type_hints) +repr(type_hints), str(expected) +repr(expected)) |
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.
Concatenating these seems a bit odd. In any case, type_hints is a dictionary, so the str() and repr() are the same.
DaraanOct 21, 2024 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
Thanks for pointing this out I was not 100% sure here and kept a lazy version. Changed to only repr.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
src/typing_extensions.py Outdated
| ): | ||
| continue | ||
| original_value=original_hints[name] | ||
| iforiginal_valueisNone: # should not happen |
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.
Why shouldn't this happen? You can put None in annotations.
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.
At this point a None value should already be converted to NoneType in original_hints. I think these two lines are redundant but to be safe I added the double check.
Added a comment to clarify this.
| ifsys.version_info< (3, 9) andget_origin(original_evaluated) isUnion: | ||
| # Union[str, None, "str"] is not reduced to Union[str, None] | ||
| original_evaluated=Union[original_evaluated.__args__] | ||
| # Compare if values differ | ||
| iforiginal_evaluated!=value: | ||
| hints[name] =original_evaluated |
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.
| ifsys.version_info< (3, 9) andget_origin(original_evaluated) isUnion: | |
| # Union[str, None, "str"] is not reduced to Union[str, None] | |
| original_evaluated=Union[original_evaluated.__args__] | |
| # Compare if values differ | |
| iforiginal_evaluated!=value: | |
| hints[name] =original_evaluated | |
| hints[name] =original_evaluated |
Would this also work?
DaraanOct 21, 2024 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
Yes it would. However there are minor differences involving identities and caching.
typing._eval_type uses _GenericAlias.copy_with or in some cases creates a new GenericAlias which do not make use of the caches.
If a ForwardRef is involved original_evaluated can be a complete new instance as _tp_cache is not queried for an existing equivalent one. Not changing hints["x"] will return an object that matches expected["x"] by identity in more cases.
That's a minor feature and I am fine with dropping it if you want; for now added a comment to clarify this.
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
+ updated comments
Uh oh!
There was an error while loading. Please reload this page.
src/typing_extensions.py Outdated
| continue | ||
| original_value=original_hints[name] | ||
| iforiginal_valueisNone: # should not happen | ||
| iforiginal_valueisNone: # should be NoneType already; check just in case |
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.
It shouldn't be NoneType already, since we get this directly from __annotations__.
DaraanOct 21, 2024 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
(Edited for more clarity)
Excuse me I have argued wrongly. You are right that None would be from __annotations__. In such a case value should be NoneType. However, in that case _could_be_inserted_optional(NoneType) will skip this iteration (as get_origin(NoneType) is not Union).
That's why code with a original_value that is None should never propagate until this line, but keeping these two lines to keep it safe; updated the comment.
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Daraan commented Jan 22, 2025
I am not sure why its still flagged as changes requested from me. I think this should be again waiting for review :) |
86cf372 into python:mainUh oh!
There was an error while loading. Please reload this page.
Fixes#310
This PR reverts injection of
Union[..., NoneType]bytyping.get_type_hintsin Python <3.11 if a function uses aNonedefault value.