You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[overload][intersection-types] undetected LSP violation in multiple inheritance #15790
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.
fromtypingimportoverloadfromcollections.abcimportHashable, SizedclassA:
deffoo(self, x: Hashable) ->int:
returnhash(x)
classB:
deffoo(self, x: Sized) ->str:
returnf"x has {len(x)} elements"classC(A, B):
@overloaddeffoo(self, x: Hashable) ->int: ...
@overloaddeffoo(self, x: Sized) ->str: ...
deffoo(self, x): # type: ignore[no-untyped-def]ifisinstance(x, Hashable):
returnA.foo(self, x)
ifisinstance(x, Sized):
returnB.foo(self, x)
# mypy does not complain about LSPa_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)) # intreveal_type(b_lsp.foo(x)) # strreveal_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>).
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).
I don't think this is a bug. A subclass defined this way is replaceably usable with each parent class. There is ongoing discussion about this in the intersection pep, this may be prematurely calling something a bug
There is disucssion about the soundness of this and the need for Not as well to correctly express the actual type which has come up in the intersections pep discussions. We need a way for these overloads to express that they don't accept types which overlap. Resolving this case is possible both now by saying in the immediate term that it should be forbidden as-is, or by saying that use of the method is ambiguous without further constraint.
As we don't currently have a way to express this, this should not be treated as incorrect, as it is possible to implement both of the behaviors the overload sets express.
Activity
[-][overload][intersection-types] multiple inheritance LSP violation[/-][+][overload][intersection-types] undetected LSP violation in multiple inheritance[/+]Fixed a bug that led to a false negative when determining whether two…
Fixed a bug that led to a false negative when determining whether two…
DiscordLiz commentedon Aug 1, 2023
I don't think this is a bug. A subclass defined this way is replaceably usable with each parent class. There is ongoing discussion about this in the intersection pep, this may be prematurely calling something a bug
DiscordLiz commentedon Aug 1, 2023
Here is an example where it can clearly be used as a drop in replacement for either base:
DiscordLiz commentedon Aug 1, 2023
There is disucssion about the soundness of this and the need for
Notas well to correctly express the actual type which has come up in the intersections pep discussions. We need a way for these overloads to express that they don't accept types which overlap. Resolving this case is possible both now by saying in the immediate term that it should be forbidden as-is, or by saying that use of the method is ambiguous without further constraint.DiscordLiz commentedon Aug 1, 2023
Copying the relevant comment portion: (note & for Intersection, ~ for Not)
a more expressive overload set for AB.foo would be
(overload) (ParamA & ParamB) -> Never
(overload) (ParamA & ~ParamB) -> ParamA
(overload) (ParamB & ~ParamA) -> ParamB
But if someone had an implementation that was
(overload) (ParamA & ParamB) -> ParamA & ParamB
(overload) (ParamA & ~ParamB) -> ParamA
(overload) (ParamB & ~ParamA) -> ParamB
it would also be valid.
As we don't currently have a way to express this, this should not be treated as incorrect, as it is possible to implement both of the behaviors the overload sets express.
Revert "Fixed a bug that led to a false negative when determining whe…