From 70decda14102df7f5eae48760c0ae69117b675e8 Mon Sep 17 00:00:00 2001 From: dr-carlos Date: Tue, 26 Aug 2025 20:18:41 +0930 Subject: [PATCH 1/4] Fix generic `ForwardRef` evaluation with new locals --- Lib/annotationlib.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index bee019cd51591e..4f762d96429c13 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -167,6 +167,13 @@ def evaluate( globals[param.__name__] = param if self.__extra_names__: locals = {**locals, **self.__extra_names__} + if (annotate := getattr(owner, "__annotate__", None)) and annotate.__closure__: + for name, cell in zip(annotate.__code__.co_freevars, annotate.__closure__): + if name != "__classdict__": + try: + locals[name] = cell.cell_contents + except ValueError: + pass arg = self.__forward_arg__ if arg.isidentifier() and not keyword.iskeyword(arg): From 9d1d98cf50f7c59211e78dd42ccae67ab3df586a Mon Sep 17 00:00:00 2001 From: dr-carlos Date: Tue, 26 Aug 2025 20:30:48 +0930 Subject: [PATCH 2/4] Add tests for `ForwardRef.evaluate()` on locally defined generics --- Lib/test/test_annotationlib.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index 88e0d611647f28..f1a429c16764c9 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -1617,6 +1617,32 @@ def test_evaluate_with_type_params_and_scope_conflict(self): TypeParamsSample.TypeParamsAlias2, ) + def test_evaluate_local_generic(self): + class Cls: + x: alias + y: alias2 + z: alias3 + + fwdref = ForwardRef("alias[int]", owner=Cls) + with self.assertRaises(NameError): + fwdref.evaluate() + + alias = list + self.assertEqual(fwdref.evaluate(), list[int]) + + del alias + fwdref = ForwardRef("alias[alias]", owner=Cls) + alias = list + self.assertEqual(fwdref.evaluate(), list[list]) + + del alias + fwdref = ForwardRef("alias[alias2, alias3]", owner=Cls) + alias = dict + alias2 = int + alias3 = str + self.assertEqual(fwdref.evaluate(), dict[int, str]) + + def test_fwdref_with_module(self): self.assertIs(ForwardRef("Format", module="annotationlib").evaluate(), Format) self.assertIs( From f000205c9e20025418e37c070d7a48fafd92cbf4 Mon Sep 17 00:00:00 2001 From: dr-carlos Date: Wed, 27 Aug 2025 11:57:13 +0930 Subject: [PATCH 3/4] Update locally defined generics tests to explicitly make aliases `nonlocal` --- Lib/test/test_annotationlib.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index f1a429c16764c9..b1962ad0358b58 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -1619,11 +1619,12 @@ def test_evaluate_with_type_params_and_scope_conflict(self): def test_evaluate_local_generic(self): class Cls: - x: alias - y: alias2 - z: alias3 + nonlocal alias, alias2, alias3 + x: alias[int] + y: alias[alias] + z: alias[alias2, alias3] - fwdref = ForwardRef("alias[int]", owner=Cls) + fwdref = get_annotations(Cls, format=Format.FORWARDREF)["x"] with self.assertRaises(NameError): fwdref.evaluate() @@ -1631,12 +1632,12 @@ class Cls: self.assertEqual(fwdref.evaluate(), list[int]) del alias - fwdref = ForwardRef("alias[alias]", owner=Cls) + fwdref = get_annotations(Cls, format=Format.FORWARDREF)["y"] alias = list self.assertEqual(fwdref.evaluate(), list[list]) del alias - fwdref = ForwardRef("alias[alias2, alias3]", owner=Cls) + fwdref = get_annotations(Cls, format=Format.FORWARDREF)["z"] alias = dict alias2 = int alias3 = str From 521cd9d684a8aa867323b590720549e472cdce18 Mon Sep 17 00:00:00 2001 From: dr-carlos Date: Wed, 27 Aug 2025 12:07:19 +0930 Subject: [PATCH 4/4] Add NEWS entry --- .../next/Library/2025-08-27-12-06-52.gh-issue-138151.ajQpH5.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-08-27-12-06-52.gh-issue-138151.ajQpH5.rst diff --git a/Misc/NEWS.d/next/Library/2025-08-27-12-06-52.gh-issue-138151.ajQpH5.rst b/Misc/NEWS.d/next/Library/2025-08-27-12-06-52.gh-issue-138151.ajQpH5.rst new file mode 100644 index 00000000000000..01ddef6914b642 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-27-12-06-52.gh-issue-138151.ajQpH5.rst @@ -0,0 +1,2 @@ +Fix :meth:`annotationlib.ForwardRef.evaluate` not handling generics with +nonlocal variables that are defined after the annotation.