Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1c8f912

Browse filesBrowse files
bpo-45759: Better error messages for non-matching 'elif'/'else' statements (#29513)
1 parent 56e59a4 commit 1c8f912
Copy full SHA for 1c8f912

File tree

Expand file treeCollapse file tree

4 files changed

+598
-437
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+598
-437
lines changed

‎Grammar/python.gram

Copy file name to clipboardExpand all lines: Grammar/python.gram
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ simple_stmt[stmt_ty] (memo):
124124
| &'nonlocal' nonlocal_stmt
125125

126126
compound_stmt[stmt_ty]:
127+
| invalid_compound_stmt
127128
| &('def' | '@' | 'async') function_def
128129
| &'if' if_stmt
129130
| &('class' | '@') class_def
@@ -1298,6 +1299,10 @@ invalid_import_from_targets:
12981299
| import_from_as_names ',' NEWLINE {
12991300
RAISE_SYNTAX_ERROR("trailing comma not allowed without surrounding parentheses") }
13001301

1302+
invalid_compound_stmt:
1303+
| a='elif' named_expression ':' { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "'elif' must match an if-statement here") }
1304+
| a='else' ':' { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "'else' must match a valid statement here") }
1305+
13011306
invalid_with_stmt:
13021307
| ['async'] 'with' ','.(expression ['as' star_target])+ NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
13031308
| ['async'] 'with' '(' ','.(expressions ['as' star_target])+ ','? ')' NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }

‎Lib/test/test_syntax.py

Copy file name to clipboardExpand all lines: Lib/test/test_syntax.py
+59-2Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,28 @@
17521752
Traceback (most recent call last):
17531753
SyntaxError: positional patterns follow keyword patterns
17541754
1755+
Non-matching 'elif'/'else' statements:
1756+
1757+
>>> if a == b:
1758+
... ...
1759+
... elif a == c:
1760+
Traceback (most recent call last):
1761+
SyntaxError: 'elif' must match an if-statement here
1762+
1763+
>>> if x == y:
1764+
... ...
1765+
... else:
1766+
Traceback (most recent call last):
1767+
SyntaxError: 'else' must match a valid statement here
1768+
1769+
>>> elif m == n:
1770+
Traceback (most recent call last):
1771+
SyntaxError: 'elif' must match an if-statement here
1772+
1773+
>>> else:
1774+
Traceback (most recent call last):
1775+
SyntaxError: 'else' must match a valid statement here
1776+
17551777
Uses of the star operator which should fail:
17561778
17571779
A[:*b]
@@ -2006,8 +2028,8 @@ def _check_error(self, code, errtext,
20062028
lineno=None, offset=None, end_lineno=None, end_offset=None):
20072029
"""Check that compiling code raises SyntaxError with errtext.
20082030
2009-
errtest is a regular expression that must be present in the
2010-
test of the exception raised. If subclass is specified it
2031+
errtext is a regular expression that must be present in the
2032+
test of the exception raised. If subclass is specified, it
20112033
is the expected subclass of SyntaxError (e.g. IndentationError).
20122034
"""
20132035
try:
@@ -2031,6 +2053,22 @@ def _check_error(self, code, errtext,
20312053
else:
20322054
self.fail("compile() did not raise SyntaxError")
20332055

2056+
def _check_noerror(self, code,
2057+
errtext="compile() raised unexpected SyntaxError",
2058+
filename="<testcase>", mode="exec", subclass=None):
2059+
"""Check that compiling code does not raise a SyntaxError.
2060+
2061+
errtext is the message passed to self.fail if there is
2062+
a SyntaxError. If the subclass parameter is specified,
2063+
it is the subclass of SyntaxError (e.g. IndentationError)
2064+
that the raised error is checked against.
2065+
"""
2066+
try:
2067+
compile(code, filename, mode)
2068+
except SyntaxError as err:
2069+
if (not subclass) or isinstance(err, subclass):
2070+
self.fail(errtext)
2071+
20342072
def test_expression_with_assignment(self):
20352073
self._check_error(
20362074
"print(end1 + end2 = ' ')",
@@ -2372,6 +2410,25 @@ def test_syntax_error_on_deeply_nested_blocks(self):
23722410
"""
23732411
self._check_error(source, "too many statically nested blocks")
23742412

2413+
def test_syntax_error_non_matching_elif_else_statements(self):
2414+
# Check bpo-45759: 'elif' statements that doesn't match an
2415+
# if-statement or 'else' statements that doesn't match any
2416+
# valid else-able statement (e.g. 'while')
2417+
self._check_error(
2418+
"elif m == n:\n ...",
2419+
"'elif' must match an if-statement here")
2420+
self._check_error(
2421+
"else:\n ...",
2422+
"'else' must match a valid statement here")
2423+
self._check_noerror("if a == b:\n ...\nelif a == c:\n ...")
2424+
self._check_noerror("if x == y:\n ...\nelse:\n ...")
2425+
self._check_error(
2426+
"else = 123",
2427+
"invalid syntax")
2428+
self._check_error(
2429+
"elif 55 = 123",
2430+
"cannot assign to literal here")
2431+
23752432
@support.cpython_only
23762433
def test_error_on_parser_stack_overflow(self):
23772434
source = "-" * 100000 + "4"
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved error messages for ``elif``/``else`` statements not matching any valid statements. Patch by Jeremiah Vivian.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.