Skip to content

Commit bcc9a5d

Browse files
sergey-miryanovpablogsalStanFromIrelandpicnixz
authored
gh-129515: Clarify syntax error messages for conditional expressions (#129880)
Co-authored-by: Pablo Galindo Salgado <[email protected]> Co-authored-by: Stan Ulbrych <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]>
1 parent 51d4bf1 commit bcc9a5d

File tree

5 files changed

+1650
-1256
lines changed

5 files changed

+1650
-1256
lines changed

Doc/whatsnew/3.14.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,31 @@ Improved error messages
175175
ValueError: too many values to unpack (expected 3, got 4)
176176
177177
178+
* If a statement (:keyword:`pass`, :keyword:`del`, :keyword:`return`,
179+
:keyword:`yield`, :keyword:`raise`, :keyword:`break`, :keyword:`continue`,
180+
:keyword:`assert`, :keyword:`import`, :keyword:`from`) is passed to the
181+
:ref:`if_expr` after :keyword:`else`, or one of :keyword:`pass`,
182+
:keyword:`break`, or :keyword:`continue` is passed before :keyword:`if`, then the
183+
error message highlights where the :token:`~python-grammar:expression` is
184+
required. (Contributed by Sergey Miryanov in :gh:`129515`.)
185+
186+
.. code-block:: pycon
187+
188+
>>> x = 1 if True else pass
189+
Traceback (most recent call last):
190+
File "<string>", line 1
191+
x = 1 if True else pass
192+
^^^^
193+
SyntaxError: expected expression after 'else', but statement is given
194+
195+
>>> x = continue if True else break
196+
Traceback (most recent call last):
197+
File "<string>", line 1
198+
x = continue if True else break
199+
^^^^^^^^
200+
SyntaxError: expected expression before 'if', but statement is given
201+
202+
178203
* When incorrectly closed strings are detected, the error message suggests
179204
that the string may be intended to be part of the string. (Contributed by
180205
Pablo Galindo in :gh:`88535`.)

Grammar/python.gram

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ simple_stmt[stmt_ty] (memo):
117117
| &'return' return_stmt
118118
| &('import' | 'from') import_stmt
119119
| &'raise' raise_stmt
120-
| 'pass' { _PyAST_Pass(EXTRA) }
120+
| &'pass' pass_stmt
121121
| &'del' del_stmt
122122
| &'yield' yield_stmt
123123
| &'assert' assert_stmt
124-
| 'break' { _PyAST_Break(EXTRA) }
125-
| 'continue' { _PyAST_Continue(EXTRA) }
124+
| &'break' break_stmt
125+
| &'continue' continue_stmt
126126
| &'global' global_stmt
127127
| &'nonlocal' nonlocal_stmt
128128

@@ -181,6 +181,15 @@ raise_stmt[stmt_ty]:
181181
| 'raise' a=expression b=['from' z=expression { z }] { _PyAST_Raise(a, b, EXTRA) }
182182
| 'raise' { _PyAST_Raise(NULL, NULL, EXTRA) }
183183

184+
pass_stmt[stmt_ty]:
185+
| 'pass' { _PyAST_Pass(EXTRA) }
186+
187+
break_stmt[stmt_ty]:
188+
| 'break' { _PyAST_Break(EXTRA) }
189+
190+
continue_stmt[stmt_ty]:
191+
| 'continue' { _PyAST_Continue(EXTRA) }
192+
184193
global_stmt[stmt_ty]: 'global' a[asdl_expr_seq*]=','.NAME+ {
185194
_PyAST_Global(CHECK(asdl_identifier_seq*, _PyPegen_map_names_to_ids(p, a)), EXTRA) }
186195

@@ -1187,6 +1196,10 @@ invalid_expression:
11871196
_PyPegen_check_legacy_stmt(p, a) ? NULL : p->tokens[p->mark-1]->level == 0 ? NULL :
11881197
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
11891198
| a=disjunction 'if' b=disjunction !('else'|':') { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "expected 'else' after 'if' expression") }
1199+
| a=disjunction 'if' b=disjunction 'else' !expression {
1200+
RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("expected expression after 'else', but statement is given") }
1201+
| a[stmt_ty]=(pass_stmt|break_stmt|continue_stmt) 'if' b=disjunction 'else' c=simple_stmt {
1202+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION (a, "expected expression before 'if', but statement is given") }
11901203
| a='lambda' [lambda_params] b=':' &FSTRING_MIDDLE {
11911204
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "f-string: lambda expressions are not allowed without parentheses") }
11921205

Lib/test/test_syntax.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@
168168
Traceback (most recent call last):
169169
SyntaxError: expected 'else' after 'if' expression
170170
171+
>>> x = 1 if 1 else pass
172+
Traceback (most recent call last):
173+
SyntaxError: expected expression after 'else', but statement is given
174+
175+
>>> x = pass if 1 else 1
176+
Traceback (most recent call last):
177+
SyntaxError: expected expression before 'if', but statement is given
178+
179+
>>> x = pass if 1 else pass
180+
Traceback (most recent call last):
181+
SyntaxError: expected expression before 'if', but statement is given
182+
171183
>>> if True:
172184
... print("Hello"
173185
...
@@ -2863,6 +2875,44 @@ def test_match_stmt_invalid_as_expr(self):
28632875
end_offset=15 + len("obj.attr"),
28642876
)
28652877

2878+
def test_ifexp_else_stmt(self):
2879+
msg = "expected expression after 'else', but statement is given"
2880+
2881+
for stmt in [
2882+
"pass",
2883+
"return",
2884+
"return 2",
2885+
"raise Exception('a')",
2886+
"del a",
2887+
"yield 2",
2888+
"assert False",
2889+
"break",
2890+
"continue",
2891+
"import",
2892+
"import ast",
2893+
"from",
2894+
"from ast import *"
2895+
]:
2896+
self._check_error(f"x = 1 if 1 else {stmt}", msg)
2897+
2898+
def test_ifexp_body_stmt_else_expression(self):
2899+
msg = "expected expression before 'if', but statement is given"
2900+
2901+
for stmt in [
2902+
"pass",
2903+
"break",
2904+
"continue"
2905+
]:
2906+
self._check_error(f"x = {stmt} if 1 else 1", msg)
2907+
2908+
def test_ifexp_body_stmt_else_stmt(self):
2909+
msg = "expected expression before 'if', but statement is given"
2910+
for lhs_stmt, rhs_stmt in [
2911+
("pass", "pass"),
2912+
("break", "pass"),
2913+
("continue", "import ast")
2914+
]:
2915+
self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg)
28662916

28672917
def load_tests(loader, tests, pattern):
28682918
tests.addTest(doctest.DocTestSuite())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Clarify syntax error messages for conditional expressions when a statement
2+
is specified before an :keyword:`if` or after an :keyword:`else` keyword.

0 commit comments

Comments
 (0)