aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Rowe <ryanf.rowe@gmail.com>2020-03-03 14:29:40 -0800
committerGitHub <noreply@github.com>2020-03-03 17:29:40 -0500
commit3eff46fc7d2e3c80c4dedba4177782f1fc8ad89b (patch)
tree0c5e7950104d13b3ebdaf72b1b876a82620df043
parentFix misleading statement about mixed-type numeric comparisons (GH-18615) (GH-... (diff)
downloadcpython-3eff46fc7d2e3c80c4dedba4177782f1fc8ad89b.tar.gz
cpython-3eff46fc7d2e3c80c4dedba4177782f1fc8ad89b.tar.bz2
cpython-3eff46fc7d2e3c80c4dedba4177782f1fc8ad89b.zip
bpo-37953: Fix ForwardRef hash and equality checks (GH-15400) (GH-18751)
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 (cherry picked from commit e082e7c) Co-authored-by: plokmijnuhby <39633434+plokmijnuhby@users.noreply.github.com>
-rw-r--r--Lib/test/test_typing.py116
-rw-r--r--Lib/typing.py8
-rw-r--r--Misc/NEWS.d/next/Library/2019-09-06-17-40-34.bpo-37953.db5FQq.rst2
3 files changed, 123 insertions, 3 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 087dc2a82f0..6e7e5a210a6 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -1505,6 +1505,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')]")
@@ -1524,6 +1583,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']):
diff --git a/Lib/typing.py b/Lib/typing.py
index 3b2e3720fff..1749c2fd35c 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -473,11 +473,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})'
diff --git a/Misc/NEWS.d/next/Library/2019-09-06-17-40-34.bpo-37953.db5FQq.rst b/Misc/NEWS.d/next/Library/2019-09-06-17-40-34.bpo-37953.db5FQq.rst
new file mode 100644
index 00000000000..4eff4f7479a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-09-06-17-40-34.bpo-37953.db5FQq.rst
@@ -0,0 +1,2 @@
+In :mod:`typing`, improved the ``__hash__`` and ``__eq__`` methods for
+:class:`ForwardReferences`.