Skip to content

Protocol method disjointness rule should consider possibility of overloads #3618

@carljm

Description

@carljm

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type propertiessubtyping, assignability, equivalence, and moreunsoundnessTy can give an expression type T, but at runtime it can have a non-T value, with no diagnostic.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions