Skip to content

bpo-46333: Honor module parameter in ForwardRef #30536

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

Merged
merged 13 commits into from
Feb 17, 2022

Conversation

aha79
Copy link
Contributor

@aha79 aha79 commented Jan 11, 2022

The module parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have same name. This affects eq, repr and hash and methods,
which were previously not honoring the module parameter.

https://bugs.python.org/issue46333

The `module` parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have same name. This affects __eq__, __repr__ and __hash__ and methods.
@the-knights-who-say-ni

This comment was marked as resolved.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome to CPython! You can add a NEWS entry to your PR using https://blurb-it.herokuapp.com/ :)

blurb-it bot and others added 3 commits January 11, 2022 15:54
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@AlexWaygood
Copy link
Member

Since this is your first contribution, you should also feel free to add your name to https://github.com/python/cpython/blob/main/Misc/ACKS (this is entirely optional, only if you'd like to).

@gvanrossum gvanrossum removed their request for review January 11, 2022 18:19
@gvanrossum
Copy link
Member

@Fidget-Spinner I hope you can review and merge this PR?

@Fidget-Spinner
Copy link
Member

@Fidget-Spinner I hope you can review and merge this PR?

@gvanrossum Sure. Leave it to me.

Copy link
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! We're almost ready to merge, I just have some minor concerns.

