Repository: python/mypy

Issue:
Multiple inheritance checks do not consider callable objects as possible subtypes of usual functions
It seems Mypy's method `TypeChecker.check_compatibility` for checking multiple inheritance compatibility does not consider that callable objects can be subtypes of usual functions, but method `TypeChecker.check_method_override_for_base_with_name` does so for single inheritance.

For the following example, Mypy reports:
- Definition of "f" in base class "A1" is incompatible with definition in base class "A2"  [misc]
- Definition of "f" in base class "A2" is incompatible with definition in base class "A1"  [misc]

```python
from typing import Any, Callable, Self

class F:
    def __init__(self, f: Callable[[Any, int], int]) -> None:
        self.wrapped = f
        self.m: Any
    def __get__(self, obj: Any, objtype: Any) -> Self:
        self.m = obj
        return self
    def __call__(self, x: int) -> int:
        return self.wrapped(self, x)

def dec(f: Callable[[Any, int], int]) -> F:
    return F(f)

class A1:
    def f(self, x: int) -> int:
        return 1 * x

class B1(A1):  # no error reported, IMO okay
    @dec
    def f(self, x: int) -> int:
        return 2 * x

class A2:
    @dec
    def f(self, x: int) -> int:
        return 3 * x

class B21(A1, A2):  # error reported, IMO okay
    pass

class B22(A2, A1):  # error reported, IMO not okay
    pass

assert B1().f(1) == 2
assert B21().f(1) == 1
assert B22().f(1) == 3

assert hasattr(B1().f, "wrapped")
# assert hasattr(B21().f), "wrapped")  # would crash
assert hasattr(B22().f, "wrapped")
```


Discussion:

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -2484,7 +2484,14 @@ class C(B, A[int]): ...  # this is unsafe because...
         first_type = get_proper_type(self.determine_type_of_member(first))
         second_type = get_proper_type(self.determine_type_of_member(second))
 
-        if isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike):
+        # start with the special case that Instance can be a subtype of FunctionLike
+        call = None
+        if isinstance(first_type, Instance):
+            call = find_member("__call__", first_type, first_type, is_operator=True)
+        if call and isinstance(second_type, FunctionLike):
+            second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
+            ok = is_subtype(call, second_sig, ignore_pos_arg_names=True)
+        elif isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike):
             if first_type.is_type_obj() and second_type.is_type_obj():
                 # For class objects only check the subtype relationship of the classes,
                 # since we allow incompatible overrides of '__init__'/'__new__'


Test patch:
diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test
--- a/test-data/unit/check-multiple-inheritance.test
+++ b/test-data/unit/check-multiple-inheritance.test
@@ -668,3 +668,41 @@ class D1(B[str], C1): ...
 class D2(B[Union[int, str]], C2): ...
 class D3(C2, B[str]): ...
 class D4(B[str], C2): ...  # E: Definition of "foo" in base class "A" is incompatible with definition in base class "C2"
+
+
+[case testMultipleInheritanceOverridingOfFunctionsWithCallableInstances]
+from typing import Any, Callable
+
+def dec1(f: Callable[[Any, int], None]) -> Callable[[Any, int], None]: ...
+
+class F:
+    def __call__(self, x: int) -> None: ...
+
+def dec2(f: Callable[[Any, int], None]) -> F: ...
+
+class B1:
+    def f(self, x: int) -> None: ...
+
+class B2:
+    @dec1
+    def f(self, x: int) -> None: ...
+
+class B3:
+    @dec2
+    def f(self, x: int) -> None: ...
+
+class B4:
+    f = F()
+
+class C12(B1, B2): ...
+class C13(B1, B3): ...  # E: Definition of "f" in base class "B1" is incompatible with definition in base class "B3"
+class C14(B1, B4): ...  # E: Definition of "f" in base class "B1" is incompatible with definition in base class "B4"
+class C21(B2, B1): ...
+class C23(B2, B3): ...  # E: Definition of "f" in base class "B2" is incompatible with definition in base class "B3"
+class C24(B2, B4): ...  # E: Definition of "f" in base class "B2" is incompatible with definition in base class "B4"
+class C31(B3, B1): ...
+class C32(B3, B2): ...
+class C34(B3, B4): ...
+class C41(B4, B1): ...
+class C42(B4, B2): ...
+class C43(B4, B3): ...