Skip to content

Commit f4f598a

Browse files
[3.13] gh-138891: fix star-unpack in get_annotations (GH-138951) (#146491)
(cherry picked from commit c6be6e4) Co-authored-by: Christoph Walcher <christoph-wa@gmx.de>
1 parent 2bb1ac2 commit f4f598a

File tree

4 files changed

+21
-10
lines changed

4 files changed

+21
-10
lines changed

Lib/inspect.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
from keyword import iskeyword
161161
from operator import attrgetter
162162
from collections import namedtuple, OrderedDict
163+
from typing import _rewrite_star_unpack
163164
from weakref import ref as make_weakref
164165

165166
# Create constants for the compiler flags in Include/code.h
@@ -288,8 +289,9 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
288289
if type_params := getattr(obj, "__type_params__", ()):
289290
locals = {param.__name__: param for param in type_params} | locals
290291

291-
return_value = {key:
292-
value if not isinstance(value, str) else eval(value, globals, locals)
292+
return_value = {
293+
key: value if not isinstance(value, str)
294+
else eval(_rewrite_star_unpack(value), globals, locals)
293295
for key, value in ann.items() }
294296
return return_value
295297

Lib/test/test_inspect/test_inspect.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,10 @@ def test_get_annotations_with_stringized_annotations(self):
18591859
self.assertEqual(inspect.get_annotations(isa2, eval_str=True), {})
18601860
self.assertEqual(inspect.get_annotations(isa2, eval_str=False), {})
18611861

1862+
def f(*args: *tuple[int, ...]): ...
1863+
self.assertEqual(inspect.get_annotations(f, eval_str=True),
1864+
{'args': (*tuple[int, ...],)[0]})
1865+
18621866
def times_three(fn):
18631867
@functools.wraps(fn)
18641868
def wrapper(a, b):

Lib/typing.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,15 +1024,8 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
10241024
if not isinstance(arg, str):
10251025
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
10261026

1027-
# If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`.
1028-
# Unfortunately, this isn't a valid expression on its own, so we
1029-
# do the unpacking manually.
1030-
if arg.startswith('*'):
1031-
arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
1032-
else:
1033-
arg_to_compile = arg
10341027
try:
1035-
code = compile(arg_to_compile, '<string>', 'eval')
1028+
code = compile(_rewrite_star_unpack(arg), '<string>', 'eval')
10361029
except SyntaxError:
10371030
raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")
10381031

@@ -1119,6 +1112,16 @@ def __repr__(self):
11191112
return f'ForwardRef({self.__forward_arg__!r}{module_repr})'
11201113

11211114

1115+
def _rewrite_star_unpack(arg):
1116+
"""If the given argument annotation expression is a star unpack e.g. `'*Ts'`
1117+
rewrite it to a valid expression.
1118+
"""
1119+
if arg.startswith("*"):
1120+
return f"({arg},)[0]" # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
1121+
else:
1122+
return arg
1123+
1124+
11221125
def _is_unpacked_typevartuple(x: Any) -> bool:
11231126
return ((not isinstance(x, type)) and
11241127
getattr(x, '__typing_is_unpacked_typevartuple__', False))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``SyntaxError`` when ``inspect.get_annotations(f, eval_str=True)`` is
2+
called on a function annotated with a :pep:`646` ``star_expression``

0 commit comments

Comments
 (0)