Skip to content

Commit e8e737b

Browse files
bpo-43224: Implement PEP 646 grammar changes (GH-31018)
Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 26cca80 commit e8e737b

12 files changed

+3056
-2128
lines changed

Grammar/python.gram

+8-1
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ star_etc[StarEtc*]:
309309
| invalid_star_etc
310310
| '*' a=param_no_default b=param_maybe_default* c=[kwds] {
311311
_PyPegen_star_etc(p, a, b, c) }
312+
| '*' a=param_no_default_star_annotation b=param_maybe_default* c=[kwds] {
313+
_PyPegen_star_etc(p, a, b, c) }
312314
| '*' ',' b=param_maybe_default+ c=[kwds] {
313315
_PyPegen_star_etc(p, NULL, b, c) }
314316
| a=kwds { _PyPegen_star_etc(p, NULL, NULL, a) }
@@ -333,14 +335,19 @@ kwds[arg_ty]:
333335
param_no_default[arg_ty]:
334336
| a=param ',' tc=TYPE_COMMENT? { _PyPegen_add_type_comment_to_arg(p, a, tc) }
335337
| a=param tc=TYPE_COMMENT? &')' { _PyPegen_add_type_comment_to_arg(p, a, tc) }
338+
param_no_default_star_annotation[arg_ty]:
339+
| a=param_star_annotation ',' tc=TYPE_COMMENT? { _PyPegen_add_type_comment_to_arg(p, a, tc) }
340+
| a=param_star_annotation tc=TYPE_COMMENT? &')' { _PyPegen_add_type_comment_to_arg(p, a, tc) }
336341
param_with_default[NameDefaultPair*]:
337342
| a=param c=default ',' tc=TYPE_COMMENT? { _PyPegen_name_default_pair(p, a, c, tc) }
338343
| a=param c=default tc=TYPE_COMMENT? &')' { _PyPegen_name_default_pair(p, a, c, tc) }
339344
param_maybe_default[NameDefaultPair*]:
340345
| a=param c=default? ',' tc=TYPE_COMMENT? { _PyPegen_name_default_pair(p, a, c, tc) }
341346
| a=param c=default? tc=TYPE_COMMENT? &')' { _PyPegen_name_default_pair(p, a, c, tc) }
342347
param[arg_ty]: a=NAME b=annotation? { _PyAST_arg(a->v.Name.id, b, NULL, EXTRA) }
348+
param_star_annotation[arg_ty]: a=NAME b=star_annotation { _PyAST_arg(a->v.Name.id, b, NULL, EXTRA) }
343349
annotation[expr_ty]: ':' a=expression { a }
350+
star_annotation[expr_ty]: ':' a=star_expression { a }
344351
default[expr_ty]: '=' a=expression { a } | invalid_default
345352

346353
# If statement
@@ -782,7 +789,7 @@ primary[expr_ty]:
782789

783790
slices[expr_ty]:
784791
| a=slice !',' { a }
785-
| a[asdl_expr_seq*]=','.slice+ [','] { _PyAST_Tuple(a, Load, EXTRA) }
792+
| a[asdl_expr_seq*]=','.(slice | starred_expression)+ [','] { _PyAST_Tuple(a, Load, EXTRA) }
786793

787794
slice[expr_ty]:
788795
| a=[expression] ':' b=[expression] c=[':' d=[expression] { d }] { _PyAST_Slice(a, b, c, EXTRA) }

Lib/ast.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -1476,20 +1476,17 @@ def visit_Call(self, node):
14761476
self.traverse(e)
14771477

14781478
def visit_Subscript(self, node):
1479-
def is_simple_tuple(slice_value):
1480-
# when unparsing a non-empty tuple, the parentheses can be safely
1481-
# omitted if there aren't any elements that explicitly requires
1482-
# parentheses (such as starred expressions).
1479+
def is_non_empty_tuple(slice_value):
14831480
return (
14841481
isinstance(slice_value, Tuple)
14851482
and slice_value.elts
1486-
and not any(isinstance(elt, Starred) for elt in slice_value.elts)
14871483
)
14881484

14891485
self.set_precedence(_Precedence.ATOM, node.value)
14901486
self.traverse(node.value)
14911487
with self.delimit("[", "]"):
1492-
if is_simple_tuple(node.slice):
1488+
if is_non_empty_tuple(node.slice):
1489+
# parentheses can be omitted if the tuple isn't empty
14931490
self.items_view(self.traverse, node.slice.elts)
14941491
else:
14951492
self.traverse(node.slice)

Lib/test/test_ast.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from test import support
1414

