Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4dbba6b
gh-129515: Clarify syntax error messages for conditional expressions
sergey-miryanov Feb 9, 2025
6686090
Update news
sergey-miryanov Feb 9, 2025
b204fdb
Replace simple_stmt_without_expressions with explicit invalid_ifexp_*
sergey-miryanov Feb 9, 2025
aa3f17d
Merge branch 'main' into gh-129515-ifexp-syntax-error
sergey-miryanov Feb 9, 2025
8acc305
elif after else
sergey-miryanov Feb 9, 2025
b43b3a2
Merge branch 'gh-129515-ifexp-syntax-error' of github.com:sergey-miry…
sergey-miryanov Feb 10, 2025
48e4796
Merge branch 'main' into gh-129515-ifexp-syntax-error
sergey-miryanov Feb 10, 2025
60c5066
Simplify test_ifexp_* tests
sergey-miryanov Feb 10, 2025
c33b08f
Remove an accidentally added test line
sergey-miryanov Feb 10, 2025
8afe66a
Merge branch 'main' into gh-129515-ifexp-syntax-error
pablogsal Feb 10, 2025
7e4ed1b
Merge branch 'main' into gh-129515-ifexp-syntax-error
sergey-miryanov Feb 11, 2025
db2784e
Simplify invalid_expression rule
sergey-miryanov Feb 11, 2025
c09bf42
Update whatsnew for 3.14
sergey-miryanov Feb 11, 2025
c66fbe3
Fix references in whatsnew
sergey-miryanov Feb 11, 2025
79a046c
Fix reference to if_expr
sergey-miryanov Feb 11, 2025
2eaa968
Merge branch 'main' into gh-129515-ifexp-syntax-error
sergey-miryanov Feb 12, 2025
c1ef4ca
Fix code-block in whatsnew
sergey-miryanov Feb 12, 2025
63b4400
Merge branch 'main' into gh-129515-ifexp-syntax-error
sergey-miryanov Feb 13, 2025
903d9b6
Fix invalid rule for ifexp if statement given after else
sergey-miryanov Feb 13, 2025
b1c0ab4
Update doc-string in test_syntax
sergey-miryanov Feb 13, 2025
a33a705
Update Doc/whatsnew/3.14.rst
sergey-miryanov Feb 16, 2025
9c711ae
Update Misc/NEWS.d/next/Core_and_Builtins/2025-02-09-11-30-38.gh-issu…
sergey-miryanov Feb 17, 2025
b4a1c55
Update tests and whatsnew entry
sergey-miryanov Feb 17, 2025
c468622
Merge branch 'gh-129515-ifexp-syntax-error' of github.com:sergey-miry…
sergey-miryanov Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.)
Expand Down
19 changes: 16 additions & 3 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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) }

Expand Down Expand Up @@ -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") }

Expand Down
50 changes: 50 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@
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"
...
Expand Down Expand Up @@ -2863,6 +2875,44 @@ def test_match_stmt_invalid_as_expr(self):
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())
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading