diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c6f08ff8a052ab..a2b76ed1303a33 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6837,6 +6837,24 @@ def test_get_type_hints_wrapped_decoratored_func(self): self.assertEqual(gth(ForRefExample.func), expects) self.assertEqual(gth(ForRefExample.nested), expects) + def test_get_type_hints_wrapped_cycle_self(self): + # gh-146553: __wrapped__ self-reference must raise ValueError, + # not loop forever. + def f(x: int) -> str: ... + f.__wrapped__ = f + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + get_type_hints(f) + + def test_get_type_hints_wrapped_cycle_mutual(self): + # gh-146553: mutual __wrapped__ cycle (a -> b -> a) must raise + # ValueError, not loop forever. + def a(): ... + def b(): ... + a.__wrapped__ = b + b.__wrapped__ = a + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + get_type_hints(a) + def test_get_type_hints_annotated(self): def foobar(x: List['X']): ... X = Annotated[int, (1, 10)] diff --git a/Lib/typing.py b/Lib/typing.py index e78fb8b71a996c..f8fbc2d159fa3c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1972,6 +1972,7 @@ def _lazy_load_getattr_static(): _cleanups.append(_lazy_load_getattr_static.cache_clear) + def _pickle_psargs(psargs): return ParamSpecArgs, (psargs.__origin__,) @@ -2483,10 +2484,9 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False, if isinstance(obj, types.ModuleType): globalns = obj.__dict__ else: - nsobj = obj # Find globalns for the unwrapped object. - while hasattr(nsobj, '__wrapped__'): - nsobj = nsobj.__wrapped__ + import inspect + nsobj = inspect.unwrap(obj) globalns = getattr(nsobj, '__globals__', {}) if localns is None: localns = globalns diff --git a/Misc/NEWS.d/next/Library/2026-03-28-11-43-25.gh-issue-146553.mXaWza.rst b/Misc/NEWS.d/next/Library/2026-03-28-11-43-25.gh-issue-146553.mXaWza.rst new file mode 100644 index 00000000000000..b75cdc14725a0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-28-11-43-25.gh-issue-146553.mXaWza.rst @@ -0,0 +1,4 @@ +Fix :func:`typing.get_type_hints` hanging indefinitely when a callable has a +circular ``__wrapped__`` chain (e.g. ``f.__wrapped__ = f``). A +:exc:`ValueError` is now raised on cycle detection, matching the behavior of +:func:`inspect.unwrap`.