1515
def to_tuple(t):
16-
if t is None or isinstance(t, (str, int, complex)):
16+
if t is None or isinstance(t, (str, int, complex)) or t is Ellipsis:
1717
return t
1818
elif isinstance(t, list):
1919
return [to_tuple(e) for e in t]
@@ -46,10 +46,20 @@ def to_tuple(t):
4646
"def f(a=0): pass",
4747
# FunctionDef with varargs
4848
"def f(*args): pass",
49+
# FunctionDef with varargs as TypeVarTuple
50+
"def f(*args: *Ts): pass",
51+
# FunctionDef with varargs as unpacked Tuple
52+
"def f(*args: *tuple[int, ...]): pass",
53+
# FunctionDef with varargs as unpacked Tuple *and* TypeVarTuple
54+
"def f(*args: *tuple[int, *Ts]): pass",
4955
# FunctionDef with kwargs
5056
"def f(**kwargs): pass",
5157
# FunctionDef with all kind of args and docstring
5258
"def f(a, b=1, c=None, d=[], e={}, *args, f=42, **kwargs): 'doc for f()'",
59+
# FunctionDef with type annotation on return involving unpacking
60+
"def f() -> tuple[*Ts]: pass",
61+
"def f() -> tuple[int, *Ts]: pass",
62+
"def f() -> tuple[int, *tuple[int, ...]]: pass",
5363
# ClassDef
5464
"class C:pass",
5565
# ClassDef with docstring
@@ -65,6 +75,10 @@ def to_tuple(t):
6575
"a,b = c",
6676
"(a,b) = c",
6777
"[a,b] = c",
78+
# AnnAssign with unpacked types
79+
"x: tuple[*Ts]",
80+
"x: tuple[int, *Ts]",
81+
"x: tuple[int, *tuple[str, ...]]",
6882
# AugAssign
6983
"v += 1",
7084
# For
@@ -2315,8 +2329,14 @@ def main():
23152329
('Module', [('FunctionDef', (1, 0, 1, 14), 'f', ('arguments', [], [('arg', (1, 6, 1, 7), 'a', None, None)], None, [], [], None, []), [('Pass', (1, 10, 1, 14))], [], None, None)], []),
23162330
('Module', [('FunctionDef', (1, 0, 1, 16), 'f', ('arguments', [], [('arg', (1, 6, 1, 7), 'a', None, None)], None, [], [], None, [('Constant', (1, 8, 1, 9), 0, None)]), [('Pass', (1, 12, 1, 16))], [], None, None)], []),
23172331
('Module', [('FunctionDef', (1, 0, 1, 18), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 11), 'args', None, None), [], [], None, []), [('Pass', (1, 14, 1, 18))], [], None, None)], []),
2332+
('Module', [('FunctionDef', (1, 0, 1, 23), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 16), 'args', ('Starred', (1, 13, 1, 16), ('Name', (1, 14, 1, 16), 'Ts', ('Load',)), ('Load',)), None), [], [], None, []), [('Pass', (1, 19, 1, 23))], [], None, None)], []),
2333+
('Module', [('FunctionDef', (1, 0, 1, 36), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 29), 'args', ('Starred', (1, 13, 1, 29), ('Subscript', (1, 14, 1, 29), ('Name', (1, 14, 1, 19), 'tuple', ('Load',)), ('Tuple', (1, 20, 1, 28), [('Name', (1, 20, 1, 23), 'int', ('Load',)), ('Constant', (1, 25, 1, 28), Ellipsis, None)], ('Load',)), ('Load',)), ('Load',)), None), [], [], None, []), [('Pass', (1, 32, 1, 36))], [], None, None)], []),
2334+
('Module', [('FunctionDef', (1, 0, 1, 36), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 29), 'args', ('Starred', (1, 13, 1, 29), ('Subscript', (1, 14, 1, 29), ('Name', (1, 14, 1, 19), 'tuple', ('Load',)), ('Tuple', (1, 20, 1, 28), [('Name', (1, 20, 1, 23), 'int', ('Load',)), ('Starred', (1, 25, 1, 28), ('Name', (1, 26, 1, 28), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), ('Load',)), None), [], [], None, []), [('Pass', (1, 32, 1, 36))], [], None, None)], []),
23182335
('Module', [('FunctionDef', (1, 0, 1, 21), 'f', ('arguments', [], [], None, [], [], ('arg', (1, 8, 1, 14), 'kwargs', None, None), []), [('Pass', (1, 17, 1, 21))], [], None, None)], []),
23192336
('Module', [('FunctionDef', (1, 0, 1, 71), 'f', ('arguments', [], [('arg', (1, 6, 1, 7), 'a', None, None), ('arg', (1, 9, 1, 10), 'b', None, None), ('arg', (1, 14, 1, 15), 'c', None, None), ('arg', (1, 22, 1, 23), 'd', None, None), ('arg', (1, 28, 1, 29), 'e', None, None)], ('arg', (1, 35, 1, 39), 'args', None, None), [('arg', (1, 41, 1, 42), 'f', None, None)], [('Constant', (1, 43, 1, 45), 42, None)], ('arg', (1, 49, 1, 55), 'kwargs', None, None), [('Constant', (1, 11, 1, 12), 1, None), ('Constant', (1, 16, 1, 20), None, None), ('List', (1, 24, 1, 26), [], ('Load',)), ('Dict', (1, 30, 1, 32), [], [])]), [('Expr', (1, 58, 1, 71), ('Constant', (1, 58, 1, 71), 'doc for f()', None))], [], None, None)], []),
2337+
('Module', [('FunctionDef', (1, 0, 1, 27), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 23, 1, 27))], [], ('Subscript', (1, 11, 1, 21), ('Name', (1, 11, 1, 16), 'tuple', ('Load',)), ('Tuple', (1, 17, 1, 20), [('Starred', (1, 17, 1, 20), ('Name', (1, 18, 1, 20), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None)], []),
2338+
('Module', [('FunctionDef', (1, 0, 1, 32), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 28, 1, 32))], [], ('Subscript', (1, 11, 1, 26), ('Name', (1, 11, 1, 16), 'tuple', ('Load',)), ('Tuple', (1, 17, 1, 25), [('Name', (1, 17, 1, 20), 'int', ('Load',)), ('Starred', (1, 22, 1, 25), ('Name', (1, 23, 1, 25), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None)], []),
2339+
('Module', [('FunctionDef', (1, 0, 1, 45), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 41, 1, 45))], [], ('Subscript', (1, 11, 1, 39), ('Name', (1, 11, 1, 16), 'tuple', ('Load',)), ('Tuple', (1, 17, 1, 38), [('Name', (1, 17, 1, 20), 'int', ('Load',)), ('Starred', (1, 22, 1, 38), ('Subscript', (1, 23, 1, 38), ('Name', (1, 23, 1, 28), 'tuple', ('Load',)), ('Tuple', (1, 29, 1, 37), [('Name', (1, 29, 1, 32), 'int', ('Load',)), ('Constant', (1, 34, 1, 37), Ellipsis, None)], ('Load',)), ('Load',)), ('Load',))], ('Load',)), ('Load',)), None)], []),
23202340
('Module', [('ClassDef', (1, 0, 1, 12), 'C', [], [], [('Pass', (1, 8, 1, 12))], [])], []),
23212341
('Module', [('ClassDef', (1, 0, 1, 32), 'C', [], [], [('Expr', (1, 9, 1, 32), ('Constant', (1, 9, 1, 32), 'docstring for class C', None))], [])], []),
23222342
('Module', [('ClassDef', (1, 0, 1, 21), 'C', [('Name', (1, 8, 1, 14), 'object', ('Load',))], [], [('Pass', (1, 17, 1, 21))], [])], []),
@@ -2326,6 +2346,9 @@ def main():
23262346
('Module', [('Assign', (1, 0, 1, 7), [('Tuple', (1, 0, 1, 3), [('Name', (1, 0, 1, 1), 'a', ('Store',)), ('Name', (1, 2, 1, 3), 'b', ('Store',))], ('Store',))], ('Name', (1, 6, 1, 7), 'c', ('Load',)), None)], []),
23272347
('Module', [('Assign', (1, 0, 1, 9), [('Tuple', (1, 0, 1, 5), [('Name', (1, 1, 1, 2), 'a', ('Store',)), ('Name', (1, 3, 1, 4), 'b', ('Store',))], ('Store',))], ('Name', (1, 8, 1, 9), 'c', ('Load',)), None)], []),
23282348
('Module', [('Assign', (1, 0, 1, 9), [('List', (1, 0, 1, 5), [('Name', (1, 1, 1, 2), 'a', ('Store',)), ('Name', (1, 3, 1, 4), 'b', ('Store',))], ('Store',))], ('Name', (1, 8, 1, 9), 'c', ('Load',)), None)], []),
2349+
('Module', [('AnnAssign', (1, 0, 1, 13), ('Name', (1, 0, 1, 1), 'x', ('Store',)), ('Subscript', (1, 3, 1, 13), ('Name', (1, 3, 1, 8), 'tuple', ('Load',)), ('Tuple', (1, 9, 1, 12), [('Starred', (1, 9, 1, 12), ('Name', (1, 10, 1, 12), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None, 1)], []),
2350+
('Module', [('AnnAssign', (1, 0, 1, 18), ('Name', (1, 0, 1, 1), 'x', ('Store',)), ('Subscript', (1, 3, 1, 18), ('Name', (1, 3, 1, 8), 'tuple', ('Load',)), ('Tuple', (1, 9, 1, 17), [('Name', (1, 9, 1, 12), 'int', ('Load',)), ('Starred', (1, 14, 1, 17), ('Name', (1, 15, 1, 17), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None, 1)], []),
2351+
('Module', [('AnnAssign', (1, 0, 1, 31), ('Name', (1, 0, 1, 1), 'x', ('Store',)), ('Subscript', (1, 3, 1, 31), ('Name', (1, 3, 1, 8), 'tuple', ('Load',)), ('Tuple', (1, 9, 1, 30), [('Name', (1, 9, 1, 12), 'int', ('Load',)), ('Starred', (1, 14, 1, 30), ('Subscript', (1, 15, 1, 30), ('Name', (1, 15, 1, 20), 'tuple', ('Load',)), ('Tuple', (1, 21, 1, 29), [('Name', (1, 21, 1, 24), 'str', ('Load',)), ('Constant', (1, 26, 1, 29), Ellipsis, None)], ('Load',)), ('Load',)), ('Load',))], ('Load',)), ('Load',)), None, 1)], []),
23292352
('Module', [('AugAssign', (1, 0, 1, 6), ('Name', (1, 0, 1, 1), 'v', ('Store',)), ('Add',), ('Constant', (1, 5, 1, 6), 1, None))], []),
23302353
('Module', [('For', (1, 0, 1, 15), ('Name', (1, 4, 1, 5), 'v', ('Store',)), ('Name', (1, 9, 1, 10), 'v', ('Load',)), [('Pass', (1, 11, 1, 15))], [], None)], []),
23312354
('Module', [('While', (1, 0, 1, 12), ('Name', (1, 6, 1, 7), 'v', ('Load',)), [('Pass', (1, 8, 1, 12))], [])], []),

Lib/test/test_future.py

+38-3
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def _exec_future(self, code):
175175
scope = {}
176176
exec(
177177
"from __future__ import annotations\n"
178-
+ code, {}, scope
178+
+ code, scope
179179
)
180180
return scope
181181

@@ -287,10 +287,11 @@ def test_annotations(self):
287287
eq("list[str]")
288288
eq("dict[str, int]")
289289
eq("set[str,]")
290+
eq("tuple[()]")
290291
eq("tuple[str, ...]")
291-
eq("tuple[(str, *types)]")
292+
eq("tuple[str, *types]")
292293
eq("tuple[str, int, (str, int)]")
293-
eq("tuple[(*int, str, str, (str, int))]")
294+
eq("tuple[*int, str, str, (str, int)]")
294295
eq("tuple[str, int, float, dict[str, int]]")
295296
eq("slice[0]")
296297
eq("slice[0:1]")
@@ -305,6 +306,21 @@ def test_annotations(self):
305306
eq("slice[1:2, 1]")
306307
eq("slice[1:2, 2, 3]")
307308
eq("slice[()]")
309+
# Note that `slice[*Ts]`, `slice[*Ts,]`, and `slice[(*Ts,)]` all have
310+
# the same AST, but only `slice[*Ts,]` passes this test, because that's
311+
# what the unparser produces.
312+
eq("slice[*Ts,]")
313+
eq("slice[1, *Ts]")
314+
eq("slice[*Ts, 2]")
315+
eq("slice[1, *Ts, 2]")
316+
eq("slice[*Ts, *Ts]")
317+
eq("slice[1, *Ts, *Ts]")
318+
eq("slice[*Ts, 1, *Ts]")
319+
eq("slice[*Ts, *Ts, 1]")
320+
eq("slice[1, *Ts, *Ts, 2]")
321+
eq("slice[1:2, *Ts]")
322+
eq("slice[*Ts, 1:2]")
323+
eq("slice[1:2, *Ts, 3:4]")
308324
eq("slice[a, b:c, d:e:f]")
309325
eq("slice[(x for x in a)]")
310326
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
@@ -403,6 +419,25 @@ def foo():
403419
def bar(arg: (yield)): pass
404420
"""))
405421

422+
def test_get_type_hints_on_func_with_variadic_arg(self):
423+
# `typing.get_type_hints` might break on a function with a variadic
424+
# annotation (e.g. `f(*args: *Ts)`) if `from __future__ import
425+
# annotations`, because it could try to evaluate `*Ts` as an expression,
426+
# which on its own isn't value syntax.
427+
namespace = self._exec_future(dedent("""\
428+
class StarredC: pass
429+
class C:
430+
def __iter__(self):
431+
yield StarredC()
432+
c = C()
433+
def f(*args: *c): pass
434+
import typing
435+
hints = typing.get_type_hints(f)
436+
"""))
437+
438+
hints = namespace.pop('hints')
439+
self.assertIsInstance(hints['args'], namespace['StarredC'])
440+
406441

407442
if __name__ == "__main__":
408443
unittest.main()

0 commit comments

Comments
 (0)