Skip to content

Commit

Permalink
Multiple inheritance considers callable objects as subtypes of functi…
Browse files Browse the repository at this point in the history
…ons (#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.
  • Loading branch information
tyralla authored and JukkaL committed Apr 5, 2023
1 parent 7f2a5b5 commit a7a995a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 1 deletion.
9 changes: 8 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__'
Expand Down
38 changes: 38 additions & 0 deletions test-data/unit/check-multiple-inheritance.test
Original file line number Diff line number Diff line change
Expand Up @@ -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): ...

0 comments on commit a7a995a

Please sign in to comment.