bpo-37953: Fix ForwardRef hash and equality checks (GH-15400)
Ideally if we stick a ForwardRef in a dictionary we would like to reliably be able to get it out again. https://bugs.python.org/issue37953
This commit is contained in:
parent
77cd0ceab2
commit
e082e7cbe4
|
@ -2361,6 +2361,65 @@ class ForwardRefTests(BaseTestCase):
|
|||
self.assertEqual(fr, typing.ForwardRef('int'))
|
||||
self.assertNotEqual(List['int'], List[int])
|
||||
|
||||
def test_forward_equality_gth(self):
|
||||
c1 = typing.ForwardRef('C')
|
||||
c1_gth = typing.ForwardRef('C')
|
||||
c2 = typing.ForwardRef('C')
|
||||
c2_gth = typing.ForwardRef('C')
|
||||
|
||||
class C:
|
||||
pass
|
||||
def foo(a: c1_gth, b: c2_gth):
|
||||
pass
|
||||
|
||||
self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': C, 'b': C})
|
||||
self.assertEqual(c1, c2)
|
||||
self.assertEqual(c1, c1_gth)
|
||||
self.assertEqual(c1_gth, c2_gth)
|
||||
self.assertEqual(List[c1], List[c1_gth])
|
||||
self.assertNotEqual(List[c1], List[C])
|
||||
self.assertNotEqual(List[c1_gth], List[C])
|
||||
self.assertEquals(Union[c1, c1_gth], Union[c1])
|
||||
self.assertEquals(Union[c1, c1_gth, int], Union[c1, int])
|
||||
|
||||
def test_forward_equality_hash(self):
|
||||
c1 = typing.ForwardRef('int')
|
||||
c1_gth = typing.ForwardRef('int')
|
||||
c2 = typing.ForwardRef('int')
|
||||
c2_gth = typing.ForwardRef('int')
|
||||
|
||||
def foo(a: c1_gth, b: c2_gth):
|
||||
pass
|
||||
get_type_hints(foo, globals(), locals())
|
||||
|
||||
self.assertEqual(hash(c1), hash(c2))
|
||||
self.assertEqual(hash(c1_gth), hash(c2_gth))
|
||||
self.assertEqual(hash(c1), hash(c1_gth))
|
||||
|
||||
def test_forward_equality_namespace(self):
|
||||
class A:
|
||||
pass
|
||||
def namespace1():
|
||||
a = typing.ForwardRef('A')
|
||||
def fun(x: a):
|
||||
pass
|
||||
get_type_hints(fun, globals(), locals())
|
||||
return a
|
||||
|
||||
def namespace2():
|
||||
a = typing.ForwardRef('A')
|
||||
|
||||
class A:
|
||||
pass
|
||||
def fun(x: a):
|
||||
pass
|
||||
|
||||
get_type_hints(fun, globals(), locals())
|
||||
return a
|
||||
|
||||
self.assertEqual(namespace1(), namespace1())
|
||||
self.assertNotEqual(namespace1(), namespace2())
|
||||
|
||||
def test_forward_repr(self):
|
||||
self.assertEqual(repr(List['int']), "typing.List[ForwardRef('int')]")
|
||||
|
||||
|
@ -2380,6 +2439,63 @@ class ForwardRefTests(BaseTestCase):
|
|||
self.assertEqual(get_type_hints(foo, globals(), locals()),
|
||||
{'a': Tuple[T]})
|
||||
|
||||
def test_forward_recursion_actually(self):
|
||||
def namespace1():
|
||||
a = typing.ForwardRef('A')
|
||||
A = a
|
||||
def fun(x: a): pass
|
||||
|
||||
ret = get_type_hints(fun, globals(), locals())
|
||||
return a
|
||||
|
||||
def namespace2():
|
||||
a = typing.ForwardRef('A')
|
||||
A = a
|
||||
def fun(x: a): pass
|
||||
|
||||
ret = get_type_hints(fun, globals(), locals())
|
||||
return a
|
||||
|
||||
def cmp(o1, o2):
|
||||
return o1 == o2
|
||||
|
||||
r1 = namespace1()
|
||||
r2 = namespace2()
|
||||
self.assertIsNot(r1, r2)
|
||||
self.assertRaises(RecursionError, cmp, r1, r2)
|
||||
|
||||
def test_union_forward_recursion(self):
|
||||
ValueList = List['Value']
|
||||
Value = Union[str, ValueList]
|
||||
|
||||
class C:
|
||||
foo: List[Value]
|
||||
class D:
|
||||
foo: Union[Value, ValueList]
|
||||
class E:
|
||||
foo: Union[List[Value], ValueList]
|
||||
class F:
|
||||
foo: Union[Value, List[Value], ValueList]
|
||||
|
||||
self.assertEqual(get_type_hints(C, globals(), locals()), get_type_hints(C, globals(), locals()))
|
||||
self.assertEqual(get_type_hints(C, globals(), locals()),
|
||||
{'foo': List[Union[str, List[Union[str, List['Value']]]]]})
|
||||
self.assertEqual(get_type_hints(D, globals(), locals()),
|
||||
{'foo': Union[str, List[Union[str, List['Value']]]]})
|
||||
self.assertEqual(get_type_hints(E, globals(), locals()),
|
||||
{'foo': Union[
|
||||
List[Union[str, List[Union[str, List['Value']]]]],
|
||||
List[Union[str, List['Value']]]
|
||||
]
|
||||
})
|
||||
self.assertEqual(get_type_hints(F, globals(), locals()),
|
||||
{'foo': Union[
|
||||
str,
|
||||
List[Union[str, List['Value']]],
|
||||
List[Union[str, List[Union[str, List['Value']]]]]
|
||||
]
|
||||
})
|
||||
|
||||
def test_callable_forward(self):
|
||||
|
||||
def foo(a: Callable[['T'], 'T']):
|
||||
|
|
|
@ -524,11 +524,13 @@ class ForwardRef(_Final, _root=True):
|
|||
def __eq__(self, other):
|
||||
if not isinstance(other, ForwardRef):
|
||||
return NotImplemented
|
||||
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__
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__forward_arg__, self.__forward_value__))
|
||||
return hash(self.__forward_arg__)
|
||||
|
||||
def __repr__(self):
|
||||
return f'ForwardRef({self.__forward_arg__!r})'
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
In :mod:`typing`, improved the ``__hash__`` and ``__eq__`` methods for
|
||||
:class:`ForwardReferences`.
|
Loading…
Reference in New Issue