-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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 strThis 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).