Skip to content

Commit 427f3ea

Browse files
committed
Fixes for try/finally binder
1 parent f2111de commit 427f3ea

File tree

2 files changed

+102
-7
lines changed

2 files changed

+102
-7
lines changed

mypy/checker.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,18 +1539,45 @@ def type_check_raise(self, e: Node, s: RaiseStmt) -> None:
15391539

15401540
def visit_try_stmt(self, s: TryStmt) -> Type:
15411541
"""Type check a try statement."""
1542+
# Our enclosing frame will get the result if the try/except falls through.
1543+
# This one gets all possible intermediate states
1544+
with self.binder.frame_context():
1545+
if s.finally_body:
1546+
self.binder.try_frames.add(len(self.binder.frames) - 1)
1547+
broken = self.visit_try_without_finally(s)
1548+
self.binder.try_frames.remove(len(self.binder.frames) - 1)
1549+
# First we check finally_body is type safe for all intermediate frames
1550+
self.accept(s.finally_body)
1551+
broken = broken or self.binder.breaking_out
1552+
else:
1553+
broken = self.visit_try_without_finally(s)
1554+
1555+
if not broken and s.finally_body:
1556+
# Then we try again for the more restricted set of options that can fall through
1557+
self.accept(s.finally_body)
1558+
self.binder.breaking_out = broken
1559+
return None
1560+
1561+
def visit_try_without_finally(self, s: TryStmt) -> bool:
1562+
"""Type check a try statement, ignoring the finally block.
1563+
1564+
Return whether we are guaranteed to be breaking out.
1565+
Otherwise, it will place the results possible frames of
1566+
that don't break out into self.binder.frames[-2].
1567+
"""
15421568
broken = True
15431569
# This frame records the possible states that exceptions can leave variables in
1570+
# during the try: block
15441571
with self.binder.frame_context():
1545-
with self.binder.frame_context(2) as frame:
1572+
with self.binder.frame_context(3) as frame:
15461573
self.binder.try_frames.add(len(self.binder.frames) - 2)
15471574
self.accept(s.body)
15481575
self.binder.try_frames.remove(len(self.binder.frames) - 2)
15491576
if s.else_body:
15501577
self.accept(s.else_body)
15511578
broken = broken and frame.broken
15521579
for i in range(len(s.handlers)):
1553-
with self.binder.frame_context(2) as frame:
1580+
with self.binder.frame_context(3) as frame:
15541581
if s.types[i]:
15551582
t = self.visit_except_handler_test(s.types[i])
15561583
if s.vars[i]:
@@ -1575,11 +1602,7 @@ def visit_try_stmt(self, s: TryStmt) -> Type:
15751602
var.type = DeletedType(source=source)
15761603
self.binder.cleanse(s.vars[i])
15771604
broken = broken and frame.broken
1578-
1579-
if s.finally_body:
1580-
self.accept(s.finally_body)
1581-
if broken:
1582-
self.binder.breaking_out = True
1605+
return broken
15831606

15841607
def visit_except_handler_test(self, n: Node) -> Type:
15851608
"""Type check an exception handler test clause."""

test-data/unit/check-isinstance.test

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,78 @@ while 1:
279279
x = B()
280280
x.z
281281
[builtins fixtures/exception.py]
282+
[case testUnionTryFinally]
283+
class A: pass
284+
class B(A): b = 1
285+
286+
x = A()
287+
x = B()
288+
try:
289+
x = A()
290+
x.b # E: "A" has no attribute "b"
291+
x = B()
292+
finally:
293+
x.b # E: "A" has no attribute "b"
294+
x.b
295+
[case testUnionTryFinally2]
296+
class A: pass
297+
class B(A): b = 1
298+
299+
x = A()
300+
x = B()
301+
try:
302+
x = A()
303+
x = B()
304+
except:
305+
pass
306+
finally:
307+
pass
308+
x.b # E: "A" has no attribute "b"
309+
[case testUnionTryFinally3]
310+
class A: pass
311+
class B(A): b = 1
312+
313+
x = A()
314+
x = B()
315+
try:
316+
x = A()
317+
x = B()
318+
except:
319+
pass
320+
finally:
321+
x = B()
322+
x.b
323+
[case testUnionTryFinally4]
324+
class A: pass
325+
class B(A): b = 1
326+
327+
while 2:
328+
x = A()
329+
x = B()
330+
try:
331+
x = A()
332+
x = B()
333+
except:
334+
pass
335+
finally:
336+
if not isinstance(x, B):
337+
break
338+
x.b
339+
[builtins fixtures/isinstancelist.py]
340+
[case testUnionTryFinally5]
341+
class A: pass
342+
class B(A): b = 1
343+
344+
while 2:
345+
x = A()
346+
try:
347+
x = A()
348+
x = B()
349+
finally:
350+
x.b # E: "A" has no attribute "b"
351+
break
352+
x.b
353+
x.b
282354
[case testUnionListIsinstance]
283355

284356
from typing import Union, List

0 commit comments

Comments
 (0)