Skip to content
Open
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
7 changes: 5 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8478,13 +8478,16 @@ def conditional_types(
if from_equality:
# We erase generic args because values with different generic types can compare equal
# For instance, cast(list[str], []) and cast(list[int], [])
# We also erase the current type for the overlap check, to correctly handle
# generic callables with different type variables (see mypy#21182).
erased_current = shallow_erase_type_for_equality(current_type)
proposed_type = shallow_erase_type_for_equality(proposed_type)
if not is_overlapping_types(current_type, proposed_type, ignore_promotions=False):
if not is_overlapping_types(erased_current, proposed_type, ignore_promotions=False):
# Equality narrowing is one of the places at runtime where subtyping with promotion
# does happen to match runtime semantics
# Expression is never of any type in proposed_type_ranges
return UninhabitedType(), default
if not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
if not is_overlapping_types(erased_current, proposed_type, ignore_promotions=True):
return default, default
else:
if not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
Expand Down
20 changes: 19 additions & 1 deletion mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,13 @@ def visit_union_type(self, t: UnionType) -> Type:


def shallow_erase_type_for_equality(typ: Type) -> ProperType:
"""Erase type variables from Instance's"""
"""Erase type variables from types for equality narrowing.

At runtime, generic type parameters are erased, so values with different
generic types can compare equal (e.g. ``cast(list[str], []) == cast(list[int], [])``).
This function erases generic type information so that equality-based type
narrowing does not incorrectly conclude that two values can never be equal.
"""
p_typ = get_proper_type(typ)
if isinstance(p_typ, Instance):
if not p_typ.args:
Expand All @@ -298,4 +304,16 @@ def shallow_erase_type_for_equality(typ: Type) -> ProperType:
if isinstance(p_typ, UnionType):
items = [shallow_erase_type_for_equality(item) for item in p_typ.items]
return UnionType.make_union(items)
if isinstance(p_typ, CallableType):
# Erase generic type variables from non-type-object callables.
# Type objects (classes like TupleLike) keep their type identity,
# but regular generic functions have their type vars erased to Any
# since at runtime, generic functions with different type args can
# be the same object (e.g. the identity function).
if not p_typ.variables or p_typ.is_type_obj():
return p_typ
any_type = AnyType(TypeOfAny.special_form)
return p_typ.copy_modified(
arg_types=[any_type for _ in p_typ.arg_types], ret_type=any_type, variables=()
)
return p_typ
Loading