Skip to content

Commit a72d456

Browse files
committed
WIP: Replace the binder's breaking_out by an 'unreachable' property of Frame
1 parent a702c40 commit a72d456

File tree

3 files changed

+48
-36
lines changed

3 files changed

+48
-36
lines changed

mypy/binder.py

+28-15
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111

1212
class Frame(Dict[Any, Type]):
13-
pass
13+
def __init__(self) -> None:
14+
self.unreachable = False
1415

1516

1617
class Key(AnyType):
@@ -55,16 +56,8 @@ def __init__(self) -> None:
5556
# Whenever a new key (e.g. x.a.b) is added, we update this
5657
self.dependencies = {} # type: Dict[Key, Set[Key]]
5758

58-
# breaking_out is set to True on return/break/continue/raise
59-
# It is cleared on pop_frame() and placed in last_pop_breaking_out
60-
# Lines of code after breaking_out = True are unreachable and not
61-
# typechecked.
62-
self.breaking_out = False
63-
6459
# Whether the last pop changed the newly top frame on exit
6560
self.last_pop_changed = False
66-
# Whether the last pop was necessarily breaking out, and couldn't fall through
67-
self.last_pop_breaking_out = False
6861

6962
self.try_frames = set() # type: Set[int]
7063
self.loop_frames = [] # type: List[int]
@@ -105,9 +98,15 @@ def push(self, expr: Node, typ: Type) -> None:
10598
self._add_dependencies(key)
10699
self._push(key, typ)
107100

101+
def unreachable(self) -> bool:
102+
self.frames[-1].unreachable = True
103+
108104
def get(self, expr: Node) -> Type:
109105
return self._get(expr.literal_hash)
110106

107+
def is_unreachable(self) -> bool:
108+
return self.frames[-1].unreachable
109+
111110
def cleanse(self, expr: Node) -> None:
112111
"""Remove all references to a Node from the binder."""
113112
self._cleanse_key(expr.literal_hash)
@@ -126,6 +125,8 @@ def update_from_options(self, frames: List[Frame]) -> bool:
126125
options are the same.
127126
"""
128127

128+
frames = [f for f in frames if not f.unreachable]
129+
# XXX What if our frame is unreachable?
129130
changed = False
130131
keys = set(key for f in frames for key in f)
131132

@@ -147,21 +148,29 @@ def update_from_options(self, frames: List[Frame]) -> bool:
147148
self._push(key, type)
148149
changed = True
149150

151+
old_unreachable = self.frames[-1].unreachable
152+
new_unreachable = old_unreachable and not frames
153+
self.frames[-1].unreachable = new_unreachable
154+
changed = changed or (old_unreachable != new_unreachable)
155+
150156
return changed
151157

152158
def pop_frame(self, fall_through: int = 0) -> Frame:
153159
"""Pop a frame and return it.
154160
155161
See frame_context() for documentation of fall_through.
156162
"""
157-
if fall_through and not self.breaking_out:
163+
164+
if fall_through > 0:
158165
self.allow_jump(-fall_through)
159166

160167
result = self.frames.pop()
161168
options = self.options_on_return.pop()
162169

170+
if fall_through == 0:
171+
self.unreachable()
172+
163173
self.last_pop_changed = self.update_from_options(options)
164-
self.last_pop_breaking_out = self.breaking_out
165174

166175
return result
167176

@@ -240,6 +249,8 @@ def allow_jump(self, index: int) -> None:
240249
frame = Frame()
241250
for f in self.frames[index + 1:]:
242251
frame.update(f)
252+
if f.unreachable:
253+
frame.unreachable = True
243254
self.options_on_return[index].append(frame)
244255

245256
def push_loop_frame(self) -> None:
@@ -255,10 +266,12 @@ def frame_context(self, fall_through: int = 0) -> Iterator[Frame]:
255266
If fall_through > 0, then it will allow the frame to escape to
256267
its ancestor `fall_through` levels higher.
257268
258-
A simple 'with binder.frame_context(): pass' will change the
259-
last_pop_* flags but nothing else.
269+
If fall_through == 0, then control cannot flow through the frame;
270+
also in this case entering the frame is not optional. Control
271+
must flow through a nested frame_context(N) block with N > 1.
272+
273+
A simple 'with binder.frame_context(1): pass' will change the
274+
last_pop_changed flag but nothing else.
260275
"""
261-
was_breaking_out = self.breaking_out
262276
yield self.push_frame()
263277
self.pop_frame(fall_through)
264-
self.breaking_out = was_breaking_out

