diff options
author | Ryan Rowe <ryanf.rowe@gmail.com> | 2020-03-03 14:29:40 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-03 17:29:40 -0500 |
commit | 3eff46fc7d2e3c80c4dedba4177782f1fc8ad89b (patch) | |
tree | 0c5e7950104d13b3ebdaf72b1b876a82620df043 | |
parent | Fix misleading statement about mixed-type numeric comparisons (GH-18615) (GH-... (diff) | |
download | cpython-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.py | 116 | ||||
-rw-r--r-- | Lib/typing.py | 8 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2019-09-06-17-40-34.bpo-37953.db5FQq.rst | 2 |
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`.
|