Skip to content

Commit 8f2e6ad

Browse files
authored
Fix is_overlapping_types for generic callables (python#21208)
Fixes python#21182 This issue exposed this pre-existing deficiency in is_overlapping_types
1 parent ff31bb2 commit 8f2e6ad

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

mypy/meet.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,13 +541,24 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
541541
return False
542542

543543
if isinstance(left, CallableType) and isinstance(right, CallableType):
544+
# We run is_callable_compatible in both directions, similar to the logic
545+
# in is_unsafe_overlapping_overload_signatures
546+
# See comments in https://github.com/python/mypy/pull/5476
544547
return is_callable_compatible(
545548
left,
546549
right,
547550
is_compat=_is_overlapping_types,
548551
is_proper_subtype=False,
549552
ignore_pos_arg_names=not overlap_for_overloads,
550553
allow_partial_overlap=True,
554+
) or is_callable_compatible(
555+
right,
556+
left,
557+
is_compat=_is_overlapping_types,
558+
is_proper_subtype=False,
559+
ignore_pos_arg_names=not overlap_for_overloads,
560+
check_args_covariantly=True,
561+
allow_partial_overlap=True,
551562
)
552563

553564
call = None

mypy/test/testtypes.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from mypy.erasetype import erase_type, remove_instance_last_known_values
99
from mypy.indirection import TypeIndirectionVisitor
1010
from mypy.join import join_types
11-
from mypy.meet import meet_types, narrow_declared_type
11+
from mypy.meet import is_overlapping_types, meet_types, narrow_declared_type
1212
from mypy.nodes import (
1313
ARG_NAMED,
1414
ARG_OPT,
@@ -645,6 +645,20 @@ def assert_simplified_union(self, original: list[Type], union: Type) -> None:
645645
assert_equal(make_simplified_union(original), union)
646646
assert_equal(make_simplified_union(list(reversed(original))), union)
647647

648+
def test_generic_callable_overlap_is_symmetric(self) -> None:
649+
any_type = AnyType(TypeOfAny.from_omitted_generics)
650+
outer_t = TypeVarType("T", "T", TypeVarId(1), [], self.fx.o, any_type)
651+
outer_s = TypeVarType("S", "S", TypeVarId(2), [], self.fx.o, any_type)
652+
generic_t = TypeVarType("T", "T", TypeVarId(-1), [], self.fx.o, any_type)
653+
654+
callable_type = CallableType([outer_t], [ARG_POS], [None], outer_s, self.fx.function)
655+
generic_identity = CallableType(
656+
[generic_t], [ARG_POS], [None], generic_t, self.fx.function, variables=[generic_t]
657+
)
658+
659+
assert is_overlapping_types(callable_type, generic_identity)
660+
assert is_overlapping_types(generic_identity, callable_type)
661+
648662
# Helpers
649663

650664
def tuple(self, *a: Type) -> TupleType:

test-data/unit/check-narrowing.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3995,6 +3995,25 @@ def f2(func: Callable[..., T], arg: str) -> T:
39953995
[builtins fixtures/primitives.pyi]
39963996

39973997

3998+
[case testNarrowGenericCallableEquality]
3999+
# flags: --strict-equality --warn-unreachable
4000+
from typing import Callable, TypeVar
4001+
4002+
S = TypeVar("S")
4003+
T = TypeVar("T")
4004+
4005+
def identity(x: T) -> T:
4006+
return x
4007+
4008+
def msg(cmp_property: Callable[[T], S]) -> None:
4009+
if cmp_property == identity:
4010+
# TODO: the swapping of these reveal's is not ideal
4011+
reveal_type(cmp_property) # N: Revealed type is "def [T] (x: T`-1) -> T`-1"
4012+
reveal_type(identity) # N: Revealed type is "def (T`-1) -> S`-2"
4013+
return
4014+
[builtins fixtures/primitives.pyi]
4015+
4016+
39984017
[case testPropagatedParentNarrowingMeet]
39994018
# flags: --strict-equality --warn-unreachable
40004019
from __future__ import annotations

0 commit comments

Comments
 (0)