mypy/checker.py

+14-21
Original file line numberDiff line numberDiff line change
@@ -911,9 +911,9 @@ def visit_block(self, b: Block) -> Type:
911911
if b.is_unreachable:
912912
return None
913913
for s in b.body:
914-
self.accept(s)
915-
if self.binder.breaking_out:
914+
if self.binder.is_unreachable():
916915
break
916+
self.accept(s)
917917

918918
def visit_assignment_stmt(self, s: AssignmentStmt) -> Type:
919919
"""Type check an assignment statement.
@@ -1354,7 +1354,10 @@ def visit_expression_stmt(self, s: ExpressionStmt) -> Type:
13541354

13551355
def visit_return_stmt(self, s: ReturnStmt) -> Type:
13561356
"""Type check a return statement."""
1357-
self.binder.breaking_out = True
1357+
self.check_return_stmt(s)
1358+
self.binder.unreachable()
1359+
1360+
def check_return_stmt(self, s: ReturnStmt) -> None:
13581361
if self.is_within_function():
13591362
if self.function_stack[-1].is_generator:
13601363
return_type = self.get_generator_return_type(self.return_types[-1])
@@ -1422,7 +1425,6 @@ def count_nested_types(self, typ: Instance, check_type: str) -> int:
14221425

14231426
def visit_if_stmt(self, s: IfStmt) -> Type:
14241427
"""Type check an if statement."""
1425-
breaking_out = True
14261428
# This frame records the knowledge from previous if/elif clauses not being taken.
14271429
with self.binder.frame_context():
14281430
for e, b in zip(s.expr, s.body):
@@ -1444,7 +1446,6 @@ def visit_if_stmt(self, s: IfStmt) -> Type:
14441446
self.binder.push(var, type)
14451447

14461448
self.accept(b)
1447-
breaking_out = breaking_out and self.binder.last_pop_breaking_out
14481449

14491450
if else_map:
14501451
for var, type in else_map.items():
@@ -1460,9 +1461,6 @@ def visit_if_stmt(self, s: IfStmt) -> Type:
14601461
with self.binder.frame_context(2):
14611462
if s.else_body:
14621463
self.accept(s.else_body)
1463-
breaking_out = breaking_out and self.binder.last_pop_breaking_out
1464-
if breaking_out:
1465-
self.binder.breaking_out = True
14661464
return None
14671465

14681466
def visit_while_stmt(self, s: WhileStmt) -> Type:
@@ -1498,11 +1496,11 @@ def visit_assert_stmt(self, s: AssertStmt) -> Type:
14981496

14991497
def visit_raise_stmt(self, s: RaiseStmt) -> Type:
15001498
"""Type check a raise statement."""
1501-
self.binder.breaking_out = True
15021499
if s.expr:
15031500
self.type_check_raise(s.expr, s)
15041501
if s.from_expr:
15051502
self.type_check_raise(s.from_expr, s)
1503+
self.binder.unreachable()
15061504

15071505
def type_check_raise(self, e: Node, s: RaiseStmt) -> None:
15081506
typ = self.accept(e)
@@ -1535,28 +1533,26 @@ def visit_try_stmt(self, s: TryStmt) -> Type:
15351533
with self.binder.frame_context():
15361534
if s.finally_body:
15371535
self.binder.try_frames.add(len(self.binder.frames) - 1)
1538-
breaking_out = self.visit_try_without_finally(s)
1536+
self.visit_try_without_finally(s)
15391537
self.binder.try_frames.remove(len(self.binder.frames) - 1)
15401538
# First we check finally_body is type safe for all intermediate frames
15411539
self.accept(s.finally_body)
1542-
breaking_out = breaking_out or self.binder.breaking_out
15431540
else:
1544-
breaking_out = self.visit_try_without_finally(s)
1541+
self.visit_try_without_finally(s)
15451542

1546-
if not breaking_out and s.finally_body:
1543+
if s.finally_body:
15471544
# Then we try again for the more restricted set of options that can fall through
15481545
self.accept(s.finally_body)
1549-
self.binder.breaking_out = breaking_out
1546+
15501547
return None
15511548

1552-
def visit_try_without_finally(self, s: TryStmt) -> bool:
1549+
def visit_try_without_finally(self, s: TryStmt) -> None:
15531550
"""Type check a try statement, ignoring the finally block.
15541551
15551552
Return whether we are guaranteed to be breaking out.
15561553
Otherwise, it will place the results possible frames of
15571554
that don't break out into self.binder.frames[-2].
15581555
"""
1559-
breaking_out = True
15601556
# This frame records the possible states that exceptions can leave variables in
15611557
# during the try: block
15621558
with self.binder.frame_context():
@@ -1566,7 +1562,6 @@ def visit_try_without_finally(self, s: TryStmt) -> bool:
15661562
self.binder.try_frames.remove(len(self.binder.frames) - 2)
15671563
if s.else_body:
15681564
self.accept(s.else_body)
1569-
breaking_out = breaking_out and self.binder.last_pop_breaking_out
15701565
for i in range(len(s.handlers)):
15711566
with self.binder.frame_context(3):
15721567
if s.types[i]:
@@ -1592,8 +1587,6 @@ def visit_try_without_finally(self, s: TryStmt) -> bool:
15921587
var = cast(Var, s.vars[i].node)
15931588
var.type = DeletedType(source=source)
15941589
self.binder.cleanse(s.vars[i])
1595-
breaking_out = breaking_out and self.binder.last_pop_breaking_out
1596-
return breaking_out
15971590

15981591
def visit_except_handler_test(self, n: Node) -> Type:
15991592
"""Type check an exception handler test clause."""
@@ -1811,13 +1804,13 @@ def visit_member_expr(self, e: MemberExpr) -> Type:
18111804
return self.expr_checker.visit_member_expr(e)
18121805

18131806
def visit_break_stmt(self, s: BreakStmt) -> Type:
1814-
self.binder.breaking_out = True
18151807
self.binder.allow_jump(self.binder.loop_frames[-1] - 1)
1808+
self.binder.unreachable()
18161809
return None
18171810

18181811
def visit_continue_stmt(self, s: ContinueStmt) -> Type:
1819-
self.binder.breaking_out = True
18201812
self.binder.allow_jump(self.binder.loop_frames[-1])
1813+
self.binder.unreachable()
18211814
return None
18221815

18231816
def visit_int_expr(self, e: IntExpr) -> Type:

mypy/checkexpr.py

+6
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,8 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
10911091
right_map = None
10921092

10931093
with self.chk.binder.frame_context():
1094+
with self.chk.binder.frame_context(2):
1095+
pass
10941096
if right_map:
10951097
for var, type in right_map.items():
10961098
self.chk.binder.push(var, type)
@@ -1489,6 +1491,8 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No
14891491
... for x in y if z
14901492
"""
14911493
with self.chk.binder.frame_context():
1494+
with self.chk.binder.frame_context(2):
1495+
pass
14921496
for index, sequence, conditions in zip(e.indices, e.sequences,
14931497
e.condlists):
14941498
sequence_type = self.chk.analyze_iterable_item_type(sequence)
@@ -1534,6 +1538,8 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
15341538
def analyze_cond_branch(self, map: Optional[Dict[Node, Type]],
15351539
node: Node, context: Optional[Type]) -> Type:
15361540
with self.chk.binder.frame_context():
1541+
with self.chk.binder.frame_context(2):
1542+
pass
15371543
if map:
15381544
for var, type in map.items():
15391545
self.chk.binder.push(var, type)

0 commit comments

Comments
 (0)