From 15149f9c2ff9a99fa16eb7d9ceabacdcd84f430c Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Sun, 31 May 2026 14:44:35 +0800 Subject: [PATCH 1/4] Fix evaluating forward references in STRING format can 'leak' internal names in `typing` --- Lib/test/test_typing.py | 17 +++++++++++++++++ Lib/typing.py | 6 +++--- ...26-05-31-14-39-25.gh-issue-150641.LLIhd1.rst | 2 ++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ad644bb31288098..9cd73cc42510e7a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7561,6 +7561,23 @@ def test_with_module(self): typing.evaluate_forward_ref( fwdref_module.fw,) + def test_evaluate_forward_ref_string_format(self): + # Test evaluating forward references in STRING format + # does not 'leak' internal names + # See https://github.com/python/cpython/issues/150641 + from annotationlib import Format, get_annotations + + def f(arg: unknown | str | int | list[str] | tuple[int, ...]): ... + + ref = get_annotations(f, format=Format.FORWARDREF)['arg'] + self.assertEqual( + typing.evaluate_forward_ref(ref, format=Format.STRING), + "unknown | str | int | list[str] | tuple[int, ...]", + ) + self.assertEqual( + typing.evaluate_forward_ref(ref, format=Format.STRING), + ref.__resolved_str__ + ) class CollectionsAbcTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index 715d08e0e1603e6..6381e80d008517c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1019,8 +1019,8 @@ def evaluate_forward_ref( """ if format == annotationlib.Format.STRING: - return forward_ref.__forward_arg__ - if forward_ref.__forward_arg__ in _recursive_guard: + return forward_ref.__resolved_str__ + if forward_ref.__resolved_str__ in _recursive_guard: return forward_ref if format is None: @@ -1044,7 +1044,7 @@ def evaluate_forward_ref( globals, locals, type_params, - recursive_guard=_recursive_guard | {forward_ref.__forward_arg__}, + recursive_guard=_recursive_guard | {forward_ref.__resolved_str__}, format=format, owner=owner, parent_fwdref=forward_ref, diff --git a/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst b/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst new file mode 100644 index 000000000000000..3b9a3cbb3c2c26b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst @@ -0,0 +1,2 @@ +Fix evaluating forward references in STRING format can 'leak' internal names +in ``typing``. From dd079fa1649740e12b3b60ea412cd867df243a7d Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Sun, 31 May 2026 23:22:21 +0800 Subject: [PATCH 2/4] Fix recursive_guard to use __forward_arg__ instead of __resolved_str__ --- Lib/typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 6381e80d008517c..b2bc52e55d0fc77 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1020,7 +1020,7 @@ def evaluate_forward_ref( """ if format == annotationlib.Format.STRING: return forward_ref.__resolved_str__ - if forward_ref.__resolved_str__ in _recursive_guard: + if forward_ref.__forward_arg__ in _recursive_guard: return forward_ref if format is None: @@ -1044,7 +1044,7 @@ def evaluate_forward_ref( globals, locals, type_params, - recursive_guard=_recursive_guard | {forward_ref.__resolved_str__}, + recursive_guard=_recursive_guard | {forward_ref.__forward_arg__}, format=format, owner=owner, parent_fwdref=forward_ref, From 6169b44bf31ceece291f1925051bd70fc53869d0 Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Tue, 30 Jun 2026 18:31:03 +0800 Subject: [PATCH 3/4] Address review --- Lib/test/test_typing.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 9cd73cc42510e7a..4cb812f7a700ed5 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7565,19 +7565,14 @@ def test_evaluate_forward_ref_string_format(self): # Test evaluating forward references in STRING format # does not 'leak' internal names # See https://github.com/python/cpython/issues/150641 - from annotationlib import Format, get_annotations def f(arg: unknown | str | int | list[str] | tuple[int, ...]): ... - ref = get_annotations(f, format=Format.FORWARDREF)['arg'] + ref = annotationlib.get_annotations(f, format=annotationlib.Format.FORWARDREF)['arg'] self.assertEqual( - typing.evaluate_forward_ref(ref, format=Format.STRING), + typing.evaluate_forward_ref(ref, format=annotationlib.Format.STRING), "unknown | str | int | list[str] | tuple[int, ...]", ) - self.assertEqual( - typing.evaluate_forward_ref(ref, format=Format.STRING), - ref.__resolved_str__ - ) class CollectionsAbcTests(BaseTestCase): From 589de3377568f456b14f23fb70e8f36d6603ce39 Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Tue, 30 Jun 2026 18:31:28 +0800 Subject: [PATCH 4/4] Update Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst Co-authored-by: Jelle Zijlstra --- .../Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst b/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst index 3b9a3cbb3c2c26b..eba899c96fcd26b 100644 --- a/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst +++ b/Misc/NEWS.d/next/Library/2026-05-31-14-39-25.gh-issue-150641.LLIhd1.rst @@ -1,2 +1,2 @@ -Fix evaluating forward references in STRING format can 'leak' internal names -in ``typing``. +Fix bug where :func:`typing.evaluate_forward_ref` with the ``STRING`` format +could leak internal names used by the annotation machinery.