]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-138425: Correctly partially evaluate global generics with undefined params...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sun, 2 Nov 2025 23:41:49 +0000 (00:41 +0100)
committerGitHub <noreply@github.com>
Sun, 2 Nov 2025 23:41:49 +0000 (23:41 +0000)
gh-138425: Correctly partially evaluate global generics with undefined params in `ref.evaluate(format=Format.FORWARDREF)` (GH-138430)
(cherry picked from commit e66f87ca73516efb4aec1f2f056d2a4efd73248a)

Co-authored-by: dr-carlos <77367421+dr-carlos@users.noreply.github.com>
Co-authored-by: sobolevn <mail@sobolevn.me>
Lib/annotationlib.py
Lib/test/test_annotationlib.py
Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst [new file with mode: 0644]

index 81886a0467d00182f35afc832256ca17b878608d..16dbb128bc9293e06989e0c62da52d8f0019df4f 100644 (file)
@@ -187,8 +187,11 @@ class ForwardRef:
             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__,
index 8da4ff096e7593b8c757d3f81cc224852b67c753..7b08f58bfb8ba210be90cec9a29b092509935fb9 100644 (file)
@@ -1877,6 +1877,32 @@ class TestForwardRefClass(unittest.TestCase):
 
         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 (file)
index 0000000..328e598
--- /dev/null
@@ -0,0 +1,2 @@
+Fix partial evaluation of :class:`annotationlib.ForwardRef` objects which rely
+on names defined as globals.