bpo-41341: Recursive evaluation of ForwardRef in get_type_hints (#21553)
The issue raised by recursive evaluation is infinite recursion with recursive types. In that case, only the first recursive ForwardRef is evaluated.
This commit is contained in:
parent
bf2f76ec09
commit
653f420b53
|
@ -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')
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Recursive evaluation of `typing.ForwardRef` in `get_type_hints`.
|
Loading…
Reference in New Issue