Skip to content

Commit 56c95df

Browse files
authored
bpo-43859: Improve the error message for IndentationError exceptions (GH-25431)
1 parent b0544ba commit 56c95df

File tree

6 files changed

+2431
-800
lines changed

6 files changed

+2431
-800
lines changed

Doc/whatsnew/3.10.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,23 @@ have been incorporated. Some of the most notable ones:
273273
274274
(Contributed by Pablo Galindo in :issue:`41064`)
275275
276+
IndentationErrors
277+
~~~~~~~~~~~~~~~~~
278+
279+
Many :exc:`IndentationError` exceptions now have more context regarding what kind of block
280+
was expecting an indentation, including the location of the statement:
281+
282+
.. code-block:: python
283+
284+
>>> def foo():
285+
... if lel:
286+
... x = 2
287+
File "<stdin>", line 3
288+
x = 2
289+
^
290+
IndentationError: expected an indented block after 'if' statement in line 2
291+
292+
276293
AttributeErrors
277294
~~~~~~~~~~~~~~~
278295

Grammar/python.gram

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,29 +164,33 @@ dotted_name[expr_ty]:
164164
| NAME
165165

166166
if_stmt[stmt_ty]:
167+
| invalid_if_stmt
167168
| 'if' a=named_expression ':' b=block c=elif_stmt {
168169
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
169170
| 'if' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
170-
| invalid_if_stmt
171171
elif_stmt[stmt_ty]:
172+
| invalid_elif_stmt
172173
| 'elif' a=named_expression ':' b=block c=elif_stmt {
173174
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
174175
| 'elif' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
175-
| invalid_elif_stmt
176-
else_block[asdl_stmt_seq*]: 'else' &&':' b=block { b }
176+
else_block[asdl_stmt_seq*]:
177+
| invalid_else_stmt
178+
| 'else' &&':' b=block { b }
177179

178180
while_stmt[stmt_ty]:
179-
| 'while' a=named_expression ':' b=block c=[else_block] { _PyAST_While(a, b, c, EXTRA) }
180181
| invalid_while_stmt
182+
| 'while' a=named_expression ':' b=block c=[else_block] { _PyAST_While(a, b, c, EXTRA) }
181183

182184
for_stmt[stmt_ty]:
185+
| invalid_for_stmt
183186
| 'for' t=star_targets 'in' ~ ex=star_expressions &&':' tc=[TYPE_COMMENT] b=block el=[else_block] {
184187
_PyAST_For(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA) }
185188
| ASYNC 'for' t=star_targets 'in' ~ ex=star_expressions &&':' tc=[TYPE_COMMENT] b=block el=[else_block] {
186189
CHECK_VERSION(stmt_ty, 5, "Async for loops are", _PyAST_AsyncFor(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
187190
| invalid_for_target
188191

189192
with_stmt[stmt_ty]:
193+
| invalid_with_stmt_indent
190194
| 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' b=block {
191195
_PyAST_With(a, b, NULL, EXTRA) }
192196
| 'with' a[asdl_withitem_seq*]=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
@@ -203,14 +207,18 @@ with_item[withitem_ty]:
203207
| e=expression { _PyAST_withitem(e, NULL, p->arena) }
204208

205209
try_stmt[stmt_ty]:
210+
| invalid_try_stmt
206211
| 'try' &&':' b=block f=finally_block { _PyAST_Try(b, NULL, NULL, f, EXTRA) }
207212
| 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_block+ el=[else_block] f=[finally_block] { _PyAST_Try(b, ex, el, f, EXTRA) }
208213
except_block[excepthandler_ty]:
214+
| invalid_except_stmt_indent
209215
| 'except' e=expression t=['as' z=NAME { z }] ':' b=block {
210216
_PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) }
211217
| 'except' ':' b=block { _PyAST_ExceptHandler(NULL, NULL, b, EXTRA) }
212-
| invalid_except_block
213-
finally_block[asdl_stmt_seq*]: 'finally' ':' a=block { a }
218+
| invalid_except_stmt
219+
finally_block[asdl_stmt_seq*]:
220+
| invalid_finally_stmt
221+
| 'finally' &&':' a=block { a }
214222

215223
match_stmt[stmt_ty]:
216224
| "match" subject=subject_expr ':' NEWLINE INDENT cases[asdl_match_case_seq*]=case_block+ DEDENT {
@@ -221,9 +229,9 @@ subject_expr[expr_ty]:
221229
_PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, value, values)), Load, EXTRA) }
222230
| named_expression
223231
case_block[match_case_ty]:
232+
| invalid_case_block
224233
| "case" pattern=patterns guard=guard? ':' body=block {
225234
_PyAST_match_case(pattern, guard, body, p->arena) }
226-
| invalid_case_block
227235
guard[expr_ty]: 'if' guard=named_expression { guard }
228236

