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:
plokmijnuhby 2019-09-13 20:40:54 +01:00 committed by Ivan Levkivskyi
parent 77cd0ceab2
commit e082e7cbe4
3 changed files with 123 additions and 3 deletions

View File

@ -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']):

View File

@ -524,11 +524,13 @@ class ForwardRef(_Final, _root=True):
def __eq__(self, other):
if not isinstance(other, ForwardRef):
return NotImplemented
return (self.__forward_arg__ == other.__forward_arg__ and
self.__forward_value__ == other.__forward_value__)
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})'

View File

@ -0,0 +1,2 @@
In :mod:`typing`, improved the ``__hash__`` and ``__eq__`` methods for
:class:`ForwardReferences`.