Skip to content

Commit 6740d17

Browse files
lysnikolaoupablogsal
authored andcommitted
bpo-40334: Produce better error messages on invalid targets (pythonGH-20106)
The following error messages get produced: - `cannot delete ...` for invalid `del` targets - `... is an illegal 'for' target` for invalid targets in for statements - `... is an illegal 'with' target` for invalid targets in with statements Additionally, a few `cut`s were added in various places before the invocation of the `invalid_*` rule, in order to speed things up. Co-authored-by: Pablo Galindo <[email protected]>
1 parent 268841c commit 6740d17

File tree

6 files changed

+1849
-1454
lines changed

6 files changed

+1849
-1454
lines changed

Grammar/python.gram

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ assignment[stmt_ty]:
9393
CHECK_VERSION(6, "Variable annotations syntax is", _Py_AnnAssign(a, b, c, 0, EXTRA)) }
9494
| a=(z=star_targets '=' { z })+ b=(yield_expr | star_expressions) !'=' tc=[TYPE_COMMENT] {
9595
_Py_Assign(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
96-
| a=single_target b=augassign c=(yield_expr | star_expressions) {
96+
| a=single_target b=augassign ~ c=(yield_expr | star_expressions) {
9797
_Py_AugAssign(a, b->kind, c, EXTRA) }
9898
| invalid_assignment
9999

@@ -121,7 +121,9 @@ yield_stmt[stmt_ty]: y=yield_expr { _Py_Expr(y, EXTRA) }
121121

122122
assert_stmt[stmt_ty]: 'assert' a=expression b=[',' z=expression { z }] { _Py_Assert(a, b, EXTRA) }
123123

124-
del_stmt[stmt_ty]: 'del' a=del_targets { _Py_Delete(a, EXTRA) }
124+
del_stmt[stmt_ty]:
125+
| 'del' a=del_targets &(';' | NEWLINE) { _Py_Delete(a, EXTRA) }
126+
| invalid_del_stmt
125127

126128
import_stmt[stmt_ty]: import_name | import_from
127129
import_name[stmt_ty]: 'import' a=dotted_as_names { _Py_Import(a, EXTRA) }
@@ -164,10 +166,11 @@ while_stmt[stmt_ty]:
164166
| 'while' a=named_expression ':' b=block c=[else_block] { _Py_While(a, b, c, EXTRA) }
165167

166168
for_stmt[stmt_ty]:
167-
| 'for' t=star_targets 'in' ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
169+
| 'for' t=star_targets 'in' ~ ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
168170
_Py_For(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA) }
169-
| ASYNC 'for' t=star_targets 'in' ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
171+
| ASYNC 'for' t=star_targets 'in' ~ ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
170172
CHECK_VERSION(5, "Async for loops are", _Py_AsyncFor(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
173+
| invalid_for_target
171174

172175
with_stmt[stmt_ty]:
173176
| 'with' '(' a=','.with_item+ ','? ')' ':' b=block {
@@ -179,7 +182,9 @@ with_stmt[stmt_ty]:
179182
| ASYNC 'with' a=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
180183
CHECK_VERSION(5, "Async with statements are", _Py_AsyncWith(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
181184
with_item[withitem_ty]:
182-
| e=expression o=['as' t=target { t }] { _Py_withitem(e, o, p->arena) }
185+
| e=expression 'as' t=target &(',' | ')' | ':') { _Py_withitem(e, t, p->arena) }
186+
| invalid_with_item
187+
| e=expression { _Py_withitem(e, NULL, p->arena) }
183188

184189
try_stmt[stmt_ty]:
185190
| 'try' ':' b=block f=finally_block { _Py_Try(b, NULL, NULL, f, EXTRA) }
@@ -311,7 +316,7 @@ star_named_expression[expr_ty]:
311316
| '*' a=bitwise_or { _Py_Starred(a, Load, EXTRA) }
312317
| named_expression
313318
named_expression[expr_ty]:
314-
| a=NAME ':=' b=expression { _Py_NamedExpr(CHECK(_PyPegen_set_expr_context(p, a, Store)), b, EXTRA) }
319+
| a=NAME ':=' ~ b=expression { _Py_NamedExpr(CHECK(_PyPegen_set_expr_context(p, a, Store)), b, EXTRA) }
315320
| expression !':='
316321
| invalid_named_expression
317322

@@ -487,18 +492,20 @@ strings[expr_ty] (memo): a=STRING+ { _PyPegen_concatenate_strings(p, a) }
487492
list[expr_ty]:
488493
| '[' a=[star_named_expressions] ']' { _Py_List(a, Load, EXTRA) }
489494
listcomp[expr_ty]:
490-
| '[' a=named_expression b=for_if_clauses ']' { _Py_ListComp(a, b, EXTRA) }
495+
| '[' a=named_expression ~ b=for_if_clauses ']' { _Py_ListComp(a, b, EXTRA) }
491496
| invalid_comprehension
492497
tuple[expr_ty]:
493498
| '(' a=[y=star_named_expression ',' z=[star_named_expressions] { _PyPegen_seq_insert_in_front(p, y, z) } ] ')' {
494499
_Py_Tuple(a, Load, EXTRA) }
495-
group[expr_ty]: '(' a=(yield_expr | named_expression) ')' { a }
500+
group[expr_ty]:
501+
| '(' a=(yield_expr | named_expression) ')' { a }
502+
| invalid_group
496503
genexp[expr_ty]:
497-
| '(' a=expression b=for_if_clauses ')' { _Py_GeneratorExp(a, b, EXTRA) }
504+
| '(' a=expression ~ b=for_if_clauses ')' { _Py_GeneratorExp(a, b, EXTRA) }
498505
| invalid_comprehension
499506
set[expr_ty]: '{' a=expressions_list '}' { _Py_Set(a, EXTRA) }
500507
setcomp[expr_ty]:
501-
| '{' a=expression b=for_if_clauses '}' { _Py_SetComp(a, b, EXTRA) }
508+
| '{' a=expression ~ b=for_if_clauses '}' { _Py_SetComp(a, b, EXTRA) }
502509
| invalid_comprehension
503510
dict[expr_ty]:
504511
| '{' a=[double_starred_kvpairs] '}' {
@@ -514,10 +521,11 @@ kvpair[KeyValuePair*]: a=expression ':' b=expression { _PyPegen_key_value_pair(p
514521
for_if_clauses[asdl_seq*]:
515522
| for_if_clause+
516523
for_if_clause[comprehension_ty]:
517-
| ASYNC 'for' a=star_targets 'in' b=disjunction c=('if' z=disjunction { z })* {
524+
| ASYNC 'for' a=star_targets 'in' ~ b=disjunction c=('if' z=disjunction { z })* {
518525
CHECK_VERSION(6, "Async comprehensions are", _Py_comprehension(a, b, c, 1, p->arena)) }
519-
| 'for' a=star_targets 'in' b=disjunction c=('if' z=disjunction { z })* {
526+
| 'for' a=star_targets 'in' ~ b=disjunction c=('if' z=disjunction { z })* {
520527
_Py_comprehension(a, b, c, 0, p->arena) }
528+
| invalid_for_target
521529

522530
yield_expr[expr_ty]:
523531
| 'yield' 'from' a=expression { _Py_YieldFrom(a, EXTRA) }
@@ -587,19 +595,15 @@ single_subscript_attribute_target[expr_ty]:
587595
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
588596

589597
del_targets[asdl_seq*]: a=','.del_target+ [','] { a }
590-
# The lookaheads to del_target_end ensure that we don't match expressions where a prefix of the
591-
# expression matches our rule, thereby letting these cases fall through to invalid_del_target.
592598
del_target[expr_ty] (memo):
593-
| a=t_primary '.' b=NAME &del_target_end { _Py_Attribute(a, b->v.Name.id, Del, EXTRA) }
594-
| a=t_primary '[' b=slices ']' &del_target_end { _Py_Subscript(a, b, Del, EXTRA) }
599+
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Del, EXTRA) }
600+
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Del, EXTRA) }
595601
| del_t_atom
596602
del_t_atom[expr_ty]:
597-
| a=NAME &del_target_end { _PyPegen_set_expr_context(p, a, Del) }
603+
| a=NAME { _PyPegen_set_expr_context(p, a, Del) }
598604
| '(' a=del_target ')' { _PyPegen_set_expr_context(p, a, Del) }
599605
| '(' a=[del_targets] ')' { _Py_Tuple(a, Del, EXTRA) }
600606
| '[' a=[del_targets] ']' { _Py_List(a, Del, EXTRA) }
601-
| invalid_del_target
602-
del_target_end: ')' | ']' | ',' | ';' | NEWLINE
603607

604608
targets[asdl_seq*]: a=','.target+ [','] { a }
605609
target[expr_ty] (memo):
@@ -650,16 +654,23 @@ invalid_assignment:
650654
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "illegal target for annotation") }
651655
| (star_targets '=')* a=star_expressions '=' {
652656
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
653-
_PyPegen_get_invalid_target(a),
654-
"cannot assign to %s", _PyPegen_get_expr_name(_PyPegen_get_invalid_target(a))) }
657+
GET_INVALID_TARGET(a),
658+
"cannot assign to %s", _PyPegen_get_expr_name(GET_INVALID_TARGET(a))) }
655659
| (star_targets '=')* a=yield_expr '=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "assignment to yield expression not possible") }
656660
| a=star_expressions augassign (yield_expr | star_expressions) {
657661
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
658662
a,
659663
"'%s' is an illegal expression for augmented assignment",
660664
_PyPegen_get_expr_name(a)
661665
)}
662-
666+
invalid_del_stmt:
667+
| 'del' a=star_expressions {
668+
GET_INVALID_DEL_TARGET(a) != NULL ?
669+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
670+
GET_INVALID_DEL_TARGET(a),
671+
"cannot delete %s", _PyPegen_get_expr_name(GET_INVALID_DEL_TARGET(a))
672+
) :
673+
RAISE_SYNTAX_ERROR("invalid syntax") }
663674
invalid_block:
664675
| NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block") }
665676
invalid_comprehension:
@@ -682,9 +693,25 @@ invalid_lambda_star_etc:
682693
invalid_double_type_comments:
683694
| TYPE_COMMENT NEWLINE TYPE_COMMENT NEWLINE INDENT {
684695
RAISE_SYNTAX_ERROR("Cannot have two type comments on def") }
685-
invalid_del_target:
686-
| a=star_expression &del_target_end {
687-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot delete %s", _PyPegen_get_expr_name(a)) }
696+
invalid_with_item:
697+
| expression 'as' a=expression {
698+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
699+
GET_INVALID_TARGET(a),
700+
"cannot assign to %s", _PyPegen_get_expr_name(GET_INVALID_TARGET(a))
701+
) }
702+
703+
invalid_for_target:
704+
| ASYNC? 'for' a=star_expressions {
705+
GET_INVALID_FOR_TARGET(a) != NULL ?
706+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
707+
GET_INVALID_FOR_TARGET(a),
708+
"cannot assign to %s", _PyPegen_get_expr_name(GET_INVALID_FOR_TARGET(a))
709+
) :
710+
RAISE_SYNTAX_ERROR("invalid syntax") }
711+
712+
invalid_group:
713+
| '(' a=starred_expression ')' {
714+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "can't use starred expression here") }
688715
invalid_import_from_targets:
689716
| import_from_as_names ',' {
690717
RAISE_SYNTAX_ERROR("trailing comma not allowed without surrounding parentheses") }