229237
patterns[expr_ty]:
@@ -334,6 +342,7 @@ function_def[stmt_ty]:
334342
| function_def_raw
335343

336344
function_def_raw[stmt_ty]:
345+
| invalid_def_raw
337346
| 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block {
338347
_PyAST_FunctionDef(n->v.Name.id,
339348
(params) ? params : CHECK(arguments_ty, _PyPegen_empty_arguments(p)),
@@ -418,6 +427,7 @@ class_def[stmt_ty]:
418427
| a=decorators b=class_def_raw { _PyPegen_class_def_decorators(p, a, b) }
419428
| class_def_raw
420429
class_def_raw[stmt_ty]:
430+
| invalid_class_def_raw
421431
| 'class' a=NAME b=['(' z=[arguments] ')' { z }] &&':' c=block {
422432
_PyAST_ClassDef(a->v.Name.id,
423433
(b) ? ((expr_ty) b)->v.Call.args : NULL,
@@ -876,23 +886,59 @@ invalid_import_from_targets:
876886
invalid_with_stmt:
877887
| [ASYNC] 'with' ','.(expression ['as' star_target])+ &&':'
878888
| [ASYNC] 'with' '(' ','.(expressions ['as' star_target])+ ','? ')' &&':'
879-
880-
invalid_except_block:
889+
invalid_with_stmt_indent:
890+
| [ASYNC] a='with' ','.(expression ['as' star_target])+ ':' NEWLINE !INDENT {
891+
RAISE_INDENTATION_ERROR("expected an indented block after 'with' statement on line %d", a->lineno) }
892+
| [ASYNC] a='with' '(' ','.(expressions ['as' star_target])+ ','? ')' ':' NEWLINE !INDENT {
893+
RAISE_INDENTATION_ERROR("expected an indented block after 'with' statement on line %d", a->lineno) }
894+
895+
invalid_try_stmt:
896+
| a='try' ':' NEWLINE !INDENT {
897+
RAISE_INDENTATION_ERROR("expected an indented block after 'try' statement on line %d", a->lineno) }
898+
invalid_except_stmt:
881899
| 'except' a=expression ',' expressions ['as' NAME ] ':' {
882900
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "exception group must be parenthesized") }
883-
| 'except' expression ['as' NAME ] &&':'
884-
| 'except' &&':'
885-
901+
| a='except' expression ['as' NAME ] NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
902+
| a='except' NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
903+
invalid_finally_stmt:
904+
| a='finally' ':' NEWLINE !INDENT {
905+
RAISE_INDENTATION_ERROR("expected an indented block after 'finally' statement on line %d", a->lineno) }
906+
invalid_except_stmt_indent:
907+
| a='except' expression ['as' NAME ] ':' NEWLINE !INDENT {
908+
RAISE_INDENTATION_ERROR("expected an indented block after 'except' statement on line %d", a->lineno) }
909+
| a='except' ':' NEWLINE !INDENT { RAISE_SYNTAX_ERROR("expected an indented block after except statement on line %d", a->lineno) }
886910
invalid_match_stmt:
887911
| "match" subject_expr !':' { CHECK_VERSION(void*, 10, "Pattern matching is", RAISE_SYNTAX_ERROR("expected ':'") ) }
912+
| a="match" subject=subject_expr ':' NEWLINE !INDENT {
913+
RAISE_INDENTATION_ERROR("expected an indented block after 'match' statement on line %d", a->lineno) }
888914
invalid_case_block:
889915
| "case" patterns guard? !':' { RAISE_SYNTAX_ERROR("expected ':'") }
916+
| a="case" patterns guard? ':' NEWLINE !INDENT {
917+
RAISE_INDENTATION_ERROR("expected an indented block after 'case' statement on line %d", a->lineno) }
890918
invalid_if_stmt:
891919
| 'if' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
920+
| a='if' a=named_expression ':' NEWLINE !INDENT {
921+
RAISE_INDENTATION_ERROR("expected an indented block after 'if' statement on line %d", a->lineno) }
892922
invalid_elif_stmt:
893923
| 'elif' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
924+
| a='elif' named_expression ':' NEWLINE !INDENT {
925+
RAISE_INDENTATION_ERROR("expected an indented block after 'elif' statement on line %d", a->lineno) }
926+
invalid_else_stmt:
927+
| a='else' ':' NEWLINE !INDENT {
928+
RAISE_INDENTATION_ERROR("expected an indented block after 'else' statement on line %d", a->lineno) }
894929
invalid_while_stmt:
895930
| 'while' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
931+
| a='while' named_expression ':' NEWLINE !INDENT {
932+
RAISE_INDENTATION_ERROR("expected an indented block after 'while' statement on line %d", a->lineno) }
933+
invalid_for_stmt:
934+
| [ASYNC] a='for' star_targets 'in' star_expressions ':' NEWLINE !INDENT {
935+
RAISE_INDENTATION_ERROR("expected an indented block after 'for' statement on line %d", a->lineno) }
936+
invalid_def_raw:
937+
| [ASYNC] a='def' NAME '(' [params] ')' ['->' expression] ':' NEWLINE !INDENT {
938+
RAISE_INDENTATION_ERROR("expected an indented block after function definition on line %d", a->lineno) }
939+
invalid_class_def_raw:
940+
| a='class' NAME ['('[arguments] ')'] ':' NEWLINE !INDENT {
941+
RAISE_INDENTATION_ERROR("expected an indented block after class definition on line %d", a->lineno) }
896942

897943
invalid_double_starred_kvpairs:
898944
| ','.double_starred_kvpair+ ',' invalid_kvpair

Lib/test/test_exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def ckmsg(src, msg, exception=SyntaxError):
179179

180180
# should not apply to subclasses, see issue #31161
181181
s = '''if True:\nprint "No indent"'''
182-
ckmsg(s, "expected an indented block", IndentationError)
182+
ckmsg(s, "expected an indented block after 'if' statement on line 1", IndentationError)
183183

184184
s = '''if True:\n print()\n\texec "mixed tabs and spaces"'''
185185
ckmsg(s, "inconsistent use of tabs and spaces in indentation", TabError)

Lib/test/test_syntax.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,138 @@
926926
Traceback (most recent call last):
927927
SyntaxError: invalid syntax
928928
929+
Specialized indentation errors:
930+
931+
>>> while condition:
932+
... pass
933+
Traceback (most recent call last):
934+
IndentationError: expected an indented block after 'while' statement on line 1
935+
936+
>>> for x in range(10):
937+
... pass
938+
Traceback (most recent call last):
939+
IndentationError: expected an indented block after 'for' statement on line 1
940+
941+
>>> for x in range(10):
942+
... pass
943+
... else:
944+
... pass
945+
Traceback (most recent call last):
946+
IndentationError: expected an indented block after 'else' statement on line 3
947+
948+
>>> async for x in range(10):
949+
... pass
950+
Traceback (most recent call last):
951+
IndentationError: expected an indented block after 'for' statement on line 1
952+
953+
>>> async for x in range(10):
954+
... pass
955+
... else:
956+
... pass
957+
Traceback (most recent call last):
958+
IndentationError: expected an indented block after 'else' statement on line 3
959+
960+
>>> if something:
961+
... pass
962+
Traceback (most recent call last):
963+
IndentationError: expected an indented block after 'if' statement on line 1
964+
965+
>>> if something:
966+
... pass
967+
... elif something_else:
968+
... pass
969+
Traceback (most recent call last):
970+
IndentationError: expected an indented block after 'elif' statement on line 3
971+
972+
>>> if something:
973+
... pass
974+
... elif something_else:
975+
... pass
976+
... else:
977+
... pass
978+
Traceback (most recent call last):
979+
IndentationError: expected an indented block after 'else' statement on line 5
980+
981+
>>> try:
982+
... pass
983+
Traceback (most recent call last):
984+
IndentationError: expected an indented block after 'try' statement on line 1
985+
986+
>>> try:
987+
... something()
988+
... except A:
989+
... pass
990+
Traceback (most recent call last):
991+
IndentationError: expected an indented block after 'except' statement on line 3
992+
993+
>>> try:
994+
... something()
995+
... except A:
996+
... pass
997+
... finally:
998+
... pass
999+
Traceback (most recent call last):
1000+
IndentationError: expected an indented block after 'finally' statement on line 5
1001+
1002+
>>> with A:
1003+
... pass
1004+
Traceback (most recent call last):
1005+
IndentationError: expected an indented block after 'with' statement on line 1
1006+
1007+
>>> with A as a, B as b:
1008+
... pass
1009+
Traceback (most recent call last):
1010+
IndentationError: expected an indented block after 'with' statement on line 1
1011+
1012+
>>> with (A as a, B as b):
1013+
... pass
1014+
Traceback (most recent call last):
1015+
IndentationError: expected an indented block after 'with' statement on line 1
1016+
1017+
>>> async with A:
1018+
... pass
1019+
Traceback (most recent call last):
1020+
IndentationError: expected an indented block after 'with' statement on line 1
1021+
1022+
>>> async with A as a, B as b:
1023+
... pass
1024+
Traceback (most recent call last):
1025+
IndentationError: expected an indented block after 'with' statement on line 1
1026+
1027+
>>> async with (A as a, B as b):
1028+
... pass
1029+
Traceback (most recent call last):
1030+
IndentationError: expected an indented block after 'with' statement on line 1
1031+
1032+
>>> def foo(x, /, y, *, z=2):
1033+
... pass
1034+
Traceback (most recent call last):
1035+
IndentationError: expected an indented block after function definition on line 1
1036+
1037+
>>> class Blech(A):
1038+
... pass
1039+
Traceback (most recent call last):
1040+
IndentationError: expected an indented block after class definition on line 1
1041+
1042+
>>> match something:
1043+
... pass
1044+
Traceback (most recent call last):
1045+
IndentationError: expected an indented block after 'match' statement on line 1
1046+
1047+
>>> match something:
1048+
... case []:
1049+
... pass
1050+
Traceback (most recent call last):
1051+
IndentationError: expected an indented block after 'case' statement on line 2
1052+
1053+
>>> match something:
1054+
... case []:
1055+
... ...
1056+
... case {}:
1057+
... pass
1058+
Traceback (most recent call last):
1059+
IndentationError: expected an indented block after 'case' statement on line 4
1060+
9291061
Make sure that the old "raise X, Y[, Z]" form is gone:
9301062
>>> raise X, Y
9311063
Traceback (most recent call last):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve the error message for :exc:`IndentationError` exceptions. Patch by
2+
Pablo Galindo

0 commit comments

Comments
 (0)