https://github.com/python/cpython/commit/c6be6e453730228053783f3444cb62e1425a3feb
commit: c6be6e453730228053783f3444cb62e1425a3feb
branch: main
author: Christoph Walcher <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-10-20T19:55:08Z
summary:
gh-138891: fix star-unpack in get_annotations (#138951)
files:
A Misc/NEWS.d/next/Library/2025-09-15-21-03-11.gh-issue-138891.oZFdtR.rst
M Lib/annotationlib.py
M Lib/test/test_annotationlib.py
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 43e1d51bc4b807..544e069626d0d9 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -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__
@@ -1025,7 +1018,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
@@ -1062,6 +1056,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.
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index a8a8bcec76a429..2c5bf2b3417344 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -787,6 +787,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)
diff --git
a/Misc/NEWS.d/next/Library/2025-09-15-21-03-11.gh-issue-138891.oZFdtR.rst
b/Misc/NEWS.d/next/Library/2025-09-15-21-03-11.gh-issue-138891.oZFdtR.rst
new file mode 100644
index 00000000000000..f7ecb05d20c241
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-09-15-21-03-11.gh-issue-138891.oZFdtR.rst
@@ -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``
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]