Skip to content

Commit b5c95d8

Browse files
authored
Exclude irrelevant members in narrow_declared_type from union overlapping with enum (#18897)
Fixes #18895. The original implementation of that block was introduced as a performance optimization in #12032. It's in fact incorrect: it produces overly optimistic meets, assuming that *any* match among union items makes them *all* relevant. As discussed in #18895, this actually results in unexpected `meet` behaviour, as demonstrated by ```python from enum import Enum from typing_extensions import TypeIs, Literal class Model(str, Enum): A = 'a' B = 'a' def is_model_a(model: str) -> TypeIs[Literal[Model.A, "foo"]]: return True def handle(model: Model) -> None: if is_model_a(model): reveal_type(model) # N: Revealed type is "Union[Literal[__main__.Model.A], Literal['foo']]" def is_int_or_list(model: object) -> TypeIs[int | list[int]]: return True def compare(x: int | str) -> None: if is_int_or_list(x): reveal_type(x) # N: Revealed type is "builtins.int" ``` This patch restores filtering of union members, but keeps it running before the expensive `is_overlapping_types` check involving expansion.
1 parent bb01516 commit b5c95d8

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

mypy/meet.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
143143
]
144144
)
145145
if is_enum_overlapping_union(declared, narrowed):
146-
return original_narrowed
146+
# Quick check before reaching `is_overlapping_types`. If it's enum/literal overlap,
147+
# avoid full expansion and make it faster.
148+
assert isinstance(narrowed, UnionType)
149+
return make_simplified_union(
150+
[narrow_declared_type(declared, x) for x in narrowed.relevant_items()]
151+
)
147152
elif not is_overlapping_types(declared, narrowed, prohibit_none_typevar_overlap=True):
148153
if state.strict_optional:
149154
return UninhabitedType()

test-data/unit/check-typeguard.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,3 +803,33 @@ def test() -> None:
803803
return
804804
reveal_type(x) # N: Revealed type is "builtins.list[__main__.C]"
805805
[builtins fixtures/tuple.pyi]
806+
807+
[case testTypeGuardedTypeDoesNotLeak]
808+
# https://github.com/python/mypy/issues/18895
809+
from enum import Enum
810+
from typing import Literal, Union
811+
from typing_extensions import TypeGuard
812+
813+
class Model(str, Enum):
814+
A1 = 'model_a1'
815+
A2 = 'model_a2'
816+
B = 'model_b'
817+
818+
MODEL_A = Literal[Model.A1, Model.A2]
819+
MODEL_B = Literal[Model.B]
820+
821+
def is_model_a(model: str) -> TypeGuard[MODEL_A]:
822+
return True
823+
824+
def is_model_b(model: str) -> TypeGuard[MODEL_B]:
825+
return True
826+
827+
def process_model(model: Union[MODEL_A, MODEL_B]) -> int:
828+
return 42
829+
830+
def handle(model: Model) -> int:
831+
if is_model_a(model) or is_model_b(model):
832+
reveal_type(model) # N: Revealed type is "__main__.Model"
833+
return process_model(model)
834+
return 0
835+
[builtins fixtures/tuple.pyi]

test-data/unit/check-typeis.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,3 +936,20 @@ def func(arg: Any) -> None:
936936
if is_dataclass(arg):
937937
reveal_type(arg) # N: Revealed type is "Union[Type[__main__.DataclassInstance], __main__.DataclassInstance]"
938938
[builtins fixtures/tuple.pyi]
939+
940+
[case testTypeIsEnumOverlappingUnionExcludesIrrelevant]
941+
from enum import Enum
942+
from typing import Literal
943+
from typing_extensions import TypeIs
944+
945+
class Model(str, Enum):
946+
A = 'a'
947+
B = 'a'
948+
949+
def is_model_a(model: str) -> TypeIs[Literal[Model.A, "foo"]]:
950+
return True
951+
952+
def handle(model: Model) -> None:
953+
if is_model_a(model):
954+
reveal_type(model) # N: Revealed type is "Literal[__main__.Model.A]"
955+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)