Skip to content

Commit ac61921

Browse files
authored
[mypyc] Don't coerce types checked with isinstance (#811) (#10245)
Prevent the coercion of things whose types are checked with isinstance to other types - such coercion is not necessary and can lead to bugs. Also, add test for avoiding coercion of isinstance() comparators. Fixes mypyc/mypyc#811.
1 parent fcbb2dd commit ac61921

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

mypyc/irbuild/specialize.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,15 @@ def gen_inner_stmts() -> None:
278278

279279
@specialize_function('builtins.isinstance')
280280
def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
281-
# Special case builtins.isinstance
282281
if (len(expr.args) == 2
283282
and expr.arg_kinds == [ARG_POS, ARG_POS]
284283
and isinstance(expr.args[1], (RefExpr, TupleExpr))):
284+
# Special case for builtins.isinstance
285+
# Prevent coercions on the thing we are checking the instance of - there is no need to
286+
# coerce something to a new type before checking what type it is, and the coercion could
287+
# lead to bugs.
288+
builder.types[expr.args[0]] = AnyType(TypeOfAny.from_error)
289+
285290
irs = builder.flatten_classes(expr.args[1])
286291
if irs is not None:
287292
return builder.builder.isinstance_helper(builder.accept(expr.args[0]), irs, expr.line)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
[case testIsinstanceInt]
2+
def is_int(value: object) -> bool:
3+
return isinstance(value, int)
4+
5+
[out]
6+
def is_int(value):
7+
value, r0 :: object
8+
r1 :: int32
9+
r2 :: bit
10+
r3 :: bool
11+
L0:
12+
r0 = load_address PyLong_Type
13+
r1 = PyObject_IsInstance(value, r0)
14+
r2 = r1 >= 0 :: signed
15+
r3 = truncate r1: int32 to builtins.bool
16+
return r3
17+
18+
[case testIsinstanceNotBool1]
19+
def is_not_bool(value: object) -> bool:
20+
return not isinstance(value, bool)
21+
22+
[out]
23+
def is_not_bool(value):
24+
value, r0 :: object
25+
r1 :: str
26+
r2 :: object
27+
r3 :: int32
28+
r4 :: bit
29+
r5, r6 :: bool
30+
L0:
31+
r0 = builtins :: module
32+
r1 = 'bool'
33+
r2 = CPyObject_GetAttr(r0, r1)
34+
r3 = PyObject_IsInstance(value, r2)
35+
r4 = r3 >= 0 :: signed
36+
r5 = truncate r3: int32 to builtins.bool
37+
r6 = r5 ^ 1
38+
return r6
39+
40+
[case testIsinstanceIntAndNotBool]
41+
# This test is to ensure that 'value' doesn't get coerced to int when we are
42+
# checking if it's a bool, since an int can never be an instance of a bool
43+
def is_not_bool_and_is_int(value: object) -> bool:
44+
return isinstance(value, int) and not isinstance(value, bool)
45+
46+
[out]
47+
def is_not_bool_and_is_int(value):
48+
value, r0 :: object
49+
r1 :: int32
50+
r2 :: bit
51+
r3, r4 :: bool
52+
r5 :: object
53+
r6 :: str
54+
r7 :: object
55+
r8 :: int32
56+
r9 :: bit
57+
r10, r11 :: bool
58+
L0:
59+
r0 = load_address PyLong_Type
60+
r1 = PyObject_IsInstance(value, r0)
61+
r2 = r1 >= 0 :: signed
62+
r3 = truncate r1: int32 to builtins.bool
63+
if r3 goto L2 else goto L1 :: bool
64+
L1:
65+
r4 = r3
66+
goto L3
67+
L2:
68+
r5 = builtins :: module
69+
r6 = 'bool'
70+
r7 = CPyObject_GetAttr(r5, r6)
71+
r8 = PyObject_IsInstance(value, r7)
72+
r9 = r8 >= 0 :: signed
73+
r10 = truncate r8: int32 to builtins.bool
74+
r11 = r10 ^ 1
75+
r4 = r11
76+
L3:
77+
return r4

mypyc/test-data/run-integers.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ def check_bitwise(x: int, y: int) -> None:
157157
check_or(ll, rr)
158158
check_xor(ll, rr)
159159

160+
[case testIsinstanceIntAndNotBool]
161+
def test_isinstance_int_and_not_bool(value: object) -> bool:
162+
return isinstance(value, int) and not isinstance(value, bool)
163+
[file driver.py]
164+
from native import test_isinstance_int_and_not_bool
165+
assert test_isinstance_int_and_not_bool(True) == False
166+
assert test_isinstance_int_and_not_bool(1) == True
167+
168+
160169
SHIFT = 30
161170
DIGIT0a = 615729753
162171
DIGIT0b = 832796681

mypyc/test/test_irbuild.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'irbuild-int.test',
3333
'irbuild-vectorcall.test',
3434
'irbuild-unreachable.test',
35+
'irbuild-isinstance.test',
3536
]
3637

3738

0 commit comments

Comments
 (0)