Bug report
Bug description:
typing.get_type_hints() traverses the __wrapped__ attribute chain with no cycle detection. A callable whose __wrapped__ refers back to itself (or forms any cycle) causes an infinite loop, hanging the Python process indefinitely.
inspect.unwrap() solves the identical problem correctly by raising ValueError on cycle detection. get_type_hints() does not use that helper and has no equivalent guard.
Minimal Reproducer
import typing def f(): pass f.__wrapped__ = f # direct self-reference typing.get_type_hints(f) # hangs forever - process must be killed
Longer cycle:
def a(): pass def b(): pass a.__wrapped__ = b b.__wrapped__ = a # mutual cycle typing.get_type_hints(a) # also hangs forever
Root Cause
File: Lib/typing.py, get_type_hints(), lines 2488-2489 (CPython main):
# Find globalns for the unwrapped object. while hasattr(nsobj, '__wrapped__'): nsobj = nsobj.__wrapped__ # NO cycle detection
The loop walks the __wrapped__ chain to reach the innermost function and obtain
its __globals__. There is no visited-set, no counter, and no recursion limit.
Comparison with inspect.unwrap()
Lib/inspect.py, unwrap() (lines 679-692) solves the identical problem:
memo = {id(f): f} recursion_limit = sys.getrecursionlimit() while not isinstance(func, type) and hasattr(func, '__wrapped__'): func = func.__wrapped__ id_func = id(func) if (id_func in memo) or (len(memo) >= recursion_limit): raise ValueError('wrapper loop when unwrapping {!r}'.format(f)) memo[id_func] = func return func
inspect.unwrap() raises ValueError immediately on cycle detection.
Fix
Replace the bare while loop with inspect.unwrap():
import inspect as _inspect nsobj = obj try: nsobj = _inspect.unwrap(nsobj) except (TypeError, ValueError): pass globalns = getattr(nsobj, '__globals__', {})
Or inline the visited-set guard:
nsobj = obj _seen = {id(nsobj)} while hasattr(nsobj, '__wrapped__'): nsobj = nsobj.__wrapped__ _id = id(nsobj) if _id in _seen: raise ValueError(f"wrapper loop when unwrapping {obj!r}") _seen.add(_id) globalns = getattr(nsobj, '__globals__', {})
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped__ #146554
- gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped__ #148595
- [3.14] gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped__ (GH-148595) #148895
- [3.13] gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped__ (GH-148595) #148896