]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-138891: fix star-unpack in get_annotations (GH-138951) (#140384)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 20 Oct 2025 20:20:47 +0000 (22:20 +0200)
committerGitHub <noreply@github.com>
Mon, 20 Oct 2025 20:20:47 +0000 (20:20 +0000)
gh-138891: fix star-unpack in get_annotations (GH-138951)
(cherry picked from commit c6be6e453730228053783f3444cb62e1425a3feb)

Co-authored-by: Christoph Walcher <christoph-wa@gmx.de>
Lib/annotationlib.py
Lib/test/test_annotationlib.py
Misc/NEWS.d/next/Library/2025-09-15-21-03-11.gh-issue-138891.oZFdtR.rst [new file with mode: 0644]

index 43e1d51bc4b807bf22bee26bfbde4fbc7e14bdbb..544e069626d0d96395d8f89cab603aca3a5ef3cc 100644 (file)
@@ -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, "<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.
 
index a8a8bcec76a4291af27676d40b2987c7f184b2bb..2c5bf2b3417344fdde339ac97793ba684f9e88e8 100644 (file)
@@ -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 (file)
index 0000000..f7ecb05
--- /dev/null
@@ -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``