Skip to content

Commit d17902d

Browse files
committed
isinstance and type checks reflect runtime behaviour better
1 parent a631d34 commit d17902d

2 files changed

Lines changed: 30 additions & 7 deletions

File tree

mypy/checker.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6812,7 +6812,7 @@ def narrow_type_by_identity_equality(
68126812
continue
68136813
expr = operands[j]
68146814

6815-
current_type_range = self.get_isinstance_type(expr)
6815+
current_type_range = self.get_isinstance_type(expr, flatten_tuples=False)
68166816
if current_type_range is not None:
68176817
target_type = get_proper_type(
68186818
make_simplified_union([tr.item for tr in current_type_range])
@@ -7907,22 +7907,27 @@ def is_writable_attribute(self, node: Node) -> bool:
79077907
return first_item.var.is_settable_property
79087908
return False
79097909

7910-
def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
7910+
def get_isinstance_type(self, expr: Expression, flatten_tuples: bool = True) -> list[TypeRange] | None:
79117911
"""Get the type(s) resulting from an isinstance check.
79127912
79137913
Returns an empty list for isinstance(x, ()).
79147914
"""
79157915
if isinstance(expr, OpExpr) and expr.op == "|":
7916-
left = self.get_isinstance_type(expr.left)
7916+
left = self.get_isinstance_type(expr.left, flatten_tuples=False)
79177917
if left is None and is_literal_none(expr.left):
79187918
left = [TypeRange(NoneType(), is_upper_bound=False)]
7919-
right = self.get_isinstance_type(expr.right)
7919+
right = self.get_isinstance_type(expr.right, flatten_tuples=False)
79207920
if right is None and is_literal_none(expr.right):
79217921
right = [TypeRange(NoneType(), is_upper_bound=False)]
79227922
if left is None or right is None:
79237923
return None
79247924
return left + right
7925-
all_types = get_proper_types(flatten_types(self.lookup_type(expr)))
7925+
7926+
if flatten_tuples:
7927+
all_types = get_proper_types(flatten_types_if_tuple(self.lookup_type(expr)))
7928+
else:
7929+
all_types = [get_proper_type(self.lookup_type(expr))]
7930+
79267931
types: list[TypeRange] = []
79277932
for typ in all_types:
79287933
if isinstance(typ, FunctionLike) and typ.is_type_obj():
@@ -8654,11 +8659,11 @@ def flatten(t: Expression) -> list[Expression]:
86548659
return [t]
86558660

86568661

8657-
def flatten_types(t: Type) -> list[Type]:
8662+
def flatten_types_if_tuple(t: Type) -> list[Type]:
86588663
"""Flatten a nested sequence of tuples into one list of nodes."""
86598664
t = get_proper_type(t)
86608665
if isinstance(t, TupleType):
8661-
return [b for a in t.items for b in flatten_types(a)]
8666+
return [b for a in t.items for b in flatten_types_if_tuple(a)]
86628667
elif is_named_instance(t, "builtins.tuple"):
86638668
return [t.args[0]]
86648669
else:

test-data/unit/check-isinstance.test

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

0 commit comments

Comments
 (0)