Skip to content

Commit c1a797e

Browse files
committed
Merge branch 'jhance-if-dict'
2 parents 6eb1e92 + d7b14d3 commit c1a797e

File tree

3 files changed

+126
-76
lines changed

3 files changed

+126
-76
lines changed

mypy/checker.py

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import itertools
44

5-
from typing import Any, Dict, Set, List, cast, Tuple, Callable, TypeVar, Union
5+
from typing import (
6+
Any, Dict, Set, List, cast, Tuple, Callable, TypeVar, Union, Optional
7+
)
68

79
from mypy.errors import Errors
810
from mypy.nodes import (
@@ -47,11 +49,6 @@
4749
from mypy.meet import meet_simple, meet_simple_away, nearest_builtin_ancestor, is_overlapping_types
4850

4951

50-
# Kinds of isinstance checks.
51-
ISINSTANCE_OVERLAPPING = 0
52-
ISINSTANCE_ALWAYS_TRUE = 1
53-
ISINSTANCE_ALWAYS_FALSE = 2
54-
5552
T = TypeVar('T')
5653

5754

@@ -1448,16 +1445,21 @@ def visit_if_stmt(self, s: IfStmt) -> Type:
14481445
for e, b in zip(s.expr, s.body):
14491446
t = self.accept(e)
14501447
self.check_not_void(t, e)
1451-
var, type, elsetype, kind = find_isinstance_check(e, self.type_map,
1452-
self.typing_mode_weak())
1453-
if kind == ISINSTANCE_ALWAYS_FALSE:
1448+
if_map, else_map = find_isinstance_check(
1449+
e, self.type_map,
1450+
self.typing_mode_weak()
1451+
)
1452+
if if_map is None:
1453+
# The condition is always false
14541454
# XXX should issue a warning?
14551455
pass
14561456
else:
14571457
# Only type check body if the if condition can be true.
14581458
self.binder.push_frame()
1459-
if var:
1460-
self.binder.push(var, type)
1459+
if if_map:
1460+
for var, type in if_map.items():
1461+
self.binder.push(var, type)
1462+
14611463
self.accept(b)
14621464
_, frame = self.binder.pop_frame()
14631465
if not self.breaking_out:
@@ -1466,9 +1468,10 @@ def visit_if_stmt(self, s: IfStmt) -> Type:
14661468

14671469
self.breaking_out = False
14681470

1469-
if var:
1470-
self.binder.push(var, elsetype)
1471-
if kind == ISINSTANCE_ALWAYS_TRUE:
1471+
if else_map:
1472+
for var, type in else_map.items():
1473+
self.binder.push(var, type)
1474+
if else_map is None:
14721475
# The condition is always true => remaining elif/else blocks
14731476
# can never be reached.
14741477

@@ -2061,24 +2064,17 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo,
20612064

20622065
def find_isinstance_check(node: Node,
20632066
type_map: Dict[Node, Type],
2064-
weak: bool=False) -> Tuple[Node, Type, Type, int]:
2065-
"""Check if node is an isinstance(variable, type) check.
2066-
2067-
If successful, return tuple (variable, target-type, else-type,
2068-
kind); otherwise, return (None, AnyType, AnyType, -1).
2067+
weak: bool=False) \
2068+
-> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]:
2069+
"""Find any isinstance checks (within a chain of ands).
20692070
2070-
When successful, the kind takes one of these values:
2071+
Return value is a map of variables to their types if the condition
2072+
is true and a map of variables to their types if the condition is false.
20712073
2072-
ISINSTANCE_OVERLAPPING: The type of variable and the target type are
2073-
partially overlapping => the test result can be True or False.
2074-
ISINSTANCE_ALWAYS_TRUE: The target type at least as general as the
2075-
variable type => the test is always True.
2076-
ISINSTANCE_ALWAYS_FALSE: The target type and the variable type are not
2077-
overlapping => the test is always False.
2074+
If either of the values in the tuple is None, then that particular
2075+
branch can never occur.
20782076
2079-
If it is an isinstance check, but we don't understand the argument
2080-
type, then in weak mode it is treated as Any and in non-weak mode
2081-
it is not treated as an isinstance.
2077+
Guaranteed to not return None, None. (But may return {}, {})
20822078
"""
20832079
if isinstance(node, CallExpr):
20842080
if refers_to_fullname(node.callee, 'builtins.isinstance'):
@@ -2087,46 +2083,48 @@ def find_isinstance_check(node: Node,
20872083
vartype = type_map[expr]
20882084
type = get_isinstance_type(node.args[1], type_map)
20892085
if type:
2090-
kind = ISINSTANCE_OVERLAPPING
20912086
elsetype = vartype
20922087
if vartype:
20932088
if is_proper_subtype(vartype, type):
2094-
kind = ISINSTANCE_ALWAYS_TRUE
20952089
elsetype = None
2090+
return {expr: type}, None
20962091
elif not is_overlapping_types(vartype, type):
2097-
kind = ISINSTANCE_ALWAYS_FALSE
2092+
return None, {expr: elsetype}
20982093
else:
20992094
elsetype = restrict_subtype_away(vartype, type)
2100-
return expr, type, elsetype, kind
2095+
return {expr: type}, {expr: elsetype}
21012096
else:
21022097
# An isinstance check, but we don't understand the type
21032098
if weak:
2104-
return expr, AnyType(), vartype, ISINSTANCE_OVERLAPPING
2099+
return {expr: AnyType()}, {expr: vartype}
21052100
elif isinstance(node, OpExpr) and node.op == 'and':
2106-
# XXX We should extend this to support two isinstance checks in the same
2107-
# expression
2108-
(var, type, elsetype, kind) = find_isinstance_check(node.left, type_map, weak)
2109-
if var is None:
2110-
(var, type, elsetype, kind) = find_isinstance_check(node.left, type_map, weak)
2111-
if var:
2112-
if kind == ISINSTANCE_ALWAYS_TRUE:
2113-
kind = ISINSTANCE_OVERLAPPING
2114-
return (var, type, AnyType(), kind)
2101+
left_if_vars, right_else_vars = find_isinstance_check(
2102+
node.left,
2103+
type_map,
2104+
weak,
2105+
)
2106+
2107+
right_if_vars, right_else_vars = find_isinstance_check(
2108+
node.right,
2109+
type_map,
2110+
weak,
2111+
)
2112+
if left_if_vars:
2113+
if right_if_vars is not None:
2114+
left_if_vars.update(right_if_vars)
2115+
else:
2116+
left_if_vars = None
2117+
else:
2118+
left_if_vars = right_if_vars
2119+
2120+
# Make no claim about the types in else
2121+
return left_if_vars, {}
21152122
elif isinstance(node, UnaryExpr) and node.op == 'not':
2116-
(var, type, elsetype, kind) = find_isinstance_check(node.expr, type_map, weak)
2117-
return (var, elsetype, type, invert_isinstance_kind(kind))
2123+
left, right = find_isinstance_check(node.expr, type_map, weak)
2124+
return right, left
21182125

21192126
# Not a supported isinstance check
2120-
return None, AnyType(), AnyType(), -1
2121-
2122-
2123-
def invert_isinstance_kind(kind: int) -> int:
2124-
if kind == ISINSTANCE_ALWAYS_TRUE:
2125-
return ISINSTANCE_ALWAYS_FALSE
2126-
elif kind == ISINSTANCE_ALWAYS_FALSE:
2127-
return ISINSTANCE_ALWAYS_TRUE
2128-
else:
2129-
return kind
2127+
return {}, {}
21302128

21312129

21322130
def get_isinstance_type(node: Node, type_map: Dict[Node, Type]) -> Type:

mypy/checkexpr.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -911,17 +911,21 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
911911
left_type = self.accept(e.left, ctx)
912912

913913
if e.op == 'and':
914-
var, type, elsetype, kind = \
915-
mypy.checker.find_isinstance_check(e, self.chk.type_map,
914+
# else_map unused
915+
if_map, else_map = \
916+
mypy.checker.find_isinstance_check(e.left, self.chk.type_map,
916917
self.chk.typing_mode_weak())
917918
else:
918-
var = None
919-
if var:
920-
self.chk.binder.push_frame()
921-
self.chk.binder.push(var, type)
919+
if_map = None
920+
921+
self.chk.binder.push_frame()
922+
if if_map:
923+
for var, type in if_map.items():
924+
self.chk.binder.push(var, type)
925+
922926
right_type = self.accept(e.right, left_type)
923-
if var:
924-
self.chk.binder.pop_frame()
927+
928+
self.chk.binder.pop_frame()
925929

926930
self.check_not_void(left_type, context)
927931
self.check_not_void(right_type, context)
@@ -1278,22 +1282,30 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
12781282

12791283
# Gain type information from isinstance if it is there
12801284
# but only for the current expression
1281-
variable, inst_if_type, inst_else_type, kind = mypy.checker.find_isinstance_check(
1285+
if_map, else_map = mypy.checker.find_isinstance_check(
12821286
e.cond,
12831287
self.chk.type_map,
12841288
self.chk.typing_mode_weak())
1285-
if variable:
1286-
self.chk.binder.push_frame()
1287-
self.chk.binder.push(variable, inst_if_type)
1288-
if_type = self.accept(e.if_expr)
1289-
self.chk.binder.pop_frame()
1290-
self.chk.binder.push_frame()
1291-
self.chk.binder.push(variable, inst_else_type)
1292-
else_type = self.accept(e.else_expr, context=if_type)
1293-
self.chk.binder.pop_frame()
1294-
else:
1295-
if_type = self.accept(e.if_expr)
1296-
else_type = self.accept(e.else_expr, context=if_type)
1289+
1290+
self.chk.binder.push_frame()
1291+
1292+
if if_map:
1293+
for var, type in if_map.items():
1294+
self.chk.binder.push(var, type)
1295+
1296+
if_type = self.accept(e.if_expr)
1297+
1298+
self.chk.binder.pop_frame()
1299+
self.chk.binder.push_frame()
1300+
1301+
if else_map:
1302+
for var, type in else_map.items():
1303+
self.chk.binder.push(var, type)
1304+
1305+
else_type = self.accept(e.else_expr, context=if_type)
1306+
1307+
self.chk.binder.pop_frame()
1308+
12971309
return join.join_types(if_type, else_type)
12981310

12991311
#

mypy/test/data/check-isinstance.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,3 +721,43 @@ x.flag if isinstance(x, B) else 0
721721
0 if not isinstance(x, B) else x.flag
722722
0 if isinstance(x, B) else x.flag # E: "A" has no attribute "flag"
723723
[builtins fixtures/isinstancelist.py]
724+
725+
[case testIsinstanceMultiAnd]
726+
class A:
727+
pass
728+
729+
class B(A):
730+
flag = 1
731+
732+
class C(A):
733+
glaf = 1
734+
735+
x = B() # type: A
736+
y = C() # type: A
737+
738+
if isinstance(x, B) and isinstance(y, C):
739+
x.flag += 1
740+
y.glaf += 1
741+
x() # E: "B" not callable
742+
y() # E: "C" not callable
743+
else:
744+
x() # E: "A" not callable
745+
y() # E: "A" not callable
746+
[builtins fixtures/isinstancelist.py]
747+
748+
[case testIsinstanceMultiAndSpecialCase]
749+
class A:
750+
pass
751+
752+
class B(A):
753+
flag = 1
754+
755+
class C(A):
756+
glaf = 1
757+
758+
x = B() # type: A
759+
y = C() # type: A
760+
761+
if isinstance(x, B) and isinstance(y, int):
762+
1() # type checking skipped
763+
[builtins fixtures/isinstancelist.py]

0 commit comments

Comments
 (0)