Skip to content

Commit 0b151c6

Browse files
committed
shortcircuiting isinstance checks
1 parent 06753e7 commit 0b151c6

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

mypy/checker.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,8 @@ def visit_if_stmt(self, s: IfStmt) -> Type:
14201420
for e, b in zip(s.expr, s.body):
14211421
t = self.accept(e)
14221422
self.check_not_void(t, e)
1423-
var, type, elsetype, kind = find_isinstance_check(e, self.type_map)
1423+
var, type, elsetype, kind = find_isinstance_check(e, self.type_map,
1424+
self.typing_mode_weak())
14241425
if kind == ISINSTANCE_ALWAYS_FALSE:
14251426
# XXX should issue a warning?
14261427
pass
@@ -2032,7 +2033,8 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo,
20322033

20332034

20342035
def find_isinstance_check(node: Node,
2035-
type_map: Dict[Node, Type]) -> Tuple[Node, Type, Type, int]:
2036+
type_map: Dict[Node, Type],
2037+
weak: bool=False) -> Tuple[Node, Type, Type, int]:
20362038
"""Check if node is an isinstance(variable, type) check.
20372039
20382040
If successful, return tuple (variable, target-type, else-type,
@@ -2046,14 +2048,18 @@ def find_isinstance_check(node: Node,
20462048
variable type => the test is always True.
20472049
ISINSTANCE_ALWAYS_FALSE: The target type and the variable type are not
20482050
overlapping => the test is always False.
2051+
2052+
If it is an isinstance check, but we don't understand the argument
2053+
type, then in weak mode it is treated as Any and in non-weak mode
2054+
it is not treated as an isinstance.
20492055
"""
20502056
if isinstance(node, CallExpr):
20512057
if refers_to_fullname(node.callee, 'builtins.isinstance'):
20522058
expr = node.args[0]
20532059
if expr.literal == LITERAL_TYPE:
2060+
vartype = type_map[expr]
20542061
type = get_isinstance_type(node.args[1], type_map)
20552062
if type:
2056-
vartype = type_map[expr]
20572063
kind = ISINSTANCE_OVERLAPPING
20582064
elsetype = vartype
20592065
if vartype:
@@ -2065,6 +2071,20 @@ def find_isinstance_check(node: Node,
20652071
else:
20662072
elsetype = restrict_subtype_away(vartype, type)
20672073
return expr, type, elsetype, kind
2074+
else:
2075+
# An isinstance check, but we don't understand the type
2076+
if weak:
2077+
return expr, AnyType(), vartype, ISINSTANCE_OVERLAPPING
2078+
elif isinstance(node, OpExpr) and node.op == 'and':
2079+
# XXX We should extend this to support two isinstance checks in the same
2080+
# expression
2081+
(var, type, elsetype, kind) = find_isinstance_check(node.left, type_map, weak)
2082+
if var is None:
2083+
(var, type, elsetype, kind) = find_isinstance_check(node.left, type_map, weak)
2084+
if var:
2085+
if kind == ISINSTANCE_ALWAYS_TRUE:
2086+
kind = ISINSTANCE_OVERLAPPING
2087+
return (var, type, AnyType(), kind)
20682088
# Not a supported isinstance check
20692089
return None, AnyType(), AnyType(), -1
20702090

mypy/checkexpr.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,19 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
909909
# '[1] or []' are inferred correctly.
910910
ctx = self.chk.type_context[-1]
911911
left_type = self.accept(e.left, ctx)
912+
913+
if e.op == 'and':
914+
var, type, elsetype, kind = \
915+
mypy.checker.find_isinstance_check(e, self.chk.type_map,
916+
self.chk.typing_mode_weak())
917+
else:
918+
var = None
919+
if var:
920+
self.chk.binder.push_frame()
921+
self.chk.binder.push(var, type)
912922
right_type = self.accept(e.right, left_type)
923+
if var:
924+
self.chk.binder.pop_frame()
913925

914926
self.check_not_void(left_type, context)
915927
self.check_not_void(right_type, context)

mypy/parse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def weak_opts(self) -> Set[str]:
151151
if not opts:
152152
opts.add('local')
153153
return opts
154-
return set()
154+
return set()
155155

156156
def parse_file(self) -> MypyFile:
157157
"""Parse a mypy source file."""

mypy/test/data/check-isinstance.test

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,3 +683,28 @@ while 1:
683683
x = 'a' # Note: no error because unreachable code
684684
[builtins fixtures/isinstancelist.py]
685685

686+
687+
[case testIsinstanceAnd]
688+
class A:
689+
pass
690+
691+
class B(A):
692+
flag = 1
693+
694+
x = B() # type: A
695+
696+
if isinstance(x, B) and 1:
697+
x.flag
698+
[builtins fixtures/isinstancelist.py]
699+
[case testIsinstanceShortcircuit]
700+
class A:
701+
pass
702+
703+
class B(A):
704+
flag = 1
705+
706+
x = B() # type: A
707+
708+
if isinstance(x, B) and x.flag:
709+
pass
710+
[builtins fixtures/isinstancelist.py]

0 commit comments

Comments
 (0)