gh-129515: Clarify syntax error messages for conditional expressions (#129880)

Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
Sergey Miryanov 2025-02-19 02:43:19 +05:00 committed by GitHub
parent 51d4bf1e0e
commit bcc9a5dddb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 2015 additions and 1621 deletions

View file

@ -175,6 +175,31 @@ Improved error messages
ValueError: too many values to unpack (expected 3, got 4)
* If a statement (:keyword:`pass`, :keyword:`del`, :keyword:`return`,
:keyword:`yield`, :keyword:`raise`, :keyword:`break`, :keyword:`continue`,
:keyword:`assert`, :keyword:`import`, :keyword:`from`) is passed to the
:ref:`if_expr` after :keyword:`else`, or one of :keyword:`pass`,
:keyword:`break`, or :keyword:`continue` is passed before :keyword:`if`, then the
error message highlights where the :token:`~python-grammar:expression` is
required. (Contributed by Sergey Miryanov in :gh:`129515`.)
.. code-block:: pycon
>>> x = 1 if True else pass
Traceback (most recent call last):
File "<string>", line 1
x = 1 if True else pass
^^^^
SyntaxError: expected expression after 'else', but statement is given
>>> x = continue if True else break
Traceback (most recent call last):
File "<string>", line 1
x = continue if True else break
^^^^^^^^
SyntaxError: expected expression before 'if', but statement is given
* When incorrectly closed strings are detected, the error message suggests
that the string may be intended to be part of the string. (Contributed by
Pablo Galindo in :gh:`88535`.)

View file

@ -117,12 +117,12 @@ simple_stmt[stmt_ty] (memo):
| &'return' return_stmt
| &('import' | 'from') import_stmt
| &'raise' raise_stmt
| 'pass' { _PyAST_Pass(EXTRA) }
| &'pass' pass_stmt
| &'del' del_stmt
| &'yield' yield_stmt
| &'assert' assert_stmt
| 'break' { _PyAST_Break(EXTRA) }
| 'continue' { _PyAST_Continue(EXTRA) }
| &'break' break_stmt
| &'continue' continue_stmt
| &'global' global_stmt
| &'nonlocal' nonlocal_stmt
@ -181,6 +181,15 @@ raise_stmt[stmt_ty]:
| 'raise' a=expression b=['from' z=expression { z }] { _PyAST_Raise(a, b, EXTRA) }
| 'raise' { _PyAST_Raise(NULL, NULL, EXTRA) }
pass_stmt[stmt_ty]:
| 'pass' { _PyAST_Pass(EXTRA) }
break_stmt[stmt_ty]:
| 'break' { _PyAST_Break(EXTRA) }
continue_stmt[stmt_ty]:
| 'continue' { _PyAST_Continue(EXTRA) }
global_stmt[stmt_ty]: 'global' a[asdl_expr_seq*]=','.NAME+ {
_PyAST_Global(CHECK(asdl_identifier_seq*, _PyPegen_map_names_to_ids(p, a)), EXTRA) }
@ -1187,6 +1196,10 @@ invalid_expression:
_PyPegen_check_legacy_stmt(p, a) ? NULL : p->tokens[p->mark-1]->level == 0 ? NULL :
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
| a=disjunction 'if' b=disjunction !('else'|':') { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "expected 'else' after 'if' expression") }
| a=disjunction 'if' b=disjunction 'else' !expression {
RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("expected expression after 'else', but statement is given") }
| a[stmt_ty]=(pass_stmt|break_stmt|continue_stmt) 'if' b=disjunction 'else' c=simple_stmt {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION (a, "expected expression before 'if', but statement is given") }
| a='lambda' [lambda_params] b=':' &FSTRING_MIDDLE {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "f-string: lambda expressions are not allowed without parentheses") }

View file

@ -168,6 +168,18 @@ SyntaxError: expected 'else' after 'if' expression
Traceback (most recent call last):
SyntaxError: expected 'else' after 'if' expression
>>> x = 1 if 1 else pass
Traceback (most recent call last):
SyntaxError: expected expression after 'else', but statement is given
>>> x = pass if 1 else 1
Traceback (most recent call last):
SyntaxError: expected expression before 'if', but statement is given
>>> x = pass if 1 else pass
Traceback (most recent call last):
SyntaxError: expected expression before 'if', but statement is given
>>> if True:
... print("Hello"
...
@ -2863,6 +2875,44 @@ while 1:
end_offset=15 + len("obj.attr"),
)
def test_ifexp_else_stmt(self):
msg = "expected expression after 'else', but statement is given"
for stmt in [
"pass",
"return",
"return 2",
"raise Exception('a')",
"del a",
"yield 2",
"assert False",
"break",
"continue",
"import",
"import ast",
"from",
"from ast import *"
]:
self._check_error(f"x = 1 if 1 else {stmt}", msg)
def test_ifexp_body_stmt_else_expression(self):
msg = "expected expression before 'if', but statement is given"
for stmt in [
"pass",
"break",
"continue"
]:
self._check_error(f"x = {stmt} if 1 else 1", msg)
def test_ifexp_body_stmt_else_stmt(self):
msg = "expected expression before 'if', but statement is given"
for lhs_stmt, rhs_stmt in [
("pass", "pass"),
("break", "pass"),
("continue", "import ast")
]:
self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg)
def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite())

View file

@ -0,0 +1,2 @@
Clarify syntax error messages for conditional expressions when a statement
is specified before an :keyword:`if` or after an :keyword:`else` keyword.

3540
Parser/parser.c generated

File diff suppressed because it is too large Load diff