-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Bug Report
Essentially, the bug occurs when trying to define a subclass of C≤A&B
, and implementing a shared method via @overload
:
class A:
def foo(self, x: Hashable) -> Hashable: ...
class B:
def foo(self, x: Sized) -> Sized: ...
class C(A, B):
@overload
def foo(self, x: Hashable) -> Hashable: ...
@overload
def foo(self, x: Sized) -> Sized: ...
C
is not a proper subclass of A & B
since C.foo(Hashable & Sized)
yields Hashable
, violating LSP for B
. mypy
does not detect this issue. To resolve this, an extra overload of the form def foo(x: Hashable & Sized) -> ...
is needed.
To Reproduce
A concrete demo with non-trivial types.
from typing import overload
from collections.abc import Hashable, Sized
class A:
def foo(self, x: Hashable) -> int:
return hash(x)
class B:
def foo(self, x: Sized) -> str:
return f"x has {len(x)} elements"
class C(A, B):
@overload
def foo(self, x: Hashable) -> int: ...
@overload
def foo(self, x: Sized) -> str: ...
def foo(self, x): # type: ignore[no-untyped-def]
if isinstance(x, Hashable):
return A.foo(self, x)
if isinstance(x, Sized):
return B.foo(self, x)
# mypy does not complain about LSP
a_lsp: A = C()
b_lsp: B = C()
c: C = C()
# tuples are hashable and sized.
x: tuple[int, int, int] = (1,2,3)
# but the return types are incompatible!
reveal_type(a_lsp.foo(x)) # int
reveal_type(b_lsp.foo(x)) # str
reveal_type(c.foo(x)) # int, is not a subtype of str
This file parses even using --strict
without raising any warnings/errors. (mypy-playground)
However, the inconsistency becomes obvious as c.foo(<tuple>)
reveals as int
, which is incompatible with b.foo(<tuple>)
.
Expected Behavior
@overload
def foo(self, x: Hashable) -> int: ...
@overload
def foo(self, x: Sized) -> str: ...
Are not type-safe overloads to satisfy LSP for both superclasses. Mypy should notice this and issue a warning. Likely, to solve this issue in a satisfactory manner, intersection-types are needed (python/typing#213).