Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8427,12 +8427,12 @@ def conditional_types(
proposed_type: Type
remaining_type: Type

proper_type = get_proper_type(current_type)
p_current_type = get_proper_type(current_type)
# factorize over union types: isinstance(A|B, C) -> yes = A_yes | B_yes
if isinstance(proper_type, UnionType):
if isinstance(p_current_type, UnionType):
yes_items: list[Type] = []
no_items: list[Type] = []
for union_item in proper_type.items:
for union_item in p_current_type.items:
yes_type, no_type = conditional_types(
union_item,
proposed_type_ranges,
Expand All @@ -8458,7 +8458,7 @@ def conditional_types(
items[i] = item
proposed_type = get_proper_type(UnionType.make_union(items))

if isinstance(proper_type, AnyType):
if isinstance(p_current_type, AnyType):
return proposed_type, current_type
if isinstance(proposed_type, AnyType):
# We don't really know much about the proposed type, so we shouldn't
Expand Down Expand Up @@ -8509,6 +8509,11 @@ def conditional_types(
proposed_precise_type,
consider_runtime_isinstance=consider_runtime_isinstance,
)

# Avoid widening the type
if is_proper_subtype(p_current_type, proposed_type, ignore_promotions=True):
proposed_type = default if default is not None else current_type

return proposed_type, remaining_type


Expand Down
55 changes: 55 additions & 0 deletions test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,61 @@ def f(x: Union[int, str], typ: type) -> None:
reveal_type(x) # N: Revealed type is "builtins.int | builtins.str"
[builtins fixtures/isinstancelist.pyi]

[case testIsInstanceWithUnknownTypeMultipleNarrowing]
# flags: --strict-equality --warn-unreachable --python-version 3.10
from __future__ import annotations
from typing import Iterable
from typing_extensions import TypeAlias
import types

# Regression test for https://github.com/python/mypy/issues/21181
# We don't have the same type context as with the real stubs, so sort of fake it
_ClassInfoLike: TypeAlias = "type | tuple[_ClassInfoLike, ...]"

class A: ...
class B(A): ...

def fake_type_context(ts: list[type[A]]) -> _ClassInfoLike:
return tuple(ts) # E: Too many arguments for "tuple"


def f1(x: A | None) -> None:
if x is not None:
reveal_type(x) # N: Revealed type is "__main__.A"
if isinstance(x, object):
reveal_type(x) # N: Revealed type is "__main__.A"


def f2(x: A | None, ts: list[type[A]]) -> None:
if x is not None:
reveal_type(x) # N: Revealed type is "__main__.A"
if isinstance(x, fake_type_context(ts)):
reveal_type(x) # N: Revealed type is "__main__.A"


def f3(x: A | None, t: type | type[A]) -> None:
if x is not None:
reveal_type(x) # N: Revealed type is "__main__.A"
if isinstance(x, t):
reveal_type(x) # N: Revealed type is "__main__.A"


def f4(x: A | None, t: type) -> None:
if x is not None:
reveal_type(x) # N: Revealed type is "__main__.A"
if isinstance(x, t):
reveal_type(x) # N: Revealed type is "__main__.A"


def f5(x: object | None, ta: type[A], tb: type[B]) -> None:
if x is not None:
reveal_type(x) # N: Revealed type is "builtins.object"
if isinstance(x, ta):
reveal_type(x) # N: Revealed type is "__main__.A"
if isinstance(x, tb):
reveal_type(x) # N: Revealed type is "__main__.B"
[builtins fixtures/isinstancelist.pyi]

[case testIsInstanceWithBoundedType]
# flags: --warn-unreachable
from typing import Union, Type
Expand Down
Loading