Skip to content

Commit 93bc02b

Browse files
authored
Avoid widening types in conditional_types (python#21242)
Fixes python#21181 Another pre-existing issue exposed by narrowing improvements
1 parent 944e15b commit 93bc02b

2 files changed

Lines changed: 64 additions & 4 deletions

File tree

mypy/checker.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8503,12 +8503,12 @@ def conditional_types(
85038503
proposed_type: Type
85048504
remaining_type: Type
85058505

8506-
proper_type = get_proper_type(current_type)
8506+
p_current_type = get_proper_type(current_type)
85078507
# factorize over union types: isinstance(A|B, C) -> yes = A_yes | B_yes
8508-
if isinstance(proper_type, UnionType):
8508+
if isinstance(p_current_type, UnionType):
85098509
yes_items: list[Type] = []
85108510
no_items: list[Type] = []
8511-
for union_item in proper_type.items:
8511+
for union_item in p_current_type.items:
85128512
yes_type, no_type = conditional_types(
85138513
union_item,
85148514
proposed_type_ranges,
@@ -8534,7 +8534,7 @@ def conditional_types(
85348534
items[i] = item
85358535
proposed_type = get_proper_type(UnionType.make_union(items))
85368536

8537-
if isinstance(proper_type, AnyType):
8537+
if isinstance(p_current_type, AnyType):
85388538
return proposed_type, current_type
85398539
if isinstance(proposed_type, AnyType):
85408540
# We don't really know much about the proposed type, so we shouldn't
@@ -8585,6 +8585,11 @@ def conditional_types(
85858585
proposed_precise_type,
85868586
consider_runtime_isinstance=consider_runtime_isinstance,
85878587
)
8588+
8589+
# Avoid widening the type
8590+
if is_proper_subtype(p_current_type, proposed_type, ignore_promotions=True):
8591+
proposed_type = default if default is not None else current_type
8592+
85888593
return proposed_type, remaining_type
85898594

85908595

test-data/unit/check-isinstance.test

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,61 @@ def f(x: Union[int, str], typ: type) -> None:
15831583
reveal_type(x) # N: Revealed type is "builtins.int | builtins.str"
15841584
[builtins fixtures/isinstancelist.pyi]
15851585

1586+
[case testIsInstanceWithUnknownTypeMultipleNarrowing]
1587+
# flags: --strict-equality --warn-unreachable --python-version 3.10
1588+
from __future__ import annotations
1589+
from typing import Iterable
1590+
from typing_extensions import TypeAlias
1591+
import types
1592+
1593+
# Regression test for https://github.com/python/mypy/issues/21181
1594+
# We don't have the same type context as with the real stubs, so sort of fake it
1595+
_ClassInfoLike: TypeAlias = "type | tuple[_ClassInfoLike, ...]"
1596+
1597+
class A: ...
1598+
class B(A): ...
1599+
1600+
def fake_type_context(ts: list[type[A]]) -> _ClassInfoLike:
1601+
return tuple(ts) # E: Too many arguments for "tuple"
1602+
1603+
1604+
def f1(x: A | None) -> None:
1605+
if x is not None:
1606+
reveal_type(x) # N: Revealed type is "__main__.A"
1607+
if isinstance(x, object):
1608+
reveal_type(x) # N: Revealed type is "__main__.A"
1609+
1610+
1611+
def f2(x: A | None, ts: list[type[A]]) -> None:
1612+
if x is not None:
1613+
reveal_type(x) # N: Revealed type is "__main__.A"
1614+
if isinstance(x, fake_type_context(ts)):
1615+
reveal_type(x) # N: Revealed type is "__main__.A"
1616+
1617+
1618+
def f3(x: A | None, t: type | type[A]) -> None:
1619+
if x is not None:
1620+
reveal_type(x) # N: Revealed type is "__main__.A"
1621+
if isinstance(x, t):
1622+
reveal_type(x) # N: Revealed type is "__main__.A"
1623+
1624+
1625+
def f4(x: A | None, t: type) -> None:
1626+
if x is not None:
1627+
reveal_type(x) # N: Revealed type is "__main__.A"
1628+
if isinstance(x, t):
1629+
reveal_type(x) # N: Revealed type is "__main__.A"
1630+
1631+
1632+
def f5(x: object | None, ta: type[A], tb: type[B]) -> None:
1633+
if x is not None:
1634+
reveal_type(x) # N: Revealed type is "builtins.object"
1635+
if isinstance(x, ta):
1636+
reveal_type(x) # N: Revealed type is "__main__.A"
1637+
if isinstance(x, tb):
1638+
reveal_type(x) # N: Revealed type is "__main__.B"
1639+
[builtins fixtures/isinstancelist.pyi]
1640+
15861641
[case testIsInstanceWithBoundedType]
15871642
# flags: --warn-unreachable
15881643
from typing import Union, Type

0 commit comments

Comments
 (0)