Skip to content

Commit e4d68da

Browse files
committed
isinstance and type checks reflect runtime behaviour better
1 parent bfc78a0 commit e4d68da

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
@@ -6781,7 +6781,7 @@ def narrow_type_by_identity_equality(
67816781
continue
67826782
expr = operands[j]
67836783

6784-
current_type_range = self.get_isinstance_type(expr)
6784+
current_type_range = self.get_isinstance_type(expr, flatten_tuples=False)
67856785
if current_type_range is not None:
67866786
target_type = get_proper_type(
67876787
make_simplified_union([tr.item for tr in current_type_range])
@@ -7876,22 +7876,27 @@ def is_writable_attribute(self, node: Node) -> bool:
78767876
return first_item.var.is_settable_property
78777877
return False
78787878

7879-
def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
7879+
def get_isinstance_type(self, expr: Expression, flatten_tuples: bool = True) -> list[TypeRange] | None:
78807880
"""Get the type(s) resulting from an isinstance check.
78817881
78827882
Returns an empty list for isinstance(x, ()).
78837883
"""
78847884
if isinstance(expr, OpExpr) and expr.op == "|":
7885-
left = self.get_isinstance_type(expr.left)
7885+
left = self.get_isinstance_type(expr.left, flatten_tuples=False)
78867886
if left is None and is_literal_none(expr.left):
78877887
left = [TypeRange(NoneType(), is_upper_bound=False)]
7888-
right = self.get_isinstance_type(expr.right)
7888+
right = self.get_isinstance_type(expr.right, flatten_tuples=False)
78897889
if right is None and is_literal_none(expr.right):
78907890
right = [TypeRange(NoneType(), is_upper_bound=False)]
78917891
if left is None or right is None:
78927892
return None
78937893
return left + right
7894-
all_types = get_proper_types(flatten_types(self.lookup_type(expr)))
7894+
7895+
if flatten_tuples:
7896+
all_types = get_proper_types(flatten_types_if_tuple(self.lookup_type(expr)))
7897+
else:
7898+
all_types = [get_proper_type(self.lookup_type(expr))]
7899+
78957900
types: list[TypeRange] = []
78967901
for typ in all_types:
78977902
if isinstance(typ, FunctionLike) and typ.is_type_obj():
@@ -8619,11 +8624,11 @@ def flatten(t: Expression) -> list[Expression]:
86198624
return [t]
86208625

86218626

8622-
def flatten_types(t: Type) -> list[Type]:
8627+
def flatten_types_if_tuple(t: Type) -> list[Type]:
86238628
"""Flatten a nested sequence of tuples into one list of nodes."""
86248629
t = get_proper_type(t)
86258630
if isinstance(t, TupleType):
8626-
return [b for a in t.items for b in flatten_types(a)]
8631+
return [b for a in t.items for b in flatten_types_if_tuple(a)]
86278632
elif is_named_instance(t, "builtins.tuple"):
86288633
return [t.args[0]]
86298634
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)