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

Question:

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

Asked By: Pedro Perpétua

||

Answers:

Maybe you could use Union as a return type. Because func_a has a return type that is a union of str and int. The Union type is used to specify that a value can be of one of several different types.

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

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:
    ...
Answered By: user2357112