After astral-sh/ruff#25315 we consider a method on a protocol to be disjoint with a method on some type if their return types are disjoint. It's noted on that PR that this is technically unsound wrt the possibility of a common subtype with method returning Never, but there are also non-Never-related problems: a common subtype could have an overloaded method:
from typing import Protocol, overload
class IntF(Protocol):
def f(self, x: int, /) -> int: ...
class StrF:
def f(self, x: str, /) -> str:
return x
class Both(StrF):
@overload
def f(self, x: str, /) -> str: ...
@overload
def f(self, x: int, /) -> int: ...
def f(self, x: str | int, /) -> str | int:
return x
Both is a valid subtype of StrF, and implements IntF -- but we now consider IntF and StrF to be disjoint.
I think the better version of this rule needs to also consider arguments. If the argument sets of the two methods are disjoint, then we can't consider the methods disjoint, even if their return types are disjoint, because an overload could satisfy both signatures at the same time.
A more conservative (and easier to implement) rule would be to only implement the disjointness if the methods can both accept the zero-argument call -- in this case their argument sets are trivially not disjoint, and an overload cannot distinguish the signatures successfully, so we can go ahead with the disjoint conclusion. This more conservative rule would be sufficient for e.g. the __len__ cases we care about.
After astral-sh/ruff#25315 we consider a method on a protocol to be disjoint with a method on some type if their return types are disjoint. It's noted on that PR that this is technically unsound wrt the possibility of a common subtype with method returning
Never, but there are also non-Never-related problems: a common subtype could have an overloaded method:Bothis a valid subtype ofStrF, and implementsIntF-- but we now considerIntFandStrFto be disjoint.I think the better version of this rule needs to also consider arguments. If the argument sets of the two methods are disjoint, then we can't consider the methods disjoint, even if their return types are disjoint, because an overload could satisfy both signatures at the same time.
A more conservative (and easier to implement) rule would be to only implement the disjointness if the methods can both accept the zero-argument call -- in this case their argument sets are trivially not disjoint, and an overload cannot distinguish the signatures successfully, so we can go ahead with the disjoint conclusion. This more conservative rule would be sufficient for e.g. the
__len__cases we care about.