Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
22 changes: 13 additions & 9 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,8 @@ def __forward_code__(self):
if self.__code__ is not None:
return self.__code__
arg = self.__forward_arg__
# If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`.
# Unfortunately, this isn't a valid expression on its own, so we
# do the unpacking manually.
if arg.startswith("*"):
arg_to_compile = f"({arg},)[0]" # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
else:
arg_to_compile = arg
try:
self.__code__ = compile(arg_to_compile, "<string>", "eval")
self.__code__ = compile(_rewrite_star_unpack(arg), "<string>", "eval")
except SyntaxError:
raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")
return self.__code__
Expand Down Expand Up @@ -987,7 +980,8 @@ def get_annotations(
locals = {param.__name__: param for param in type_params} | locals

return_value = {
key: value if not isinstance(value, str) else eval(value, globals, locals)
key: value if not isinstance(value, str)
else eval(_rewrite_star_unpack(value), globals, locals)
for key, value in ann.items()
}
return return_value
Expand Down Expand Up @@ -1024,6 +1018,16 @@ def annotations_to_string(annotations):
}


def _rewrite_star_unpack(arg):
"""If the given argument annotation expression is a star unpack e.g. `'*Ts'`
rewrite it to a valid expression.
"""
if arg.startswith("*"):
return f"({arg},)[0]" # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
else:
return arg


def _get_and_call_annotate(obj, format):
"""Get the __annotate__ function and call it.

Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,12 @@ def test_stringized_annotations_in_empty_module(self):
self.assertEqual(get_annotations(isa2, eval_str=True), {})
self.assertEqual(get_annotations(isa2, eval_str=False), {})

def test_stringized_annotations_with_star_unpack(self):
def f(*args: *tuple[int, ...]): ...
self.assertEqual(get_annotations(f, eval_str=True),
{'args': (*tuple[int, ...],)[0]})


def test_stringized_annotations_on_wrapper(self):
isa = inspect_stringized_annotations
wrapped = times_three(isa.function)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``SyntaxError`` when ``inspect.get_annotations(f, eval_str=True)`` is
called on a function annotated with a :pep:`646` ``star_expression``
Loading