8000 gh-132805: annotationlib: Fix handling of non-constant values in FORWARDREF by JelleZijlstra · Pull Request #132812 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-132805: annotationlib: Fix handling of non-constant values in FORWARDREF #132812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 4, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
fix bad merge
  • Loading branch information
JelleZijlstra committed May 4, 2025
commit 22a9691884f9db71bbac7741ce449a0111d793ae
106 changes: 0 additions & 106 deletions Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,112 +413,6 @@ def f(x: x | (1).__class__, y: (1).__class__):
)


class TestForwardRefClass(unittest.TestCase):
def test_special_attrs(self):
# Forward refs provide a different introspection API. __name__ and
# __qualname__ make little sense for forward refs as they can store
# complex typing expressions.
fr = ForwardRef("set[Any]")
self.assertFalse(hasattr(fr, "__name__"))
self.assertFalse(hasattr(fr, "__qualname__"))
self.assertEqual(fr.__module__, "annotationlib")
# Forward refs are currently unpicklable once they contain a code object.
fr.__forward_code__ # fill the cache
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.assertRaises(TypeError):
pickle.dumps(fr, proto)

def test_evaluate_with_type_params(self):
class Gen[T]:
alias = int

with self.assertRaises(NameError):
ForwardRef("T").evaluate()
with self.assertRaises(NameError):
ForwardRef("T").evaluate(type_params=())
with self.assertRaises(NameError):
ForwardRef("T").evaluate(owner=int)

(T,) = Gen.__type_params__
self.assertIs(ForwardRef("T").evaluate(type_params=Gen.__type_params__), T)
self.assertIs(ForwardRef("T").evaluate(owner=Gen), T)

with self.assertRaises(NameError):
ForwardRef("alias").evaluate(type_params=Gen.__type_params__)
self.assertIs(ForwardRef("alias").evaluate(owner=Gen), int)
# If you pass custom locals, we don't look at the owner's locals
with self.assertRaises(NameError):
ForwardRef("alias").evaluate(owner=Gen, locals={})
# But if the name exists in the locals, it works
self.assertIs(
ForwardRef("alias").evaluate(owner=Gen, locals={"alias": str}), str
)

def test_fwdref_with_module(self):
self.assertIs(ForwardRef("Format", module="annotationlib").evaluate(), Format)
self.assertIs(
ForwardRef("Counter", module="collections").evaluate(), collections.Counter
)
self.assertEqual(
ForwardRef("Counter[int]", module="collections").evaluate(),
collections.Counter[int],
)

with self.assertRaises(NameError):
# If globals are passed explicitly, we don't look at the module dict
ForwardRef("Format", module="annotationlib").evaluate(globals={})

def test_fwdref_to_builtin(self):
self.assertIs(ForwardRef("int").evaluate(), int)
self.assertIs(ForwardRef("int", module="collections").evaluate(), int)
self.assertIs(ForwardRef("int", owner=str).evaluate(), int)

# builtins are still searched with explicit globals
self.assertIs(ForwardRef("int").evaluate(globals={}), int)

# explicit values in globals have precedence
obj = object()
self.assertIs(ForwardRef("int").evaluate(globals={"int": obj}), obj)

def test_fwdref_value_is_not_cached(self):
fr = ForwardRef("hello")
with self.assertRaises(NameError):
fr.evaluate()
self.assertIs(fr.evaluate(globals={"hello": str}), str)
with self.assertRaises(NameError):
fr.evaluate()

def test_fwdref_with_owner(self):
self.assertEqual(
ForwardRef("Counter[int]", owner=collections).evaluate(),
collections.Counter[int],
)

def test_name_lookup_without_eval(self):
# test the codepath where we look up simple names directly in the
# namespaces without going through eval()
self.assertIs(ForwardRef("int").evaluate(), int)
self.assertIs(ForwardRef("int").evaluate(locals={"int": str}), str)
self.assertIs(
ForwardRef("int").evaluate(locals={"int": float}, globals={"int": str}),
float,
)
self.assertIs(ForwardRef("int").evaluate(globals={"int": str}), str)
with support.swap_attr(builtins, "int", dict):
self.assertIs(ForwardRef("int").evaluate(), dict)

with self.assertRaises(NameError):
ForwardRef("doesntexist").evaluate()

def test_fwdref_invalid_syntax(self):
fr = ForwardRef("if")
with self.assertRaises(SyntaxError):
fr.evaluate()
fr = ForwardRef("1+")
with self.assertRaises(SyntaxError):
fr.evaluate()


class TestGetAnnotations(unittest.TestCase):
def test_builtin_type(self):
self.assertEqual(get_annotations(int), {})
Expand Down
Loading
0