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]
