Skip to content

Commit 2d54024

Browse files
authored
[mypyc] Don't crash on unreachable statements (#16311)
Skip them instead. This applies to statements after break, continue, return and raise statements. It's common to have unreachable statements temporarily while working on a half-finished change, so generating an error is perhaps not the best option. Fixes mypyc/mypyc#1028.
1 parent 27c4b46 commit 2d54024

File tree

4 files changed

+156
-1
lines changed

4 files changed

+156
-1
lines changed

mypyc/irbuild/builder.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ def __init__(
165165
self.runtime_args: list[list[RuntimeArg]] = [[]]
166166
self.function_name_stack: list[str] = []
167167
self.class_ir_stack: list[ClassIR] = []
168+
# Keep track of whether the next statement in a block is reachable
169+
# or not, separately for each block nesting level
170+
self.block_reachable_stack: list[bool] = [True]
168171

169172
self.current_module = current_module
170173
self.mapper = mapper
@@ -1302,6 +1305,14 @@ def is_native_attr_ref(self, expr: MemberExpr) -> bool:
13021305
and not obj_rtype.class_ir.get_method(expr.name)
13031306
)
13041307

1308+
def mark_block_unreachable(self) -> None:
1309+
"""Mark statements in the innermost block being processed as unreachable.
1310+
1311+
This should be called after a statement that unconditionally leaves the
1312+
block, such as 'break' or 'return'.
1313+
"""
1314+
self.block_reachable_stack[-1] = False
1315+
13051316
# Lacks a good type because there wasn't a reasonable type in 3.5 :(
13061317
def catch_errors(self, line: int) -> Any:
13071318
return catch_errors(self.module_path, line)

mypyc/irbuild/statement.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,13 @@
118118

119119
def transform_block(builder: IRBuilder, block: Block) -> None:
120120
if not block.is_unreachable:
121+
builder.block_reachable_stack.append(True)
121122
for stmt in block.body:
122123
builder.accept(stmt)
124+
if not builder.block_reachable_stack[-1]:
125+
# The rest of the block is unreachable, so skip it
126+
break
127+
builder.block_reachable_stack.pop()
123128
# Raise a RuntimeError if we hit a non-empty unreachable block.
124129
# Don't complain about empty unreachable blocks, since mypy inserts
125130
# those after `if MYPY`.

mypyc/irbuild/visitor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:
194194

195195
def visit_return_stmt(self, stmt: ReturnStmt) -> None:
196196
transform_return_stmt(self.builder, stmt)
197+
self.builder.mark_block_unreachable()
197198

198199
def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None:
199200
transform_assignment_stmt(self.builder, stmt)
@@ -212,12 +213,15 @@ def visit_for_stmt(self, stmt: ForStmt) -> None:
212213

213214
def visit_break_stmt(self, stmt: BreakStmt) -> None:
214215
transform_break_stmt(self.builder, stmt)
216+
self.builder.mark_block_unreachable()
215217

216218
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
217219
transform_continue_stmt(self.builder, stmt)
220+
self.builder.mark_block_unreachable()
218221

219222
def visit_raise_stmt(self, stmt: RaiseStmt) -> None:
220223
transform_raise_stmt(self.builder, stmt)
224+
self.builder.mark_block_unreachable()
221225

222226
def visit_try_stmt(self, stmt: TryStmt) -> None:
223227
transform_try_stmt(self.builder, stmt)

mypyc/test-data/irbuild-unreachable.test

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Test cases for unreachable expressions
1+
# Test cases for unreachable expressions and statements
22

33
[case testUnreachableMemberExpr]
44
import sys
@@ -104,3 +104,138 @@ L5:
104104
L6:
105105
y = r11
106106
return 1
107+
108+
[case testUnreachableStatementAfterReturn]
109+
def f(x: bool) -> int:
110+
if x:
111+
return 1
112+
f(False)
113+
return 2
114+
[out]
115+
def f(x):
116+
x :: bool
117+
L0:
118+
if x goto L1 else goto L2 :: bool
119+
L1:
120+
return 2
121+
L2:
122+
return 4
123+
124+
[case testUnreachableStatementAfterContinue]
125+
def c() -> bool:
126+
return False
127+
128+
def f() -> None:
129+
n = True
130+
while n:
131+
if c():
132+
continue
133+
if int():
134+
f()
135+
n = False
136+
[out]
137+
def c():
138+
L0:
139+
return 0
140+
def f():
141+
n, r0 :: bool
142+
L0:
143+
n = 1
144+
L1:
145+
if n goto L2 else goto L5 :: bool
146+
L2:
147+
r0 = c()
148+
if r0 goto L3 else goto L4 :: bool
149+
L3:
150+
goto L1
151+
L4:
152+
n = 0
153+
goto L1
154+
L5:
155+
return 1
156+
157+
[case testUnreachableStatementAfterBreak]
158+
def c() -> bool:
159+
return False
160+
161+
def f() -> None:
162+
n = True
163+
while n:
164+
if c():
165+
break
166+
if int():
167+
f()
168+
n = False
169+
[out]
170+
def c():
171+
L0:
172+
return 0
173+
def f():
174+
n, r0 :: bool
175+
L0:
176+
n = 1
177+
L1:
178+
if n goto L2 else goto L5 :: bool
179+
L2:
180+
r0 = c()
181+
if r0 goto L3 else goto L4 :: bool
182+
L3:
183+
goto L5
184+
L4:
185+
n = 0
186+
goto L1
187+
L5:
188+
return 1
189+
190+
[case testUnreachableStatementAfterRaise]
191+
def f(x: bool) -> int:
192+
if x:
193+
raise ValueError()
194+
print('hello')
195+
return 2
196+
[out]
197+
def f(x):
198+
x :: bool
199+
r0 :: object
200+
r1 :: str
201+
r2, r3 :: object
202+
L0:
203+
if x goto L1 else goto L2 :: bool
204+
L1:
205+
r0 = builtins :: module
206+
r1 = 'ValueError'
207+
r2 = CPyObject_GetAttr(r0, r1)
208+
r3 = PyObject_CallFunctionObjArgs(r2, 0)
209+
CPy_Raise(r3)
210+
unreachable
211+
L2:
212+
return 4
213+
214+
[case testUnreachableStatementAfterAssertFalse]
215+
def f(x: bool) -> int:
216+
if x:
217+
assert False
218+
print('hello')
219+
return 2
220+
[out]
221+
def f(x):
222+
x, r0 :: bool
223+
r1 :: str
224+
r2 :: object
225+
r3 :: str
226+
r4, r5 :: object
227+
L0:
228+
if x goto L1 else goto L4 :: bool
229+
L1:
230+
if 0 goto L3 else goto L2 :: bool
231+
L2:
232+
r0 = raise AssertionError
233+
unreachable
234+
L3:
235+
r1 = 'hello'
236+
r2 = builtins :: module
237+
r3 = 'print'
238+
r4 = CPyObject_GetAttr(r2, r3)
239+
r5 = PyObject_CallFunctionObjArgs(r4, r1, 0)
240+
L4:
241+
return 4

0 commit comments

Comments
 (0)