Skip to content

Commit 8c01031

Browse files
committed
Reduce duplication with find_isinstance_check and binder.push
The resulting simplification of visit_if_expr revealed a bug in the binder: if a frame was marked as unreachable, but then a new frame context was begun immediately, the new frame was not marked as unreachable, and a block visited inside the new frame would be type checked. For now we check all frames for reachability; probably there is a more efficient way to do this. This commit also cleans up check_boolean_op slightly.
1 parent 2344f41 commit 8c01031

File tree

3 files changed

+42
-69
lines changed

3 files changed

+42
-69
lines changed

mypy/binder.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ def get(self, expr: Node) -> Type:
118118
return self._get(expr.literal_hash)
119119

120120
def is_unreachable(self) -> bool:
121-
return self.frames[-1].unreachable
121+
# TODO: Copy the value of unreachable into new frames to avoid
122+
# this traversal on every statement?
123+
return any(f.unreachable for f in self.frames)
122124

123125
def cleanse(self, expr: Node) -> None:
124126
"""Remove all references to a Node from the binder."""

mypy/checker.py

Lines changed: 28 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,8 @@ def accept_loop(self, body: Node, else_body: Node = None, *,
243243
break
244244
self.binder.pop_loop_frame()
245245
if exit_condition:
246-
_, else_map = find_isinstance_check(
247-
exit_condition, self.type_map, self.typing_mode_weak()
248-
)
249-
if else_map is None:
250-
self.binder.unreachable()
251-
else:
252-
for var, type in else_map.items():
253-
self.binder.push(var, type)
246+
_, else_map = self.find_isinstance_check(exit_condition)
247+
self.push_type_map(else_map)
254248
if else_body:
255249
self.accept(else_body)
256250

@@ -1441,37 +1435,19 @@ def visit_if_stmt(self, s: IfStmt) -> Type:
14411435
for e, b in zip(s.expr, s.body):
14421436
t = self.accept(e)
14431437
self.check_not_void(t, e)
1444-
if_map, else_map = find_isinstance_check(
1445-
e, self.type_map,
1446-
self.typing_mode_weak()
1447-
)
1448-
if if_map is None:
1449-
# The condition is always false
1450-
# XXX should issue a warning?
1451-
pass
1452-
else:
1453-
# Only type check body if the if condition can be true.
1454-
with self.binder.frame_context(can_skip=True, fall_through=2):
1455-
if if_map:
1456-
for var, type in if_map.items():
1457-
self.binder.push(var, type)
1458-
1459-
self.accept(b)
1460-
1461-
if else_map:
1462-
for var, type in else_map.items():
1463-
self.binder.push(var, type)
1464-
if else_map is None:
1465-
# The condition is always true => remaining elif/else blocks
1466-
# can never be reached.
1467-
1468-
# Might also want to issue a warning
1469-
# print("Warning: isinstance always true")
1470-
break
1471-
else: # Didn't break => can't prove one of the conditions is always true
1472-
with self.binder.frame_context(can_skip=False, fall_through=2):
1473-
if s.else_body:
1474-
self.accept(s.else_body)
1438+
if_map, else_map = self.find_isinstance_check(e)
1439+
1440+
# XXX Issue a warning if condition is always False?
1441+
with self.binder.frame_context(can_skip=True, fall_through=2):
1442+
self.push_type_map(if_map)
1443+
self.accept(b)
1444+
1445+
# XXX Issue a warning if condition is always True?
1446+
self.push_type_map(else_map)
1447+
1448+
with self.binder.frame_context(can_skip=False, fall_through=2):
1449+
if s.else_body:
1450+
self.accept(s.else_body)
14751451
return None
14761452

14771453
def visit_while_stmt(self, s: WhileStmt) -> Type:
@@ -1497,16 +1473,9 @@ def visit_assert_stmt(self, s: AssertStmt) -> Type:
14971473
self.accept(s.expr)
14981474

14991475
# If this is asserting some isinstance check, bind that type in the following code
1500-
true_map, _ = find_isinstance_check(
1501-
s.expr, self.type_map,
1502-
self.typing_mode_weak()
1503-
)
1476+
true_map, _ = self.find_isinstance_check(s.expr)
15041477

1505-
if true_map is None:
1506-
self.binder.unreachable()
1507-
else:
1508-
for var, type in true_map.items():
1509-
self.binder.push(var, type)
1478+
self.push_type_map(true_map)
15101479

15111480
def visit_raise_stmt(self, s: RaiseStmt) -> Type:
15121481
"""Type check a raise statement."""
@@ -2135,6 +2104,17 @@ def function_type(self, func: FuncBase) -> FunctionLike:
21352104
def method_type(self, func: FuncBase) -> FunctionLike:
21362105
return method_type_with_fallback(func, self.named_type('builtins.function'))
21372106

2107+
def find_isinstance_check(self, n: Node) -> Tuple[Optional[Dict[Node, Type]],
2108+
Optional[Dict[Node, Type]]]:
2109+
return find_isinstance_check(n, self.type_map, self.typing_mode_weak())
2110+
2111+
def push_type_map(self, type_map: Optional[Dict[Node, Type]]) -> None:
2112+
if type_map is None:
2113+
self.binder.unreachable()
2114+
else:
2115+
for expr, type in type_map.items():
2116+
self.binder.push(expr, type)
2117+
21382118

21392119
# Data structure returned by find_isinstance_check representing
21402120
# information learned from the truth or falsehood of a condition. The

mypy/checkexpr.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,22 +1080,13 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
10801080
left_type = self.accept(e.left, ctx)
10811081

10821082
if e.op == 'and':
1083-
right_map, _ = \
1084-
mypy.checker.find_isinstance_check(e.left, self.chk.type_map,
1085-
self.chk.typing_mode_weak())
1083+
right_map, _ = self.chk.find_isinstance_check(e.left)
10861084
elif e.op == 'or':
1087-
_, right_map = \
1088-
mypy.checker.find_isinstance_check(e.left, self.chk.type_map,
1089-
self.chk.typing_mode_weak())
1085+
_, right_map = self.chk.find_isinstance_check(e.left)
10901086
else:
1091-
right_map = None
1087+
assert False, "check_boolean_op only supports 'and' and 'or' expressions"
10921088

1093-
with self.chk.binder.frame_context(can_skip=True, fall_through=0):
1094-
if right_map:
1095-
for var, type in right_map.items():
1096-
self.chk.binder.push(var, type)
1097-
1098-
right_type = self.accept(e.right, left_type)
1089+
right_type = self.analyze_cond_branch(right_map, e.right, left_type)
10991090

11001091
self.check_not_void(left_type, context)
11011092
self.check_not_void(right_type, context)
@@ -1503,10 +1494,7 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
15031494

15041495
# Gain type information from isinstance if it is there
15051496
# but only for the current expression
1506-
if_map, else_map = mypy.checker.find_isinstance_check(
1507-
e.cond,
1508-
self.chk.type_map,
1509-
self.chk.typing_mode_weak())
1497+
if_map, else_map = self.chk.find_isinstance_check(e.cond)
15101498

15111499
if_type = self.analyze_cond_branch(if_map, e.if_expr, context=ctx)
15121500

@@ -1534,9 +1522,12 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
15341522
def analyze_cond_branch(self, map: Optional[Dict[Node, Type]],
15351523
node: Node, context: Optional[Type]) -> Type:
15361524
with self.chk.binder.frame_context(can_skip=True, fall_through=0):
1537-
if map:
1538-
for var, type in map.items():
1539-
self.chk.binder.push(var, type)
1525+
if map is None:
1526+
# We still need to type check node, in case we want to
1527+
# process it for isinstance checks later
1528+
self.accept(node, context=context)
1529+
return UninhabitedType()
1530+
self.chk.push_type_map(map)
15401531
return self.accept(node, context=context)
15411532

15421533
def visit_backquote_expr(self, e: BackquoteExpr) -> Type:

0 commit comments

Comments
 (0)