Search code examples
pythonoverloadingmypypython-typing

Python Typing: Mypy errors with overload "overlap" when signatures are different


The following code appears to generate two mypy errors: Overloaded function signatures 1 and 3 overlap with incompatible return types and Overloaded function signatures 2 and 3 overlap with incompatible return types; but all overloads have different signatures - Literal[True], Literal[False] and None do not overlap.

@overload
def func_a(*, a: Literal[False] = ...) -> str:
    ...


@overload
def func_a(*, a: None = ...) -> str:
    ...


@overload
def func_a(*, a: Literal[True] = ...) -> int:
    ...


def func_a(*, a: Optional[bool] = None) -> str | int:
    if a:
        return 1
    return "foo"


var1 = func_a()  # str correctly discovered by VSCode Pylance
var2 = func_a(a=False)  # str correctly discovered by VSCode Pylance
var3 = func_a(a=True)  # int correctly discovered by VSCode Pylance

Why does Mypy think they overlap and how could I go about fixing this?

Mypy version: 0.991

Python version: 3.11.1


Solution

  • The problem is that by writing = ... default values for every overload, you've marked the parameter as optional in every overload. A plain func_a() call matches every single overload of your function.

    You need to resolve that, so func_a() only matches one overload. Here's one way:

    @overload
    def func_a(*, a: Literal[False]) -> str:
        ...
    
    @overload
    def func_a(*, a: Literal[True]) -> int:
        ...
    
    @overload
    def func_a(*, a: None = None) -> str:
        ...
    

    Here, only the None overload marks the parameter as optional, so func_a() only matches that overload.

    Alternatively, you could make the no-argument version its own overload:

    @overload
    def func_a(*, a: Literal[False]) -> str:
        ...
    
    @overload
    def func_a(*, a: Literal[True]) -> int:
        ...
    
    @overload
    def func_a(*, a: None) -> str:
        ...
    
    @overload
    def func_a() -> str:
        ...