Skip to content

Commit 3e0a6f3

Browse files
authored
bpo-40334: Add support for feature_version in new PEG parser (GH-19827)
`ast.parse` and `compile` support a `feature_version` parameter that tells the parser to parse the input string, as if it were written in an older Python version. The `feature_version` is propagated to the tokenizer, which uses it to handle the three different stages of support for `async` and `await`. Additionally, it disallows the following at parser level: - The '@' operator in < 3.5 - Async functions in < 3.5 - Async comprehensions in < 3.6 - Underscores in numeric literals in < 3.6 - Await expression in < 3.5 - Variable annotations in < 3.6 - Async for-loops in < 3.5 - Async with-statements in < 3.5 - F-strings in < 3.6 Closes we-like-parsers#124.
1 parent eb0d359 commit 3e0a6f3

File tree

6 files changed

+1380
-835
lines changed

6 files changed

+1380
-835
lines changed

‎Grammar/python.gram‎

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,30 +80,34 @@ compound_stmt[stmt_ty]:
8080
# NOTE: annotated_rhs may start with 'yield'; yield_expr must start with 'yield'
8181
assignment:
8282
| a=NAME ':' b=expression c=['=' d=annotated_rhs { d }] {
83-
_Py_AnnAssign(CHECK(_PyPegen_set_expr_context(p, a, Store)), b, c, 1, EXTRA) }
83+
CHECK_VERSION(
84+
6,
85+
"Variable annotation syntax is",
86+
_Py_AnnAssign(CHECK(_PyPegen_set_expr_context(p, a, Store)), b, c, 1, EXTRA)
87+
) }
8488
| a=('(' b=inside_paren_ann_assign_target ')' { b }
8589
| ann_assign_subscript_attribute_target) ':' b=expression c=['=' d=annotated_rhs { d }] {
86-
_Py_AnnAssign(a, b, c, 0, EXTRA)}
90+
CHECK_VERSION(6, "Variable annotations syntax is", _Py_AnnAssign(a, b, c, 0, EXTRA)) }
8791
| a=(z=star_targets '=' { z })+ b=(yield_expr | star_expressions) tc=[TYPE_COMMENT] {
8892
_Py_Assign(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
8993
| a=target b=augassign c=(yield_expr | star_expressions) {
9094
_Py_AugAssign(a, b->kind, c, EXTRA) }
9195
| invalid_assignment
9296

9397
augassign[AugOperator*]:
94-
| '+=' {_PyPegen_augoperator(p, Add)}
95-
| '-=' {_PyPegen_augoperator(p, Sub)}
96-
| '*=' {_PyPegen_augoperator(p, Mult)}
97-
| '@=' {_PyPegen_augoperator(p, MatMult)}
98-
| '/=' {_PyPegen_augoperator(p, Div)}
99-
| '%=' {_PyPegen_augoperator(p, Mod)}
100-
| '&=' {_PyPegen_augoperator(p, BitAnd)}
101-
| '|=' {_PyPegen_augoperator(p, BitOr)}
102-
| '^=' {_PyPegen_augoperator(p, BitXor)}
103-
| '<<=' {_PyPegen_augoperator(p, LShift)}
104-
| '>>=' {_PyPegen_augoperator(p, RShift)}
105-
| '**=' {_PyPegen_augoperator(p, Pow)}
106-
| '//=' {_PyPegen_augoperator(p, FloorDiv)}
98+
| '+=' { _PyPegen_augoperator(p, Add) }
99+
| '-=' { _PyPegen_augoperator(p, Sub) }
100+
| '*=' { _PyPegen_augoperator(p, Mult) }
101+
| '@=' { CHECK_VERSION(5, "The '@' operator is", _PyPegen_augoperator(p, MatMult)) }
102+
| '/=' { _PyPegen_augoperator(p, Div) }
103+
| '%=' { _PyPegen_augoperator(p, Mod) }
104+
| '&=' { _PyPegen_augoperator(p, BitAnd) }
105+
| '|=' { _PyPegen_augoperator(p, BitOr) }
106+
| '^=' { _PyPegen_augoperator(p, BitXor) }
107+
| '<<=' { _PyPegen_augoperator(p, LShift) }
108+
| '>>=' { _PyPegen_augoperator(p, RShift) }
109+
| '**=' { _PyPegen_augoperator(p, Pow) }
110+
| '//=' { _PyPegen_augoperator(p, FloorDiv) }
107111

108112
global_stmt[stmt_ty]: 'global' a=','.NAME+ {
109113
_Py_Global(CHECK(_PyPegen_map_names_to_ids(p, a)), EXTRA) }
@@ -156,14 +160,20 @@ while_stmt[stmt_ty]:
156160
| 'while' a=named_expression ':' b=block c=[else_block] { _Py_While(a, b, c, EXTRA) }
157161

158162
for_stmt[stmt_ty]:
159-
| is_async=[ASYNC] 'for' t=star_targets 'in' ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
160-
(is_async ? _Py_AsyncFor : _Py_For)(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA) }
163+
| 'for' t=star_targets 'in' ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
164+
_Py_For(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA) }
165+
| ASYNC 'for' t=star_targets 'in' ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
166+
CHECK_VERSION(5, "Async for loops are", _Py_AsyncFor(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
161167

162168
with_stmt[stmt_ty]:
163-
| is_async=[ASYNC] 'with' '(' a=','.with_item+ ')' ':' b=block {
164-
(is_async ? _Py_AsyncWith : _Py_With)(a, b, NULL, EXTRA) }
165-
| is_async=[ASYNC] 'with' a=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
166-
(is_async ? _Py_AsyncWith : _Py_With)(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
169+
| 'with' '(' a=','.with_item+ ')' ':' b=block {
170+
_Py_With(a, b, NULL, EXTRA) }
171+
| 'with' a=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
172+
_Py_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
173+
| ASYNC 'with' '(' a=','.with_item+ ')' ':' b=block {
174+
CHECK_VERSION(5, "Async with statements are", _Py_AsyncWith(a, b, NULL, EXTRA)) }
175+
| ASYNC 'with' a=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
176+
CHECK_VERSION(5, "Async with statements are", _Py_AsyncWith(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
167177
with_item[withitem_ty]:
168178
| e=expression o=['as' t=target { t }] { _Py_withitem(e, o, p->arena) }
169179

@@ -188,10 +198,18 @@ function_def[stmt_ty]:
188198
| function_def_raw
189199

190200
function_def_raw[stmt_ty]:
191-
| is_async=[ASYNC] 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
192-
(is_async ? _Py_AsyncFunctionDef : _Py_FunctionDef)(n->v.Name.id,
193-
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
194-
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA) }
201+
| 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
202+
_Py_FunctionDef(n->v.Name.id,
203+
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
204+
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA) }
205+
| ASYNC 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
206+
CHECK_VERSION(
207+
5,
208+
"Async functions are",
209+
_Py_AsyncFunctionDef(n->v.Name.id,
210+
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
211+
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA)
212+
) }
195213
func_type_comment[PyObject*]:
196214
| NEWLINE t=TYPE_COMMENT &(NEWLINE INDENT) { t } # Must be followed by indented block
197215
| invalid_double_type_comments
@@ -399,7 +417,7 @@ term[expr_ty]:
399417
| a=term '/' b=factor { _Py_BinOp(a, Div, b, EXTRA) }
400418
| a=term '//' b=factor { _Py_BinOp(a, FloorDiv, b, EXTRA) }
401419
| a=term '%' b=factor { _Py_BinOp(a, Mod, b, EXTRA) }
402-
| a=term '@' b=factor { _Py_BinOp(a, MatMult, b, EXTRA) }
420+
| a=term '@' b=factor { CHECK_VERSION(5, "The '@' operator is", _Py_BinOp(a, MatMult, b, EXTRA)) }
403421
| factor
404422
factor[expr_ty] (memo):
405423
| '+' a=factor { _Py_UnaryOp(UAdd, a, EXTRA) }
@@ -410,7 +428,7 @@ power[expr_ty]:
410428
| a=await_primary '**' b=factor { _Py_BinOp(a, Pow, b, EXTRA) }
411429
| await_primary
412430
await_primary[expr_ty] (memo):
413-
| AWAIT a=primary { _Py_Await(a, EXTRA) }
431+
| AWAIT a=primary { CHECK_VERSION(5, "Await expressions are", _Py_Await(a, EXTRA)) }
414432
| primary
415433
primary[expr_ty]:
416434
| a=primary '.' b=NAME { _Py_Attribute(a, b->v.Name.id, Load, EXTRA) }
@@ -469,8 +487,12 @@ kvpair[KeyValuePair*]:
469487
| '**' a=bitwise_or { _PyPegen_key_value_pair(p, NULL, a) }
470488
| a=expression ':' b=expression { _PyPegen_key_value_pair(p, a, b) }
471489
for_if_clauses[asdl_seq*]:
472-
| a=(y=[ASYNC] 'for' a=star_targets 'in' b=disjunction c=('if' z=disjunction { z })*
473-
{ _Py_comprehension(a, b, c, y != NULL, p->arena) })+ { a }
490+
| for_if_clause+
491+
for_if_clause[comprehension_ty]:
492+
| ASYNC 'for' a=star_targets 'in' b=disjunction c=('if' z=disjunction { z })* {
493+
CHECK_VERSION(6, "Async comprehensions are", _Py_comprehension(a, b, c, 1, p->arena)) }
494+
| 'for' a=star_targets 'in' b=disjunction c=('if' z=disjunction { z })* {
495+
_Py_comprehension(a, b, c, 0, p->arena) }
474496

475497
yield_expr[expr_ty]:
476498
| 'yield' 'from' a=expression { _Py_YieldFrom(a, EXTRA) }

‎Lib/test/test_type_comments.py‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ def test_funcdef(self):
252252
self.assertEqual(tree.body[0].type_comment, None)
253253
self.assertEqual(tree.body[1].type_comment, None)
254254

255-
@support.skip_if_new_parser("Pegen does not support feature_version yet")
256255
def test_asyncdef(self):
257256
for tree in self.parse_all(asyncdef, minver=5):
258257
self.assertEqual(tree.body[0].type_comment, "() -> int")
@@ -261,27 +260,22 @@ def test_asyncdef(self):
261260
self.assertEqual(tree.body[0].type_comment, None)
262261
self.assertEqual(tree.body[1].type_comment, None)
263262

264-
@support.skip_if_new_parser("Pegen does not support feature_version yet")
265263
def test_asyncvar(self):
266264
for tree in self.parse_all(asyncvar, maxver=6):
267265
pass
268266

269-
@support.skip_if_new_parser("Pegen does not support feature_version yet")
270267
def test_asynccomp(self):
271268
for tree in self.parse_all(asynccomp, minver=6):
272269
pass
273270

274-
@support.skip_if_new_parser("Pegen does not support feature_version yet")
275271
def test_matmul(self):
276272
for tree in self.parse_all(matmul, minver=5):
277273
pass
278274

279-
@support.skip_if_new_parser("Pegen does not support feature_version yet")
280275
def test_fstring(self):
281276
for tree in self.parse_all(fstring, minver=6):
282277
pass
283278

284-
@support.skip_if_new_parser("Pegen does not support feature_version yet")
285279
def test_underscorednumber(self):
286280
for tree in self.parse_all(underscorednumber, minver=6):
287281
pass

0 commit comments

Comments
 (0)