]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-137969: Fix evaluation of `ref.evaluate(format=Format.FORWARDREF)` objects...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 3 Nov 2025 01:45:44 +0000 (02:45 +0100)
committerGitHub <noreply@github.com>
Mon, 3 Nov 2025 01:45:44 +0000 (01:45 +0000)
gh-137969: Fix evaluation of `ref.evaluate(format=Format.FORWARDREF)` objects (GH-138075)
(cherry picked from commit 63e01d6bae9ddc9ff35aca2134945670eacef163)

Co-authored-by: dr-carlos <77367421+dr-carlos@users.noreply.github.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Lib/annotationlib.py
Lib/test/test_annotationlib.py
Misc/NEWS.d/next/Library/2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst [new file with mode: 0644]

index 16dbb128bc9293e06989e0c62da52d8f0019df4f..26e7c200248d36d3c0ae205d226bcda6a200aa69 100644 (file)
@@ -159,12 +159,12 @@ class ForwardRef:
             type_params = getattr(owner, "__type_params__", None)
 
         # Type parameters exist in their own scope, which is logically
-        # between the locals and the globals. We simulate this by adding
-        # them to the globals.
+        # between the locals and the globals.
+        type_param_scope = {}
         if type_params is not None:
-            globals = dict(globals)
             for param in type_params:
-                globals[param.__name__] = param
+                type_param_scope[param.__name__] = param
+
         if self.__extra_names__:
             locals = {**locals, **self.__extra_names__}
 
@@ -172,6 +172,8 @@ class ForwardRef:
         if arg.isidentifier() and not keyword.iskeyword(arg):
             if arg in locals:
                 return locals[arg]
+            elif arg in type_param_scope:
+                return type_param_scope[arg]
             elif arg in globals:
                 return globals[arg]
             elif hasattr(builtins, arg):
@@ -183,7 +185,7 @@ class ForwardRef:
         else:
             code = self.__forward_code__
             try:
-                return eval(code, globals=globals, locals=locals)
+                return eval(code, globals=globals, locals={**type_param_scope, **locals})
             except Exception:
                 if not is_forwardref_format:
                     raise
@@ -191,7 +193,7 @@ class ForwardRef:
             # All variables, in scoping order, should be checked before
             # triggering __missing__ to create a _Stringifier.
             new_locals = _StringifierDict(
-                {**builtins.__dict__, **globals, **locals},
+                {**builtins.__dict__, **globals, **type_param_scope, **locals},
                 globals=globals,
                 owner=owner,
                 is_class=self.__forward_is_class__,
index 7b08f58bfb8ba210be90cec9a29b092509935fb9..08f7161a2736e1bdd8ffb3c00a17f84a8e82a24c 100644 (file)
@@ -1911,6 +1911,15 @@ class TestForwardRefClass(unittest.TestCase):
         with self.assertRaises(SyntaxError):
             fr.evaluate()
 
+    def test_re_evaluate_generics(self):
+        global alias
+        class C:
+            x: alias[int]
+
+        evaluated = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(format=Format.FORWARDREF)
+        alias = list
+        self.assertEqual(evaluated.evaluate(), list[int])
+
 
 class TestAnnotationLib(unittest.TestCase):
     def test__all__(self):
diff --git a/Misc/NEWS.d/next/Library/2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst b/Misc/NEWS.d/next/Library/2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst
new file mode 100644 (file)
index 0000000..59f9e6e
--- /dev/null
@@ -0,0 +1,2 @@
+Fix :meth:`annotationlib.ForwardRef.evaluate` returning :class:`annotationlib.ForwardRef`\r
+objects which do not update in new contexts.