https://github.com/python/cpython/commit/e66f87ca73516efb4aec1f2f056d2a4efd73248a
commit: e66f87ca73516efb4aec1f2f056d2a4efd73248a
branch: main
author: dr-carlos <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-11-02T15:15:47-08:00
summary:

gh-138425: Correctly partially evaluate global generics with undefined params 
in `ref.evaluate(format=Format.FORWARDREF)` (#138430)

Co-authored-by: sobolevn <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst
M Lib/annotationlib.py
M Lib/test/test_annotationlib.py

diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 81886a0467d001..16dbb128bc9293 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -187,8 +187,11 @@ def evaluate(
             except Exception:
                 if not is_forwardref_format:
                     raise
+
+            # All variables, in scoping order, should be checked before
+            # triggering __missing__ to create a _Stringifier.
             new_locals = _StringifierDict(
-                {**builtins.__dict__, **locals},
+                {**builtins.__dict__, **globals, **locals},
                 globals=globals,
                 owner=owner,
                 is_class=self.__forward_is_class__,
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index 8da4ff096e7593..7b08f58bfb8ba2 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -1877,6 +1877,32 @@ def test_name_lookup_without_eval(self):
 
         self.assertEqual(exc.exception.name, "doesntexist")
 
+    def test_evaluate_undefined_generic(self):
+        # Test the codepath where have to eval() with undefined variables.
+        class C:
+            x: alias[int, undef]
+
+        generic = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(
+            format=Format.FORWARDREF,
+            globals={"alias": dict}
+        )
+        self.assertNotIsInstance(generic, ForwardRef)
+        self.assertIs(generic.__origin__, dict)
+        self.assertEqual(len(generic.__args__), 2)
+        self.assertIs(generic.__args__[0], int)
+        self.assertIsInstance(generic.__args__[1], ForwardRef)
+
+        generic = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(
+            format=Format.FORWARDREF,
+            globals={"alias": Union},
+            locals={"alias": dict}
+        )
+        self.assertNotIsInstance(generic, ForwardRef)
+        self.assertIs(generic.__origin__, dict)
+        self.assertEqual(len(generic.__args__), 2)
+        self.assertIs(generic.__args__[0], int)
+        self.assertIsInstance(generic.__args__[1], ForwardRef)
+
     def test_fwdref_invalid_syntax(self):
         fr = ForwardRef("if")
         with self.assertRaises(SyntaxError):
diff --git 
a/Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst 
b/Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst
new file mode 100644
index 00000000000000..328e5988cb0b51
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst
@@ -0,0 +1,2 @@
+Fix partial evaluation of :class:`annotationlib.ForwardRef` objects which rely
+on names defined as globals.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to