From a7a995a0409b623b941a1e2f882792abed45fddf Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sat, 25 Mar 2023 23:08:37 +0100 Subject: [PATCH] Multiple inheritance considers callable objects as subtypes of functions (#14855) Let the multiple inheritance checks consider callable objects as possible subtypes of usual functions (Fixes #14852). The solution is inspired by the `visit_instance` method of `SubtypeVisitor`. The `testMultipleInheritanceOverridingOfFunctionsWithCallableInstances` tests the new functionality for decorated and non-decorated functions and callable objects. --- mypy/checker.py | 9 ++++- .../unit/check-multiple-inheritance.test | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index e0bfd08260ef..dbd6db796ffe 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2485,7 +2485,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__' diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index a8d053f9504e..d03f2e35e1c4 100644 --- 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): ...