Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 41d1c04

Browse files
miss-islingtonwyfo
andauthored
bpo-41341: Recursive evaluation of ForwardRef in get_type_hints (GH-21553)
The issue raised by recursive evaluation is infinite recursion with recursive types. In that case, only the first recursive ForwardRef is evaluated. (cherry picked from commit 653f420) Co-authored-by: wyfo <[email protected]>
1 parent 8b7544c commit 41d1c04

File tree

3 files changed

+21
-6
lines changed

3 files changed

+21
-6
lines changed

Lib/test/test_typing.py

+6
Original file line numberDiff line numberDiff line change
@@ -2456,6 +2456,12 @@ def foo(a: tuple[ForwardRef('T')]):
24562456
self.assertEqual(get_type_hints(foo, globals(), locals()),
24572457
{'a': tuple[T]})
24582458

2459+
def test_double_forward(self):
2460+
def foo(a: 'List[\'int\']'):
2461+
pass
2462+
self.assertEqual(get_type_hints(foo, globals(), locals()),
2463+
{'a': List[int]})
2464+
24592465
def test_forward_recursion_actually(self):
24602466
def namespace1():
24612467
a = typing.ForwardRef('A')

Lib/typing.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,16 @@ def inner(*args, **kwds):
244244
return inner
245245

246246

247-
def _eval_type(t, globalns, localns):
247+
def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
248248
"""Evaluate all forward reverences in the given type t.
249249
For use of globalns and localns see the docstring for get_type_hints().
250+
recursive_guard is used to prevent prevent infinite recursion
251+
with recursive ForwardRef.
250252
"""
251253
if isinstance(t, ForwardRef):
252-
return t._evaluate(globalns, localns)
254+
return t._evaluate(globalns, localns, recursive_guard)
253255
if isinstance(t, (_GenericAlias, GenericAlias)):
254-
ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__)
256+
ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
255257
if ev_args == t.__args__:
256258
return t
257259
if isinstance(t, GenericAlias):
@@ -477,18 +479,24 @@ def __init__(self, arg, is_argument=True):
477479
self.__forward_value__ = None
478480
self.__forward_is_argument__ = is_argument
479481

480-
def _evaluate(self, globalns, localns):
482+
def _evaluate(self, globalns, localns, recursive_guard):
483+
if self.__forward_arg__ in recursive_guard:
484+
return self
481485
if not self.__forward_evaluated__ or localns is not globalns:
482486
if globalns is None and localns is None:
483487
globalns = localns = {}
484488
elif globalns is None:
485489
globalns = localns
486490
elif localns is None:
487491
localns = globalns
488-
self.__forward_value__ = _type_check(
492+
type_ =_type_check(
489493
eval(self.__forward_code__, globalns, localns),
490494
"Forward references must evaluate to types.",
491-
is_argument=self.__forward_is_argument__)
495+
is_argument=self.__forward_is_argument__,
496+
)
497+
self.__forward_value__ = _eval_type(
498+
type_, globalns, localns, recursive_guard | {self.__forward_arg__}
499+
)
492500
self.__forward_evaluated__ = True
493501
return self.__forward_value__
494502

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Recursive evaluation of `typing.ForwardRef` in `get_type_hints`.

0 commit comments

Comments
 (0)