Skip to content

Commit 16ab070

Browse files
bpo-40334: Correctly identify invalid target in assignment errors (GH-20076)
Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com>
1 parent 7ba1f75 commit 16ab070

File tree

10 files changed

+136
-44
lines changed

10 files changed

+136
-44
lines changed

Grammar/python.gram

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,8 +640,17 @@ invalid_assignment:
640640
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "only single target (not tuple) can be annotated") }
641641
| a=expression ':' expression ['=' annotated_rhs] {
642642
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "illegal target for annotation") }
643-
| a=expression ('=' | augassign) (yield_expr | star_expressions) {
644-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot assign to %s", _PyPegen_get_expr_name(a)) }
643+
| a=star_expressions '=' (yield_expr | star_expressions) {
644+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
645+
_PyPegen_get_invalid_target(a),
646+
"cannot assign to %s", _PyPegen_get_expr_name(_PyPegen_get_invalid_target(a))) }
647+
| a=star_expressions augassign (yield_expr | star_expressions) {
648+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
649+
a,
650+
"'%s' is an illegal expression for augmented assignment",
651+
_PyPegen_get_expr_name(a)
652+
)}
653+
645654
invalid_block:
646655
| NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block") }
647656
invalid_comprehension:

Lib/test/test_dictcomps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def test_illegal_assignment(self):
7777
compile("{x: y for y, x in ((1, 2), (3, 4))} = 5", "<test>",
7878
"exec")
7979

80-
with self.assertRaisesRegex(SyntaxError, "cannot assign"):
80+
with self.assertRaisesRegex(SyntaxError, "illegal expression"):
8181
compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>",
8282
"exec")
8383

Lib/test/test_generators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1921,7 +1921,7 @@ def printsolution(self, x):
19211921
>>> def f(): (yield bar) += y
19221922
Traceback (most recent call last):
19231923
...
1924-
SyntaxError: cannot assign to yield expression
1924+
SyntaxError: 'yield expression' is an illegal expression for augmented assignment
19251925
19261926
19271927
Now check some throw() conditions:

Lib/test/test_genexps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@
158158
>>> (y for y in (1,2)) += 10
159159
Traceback (most recent call last):
160160
...
161-
SyntaxError: cannot assign to generator expression
161+
SyntaxError: 'generator expression' is an illegal expression for augmented assignment
162162
163163
164164
########### Tests borrowed from or inspired by test_generators.py ############

Lib/test/test_peg_parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ def f():
625625
("(a, b): int", "only single target (not tuple) can be annotated"),
626626
("[a, b]: int", "only single target (not list) can be annotated"),
627627
("a(): int", "illegal target for annotation"),
628-
("1 += 1", "cannot assign to literal"),
628+
("1 += 1", "'literal' is an illegal expression for augmented assignment"),
629629
("pass\n pass", "unexpected indent"),
630630
("def f():\npass", "expected an indented block"),
631631
("def f(*): pass", "named arguments must follow bare *"),

