Skip to content

Commit 44bff04

Browse files
committed
Better model runtime in isinstance and type checks
1 parent 94a3cf6 commit 44bff04

3 files changed

Lines changed: 39 additions & 12 deletions

File tree

mypy/checker.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6829,8 +6829,10 @@ def narrow_type_by_identity_equality(
68296829
continue
68306830
expr = operands[j]
68316831

6832-
current_type_range = self.get_isinstance_type(expr)
6833-
if current_type_range is not None:
6832+
current_type_range = self.get_isinstance_type(expr, flatten_tuples=False)
6833+
if current_type_range is None:
6834+
current_type_range = []
6835+
else:
68346836
target_type = make_simplified_union([tr.item for tr in current_type_range])
68356837
if isinstance(target_type, AnyType):
68366838
# Avoid widening to Any for checks like `type(x) is type(y: Any)`.
@@ -7921,22 +7923,29 @@ def is_writable_attribute(self, node: Node) -> bool:
79217923
return first_item.var.is_settable_property
79227924
return False
79237925

7924-
def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
7926+
def get_isinstance_type(
7927+
self, expr: Expression, flatten_tuples: bool = True
7928+
) -> list[TypeRange] | None:
79257929
"""Get the type(s) resulting from an isinstance check.
79267930
79277931
Returns an empty list for isinstance(x, ()).
79287932
"""
79297933
if isinstance(expr, OpExpr) and expr.op == "|":
7930-
left = self.get_isinstance_type(expr.left)
7934+
left = self.get_isinstance_type(expr.left, flatten_tuples=False)
79317935
if left is None and is_literal_none(expr.left):
79327936
left = [TypeRange(NoneType(), is_upper_bound=False)]
7933-
right = self.get_isinstance_type(expr.right)
7937+
right = self.get_isinstance_type(expr.right, flatten_tuples=False)
79347938
if right is None and is_literal_none(expr.right):
79357939
right = [TypeRange(NoneType(), is_upper_bound=False)]
79367940
if left is None or right is None:
79377941
return None
79387942
return left + right
7939-
all_types = get_proper_types(flatten_types(self.lookup_type(expr)))
7943+
7944+
if flatten_tuples:
7945+
all_types = get_proper_types(flatten_types_if_tuple(self.lookup_type(expr)))
7946+
else:
7947+
all_types = [get_proper_type(self.lookup_type(expr))]
7948+
79407949
types: list[TypeRange] = []
79417950
for typ in all_types:
79427951
if isinstance(typ, FunctionLike) and typ.is_type_obj():
@@ -8640,11 +8649,11 @@ def flatten(t: Expression) -> list[Expression]:
86408649
return [t]
86418650

86428651

8643-
def flatten_types(t: Type) -> list[Type]:
8652+
def flatten_types_if_tuple(t: Type) -> list[Type]:
86448653
"""Flatten a nested sequence of tuples into one list of nodes."""
86458654
t = get_proper_type(t)
86468655
if isinstance(t, TupleType):
8647-
return [b for a in t.items for b in flatten_types(a)]
8656+
return [b for a in t.items for b in flatten_types_if_tuple(a)]
86488657
elif is_named_instance(t, "builtins.tuple"):
86498658
return [t.args[0]]
86508659
else:

test-data/unit/check-isinstance.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2938,3 +2938,21 @@ def foo(x: object, t: type[Any]):
29382938
if isinstance(x, t):
29392939
reveal_type(x) # N: Revealed type is "Any"
29402940
[builtins fixtures/isinstance.pyi]
2941+
2942+
[case testIsInstanceUnionTuple]
2943+
# flags: --strict-equality --warn-unreachable
2944+
from typing import Any
2945+
2946+
def f1(x: object):
2947+
if isinstance(x, str | (int, dict)): # E: Argument 2 to "isinstance" has incompatible type "object"; expected "type | tuple[Any, ...]"
2948+
reveal_type(x) # N: Revealed type is "builtins.object"
2949+
if type(x) == str | (int, dict):
2950+
reveal_type(x) # E: Statement is unreachable
2951+
2952+
def f2(x: Any):
2953+
if isinstance(x, str | (int, dict)): # E: Argument 2 to "isinstance" has incompatible type "object"; expected "type | tuple[Any, ...]"
2954+
reveal_type(x) # N: Revealed type is "Any"
2955+
if type(x) == str | (int, dict):
2956+
reveal_type(x) # E: Statement is unreachable
2957+
2958+
[builtins fixtures/primitives.pyi]

test-data/unit/check-narrowing.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3166,13 +3166,13 @@ if type(x) is type(y) is type(z):
31663166
reveal_type(y) # N: Revealed type is "collections.defaultdict[Any, Any]"
31673167
reveal_type(z) # N: Revealed type is "collections.defaultdict[Any, Any]"
31683168

3169-
[case testUnionTypeEquality-xfail]
3169+
[case testUnionTypeEquality]
31703170
# flags: --strict-equality --warn-unreachable
31713171
from typing import Any, reveal_type
31723172

3173-
x: Any = ()
3174-
if type(x) == (int, str):
3175-
reveal_type(x) # E: Statement is unreachable
3173+
def f(x: Any):
3174+
if type(x) == (int, str): # E: Non-overlapping equality check (left operand type: "type[Any]", right operand type: "tuple[type[int], type[str]]")
3175+
reveal_type(x) # E: Statement is unreachable
31763176
[builtins fixtures/tuple.pyi]
31773177

31783178
[case testTypeIntersectionWithConcreteTypes]

0 commit comments

Comments
 (0)