Skip to content

Commit 1c8f912

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

File tree

4 files changed

+598
-437
lines changed

4 files changed

+598
-437
lines changed

Grammar/python.gram

+5
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

+59-2
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"
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)