Skip to content

Commit 59f2f9a

Browse files
committed
Explain why two try frames are needed for a finally clause; add a test
1 parent 4b55083 commit 59f2f9a

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

mypy/checker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1640,10 +1640,15 @@ def visit_try_stmt(self, s: TryStmt) -> Type:
16401640
# (by exception, return, break, etc.)
16411641
with self.binder.frame_context(can_skip=False, fall_through=0):
16421642
if s.finally_body:
1643+
# Not only might the body of the try statement exit abnormally,
1644+
# but so might an exception handler or else clause. The finally
1645+
# clause runs in *all* cases, so we need an outer try frame to
1646+
# catch all intermediate states in case an exception is raised
1647+
# during an except or else clause.
16431648
self.binder.try_frames.add(len(self.binder.frames) - 1)
16441649
self.visit_try_without_finally(s)
16451650
self.binder.try_frames.remove(len(self.binder.frames) - 1)
1646-
# First we check finally_body is type safe for all intermediate frames
1651+
# First we check finally_body is type safe on all abnormal exit paths
16471652
self.accept(s.finally_body)
16481653
else:
16491654
self.visit_try_without_finally(s)

test-data/unit/check-isinstance.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,22 @@ while 2:
352352
break
353353
x.b
354354
x.b
355+
[case testUnionTryFinally6]
356+
class A: pass
357+
class B(A): b = 1
358+
359+
def f() -> int:
360+
x = B() # type: A
361+
try:
362+
x = B()
363+
except:
364+
x = A()
365+
# An exception could occur here
366+
x = B()
367+
finally:
368+
return x.b # E: "A" has no attribute "b"
369+
[out]
370+
main: note: In function "f":
355371
[case testUnionListIsinstance]
356372

357373
from typing import Union, List

0 commit comments

Comments
 (0)