From 0515a54d077e40b7f1e31940b11f9a8c98be757e Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Wed, 15 Apr 2026 11:00:53 +0300 Subject: [PATCH] gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped__ --- Lib/test/test_typing.py | 18 ++++++++++++++++++ Lib/typing.py | 4 ++++ ...6-04-15-11-00-39.gh-issue-146553.VGOsoP.rst | 2 ++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst 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..7641106394ccbc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2485,8 +2485,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False, else: nsobj = obj # Find globalns for the unwrapped object. + seen = {id(nsobj)} while hasattr(nsobj, '__wrapped__'): nsobj = nsobj.__wrapped__ + if id(nsobj) in seen: + raise ValueError(f'wrapper loop when unwrapping {obj!r}') + seen.add(id(nsobj)) globalns = getattr(nsobj, '__globals__', {}) if localns is None: localns = globalns diff --git a/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst b/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst new file mode 100644 index 00000000000000..44216318d474a9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst @@ -0,0 +1,2 @@ +Fix infinite loop in :func:`typing.get_type_hints` when ``__wrapped__`` +forms a cycle. Patch by Shamil Abdulaev.