Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
33 changes: 30 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 @@ -1177,13 +1186,31 @@ invalid_type_param:
: "cannot use bound with ParamSpec")
}

invalid_ifexp_orelse_stmt[stmt_ty]:
| return_stmt
| raise_stmt
| pass_stmt
| del_stmt
| yield_stmt
| assert_stmt
| break_stmt
| continue_stmt

invalid_ifexp_body_stmt[stmt_ty]:
| pass_stmt
| break_stmt
| continue_stmt

invalid_expression:
# !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"
# Soft keywords need to also be ignored because they can be parsed as NAME NAME
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {
_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' c=invalid_ifexp_orelse_stmt { RAISE_SYNTAX_ERROR_KNOWN_LOCATION (c, "statement given where 'orelse' expression required")}
| a=invalid_ifexp_body_stmt 'if' b=disjunction 'else' c=expression { RAISE_SYNTAX_ERROR_KNOWN_LOCATION (a, "statement given where 'body' expression required")}
| a=invalid_ifexp_body_stmt 'if' b=disjunction 'else' c=invalid_ifexp_orelse_stmt { RAISE_SYNTAX_ERROR_KNOWN_LOCATION (a, "statement given where 'body' expression required")}
| 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
34 changes: 34 additions & 0 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,40 @@ def test_ifexp(self):
for args in (s, l, l), (l, s, l), (l, l, s):
self.expr(ast.IfExp(*args), "must have Load context")

def test_ifexp_orelse_stmt(self):
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else pass")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else return")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else raise Exception('a')")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("a = 1; x = 1 if 1 else del a")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else yield 2")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else assert False")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else break")
with self.assertRaisesRegex(SyntaxError, "statement given where 'orelse' expression required"):
ast.parse("x = 1 if 1 else continue")

def test_ifexp_body_stmt_orelse_expression(self):
with self.assertRaisesRegex(SyntaxError, "statement given where 'body' expression required"):
ast.parse("x = pass if 1 else 1")
with self.assertRaisesRegex(SyntaxError, "statement given where 'body' expression required"):
ast.parse("x = break if 1 else 1")
with self.assertRaisesRegex(SyntaxError, "statement given where 'body' expression required"):
ast.parse("x = continue if 1 else 1")

def test_ifexp_body_stmt_orelse_stmt(self):
with self.assertRaisesRegex(SyntaxError, "statement given where 'body' expression required"):
ast.parse("x = pass if 1 else pass")
with self.assertRaisesRegex(SyntaxError, "statement given where 'body' expression required"):
ast.parse("x = break if 1 else pass")
with self.assertRaisesRegex(SyntaxError, "statement given where 'body' expression required"):
ast.parse("x = continue if 1 else pass")

def test_dict(self):
d = ast.Dict([], [ast.Name("x", ast.Load())])
self.expr(d, "same number of keys as values")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Clarify syntax error messages for conditional expressions if statement
specified in body and orelse parts.
Loading
Loading