Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False positive when decorating (e.g. with @final) an overloaded method in a subclass #8393

Closed
jonasrauber opened this issue Feb 12, 2020 · 5 comments
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal

Comments

@jonasrauber
Copy link

Example

from typing import List, Union, overload
from typing_extensions import final


class Foo:
    @overload
    def get(self, index: int) -> int:
        ...

    @overload  # noqa: F811
    def get(self, index: slice) -> List[int]:
        ...

    def get(self, index: Union[int, slice]) -> Union[int, List[int]]:  # noqa: F811
        ...


class Bar(Foo):
    @overload
    def get(self, index: int) -> int:
        ...

    @overload  # noqa: F811
    def get(self, index: slice) -> List[int]:
        ...

    @final  # noqa: F811
    def get(self, index: Union[int, slice]) -> Union[int, List[int]]:
        if isinstance(index, int):
            return 3
        elif isinstance(index, slice):
            return [3, 4, 5]
        else:
            raise TypeError(...)
  • What is the actual behavior/output?
example.py:28: error: Signature of "get" incompatible with supertype "Foo"
  • What is the behavior/output you expect?

No error. (without the @final it works)

  • What are the versions of mypy and Python you are using?

mypy 0.761, Python 3.6

Second example (without @final)

from typing import List, Union, overload
from abc import ABC, abstractmethod


class Foo(ABC):
    @overload
    def get(self, index: int) -> int:
        ...

    @overload  # noqa: F811
    def get(self, index: slice) -> List[int]:
        ...

    @abstractmethod  # noqa: F811
    def get(self, index: Union[int, slice]) -> Union[int, List[int]]:
        ...


class Bar(Foo):
    @overload
    def get(self, index: int) -> int:
        ...

    @overload  # noqa: F811
    def get(self, index: slice) -> List[int]:
        ...

    @abstractmethod  # noqa: F811
    def get(self, index: Union[int, slice]) -> Union[int, List[int]]:
        if isinstance(index, int):
            return 3
        elif isinstance(index, slice):
            return [3, 4, 5]
        else:
            raise TypeError(...)

Same error like the first example.

Workaround

Only known workaround is adding type: ignore

@ilevkivskyi
Copy link
Member

I confirm this still occurs on master. We recently fixed a similar issue, but it looks like only non-overloaded methods were fixed.

Btw, this is another manifestation of #7724

@ilevkivskyi ilevkivskyi added bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal labels Feb 17, 2020
@Arfey
Copy link
Contributor

Arfey commented May 14, 2020

any updates?

@JulienPalard
Copy link
Member

With mypy 0.790, the following works:

from typing import Sequence, Union, overload


class MyList(Sequence[int]):
    @overload
    def __getitem__(self, index: int) -> int:
        ...

    @overload
    def __getitem__(self, index: slice) -> Sequence[int]:
        ...

    def __getitem__(self, index: Union[int, slice]) -> Union[int, Sequence[int]]:
        if isinstance(index, int):
            return index
        elif isinstance(index, slice):
            return list(range(slice.start or 0, slice.stop, slice.step or 1))
        else:
            raise TypeError


class MyTwice(MyList):
    @overload
    def __getitem__(self, index: int) -> int:
        ...

    @overload
    def __getitem__(self, index: slice) -> Sequence[int]:
        ...

    def __getitem__(self, index: Union[int, slice]) -> Union[int, Sequence[int]]:
        if isinstance(index, int):
            return index * 2
        elif isinstance(index, slice):
            return list(range(slice.start * 2 or 0, slice.stop * 2, slice.step or 1))
        else:
            raise TypeError

But it's a bit lengthy to write: all subclasses has to "copy/paste" the two overloads, removing them give back the:

test.py:23: error: Signature of "__getitem__" incompatible with supertype "MyList"
test.py:23: error: Signature of "__getitem__" incompatible with supertype "Sequence"

@posita
Copy link
Contributor

posita commented Jul 1, 2021

@JulienPalard, I don't think that addresses this issue. What happens when you decorate the __getitem__ implementation(s) (e.g., with @final like the original issue report)? Consider:

# test_case.py
from typing import TypeVar, Union, overload
_T = TypeVar("_T")

def identity(f: _T) -> _T:
    return f

class MyTuple(tuple[int]):
    @overload
    def __getitem__(self, key: int) -> int:
        ...
    @overload
    def __getitem__(self, key: slice) -> "MyTuple":
        ...
    @identity  # <- comment this out and the error goes away
    def __getitem__(self, key: Union[int, slice]) -> Union[int, "MyTuple"]:
        if isinstance(key, int):
            return 0
        elif isinstance(key, slice):
            return self
        else:
            raise TypeError(
                "{} indices must be integers or slices, not {}".format(
                    type(self).__name__, type(key).__name__
                )
            )

The above results in:

$ mypy --version
mypy 0.902
$ mypy test_case.py
test_case.py:16: error: Signature of "__getitem__" incompatible with supertype "tuple"
test_case.py:16: error: Signature of "__getitem__" incompatible with supertype "Sequence"

If I apply decorators to your example, the error(s) return:

# test_case2.py
from typing import Sequence, TypeVar, Union, final, overload
_T = TypeVar("_T")

def identity(f: _T) -> _T:
    return f

class MyList(Sequence[int]):
    @overload
    def __getitem__(self, index: int) -> int:
        ...
    @overload
    def __getitem__(self, index: slice) -> Sequence[int]:
        ...
    def __getitem__(self, index: Union[int, slice]) -> Union[int, Sequence[int]]:
        if isinstance(index, int):
            return index
        elif isinstance(index, slice):
            return list(range(slice.start or 0, slice.stop, slice.step or 1))
        else:
            raise TypeError

class MyFinal(MyList):
    @overload
    def __getitem__(self, index: int) -> int:
        ...
    @overload
    def __getitem__(self, index: slice) -> Sequence[int]:
        ...
    @final  # <- comment this out and the error goes away
    def __getitem__(self, index: Union[int, slice]) -> Union[int, Sequence[int]]:
        if isinstance(index, int):
            return index * 2
        elif isinstance(index, slice):
            return list(range(slice.start * 2 or 0, slice.stop * 2, slice.step or 1))
        else:
            raise TypeError

class MyIdentity(MyList):
    @overload
    def __getitem__(self, index: int) -> int:
        ...
    @overload
    def __getitem__(self, index: slice) -> Sequence[int]:
        ...
    @identity  # <- comment this out and the error goes away
    def __getitem__(self, index: Union[int, slice]) -> Union[int, Sequence[int]]:
        if isinstance(index, int):
            return index * 2
        elif isinstance(index, slice):
            return list(range(slice.start * 2 or 0, slice.stop * 2, slice.step or 1))
        else:
            raise TypeError

The above results in:

$ mypy test_case.py
test_case2.py:31: error: Signature of "__getitem__" incompatible with supertype "MyList"
test_case2.py:31: error: Signature of "__getitem__" incompatible with supertype "Sequence"
test_case2.py:47: error: Signature of "__getitem__" incompatible with supertype "MyList"
test_case2.py:47: error: Signature of "__getitem__" incompatible with supertype "Sequence"

If I decorate MyList.__getitem__ (e.g., with @identity), I get yet another error:

test_case2.py:16: error: Signature of "__getitem__" incompatible with supertype "Sequence"

@erictraut
Copy link

I think this bug was fixed along the way. I'm not able to repro it with the latest version of mypy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal
Projects
None yet
Development

No branches or pull requests

7 participants