gh-138425: Correctly partially evaluate global generics with undefined params in ref.evaluate(format=Format.FORWARDREF) (#138430)

Co-authored-by: sobolevn <mail@sobolevn.me>
This commit is contained in:
dr-carlos
2025-11-03 09:45:47 +10:30
committed by GitHub
parent 31de83d5e2
commit e66f87ca73
3 changed files with 32 additions and 1 deletions

View File

@@ -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__,

View File

@@ -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):

View File

@@ -0,0 +1,2 @@
Fix partial evaluation of :class:`annotationlib.ForwardRef` objects which rely
on names defined as globals.