From fb1944f1fa2683b7d5f69b5a043d02524791ba3f Mon Sep 17 00:00:00 2001 From: hongweipeng <961365124@qq.com> Date: Wed, 28 Aug 2019 19:03:51 +0800 Subject: [PATCH 1/4] Fix infinite recursion with typing.get_type_hints. --- Lib/typing.py | 13 +++++++++++++ .../2019-08-28-19-00-50.bpo-37806.HdrlIB.rst | 1 + 2 files changed, 14 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst diff --git a/Lib/typing.py b/Lib/typing.py index 5f1a0ad3d63743..d09910c2c3e3de 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -521,6 +521,17 @@ def _evaluate(self, globalns, localns): self.__forward_evaluated__ = True return self.__forward_value__ + def _check_recursion(self, value, forward_args): + if isinstance(value, _GenericAlias): + for val in value.__args__: + if isinstance(val, _GenericAlias): + self._check_recursion(val, forward_args) + elif self is val and self.__forward_evaluated__: + forward_args.append(...) + else: + forward_args.append(val) + return tuple(forward_args) + def __eq__(self, other): if not isinstance(other, ForwardRef): return NotImplemented @@ -528,6 +539,8 @@ def __eq__(self, other): self.__forward_value__ == other.__forward_value__) def __hash__(self): + if self.__forward_evaluated__: + self.__forward_value__ = self._check_recursion(self.__forward_value__, []) return hash((self.__forward_arg__, self.__forward_value__)) def __repr__(self): diff --git a/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst b/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst new file mode 100644 index 00000000000000..1bf70cf14b0f5f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst @@ -0,0 +1 @@ +Fix infinite recursion from :func:`typing.get_type_hints`.Patch by hongweipeng. From 85a9692d44f5f3e700959a5ae61b2be5e79c7e77 Mon Sep 17 00:00:00 2001 From: HongWeipeng Date: Wed, 28 Aug 2019 21:31:59 +0800 Subject: [PATCH 2/4] code review and add test --- Lib/test/test_typing.py | 31 +++++++++++++++++++ Lib/typing.py | 2 +- .../2019-08-28-19-00-50.bpo-37806.HdrlIB.rst | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ba0800fae90e30..aff05cd38bb695 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2364,6 +2364,37 @@ def foo(a: Union['T']): self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': Union[T]}) + 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()), + {'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_tuple_forward(self): def foo(a: Tuple['T']): diff --git a/Lib/typing.py b/Lib/typing.py index d09910c2c3e3de..aee8886df389e9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -526,7 +526,7 @@ def _check_recursion(self, value, forward_args): for val in value.__args__: if isinstance(val, _GenericAlias): self._check_recursion(val, forward_args) - elif self is val and self.__forward_evaluated__: + elif self is val: forward_args.append(...) else: forward_args.append(val) diff --git a/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst b/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst index 1bf70cf14b0f5f..50853efd7ace40 100644 --- a/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst +++ b/Misc/NEWS.d/next/Library/2019-08-28-19-00-50.bpo-37806.HdrlIB.rst @@ -1 +1 @@ -Fix infinite recursion from :func:`typing.get_type_hints`.Patch by hongweipeng. +Fix infinite recursion from :func:`typing.get_type_hints`. Patch by hongweipeng. From 97728f096c57bd06cd2070dce3914e125d746491 Mon Sep 17 00:00:00 2001 From: HongWeipeng Date: Fri, 30 Aug 2019 01:44:41 +0800 Subject: [PATCH 3/4] keep the __forward_value__ type by using copy_with --- Lib/typing.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index aee8886df389e9..f2c2324637fcf4 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -522,14 +522,13 @@ def _evaluate(self, globalns, localns): return self.__forward_value__ def _check_recursion(self, value, forward_args): - if isinstance(value, _GenericAlias): - for val in value.__args__: - if isinstance(val, _GenericAlias): - self._check_recursion(val, forward_args) - elif self is val: - forward_args.append(...) - else: - forward_args.append(val) + for val in value.__args__: + if isinstance(val, _GenericAlias): + self._check_recursion(val, forward_args) + elif self is val: + forward_args.append(...) + else: + forward_args.append(val) return tuple(forward_args) def __eq__(self, other): @@ -539,8 +538,9 @@ def __eq__(self, other): self.__forward_value__ == other.__forward_value__) def __hash__(self): - if self.__forward_evaluated__: - self.__forward_value__ = self._check_recursion(self.__forward_value__, []) + forward_value = self.__forward_value__ + if self.__forward_evaluated__ and isinstance(forward_value, _GenericAlias): + self.__forward_value__ = forward_value.copy_with(self._check_recursion(forward_value, [])) return hash((self.__forward_arg__, self.__forward_value__)) def __repr__(self): From be79f5af40df68d9148853cea5a7965d239327a6 Mon Sep 17 00:00:00 2001 From: hongweipeng <961365124@qq.com> Date: Sat, 31 Aug 2019 16:11:02 +0800 Subject: [PATCH 4/4] add __filter_recursion_value__ to save _check_recursion return --- Lib/test/test_typing.py | 1 + Lib/typing.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index aff05cd38bb695..d8a909d3502f9c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2377,6 +2377,7 @@ class E: 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()), diff --git a/Lib/typing.py b/Lib/typing.py index f2c2324637fcf4..f24bae7fdf00ca 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -491,7 +491,7 @@ class ForwardRef(_Final, _root=True): __slots__ = ('__forward_arg__', '__forward_code__', '__forward_evaluated__', '__forward_value__', - '__forward_is_argument__') + '__forward_is_argument__', '__filter_recursion_value__') def __init__(self, arg, is_argument=True): if not isinstance(arg, str): @@ -505,6 +505,7 @@ def __init__(self, arg, is_argument=True): self.__forward_evaluated__ = False self.__forward_value__ = None self.__forward_is_argument__ = is_argument + self.__filter_recursion_value__ = None def _evaluate(self, globalns, localns): if not self.__forward_evaluated__ or localns is not globalns: @@ -539,9 +540,10 @@ def __eq__(self, other): def __hash__(self): forward_value = self.__forward_value__ - if self.__forward_evaluated__ and isinstance(forward_value, _GenericAlias): - self.__forward_value__ = forward_value.copy_with(self._check_recursion(forward_value, [])) - return hash((self.__forward_arg__, self.__forward_value__)) + if self.__forward_evaluated__ and not self.__filter_recursion_value__ \ + and isinstance(forward_value, _GenericAlias): + self.__filter_recursion_value__ = forward_value.copy_with(self._check_recursion(forward_value, [])) + return hash((self.__forward_arg__, self.__filter_recursion_value__ or forward_value)) def __repr__(self): return f'ForwardRef({self.__forward_arg__!r})'