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()),
|
self.assertEqual(get_type_hints(foo, globals(), locals()),
|
||||||
{'a': tuple[T]})
|
{'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 test_forward_recursion_actually(self):
|
||||||
def namespace1():
|
def namespace1():
|
||||||
a = typing.ForwardRef('A')
|
a = typing.ForwardRef('A')
|
||||||
|
|
|
@ -244,14 +244,16 @@ def _tp_cache(func):
|
||||||
return inner
|
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.
|
"""Evaluate all forward reverences in the given type t.
|
||||||
For use of globalns and localns see the docstring for get_type_hints().
|
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):
|
if isinstance(t, ForwardRef):
|
||||||
return t._evaluate(globalns, localns)
|
return t._evaluate(globalns, localns, recursive_guard)
|
||||||
if isinstance(t, (_GenericAlias, GenericAlias)):
|
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__:
|
if ev_args == t.__args__:
|
||||||
return t
|
return t
|
||||||
if isinstance(t, GenericAlias):
|
if isinstance(t, GenericAlias):
|
||||||
|
@ -477,7 +479,9 @@ class ForwardRef(_Final, _root=True):
|
||||||
self.__forward_value__ = None
|
self.__forward_value__ = None
|
||||||
self.__forward_is_argument__ = is_argument
|
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 not self.__forward_evaluated__ or localns is not globalns:
|
||||||
if globalns is None and localns is None:
|
if globalns is None and localns is None:
|
||||||
globalns = localns = {}
|
globalns = localns = {}
|
||||||
|
@ -485,10 +489,14 @@ class ForwardRef(_Final, _root=True):
|
||||||
globalns = localns
|
globalns = localns
|
||||||
elif localns is None:
|
elif localns is None:
|
||||||
localns = globalns
|
localns = globalns
|
||||||
self.__forward_value__ = _type_check(
|
type_ =_type_check(
|
||||||
eval(self.__forward_code__, globalns, localns),
|
eval(self.__forward_code__, globalns, localns),
|
||||||
"Forward references must evaluate to types.",
|
"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
|
self.__forward_evaluated__ = True
|
||||||
return self.__forward_value__
|
return self.__forward_value__
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Recursive evaluation of `typing.ForwardRef` in `get_type_hints`.
|
Loading…
Reference in New Issue