@@ -2611,6 +2611,10 @@ def test_forward_equality(self):
fr = typing.ForwardRef('int')
self.assertEqual(fr, typing.ForwardRef('int'))
self.assertNotEqual(List['int'], List[int])
self.assertNotEqual(fr, typing.ForwardRef('int', module=__name__))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I wonder if this test is run directly, will __name__ be `main``? Wouldn't that make the test pass incorrectly? (Throwing this question out there because I'm not too sure myself). Maybe we should be safe and give it a distinct name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test is to ensure that ForwardRef with module set is different from ForwardRef with module not set (That was previsously not the case). The actual value of module (or __name__) is not relevant. Under the hood the comparison is None vs str

Copy link
Member

@Fidget-Spinner Fidget-Spinner Jan 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, thanks for pointing that out. I mistakenly assumed it would grab a default value from the current scope to populate module in things like ClassVar['int'] due to the _type_check/_type_convert function (I'm now shocked that it doesn't and only does that for TypedDict).

Copy link
Contributor Author

@aha79 aha79 Jan 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, unfortunately the current module is not captured automatically.
The current scope/module is typicallly obtained by get_type_hints because a class knows its module (and so all ForwardRefs in member annotations can be correctly resolved. But each forward Ref does originally not know its module).
However for type aliases this does not work, and there one needs the explicit module.

For example

Json = Union[ List['Json'], Dict[str,'Json'], int, float, bool, None ]

cannot be exported (and expect to work) unless one sets the module for each forward ref.

Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
@AlexWaygood
Copy link
Member

@Fidget-Spinner, do you see this one as more of a bugfix that should be backported, or more of an enhancement/behaviour change? (This is just for my clarification, @aha79 — I'm fairly new to the triage team 🙂)

@Fidget-Spinner
Copy link
Member

@Fidget-Spinner, do you see this one as more of a bugfix that should be backported, or more of an enhancement/behaviour change? (This is just for my clarification, @aha79 — I'm fairly new to the triage team 🙂)

@AlexWaygood this is a grey area.

On one hand, generally, anything dealing with __eq__ and __hash__ in typing.py == bug. typing.py relies heavily on an LRU cache when subscripting things (see _tp_cache), so in the past, any hash/equality comparisons would mean incorrect values when using almost anything in typing.py, as Andreas rightly pointed out.

However, this specific module parameter is used only for differentiating different-module TypedDicts. TypedDict doesn't use the type cache, and different module TypedDict will automatically have different hash() values. So I don't know of anyone who's affected unless you use ForwardRef directly. But that's explicitly discouraged as an internal detail as I mentioned on the bug tracker.

I'm inclined to accept the PR without the __repr__ change, and I will likely backport it like that since it fixes something (albeit in a use case not really supported by us).

@AlexWaygood
Copy link
Member

@AlexWaygood this is a grey area.

Thanks, that's really clarifying! It might be worth noting that @sobolevn proposes in #29158 (not yet merged) to use the module parameter of typing.ForwardRef to solve a bug relating to dataclasses and get_type_hints.

@AlexWaygood
Copy link
Member

Btw @aha79 — could I ask if you've signed the CLA? For legal reasons, it may be difficult for CPython to merge a PR from you if you haven't 🙂

@aha79
Copy link
Contributor Author

aha79 commented Jan 14, 2022

Btw @aha79 — could I ask if you've signed the CLA? For legal reasons, it may be difficult for CPython to merge a PR from you if you haven't 🙂

Yes, sorry. I completely understand that. I clarify with my organization (This will be ok, but may take a few more days)

@AlexWaygood
Copy link
Member

Yes, sorry. I completely understand that. I clarify with my organization (This will be ok, but may take a few more days)

Of course! No rush :)

@aha79
Copy link
Contributor Author

aha79 commented Feb 9, 2022

Yes, sorry. I completely understand that. I clarify with my organization (This will be ok, but may take a few more days)

Of course! No rush :)

@AlexWaygood The CLA is now signed. Sorry that it took a while.
Should I undo the __ repr__ change as @Fidget-Spinner suggests? Not sure if this is a final descision.

@AlexWaygood
Copy link
Member

@AlexWaygood The CLA is now signed. Sorry that it took a while.

Thanks, no worries!

Should I undo the __ repr__ change as Fidget-Spinner suggests?

To me it makes sense to split off the __repr__ changes into a separate PR: the rest of the PR is clearly backportable, but the __repr__ changes seem like more of an enhancement.

Fidget-Spinner is currently on a break from open source — @JelleZijlstra, if you've got the time, do you have any thoughts on this PR?

@JelleZijlstra JelleZijlstra self-requested a review February 9, 2022 15:06
@aha79
Copy link
Contributor Author

aha79 commented Feb 11, 2022

Could you possibly add some tests for the changes to __hash__, as well as the changes to __eq__? (test_forward_equality_hash() looks like a good candidate for where tests might be added.)

There were already tests for __eq__. But I added some for __hash__ as well (even it would not strictly be required that __hash__ changes with module - inclusion of module in __hash__ just avoids hash collisions but does not change behavior)

@AlexWaygood
Copy link
Member

Sorry, I think I was unclear — my bad :)

We now have two test methods for testing ForwardRef.__hash__ — the one you've just added, and the existing one here:

def test_forward_equality_hash(self):
.

Could you move the new tests for ForwardRef.__hash__ that you've just added (which look good) into the existing test method?

@AlexWaygood
Copy link
Member

AlexWaygood commented Feb 11, 2022

There were already tests for __eq__.

I didn't mean to imply otherwise!

@aha79
Copy link
Contributor Author

aha79 commented Feb 11, 2022

Sorry, I think I was unclear — my bad :)

We now have two test methods for testing ForwardRef.__hash__ — the one you've just added, and the existing one here:

def test_forward_equality_hash(self):

.
Could you move the new tests for ForwardRef.__hash__ that you've just added (which look good) into the existing test method?

Sorry I misunderstood. I move/adapted the tests to the right place

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Feel free to open a new PR for the __repr__ changes (you can link to the same BPO issue).

@AlexWaygood AlexWaygood added needs backport to 3.9 only security fixes needs backport to 3.10 only security fixes labels Feb 11, 2022
@AlexWaygood
Copy link
Member

@gvanrossum could we possibly get the full test suite run on this PR, please? :)

@gvanrossum
Copy link
Member

@AlexWaygood / @JelleZijlstra Is this ready to merge?

@AlexWaygood
Copy link
Member

@AlexWaygood / @JelleZijlstra Is this ready to merge?

Looks that way to me :)

@JelleZijlstra
Copy link
Member

Yes I think it's good. It can wait though, hopefully I can merge it myself next week :)

@JelleZijlstra JelleZijlstra merged commit 6e7b813 into python:main Feb 17, 2022
@miss-islington
Copy link
Contributor

Thanks @aha79 for the PR, and @JelleZijlstra for merging it 🌮🎉.. I'm working now to backport this PR to: 3.9, 3.10.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 17, 2022
The `module` parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have the same name. This affects the `__eq__`, `__repr__` and `__hash__` methods.

Co-authored-by: Andreas Hangauer <andreas.hangauer@siemens.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
(cherry picked from commit 6e7b813)

Co-authored-by: aha79 <34090357+aha79@users.noreply.github.com>
@bedevere-bot bedevere-bot removed the needs backport to 3.10 only security fixes label Feb 17, 2022
@bedevere-bot
Copy link

GH-31379 is a backport of this pull request to the 3.10 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 17, 2022
The `module` parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have the same name. This affects the `__eq__`, `__repr__` and `__hash__` methods.

Co-authored-by: Andreas Hangauer <andreas.hangauer@siemens.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
(cherry picked from commit 6e7b813)

Co-authored-by: aha79 <34090357+aha79@users.noreply.github.com>
@bedevere-bot bedevere-bot removed the needs backport to 3.9 only security fixes label Feb 17, 2022
@bedevere-bot
Copy link

GH-31380 is a backport of this pull request to the 3.9 branch.

miss-islington added a commit that referenced this pull request Feb 17, 2022
…H-31379)

The `module` parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have the same name. This affects the `__eq__`, `__repr__` and `__hash__` methods.

Co-authored-by: Andreas Hangauer <andreas.hangauer@siemens.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
(cherry picked from commit 6e7b813)


Co-authored-by: aha79 <34090357+aha79@users.noreply.github.com>

Automerge-Triggered-By: GH:JelleZijlstra
miss-islington added a commit that referenced this pull request Feb 17, 2022
The `module` parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have the same name. This affects the `__eq__`, `__repr__` and `__hash__` methods.

Co-authored-by: Andreas Hangauer <andreas.hangauer@siemens.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
(cherry picked from commit 6e7b813)

Co-authored-by: aha79 <34090357+aha79@users.noreply.github.com>
hello-adam pushed a commit to hello-adam/cpython that referenced this pull request Jun 2, 2022
The `module` parameter carries semantic information about the forward ref.
Forward refs are different if they refer to different module even if they
have the same name. This affects the `__eq__`, `__repr__` and `__hash__` methods.

Co-authored-by: Andreas Hangauer <andreas.hangauer@siemens.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
(cherry picked from commit 6e7b813)

Co-authored-by: aha79 <34090357+aha79@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants