Skip to content

Order of additional overloads matters in subclass where it shouldnt [reportIncompatibleMethodOverride] #11549

Description

Describe the bug

In some cases, a valid order of instance-method overloads will be (wrongly) rejected by pyright/pylance, but only if the implementing class inherits from a class that defines some of the overloads.

For example (see below for a complete source code including all imports etc) the following two classes are type hinted correctly (as expected):

class ValidBase:
    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    def map(self, x: str|int, wrap: Literal[True]) -> Tuple[str]|Tuple[int]: ...

class ValidComplete:
    @overload
    def map(self, x: str, wrap: Literal[False]=False) -> str: ...
    @overload
    def map(self, x: int, wrap: Literal[False]=False) -> int: ...

    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    def map(self, x: str|int, wrap: bool=False) -> str|int|Tuple[str]|Tuple[int]: ...

Notice, that the two last overloads in ValidComplete are the same as in ValidBase and that every object inheriting from ValidComplete also completely matches the interface defined in ValidBase.

However, the following class, although being a 1:1 copy of ValidComplete (+ inheriting from ValidBase) and having the exact same (mutually exclusive) overloads on the map(...) method as ValidBase will show a type error:

class InvalidDerived(ValidBase):
    @overload
    def map(self, x: str, wrap: Literal[False]=False) -> str: ...
    @overload
    def map(self, x: int, wrap: Literal[False]=False) -> int: ...

    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    def map(self, x: str|int, wrap: bool=False) -> str|int|Tuple[str]|Tuple[int]: ... # -> type error saying that "map(...)" overrides "ValidBase" in an incompatible manner

Finally, if the two overload blocks are switched in the derived class, the type error disappears:

class ValidDerived(ValidBase):
    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    @overload
    def map(self, x: str, wrap: Literal[False]=False) -> str: ...
    @overload
    def map(self, x: int, wrap: Literal[False]=False) -> int: ...

    def map(self, x: str|int, wrap: bool=False) -> str|int|Tuple[str]|Tuple[int]: ... # -> no type error?

I think the following behaviours are wrong here:

  1. map(...) on InvalidDerived should not be marked as invalid by pyright
  2. The order between these mutually exclusive overloads should not matter, so if the behaviour of InvalidDerived is intended to be a type error, then ValidDerived should also be marked as invalid

Code or Screenshots

from typing import Literal, Tuple, overload

# works as expected:
class ValidBase:
    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    def map(self, x: str|int, wrap: Literal[True]) -> Tuple[str]|Tuple[int]: ...

# works as expected
class ValidComplete:
    @overload
    def map(self, x: str, wrap: Literal[False]=False) -> str: ...
    @overload
    def map(self, x: int, wrap: Literal[False]=False) -> int: ...

    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    def map(self, x: str|int, wrap: bool=False) -> str|int|Tuple[str]|Tuple[int]: ...

# does not work, this is the issue i am reporting:
class InvalidDerived(ValidBase):
    @overload
    def map(self, x: str, wrap: Literal[False]=False) -> str: ...
    @overload
    def map(self, x: int, wrap: Literal[False]=False) -> int: ...

    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    def map(self, x: str|int, wrap: bool=False) -> str|int|Tuple[str]|Tuple[int]: ... # -> type error saying that "map(...)" overrides "ValidBase" in an incompatible manner

# works, but i dont understand why the order between the overloads would matter?
class ValidDerived(ValidBase):
    @overload
    def map(self, x: str, wrap: Literal[True]) -> Tuple[str]: ...
    @overload
    def map(self, x: int, wrap: Literal[True]) -> Tuple[int]: ...

    @overload
    def map(self, x: str, wrap: Literal[False]=False) -> str: ...
    @overload
    def map(self, x: int, wrap: Literal[False]=False) -> int: ...

    def map(self, x: str|int, wrap: bool=False) -> str|int|Tuple[str]|Tuple[int]: ... # -> no type error?
Image

(roughly translates to)

The method "map" overrides the class "ValidBase" in an incompatible manner.
The override does not handle all overloads of the base method.
Pylance(reportIncompatibleMethodOverride)
Untitled-6(10, 9): Overriden method

VS Code extension or command-line
The screenshot was made in VSCode (1.127.0 Universal) with the latest pylance Plugin (2026.2.1) installed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions