From e66f87ca73516efb4aec1f2f056d2a4efd73248a Mon Sep 17 00:00:00 2001 From: dr-carlos <77367421+dr-carlos@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:45:47 +1030 Subject: [PATCH] gh-138425: Correctly partially evaluate global generics with undefined params in `ref.evaluate(format=Format.FORWARDREF)` (#138430) Co-authored-by: sobolevn --- Lib/annotationlib.py | 5 +++- Lib/test/test_annotationlib.py | 26 +++++++++++++++++++ ...-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index 81886a0467d0..16dbb128bc92 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -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__, diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index 8da4ff096e75..7b08f58bfb8b 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -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 index 000000000000..328e5988cb0b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-03-18-26-07.gh-issue-138425.cVE9Ho.rst @@ -0,0 +1,2 @@ +Fix partial evaluation of :class:`annotationlib.ForwardRef` objects which rely +on names defined as globals. -- 2.47.3