Lib/test/test_exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ def baz():
251251
check('def f():\n x, y: int', 2, 3)
252252
check('[*x for x in xs]', 1, 2)
253253
check('foo(x for x in range(10), 100)', 1, 5)
254+
check('for 1 in []: pass', 1, 5)
254255
check('(yield i) = 2', 1, 2)
255256
check('def f(*):\n pass', 1, 8)
256-
check('for 1 in []: pass', 1, 7)
257257

258258
@cpython_only
259259
def testSettingException(self):

Lib/test/test_syntax.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,65 @@
164164
Traceback (most recent call last):
165165
SyntaxError: 'list' is an illegal expression for augmented assignment
166166
167+
Invalid targets in `for` loops and `with` statements should also
168+
produce a specialized error message
169+
170+
>>> for a() in b: pass
171+
Traceback (most recent call last):
172+
SyntaxError: cannot assign to function call
173+
174+
>>> for (a, b()) in b: pass
175+
Traceback (most recent call last):
176+
SyntaxError: cannot assign to function call
177+
178+
>>> for [a, b()] in b: pass
179+
Traceback (most recent call last):
180+
SyntaxError: cannot assign to function call
181+
182+
>>> for (*a, b, c+1) in b: pass
183+
Traceback (most recent call last):
184+
SyntaxError: cannot assign to operator
185+
186+
>>> for (x, *(y, z.d())) in b: pass
187+
Traceback (most recent call last):
188+
SyntaxError: cannot assign to function call
189+
190+
>>> for a, b() in c: pass
191+
Traceback (most recent call last):
192+
SyntaxError: cannot assign to function call
193+
194+
>>> for a, b, (c + 1, d()): pass
195+
Traceback (most recent call last):
196+
SyntaxError: cannot assign to operator
197+
198+
>>> for i < (): pass
199+
Traceback (most recent call last):
200+
SyntaxError: invalid syntax
201+
202+
>>> with a as b(): pass
203+
Traceback (most recent call last):
204+
SyntaxError: cannot assign to function call
205+
206+
>>> with a as (b, c()): pass
207+
Traceback (most recent call last):
208+
SyntaxError: cannot assign to function call
209+
210+
>>> with a as [b, c()]: pass
211+
Traceback (most recent call last):
212+
SyntaxError: cannot assign to function call
213+
214+
>>> with a as (*b, c, d+1): pass
215+
Traceback (most recent call last):
216+
SyntaxError: cannot assign to operator
217+
218+
>>> with a as (x, *(y, z.d())): pass
219+
Traceback (most recent call last):
220+
SyntaxError: cannot assign to function call
221+
222+
>>> with a as b, c as d(): pass
223+
Traceback (most recent call last):
224+
SyntaxError: cannot assign to function call
225+
167226
>>> p = p =
168227
Traceback (most recent call last):
169228
SyntaxError: invalid syntax
@@ -739,7 +798,7 @@ def test_assign_del(self):
739798
self._check_error("del (1, 2)", "delete literal")
740799
self._check_error("del None", "delete None")
741800
self._check_error("del *x", "delete starred")
742-
self._check_error("del (*x)", "delete starred")
801+
self._check_error("del (*x)", "use starred expression")
743802
self._check_error("del (*x,)", "delete starred")
744803
self._check_error("del [*x,]", "delete starred")
745804
self._check_error("del f()", "delete function call")

0 commit comments

Comments
 (0)