Lib/test/test_syntax.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,46 +100,53 @@
100100
This test just checks a couple of cases rather than enumerating all of
101101
them.
102102
103-
# All of the following also produce different error messages with pegen
104-
# >>> (a, "b", c) = (1, 2, 3)
105-
# Traceback (most recent call last):
106-
# SyntaxError: cannot assign to literal
103+
>>> (a, "b", c) = (1, 2, 3)
104+
Traceback (most recent call last):
105+
SyntaxError: cannot assign to literal
107106
108-
# >>> (a, True, c) = (1, 2, 3)
109-
# Traceback (most recent call last):
110-
# SyntaxError: cannot assign to True
107+
>>> (a, True, c) = (1, 2, 3)
108+
Traceback (most recent call last):
109+
SyntaxError: cannot assign to True
111110
112111
>>> (a, __debug__, c) = (1, 2, 3)
113112
Traceback (most recent call last):
114113
SyntaxError: cannot assign to __debug__
115114
116-
# >>> (a, *True, c) = (1, 2, 3)
117-
# Traceback (most recent call last):
118-
# SyntaxError: cannot assign to True
115+
>>> (a, *True, c) = (1, 2, 3)
116+
Traceback (most recent call last):
117+
SyntaxError: cannot assign to True
119118
120119
>>> (a, *__debug__, c) = (1, 2, 3)
121120
Traceback (most recent call last):
122121
SyntaxError: cannot assign to __debug__
123122
124-
# >>> [a, b, c + 1] = [1, 2, 3]
125-
# Traceback (most recent call last):
126-
# SyntaxError: cannot assign to operator
123+
>>> [a, b, c + 1] = [1, 2, 3]
124+
Traceback (most recent call last):
125+
SyntaxError: cannot assign to operator
126+
127+
>>> [a, b[1], c + 1] = [1, 2, 3]
128+
Traceback (most recent call last):
129+
SyntaxError: cannot assign to operator
130+
131+
>>> [a, b.c.d, c + 1] = [1, 2, 3]
132+
Traceback (most recent call last):
133+
SyntaxError: cannot assign to operator
127134
128135
>>> a if 1 else b = 1
129136
Traceback (most recent call last):
130137
SyntaxError: cannot assign to conditional expression
131138
132139
>>> a, b += 1, 2
133140
Traceback (most recent call last):
134-
SyntaxError: invalid syntax
141+
SyntaxError: 'tuple' is an illegal expression for augmented assignment
135142
136143
>>> (a, b) += 1, 2
137144
Traceback (most recent call last):
138-
SyntaxError: cannot assign to tuple
145+
SyntaxError: 'tuple' is an illegal expression for augmented assignment
139146
140147
>>> [a, b] += 1, 2
141148
Traceback (most recent call last):
142-
SyntaxError: cannot assign to list
149+
SyntaxError: 'list' is an illegal expression for augmented assignment
143150
144151
From compiler_complex_args():
145152
@@ -346,16 +353,16 @@
346353
347354
>>> (x for x in x) += 1
348355
Traceback (most recent call last):
349-
SyntaxError: cannot assign to generator expression
356+
SyntaxError: 'generator expression' is an illegal expression for augmented assignment
350357
>>> None += 1
351358
Traceback (most recent call last):
352-
SyntaxError: cannot assign to None
359+
SyntaxError: 'None' is an illegal expression for augmented assignment
353360
>>> __debug__ += 1
354361
Traceback (most recent call last):
355362
SyntaxError: cannot assign to __debug__
356363
>>> f() += 1
357364
Traceback (most recent call last):
358-
SyntaxError: cannot assign to function call
365+
SyntaxError: 'function call' is an illegal expression for augmented assignment
359366
360367
361368
Test continue in finally in weird combinations.
@@ -688,6 +695,7 @@ def _check_error(self, code, errtext,
688695
def test_assign_call(self):
689696
self._check_error("f() = 1", "assign")
690697

698+
@unittest.skipIf(support.use_old_parser(), "The old parser cannot generate these error messages")
691699
def test_assign_del(self):
692700
self._check_error("del (,)", "invalid syntax")
693701
self._check_error("del 1", "delete literal")

Parser/pegen/parse.c

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10747,7 +10747,8 @@ invalid_named_expression_rule(Parser *p)
1074710747
// | tuple ':'
1074810748
// | star_named_expression ',' star_named_expressions* ':'
1074910749
// | expression ':' expression ['=' annotated_rhs]
10750-
// | expression ('=' | augassign) (yield_expr | star_expressions)
10750+
// | star_expressions '=' (yield_expr | star_expressions)
10751+
// | star_expressions augassign (yield_expr | star_expressions)
1075110752
static void *
1075210753
invalid_assignment_rule(Parser *p)
1075310754
{
@@ -10841,19 +10842,40 @@ invalid_assignment_rule(Parser *p)
1084110842
}
1084210843
p->mark = _mark;
1084310844
}
10844-
{ // expression ('=' | augassign) (yield_expr | star_expressions)
10845+
{ // star_expressions '=' (yield_expr | star_expressions)
10846+
Token * _literal;
1084510847
void *_tmp_128_var;
10848+
expr_ty a;
10849+
if (
10850+
(a = star_expressions_rule(p)) // star_expressions
10851+
&&
10852+
(_literal = _PyPegen_expect_token(p, 22)) // token='='
10853+
&&
10854+
(_tmp_128_var = _tmp_128_rule(p)) // yield_expr | star_expressions
10855+
)
10856+
{
10857+
_res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( _PyPegen_get_invalid_target ( a ) , "cannot assign to %s" , _PyPegen_get_expr_name ( _PyPegen_get_invalid_target ( a ) ) );
10858+
if (_res == NULL && PyErr_Occurred()) {
10859+
p->error_indicator = 1;
10860+
return NULL;
10861+
}
10862+
goto done;
10863+
}
10864+
p->mark = _mark;
10865+
}
10866+
{ // star_expressions augassign (yield_expr | star_expressions)
1084610867
void *_tmp_129_var;
1084710868
expr_ty a;
10869+
AugOperator* augassign_var;
1084810870
if (
10849-
(a = expression_rule(p)) // expression
10871+
(a = star_expressions_rule(p)) // star_expressions
1085010872
&&
10851-
(_tmp_128_var = _tmp_128_rule(p)) // '=' | augassign
10873+
(augassign_var = augassign_rule(p)) // augassign
1085210874
&&
1085310875
(_tmp_129_var = _tmp_129_rule(p)) // yield_expr | star_expressions
1085410876
)
1085510877
{
10856-
_res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "cannot assign to %s" , _PyPegen_get_expr_name ( a ) );
10878+
_res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "'%s' is an illegal expression for augmented assignment" , _PyPegen_get_expr_name ( a ) );
1085710879
if (_res == NULL && PyErr_Occurred()) {
1085810880
p->error_indicator = 1;
1085910881
return NULL;
@@ -16675,7 +16697,7 @@ _tmp_127_rule(Parser *p)
1667516697
return _res;
1667616698
}
1667716699

16678-
// _tmp_128: '=' | augassign
16700+
// _tmp_128: yield_expr | star_expressions
1667916701
static void *
1668016702
_tmp_128_rule(Parser *p)
1668116703
{
@@ -16684,24 +16706,24 @@ _tmp_128_rule(Parser *p)
1668416706
}
1668516707
void * _res = NULL;
1668616708
int _mark = p->mark;
16687-
{ // '='
16688-
Token * _literal;
16709+
{ // yield_expr
16710+
expr_ty yield_expr_var;
1668916711
if (
16690-
(_literal = _PyPegen_expect_token(p, 22)) // token='='
16712+
(yield_expr_var = yield_expr_rule(p)) // yield_expr
1669116713
)
1669216714
{
16693-
_res = _literal;
16715+
_res = yield_expr_var;
1669416716
goto done;
1669516717
}
1669616718
p->mark = _mark;
1669716719
}
16698-
{ // augassign
16699-
AugOperator* augassign_var;
16720+
{ // star_expressions
16721+
expr_ty star_expressions_var;
1670016722
if (
16701-
(augassign_var = augassign_rule(p)) // augassign
16723+
(star_expressions_var = star_expressions_rule(p)) // star_expressions
1670216724
)
1670316725
{
16704-
_res = augassign_var;
16726+
_res = star_expressions_var;
1670516727
goto done;
1670616728
}
1670716729
p->mark = _mark;

Parser/pegen/pegen.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,3 +2054,49 @@ _PyPegen_make_module(Parser *p, asdl_seq *a) {
20542054
}
20552055
return Module(a, type_ignores, p->arena);
20562056
}
2057+
2058+
// Error reporting helpers
2059+
2060+
expr_ty
2061+
_PyPegen_get_invalid_target(expr_ty e)
2062+
{
2063+
if (e == NULL) {
2064+
return NULL;
2065+
}
2066+
2067+
#define VISIT_CONTAINER(CONTAINER, TYPE) do { \
2068+
Py_ssize_t len = asdl_seq_LEN(CONTAINER->v.TYPE.elts);\
2069+
for (Py_ssize_t i = 0; i < len; i++) {\
2070+
expr_ty other = asdl_seq_GET(CONTAINER->v.TYPE.elts, i);\
2071+
expr_ty child = _PyPegen_get_invalid_target(other);\
2072+
if (child != NULL) {\
2073+
return child;\
2074+
}\
2075+
}\
2076+
} while (0)
2077+
2078+
// We only need to visit List and Tuple nodes recursively as those
2079+
// are the only ones that can contain valid names in targets when
2080+
// they are parsed as expressions. Any other kind of expression
2081+
// that is a container (like Sets or Dicts) is directly invalid and
2082+
// we don't need to visit it recursively.
2083+
2084+
switch (e->kind) {
2085+
case List_kind: {
2086+
VISIT_CONTAINER(e, List);
2087+
return NULL;
2088+
}
2089+
case Tuple_kind: {
2090+
VISIT_CONTAINER(e, Tuple);
2091+
return NULL;
2092+
}
2093+
case Starred_kind:
2094+
return _PyPegen_get_invalid_target(e->v.Starred.value);
2095+
case Name_kind:
2096+
case Subscript_kind:
2097+
case Attribute_kind:
2098+
return NULL;
2099+
default:
2100+
return e;
2101+
}
2102+
}

Parser/pegen/pegen.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ void *_PyPegen_arguments_parsing_error(Parser *, expr_ty);
260260
int _PyPegen_check_barry_as_flufl(Parser *);
261261
mod_ty _PyPegen_make_module(Parser *, asdl_seq *);
262262

263+
// Error reporting helpers
264+
265+
expr_ty _PyPegen_get_invalid_target(expr_ty e);
266+
263267
void *_PyPegen_parse(Parser *);
264268

265269
#endif

Python/ast.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3164,10 +3164,7 @@ ast_for_expr_stmt(struct compiling *c, const node *n)
31643164
expr1 = ast_for_testlist(c, ch);
31653165
if (!expr1)
31663166
return NULL;
3167-
if(!set_context(c, expr1, Store, ch))
3168-
return NULL;
3169-
/* set_context checks that most expressions are not the left side.
3170-
Augmented assignments can only have a name, a subscript, or an
3167+
/* Augmented assignments can only have a name, a subscript, or an
31713168
attribute on the left, though, so we have to explicitly check for
31723169
those. */
31733170
switch (expr1->kind) {
@@ -3176,10 +3173,16 @@ ast_for_expr_stmt(struct compiling *c, const node *n)
31763173
case Subscript_kind:
31773174
break;
31783175
default:
3179-
ast_error(c, ch, "illegal expression for augmented assignment");
3176+
ast_error(c, ch, "'%s' is an illegal expression for augmented assignment",
3177+
get_expr_name(expr1));
31803178
return NULL;
31813179
}
31823180

3181+
/* set_context checks that most expressions are not the left side. */
3182+
if(!set_context(c, expr1, Store, ch)) {
3183+
return NULL;
3184+
}
3185+
31833186
ch = CHILD(n, 2);
31843187
if (TYPE(ch) == testlist)
31853188
expr2 = ast_for_testlist(c, ch);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy