Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

frm = typing.ForwardRef('int', module=__name__)
self.assertEqual(frm, typing.ForwardRef('int', module=__name__))
self.assertNotEqual(frm, typing.ForwardRef('int', module='__other_name__'))

def test_forward_equality_gth(self):
c1 = typing.ForwardRef('C')
Expand Down Expand Up @@ -2673,6 +2677,8 @@ def fun(x: a):

def test_forward_repr(self):
self.assertEqual(repr(List['int']), "typing.List[ForwardRef('int')]")
self.assertEqual(repr(List[ForwardRef('int', module='mod')]),
"typing.List[ForwardRef('int', module='mod')]")

def test_union_forward(self):

Expand Down
11 changes: 8 additions & 3 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,10 +713,11 @@ def __eq__(self, other):
if self.__forward_evaluated__ and other.__forward_evaluated__:
return (self.__forward_arg__ == other.__forward_arg__ and
self.__forward_value__ == other.__forward_value__)
return self.__forward_arg__ == other.__forward_arg__
return (self.__forward_arg__ == other.__forward_arg__ and
self.__forward_module__ == other.__forward_module__)

def __hash__(self):
return hash(self.__forward_arg__)
return hash((self.__forward_arg__, self.__forward_module__))

def __or__(self, other):
return Union[self, other]
Expand All @@ -725,7 +726,11 @@ def __ror__(self, other):
return Union[other, self]

def __repr__(self):
return f'ForwardRef({self.__forward_arg__!r})'
if self.__forward_module__ is None:
module_repr = ''
else:
module_repr = f', module={self.__forward_module__!r}'
return f'ForwardRef({self.__forward_arg__!r}{module_repr})'

class _TypeVarLike:
"""Mixin for TypeVar-like types (TypeVar and ParamSpec)."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Honor module parameter in :class:`ForwardRef` as it carries semantic information about the forward ref. This affects :func:`__eq__`, :func:`__repr__` and :func:`__hash__` .