From 2389c257ea8a480c9a011cac5cda7eb947ed3bca Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Mon, 20 Jul 2020 03:34:46 +0200 Subject: [PATCH 1/2] bpo-41341: Recursive evaluation of ForwardRef in get_type_hints The issue raised by recursive evaluation is infinite recursion with recursive types. In that case, only the first recursive ForwardRef is evaluated. --- Lib/test/test_typing.py | 6 ++++++ Lib/typing.py | 20 ++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 398add05a12b99..7f96aff7104551 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2456,6 +2456,12 @@ def foo(a: tuple[ForwardRef('T')]): self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': tuple[T]}) + def test_double_forward(self): + def foo(a: 'List[\'int\']'): + pass + self.assertEqual(get_type_hints(foo, globals(), locals()), + {'a': List[int]}) + def test_forward_recursion_actually(self): def namespace1(): a = typing.ForwardRef('A') diff --git a/Lib/typing.py b/Lib/typing.py index fd657caafee870..5da032bbee8f1e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -244,14 +244,16 @@ def inner(*args, **kwds): return inner -def _eval_type(t, globalns, localns): +def _eval_type(t, globalns, localns, recursive_guard=frozenset()): """Evaluate all forward reverences in the given type t. For use of globalns and localns see the docstring for get_type_hints(). + recursive_guard is used to prevent prevent infinite recursion + with recursive ForwardRef. """ if isinstance(t, ForwardRef): - return t._evaluate(globalns, localns) + return t._evaluate(globalns, localns, recursive_guard) if isinstance(t, (_GenericAlias, GenericAlias)): - ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__) + ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__) if ev_args == t.__args__: return t if isinstance(t, GenericAlias): @@ -477,7 +479,9 @@ def __init__(self, arg, is_argument=True): self.__forward_value__ = None self.__forward_is_argument__ = is_argument - def _evaluate(self, globalns, localns): + def _evaluate(self, globalns, localns, recursive_guard): + if self.__forward_arg__ in recursive_guard: + return self if not self.__forward_evaluated__ or localns is not globalns: if globalns is None and localns is None: globalns = localns = {} @@ -485,10 +489,14 @@ def _evaluate(self, globalns, localns): globalns = localns elif localns is None: localns = globalns - self.__forward_value__ = _type_check( + type_ =_type_check( eval(self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", - is_argument=self.__forward_is_argument__) + is_argument=self.__forward_is_argument__, + ) + self.__forward_value__ = _eval_type( + type_, globalns, localns, recursive_guard | {self.__forward_arg__} + ) self.__forward_evaluated__ = True return self.__forward_value__ From 0a29c8b4cb2687abe0e733ca0540c7497c4581ad Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2020 19:13:18 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2020-07-20-19-13-17.bpo-41341.wqrj8C.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2020-07-20-19-13-17.bpo-41341.wqrj8C.rst diff --git a/Misc/NEWS.d/next/Library/2020-07-20-19-13-17.bpo-41341.wqrj8C.rst b/Misc/NEWS.d/next/Library/2020-07-20-19-13-17.bpo-41341.wqrj8C.rst new file mode 100644 index 00000000000000..c78b24d2faae72 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-07-20-19-13-17.bpo-41341.wqrj8C.rst @@ -0,0 +1 @@ +Recursive evaluation of `typing.ForwardRef` in `get_type_hints`. \ No newline at end of file