Skip to content

Commit a58b9cc

Browse files
committed
isinstance and type checks reflect runtime behaviour better
1 parent 4a666d3 commit a58b9cc

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
@@ -6770,7 +6770,7 @@ def narrow_type_by_identity_equality(
67706770
continue
67716771
expr = operands[j]
67726772

6773-
current_type_range = self.get_isinstance_type(expr)
6773+
current_type_range = self.get_isinstance_type(expr, flatten_tuples=False)
67746774
if current_type_range is not None:
67756775
target_type = get_proper_type(
67766776
make_simplified_union([tr.item for tr in current_type_range])
@@ -7865,22 +7865,27 @@ def is_writable_attribute(self, node: Node) -> bool:
78657865
return first_item.var.is_settable_property
78667866
return False
78677867

7868-
def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
7868+
def get_isinstance_type(self, expr: Expression, flatten_tuples: bool = True) -> list[TypeRange] | None:
78697869
"""Get the type(s) resulting from an isinstance check.
78707870
78717871
Returns an empty list for isinstance(x, ()).
78727872
"""
78737873
if isinstance(expr, OpExpr) and expr.op == "|":
7874-
left = self.get_isinstance_type(expr.left)
7874+
left = self.get_isinstance_type(expr.left, flatten_tuples=False)
78757875
if left is None and is_literal_none(expr.left):
78767876
left = [TypeRange(NoneType(), is_upper_bound=False)]
7877-
right = self.get_isinstance_type(expr.right)
7877+
right = self.get_isinstance_type(expr.right, flatten_tuples=False)
78787878
if right is None and is_literal_none(expr.right):
78797879
right = [TypeRange(NoneType(), is_upper_bound=False)]
78807880
if left is None or right is None:
78817881
return None
78827882
return left + right
7883-
all_types = get_proper_types(flatten_types(self.lookup_type(expr)))
7883+
7884+
if flatten_tuples:
7885+
all_types = get_proper_types(flatten_types_if_tuple(self.lookup_type(expr)))
7886+
else:
7887+
all_types = [get_proper_type(self.lookup_type(expr))]
7888+
78847889
types: list[TypeRange] = []
78857890
for typ in all_types:
78867891
if isinstance(typ, FunctionLike) and typ.is_type_obj():
@@ -8605,11 +8610,11 @@ def flatten(t: Expression) -> list[Expression]:
86058610
return [t]
86068611

86078612

8608-
def flatten_types(t: Type) -> list[Type]:
8613+
def flatten_types_if_tuple(t: Type) -> list[Type]:
86098614
"""Flatten a nested sequence of tuples into one list of nodes."""
86108615
t = get_proper_type(t)
86118616
if isinstance(t, TupleType):
8612-
return [b for a in t.items for b in flatten_types(a)]
8617+
return [b for a in t.items for b in flatten_types_if_tuple(a)]
86138618
elif is_named_instance(t, "builtins.tuple"):
86148619
return [t.args[0]]
86158620
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)