Pedro Perpétua
Pedro Perpétua

Reputation: 83

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

Upvotes: 4

Views: 1423

Answers (1)

user2357112
user2357112

Reputation: 281843

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:
    ...

Upvotes: 9

Related Questions