From 63e01d6bae9ddc9ff35aca2134945670eacef163 Mon Sep 17 00:00:00 2001 From: dr-carlos <77367421+dr-carlos@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:50:30 +1030 Subject: [PATCH] gh-137969: Fix evaluation of `ref.evaluate(format=Format.FORWARDREF)` objects (#138075) Co-authored-by: Jelle Zijlstra --- Lib/annotationlib.py | 14 ++++++++------ Lib/test/test_annotationlib.py | 9 +++++++++ .../2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst | 2 ++ 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-08-22-23-50-38.gh-issue-137969.Fkvis3.rst diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index 16dbb128bc92..26e7c200248d 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -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__, diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index 7b08f58bfb8b..08f7161a2736 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -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 index 000000000000..59f9e6e3d331 --- /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. -- 2.47.3