Skip to content

Commit b86ed8e

Browse files
bpo-43797: Improve syntax error for invalid comparisons (#25317)
* bpo-43797: Improve syntax error for invalid comparisons * Update Lib/test/test_fstring.py Co-authored-by: Guido van Rossum <[email protected]> * Apply review comments * can't -> cannot Co-authored-by: Guido van Rossum <[email protected]>
1 parent 2459b92 commit b86ed8e

File tree

12 files changed

+1261
-665
lines changed

12 files changed

+1261
-665
lines changed

Grammar/python.gram

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,20 @@ dotted_name[expr_ty]:
163163
| NAME
164164

165165
if_stmt[stmt_ty]:
166-
| 'if' a=named_expression &&':' b=block c=elif_stmt {
166+
| 'if' a=named_expression ':' b=block c=elif_stmt {
167167
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
168-
| 'if' a=named_expression &&':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
168+
| 'if' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
169+
| invalid_if_stmt
169170
elif_stmt[stmt_ty]:
170-
| 'elif' a=named_expression &&':' b=block c=elif_stmt {
171+
| 'elif' a=named_expression ':' b=block c=elif_stmt {
171172
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
172-
| 'elif' a=named_expression &&':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
173+
| 'elif' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
174+
| invalid_elif_stmt
173175
else_block[asdl_stmt_seq*]: 'else' &&':' b=block { b }
174176

175177
while_stmt[stmt_ty]:
176-
| 'while' a=named_expression &&':' b=block c=[else_block] { _PyAST_While(a, b, c, EXTRA) }
178+
| 'while' a=named_expression ':' b=block c=[else_block] { _PyAST_While(a, b, c, EXTRA) }
179+
| invalid_while_stmt
177180

178181
for_stmt[stmt_ty]:
179182
| 'for' t=star_targets 'in' ~ ex=star_expressions &&':' tc=[TYPE_COMMENT] b=block el=[else_block] {
@@ -438,10 +441,11 @@ star_named_expressions[asdl_expr_seq*]: a[asdl_expr_seq*]=','.star_named_express
438441
star_named_expression[expr_ty]:
439442
| '*' a=bitwise_or { _PyAST_Starred(a, Load, EXTRA) }
440443
| named_expression
444+
441445
named_expression[expr_ty]:
442446
| a=NAME ':=' ~ b=expression { _PyAST_NamedExpr(CHECK(expr_ty, _PyPegen_set_expr_context(p, a, Store)), b, EXTRA) }
443-
| expression !':='
444447
| invalid_named_expression
448+
| expression !':='
445449

446450
annotated_rhs[expr_ty]: yield_expr | star_expressions
447451

@@ -772,6 +776,12 @@ invalid_named_expression:
772776
| a=expression ':=' expression {
773777
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
774778
a, "cannot use assignment expressions with %s", _PyPegen_get_expr_name(a)) }
779+
| a=NAME b='=' bitwise_or !('='|':='|',') {
780+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?") }
781+
| !(list|tuple|genexp|'True'|'None'|'False') a=bitwise_or b='=' bitwise_or !('='|':='|',') {
782+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(b, "cannot assign to %s here. Maybe you meant '==' instead of '='?",
783+
_PyPegen_get_expr_name(a)) }
784+
775785
invalid_assignment:
776786
| a=invalid_ann_assign_target ':' expression {
777787
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
@@ -841,9 +851,9 @@ invalid_for_target:
841851

842852
invalid_group:
843853
| '(' a=starred_expression ')' {
844-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "can't use starred expression here") }
854+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use starred expression here") }
845855
| '(' a='**' expression ')' {
846-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "can't use double starred expression here") }
856+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use double starred expression here") }
847857
invalid_import_from_targets:
848858
| import_from_as_names ',' {
849859
RAISE_SYNTAX_ERROR("trailing comma not allowed without surrounding parentheses") }
@@ -860,6 +870,11 @@ invalid_except_block:
860870

861871
invalid_match_stmt:
862872
| "match" subject_expr !':' { CHECK_VERSION(void*, 10, "Pattern matching is", RAISE_SYNTAX_ERROR("expected ':'") ) }
863-
864873
invalid_case_block:
865874
| "case" patterns guard? !':' { RAISE_SYNTAX_ERROR("expected ':'") }
875+
invalid_if_stmt:
876+
| 'if' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
877+
invalid_elif_stmt:
878+
| 'elif' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
879+
invalid_while_stmt:
880+
| 'while' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }

Lib/test/test_cmd_line_script.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,8 @@ def test_syntaxerror_unindented_caret_position(self):
600600
script_name = _make_test_script(script_dir, 'script', script)
601601
exitcode, stdout, stderr = assert_python_failure(script_name)
602602
text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
603-
# Confirm that the caret is located under the first 1 character
604-
self.assertIn("\n 1 + 1 = 2\n ^", text)
603+
# Confirm that the caret is located under the '=' sign
604+
self.assertIn("\n 1 + 1 = 2\n ^\n", text)
605605

606606
def test_syntaxerror_indented_caret_position(self):
607607
script = textwrap.dedent("""\
@@ -613,7 +613,7 @@ def test_syntaxerror_indented_caret_position(self):
613613
exitcode, stdout, stderr = assert_python_failure(script_name)
614614
text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
615615
# Confirm that the caret is located under the first 1 character
616-
self.assertIn("\n 1 + 1 = 2\n ^", text)
616+
self.assertIn("\n 1 + 1 = 2\n ^\n", text)
617617

618618
# Try the same with a form feed at the start of the indented line
619619
script = (
@@ -624,7 +624,7 @@ def test_syntaxerror_indented_caret_position(self):
624624
exitcode, stdout, stderr = assert_python_failure(script_name)
625625
text = io.TextIOWrapper(io.BytesIO(stderr), "ascii").read()
626626
self.assertNotIn("\f", text)
627-
self.assertIn("\n 1 + 1 = 2\n ^", text)
627+
self.assertIn("\n 1 + 1 = 2\n ^\n", text)
628628

629629
def test_syntaxerror_multi_line_fstring(self):
630630
script = 'foo = f"""{}\nfoo"""\n'

Lib/test/test_codeop.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ def test_invalid(self):
275275
ai("a = 'a\\\n")
276276

277277
ai("a = 1","eval")
278-
ai("a = (","eval")
279278
ai("]","eval")
280279
ai("())","eval")
281280
ai("[}","eval")

Lib/test/test_exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def baz():
260260
check('[*x for x in xs]', 1, 2)
261261
check('foo(x for x in range(10), 100)', 1, 5)
262262
check('for 1 in []: pass', 1, 5)
263-
check('(yield i) = 2', 1, 2)
263+
check('(yield i) = 2', 1, 11)
264264
check('def f(*):\n pass', 1, 8)
265265

266266
@cpython_only

Lib/test/test_fstring.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ def test_conversions(self):
990990
])
991991

992992
def test_assignment(self):
993-
self.assertAllRaise(SyntaxError, 'invalid syntax',
993+
self.assertAllRaise(SyntaxError, r'invalid syntax',
994994
["f'' = 3",
995995
"f'{0}' = x",
996996
"f'{x}' = x",
@@ -1276,11 +1276,11 @@ def test_with_an_underscore_and_a_comma_in_format_specifier(self):
12761276
f'{1:_,}'
12771277

12781278
def test_syntax_error_for_starred_expressions(self):
1279-
error_msg = re.escape("can't use starred expression here")
1279+
error_msg = re.escape("cannot use starred expression here")
12801280
with self.assertRaisesRegex(SyntaxError, error_msg):
12811281
compile("f'{*a}'", "?", "exec")
12821282

1283-
error_msg = re.escape("can't use double starred expression here")
1283+
error_msg = re.escape("cannot use double starred expression here")
12841284
with self.assertRaisesRegex(SyntaxError, error_msg):
12851285
compile("f'{**a}'", "?", "exec")
12861286

Lib/test/test_generators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2013,7 +2013,7 @@ def printsolution(self, x):
20132013
>>> def f(): (yield bar) = y
20142014
Traceback (most recent call last):
20152015
...
2016-
SyntaxError: cannot assign to yield expression
2016+
SyntaxError: cannot assign to yield expression here. Maybe you meant '==' instead of '='?
20172017
20182018
>>> def f(): (yield bar) += y
20192019
Traceback (most recent call last):

Lib/test/test_genexps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
>>> dict(a = i for i in range(10))
104104
Traceback (most recent call last):
105105
...
106-
SyntaxError: invalid syntax
106+
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
107107
108108
Verify that parenthesis are required when used as a keyword argument value
109109

0 commit comments

Comments
 (0)