https://github.com/python/cpython/commit/63e01d6bae9ddc9ff35aca2134945670eacef163
commit: 63e01d6bae9ddc9ff35aca2134945670eacef163
branch: main
author: dr-carlos <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-11-02T17:20:30-08:00
summary:

gh-137969: Fix evaluation of `ref.evaluate(format=Format.FORWARDREF)` objects 
(#138075)

Co-authored-by: Jelle Zijlstra <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst
M Lib/annotationlib.py
M Lib/test/test_annotationlib.py

diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 16dbb128bc9293..26e7c200248d36 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -159,12 +159,12 @@ def evaluate(
             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 @@ def evaluate(
         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 @@ def evaluate(
         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 @@ def evaluate(
             # 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__,
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index 7b08f58bfb8ba2..08f7161a2736e1 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -1911,6 +1911,15 @@ def test_fwdref_invalid_syntax(self):
         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
index 00000000000000..59f9e6e3d331ec
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst
@@ -0,0 +1,2 @@
+Fix :meth:`annotationlib.ForwardRef.evaluate` returning 
:class:`annotationlib.ForwardRef`
+objects which do not update in new contexts.

_______________________________________________
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