mypy icon indicating copy to clipboard operation
mypy copied to clipboard

False positive error: Overloaded function signatures 1 and 2 overlap with incompatible return types

Open huzecong opened this issue 6 years ago • 5 comments

Are you reporting a bug, or opening a feature request?

Bug.

Please insert below the code you are checking with mypy, or a mock-up repro if the source is private.

from typing import overload, Tuple

@overload
def foo(a: int, c: bool = True) -> int: ...

@overload
def foo(a: int, b: int, *args: int, c: bool = True) -> Tuple[int, ...]: ...

def foo(a, *args, c=True):
    if len(args) == 0:
        return a
    return (a,) + args


if __name__ == '__main__':
    print(foo(1))        # 1
    print(foo(1, 2, 3))  # (1, 2, 3)

What is the actual behavior/output?

mypy reported error: Overloaded function signatures 1 and 2 overlap with incompatible return types.

What is the behavior/output you expect?

The two signatures do not overlap so no error should be raised.

Also, no errors were reported when the argument c was removed.

What are the versions of mypy and Python you are using? Do you see the same issue after installing mypy from Git master?

mypy: The master version (d90eb18). Python: Both 3.7.3 and 3.6.3 are tested and the issue is reproducable.

What are the mypy flags you are using?

No flags were used.

huzecong avatar Jun 13 '19 23:06 huzecong

Alas, this is a legitimate unsafe overlap -- the unsafeness stems from how bools are subtypes of ints in Python.

Basically, we get an unsafe overlap if we try running mypy on following:

# bools are subtypes of ints, so this assignment is safe!
x: int = True

# So we get inconsistent and incompatible results despite that we're using the
# same runtime values:
reveal_type(foo(1, True))  # Revealed type is 'int'
reveal_type(foo(1, x))     # Revealed type is 'Tuple[int, ...]'

Probably the quickest fix would be to just make c a keyword-only argument in your first overload:

@overload
def foo(a: int, *, c: bool = True) -> int: ...

# Note that 'c' is already a keyword-only argument here.
@overload
def foo(a: int, b: int, *args: int, c: bool = True) -> Tuple[int, ...]: ...

def foo(a, *args, c=True):
    if len(args) == 0:
        return a
    return (a,) + args

This will make it so that doing foo(3, True) will no longer select the first overload: you need to do foo(3, c=True) instead.

After this change is made, both calls to foo(...) above will select the second overload and return a Tuple[int, ...]. (This also happens to match what your implementation is actually doing.)

Michael0x2a avatar Jun 14 '19 00:06 Michael0x2a

@Michael0x2a Thank you so much for the explanation! I had no idea that bool was a subtype of int 😲. And yes, making c a keyword-only argument solved the problem.

However, if c is kept as is, and types of all other arguments are changed from int to str, the issue still exists.

@overload
def foo(a: str, c: bool = True) -> str: ...

@overload
def foo(a: str, b: str, *args: str, c: bool = True) -> Tuple[str, ...]: ...

Since bool is not a subtype of str, I believe these two signatures do not overlap. However, mypy still reports the said error.

huzecong avatar Jun 14 '19 00:06 huzecong

Hmm, I think you might be right -- it looks like this is a legitimate bug after all!

Here's a simpler repro:

from typing import overload

@overload
def foo(b: bool = True) -> str: ...

@overload
def foo(a: str, *, b: bool = True) -> int: ...

def foo(*args, **kwargs): pass

Michael0x2a avatar Jun 14 '19 13:06 Michael0x2a

Is there an issue that requests making the error message more explicit about why the function overload has incompatible return types?

bartenra avatar Mar 30 '21 14:03 bartenra

@bartenra This? https://github.com/python/mypy/issues/3819

Avasam avatar Aug 22 '24 18:08 Avasam

Rules for overload overlaps have been relaxed (even allowing some actual unsafe definitions). The original example is now allowed because the arg names (b vs c) are different.

ilevkivskyi avatar Feb 05 '25 10:02 ilevkivskyi