]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-41341: Recursive evaluation of ForwardRef in get_type_hints (#21553)
authorwyfo <joperez@hotmail.fr>
Wed, 22 Jul 2020 19:47:28 +0000 (21:47 +0200)
committerGitHub <noreply@github.com>
Wed, 22 Jul 2020 19:47:28 +0000 (12:47 -0700)
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
Lib/typing.py
Misc/NEWS.d/next/Library/2020-07-20-19-13-17.bpo-41341.wqrj8C.rst [new file with mode: 0644]

index 398add05a12b992d4815a9960698dd338a9aae95..7f96aff71045511fd3b8a88fc41c8e400bab35e8 100644 (file)
@@ -2456,6 +2456,12 @@ class ForwardRefTests(BaseTestCase):
         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')
index fd657caafee870f59ad112f97ff6e80d084e2319..5da032bbee8f1e871885d921a7e51082f217128b 100644 (file)
@@ -244,14 +244,16 @@ def _tp_cache(func):
     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 @@ class ForwardRef(_Final, _root=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 @@ class ForwardRef(_Final, _root=True):
                 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__
 
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 (file)
index 0000000..c78b24d
--- /dev/null
@@ -0,0 +1 @@
+Recursive evaluation of `typing.ForwardRef` in `get_type_hints`.
\ No newline at end of file