From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Mon, 20 Oct 2025 20:20:47 +0000 (+0200) Subject: [3.14] gh-138891: fix star-unpack in get_annotations (GH-138951) (#140384) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=62f44dda1c327a2b1fede7ebb47cf33a7a2ce6bb;p=thirdparty%2FPython%2Fcpython.git [3.14] gh-138891: fix star-unpack in get_annotations (GH-138951) (#140384) gh-138891: fix star-unpack in get_annotations (GH-138951) (cherry picked from commit c6be6e453730228053783f3444cb62e1425a3feb) Co-authored-by: Christoph Walcher --- diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index 43e1d51bc4b8..544e069626d0 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -241,15 +241,8 @@ class ForwardRef: 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, "", "eval") + self.__code__ = compile(_rewrite_star_unpack(arg), "", "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 a8a8bcec76a4..2c5bf2b34173 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -787,6 +787,12 @@ class TestGetAnnotations(unittest.TestCase): 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 000000000000..f7ecb05d20c2 --- /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``