I have a type and a subtype that define some binary operations in such a way that I'd like the operation to return the most-specific type possible. For example, in the following code example I expect the following behavior.
Logic + Logic => Logic
Logic + Bit => Logic
Bit + Logic => Logic
Bit + Bit => Bit
Example:
class Logic:
"""
4-value logic type: 0, 1, X, Z
"""
def __and__(self, other: 'Logic') -> 'Logic':
if not isinstance(other, Logic):
return NotImplemented
# ...
def __rand__(self, other: 'Logic') -> 'Logic':
return self & other
class Bit(Logic):
"""
2-value bit type: 0, 1
As a subtype of Logic, Logic(0) == Bit(0) and hash(Logic(0)) == hash(Bit(0))
"""
def __and__(self, other: 'Bit') -> 'Bit':
if not isinstance(other, Bit):
return NotImplemented
# ...
def __rand__(self, other: 'Bit') -> 'Bit':
return self & other
While this works at runtime, mypy complains:
example.pyi:19: error: Argument 1 of "__and__" is incompatible with supertype "Logic"; supertype defines the argument type as "Logic"
example.pyi:19: note: This violates the Liskov substitution principle
example.pyi:19: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
How do I express this relationship? I feel like the problem might have something to do with the fact that Python doesn't OOTB support multiple dispatch and that is encoded in mypy's type system in a way that I can't express this. It could be that mypy is being too nitpicky here, this should be fine because the Bit
argument mentioned in Bit.__and__
is a subtype of Logic
, so "incompatible" is not correct.