python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.36k stars 2.81k forks source link

Inconsistent error report for islice/zip "no overload match" error depending if cache is present #16363

Open giuliano-macedo opened 12 months ago

giuliano-macedo commented 12 months ago

Bug Report Depending if the cache is present or not, mypy will accuse different overload methods names for typing errors in zip/islice uses. I've tried to reproduce de error using specific overload methods in a custom class, however mypy is consistent in this case (see extra section in the end of the issue) .

To Reproduce

main.py file:

from itertools import islice
islice([], "2")

steps to reproduce inconsistent report:

 rm -rf .mypy_cache/
 mypy main.py # no cache present
 mypy main.py # cache is present

Expected Behavior I would expect mypy to always report that the islice function/class doesn't have the required overload even if it doesn't have a cache already built.

$ rm -rf .mypy_cache
$ mypy main.py
main.py:2: error: No overload variant of "islice" matches argument types "list[<nothing>]", "str"  [call-overload]
main.py:2: note: Possible overload variants:
main.py:2: note:     def [_T] islice(self, Iterable[_T], int | None, /) -> islice[_T]
main.py:2: note:     def [_T] islice(self, Iterable[_T], int | None, int | None, int | None = ..., /) -> islice[_T]
Found 1 error in 1 file (checked 1 source file)
$ mypy main.py
main.py:2: error: No overload variant of "islice" matches argument types "list[<nothing>]", "str"  [call-overload]
main.py:2: note: Possible overload variants:
main.py:2: note:     def [_T] islice(self, Iterable[_T], int | None, /) -> islice[_T]
main.py:2: note:     def [_T] islice(self, Iterable[_T], int | None, int | None, int | None = ..., /) -> islice[_T]
Found 1 error in 1 file (checked 1 source file)

Actual Behavior

$ rm -rf .mypy_cache
$ mypy main.py
main.py:2: error: No overload variant of "islice" matches argument types "list[<nothing>]", "str"  [call-overload]
main.py:2: note: Possible overload variants:
main.py:2: note:     def [_T] __init__(self, Iterable[_T], int | None, /) -> islice[_T]
main.py:2: note:     def [_T] __init__(self, Iterable[_T], int | None, int | None, int | None = ..., /) -> islice[_T]
Found 1 error in 1 file (checked 1 source file)
$ mypy main.py
main.py:2: error: No overload variant of "islice" matches argument types "list[<nothing>]", "str"  [call-overload]
main.py:2: note: Possible overload variants:
main.py:2: note:     def [_T] islice(self, Iterable[_T], int | None, /) -> islice[_T]
main.py:2: note:     def [_T] islice(self, Iterable[_T], int | None, int | None, int | None = ..., /) -> islice[_T]
Found 1 error in 1 file (checked 1 source file)

Note that the first mypy execution accused the __init__ method not having the requested overload, and in the second execution it changed to islice.

Your Environment

extra

If main.py is:

from typing import Generic, Iterable, Iterator, Self, TypeVar, overload
_T = TypeVar("_T")
class islice(Iterator[_T], Generic[_T]):
    @overload
    def __init__(self, __iterable: Iterable[_T], __stop: int | None) -> None: ...
    @overload
    def __init__(self, __iterable: Iterable[_T], __start: int | None, __stop: int | None, __step: int | None = ...) -> None: ...
    def __iter__(self) -> Self: ...
    def __next__(self) -> _T: ...
islice([], "2")

mypy will always accuse that the __init__ method doesn't have the overload requested:

$ rm -rf .mypy_cache
$ mypy main.py
main.py:4: error: An overloaded function outside a stub file must have an implementation  [no-overload-impl]
main.py:8: error: Missing return statement  [empty-body]
main.py:8: note: If the method is meant to be abstract, use @abc.abstractmethod
main.py:9: error: Missing return statement  [empty-body]
main.py:9: note: If the method is meant to be abstract, use @abc.abstractmethod
main.py:10: error: No overload variant of "islice" matches argument types "list[<nothing>]", "str"  [call-overload]
main.py:10: note: Possible overload variants:
main.py:10: note:     def [_T] __init__(self, Iterable[_T], int | None, /) -> islice[_T]
main.py:10: note:     def [_T] __init__(self, Iterable[_T], int | None, int | None, int | None = ..., /) -> islice[_T]
Found 4 errors in 1 file (checked 1 source file)
$ mypy main.py
main.py:4: error: An overloaded function outside a stub file must have an implementation  [no-overload-impl]
main.py:8: error: Missing return statement  [empty-body]
main.py:8: note: If the method is meant to be abstract, use @abc.abstractmethod
main.py:9: error: Missing return statement  [empty-body]
main.py:9: note: If the method is meant to be abstract, use @abc.abstractmethod
main.py:10: error: No overload variant of "islice" matches argument types "list[<nothing>]", "str"  [call-overload]
main.py:10: note: Possible overload variants:
main.py:10: note:     def [_T] __init__(self, Iterable[_T], int | None, /) -> islice[_T]
main.py:10: note:     def [_T] __init__(self, Iterable[_T], int | None, int | None, int | None = ..., /) -> islice[_T]
Found 4 errors in 1 file (checked 1 source file)

another example

the same issue can happen with the zip method:

main.py

zip([], 2)

mypy execution:

$ rm -rf .mypy_cache
$ mypy main.py
main.py:1: error: No overload variant of "zip" matches argument types "list[<nothing>]", "int"  [call-overload]
main.py:1: note: Possible overload variants:
main.py:1: note:     def [_T_co, _T1] __new__(cls, Iterable[_T1], /, *, strict: bool = ...) -> zip[tuple[_T1]]
main.py:1: note:     def [_T_co, _T1, _T2] __new__(cls, Iterable[_T1], Iterable[_T2], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2]]
main.py:1: note:     def [_T_co, _T1, _T2, _T3] __new__(cls, Iterable[_T1], Iterable[_T2], Iterable[_T3], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2, _T3]]
main.py:1: note:     def [_T_co, _T1, _T2, _T3, _T4] __new__(cls, Iterable[_T1], Iterable[_T2], Iterable[_T3], Iterable[_T4], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2, _T3, _T4]]
main.py:1: note:     def [_T_co, _T1, _T2, _T3, _T4, _T5] __new__(cls, Iterable[_T1], Iterable[_T2], Iterable[_T3], Iterable[_T4], Iterable[_T5], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]
main.py:1: note:     def [_T_co] __new__(cls, Iterable[Any], Iterable[Any], Iterable[Any], Iterable[Any], Iterable[Any], Iterable[Any], /, *iterables: Iterable[Any], strict: bool = ...) -> zip[tuple[Any, ...]]
Found 1 error in 1 file (checked 1 source file)
$ mypy main.py
main.py:1: error: No overload variant of "zip" matches argument types "list[<nothing>]", "int"  [call-overload]
main.py:1: note: Possible overload variants:
main.py:1: note:     def [_T_co, _T1] zip(Iterable[_T1], /, *, strict: bool = ...) -> zip[tuple[_T1]]
main.py:1: note:     def [_T_co, _T1, _T2] zip(Iterable[_T1], Iterable[_T2], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2]]
main.py:1: note:     def [_T_co, _T1, _T2, _T3] zip(Iterable[_T1], Iterable[_T2], Iterable[_T3], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2, _T3]]
main.py:1: note:     def [_T_co, _T1, _T2, _T3, _T4] zip(Iterable[_T1], Iterable[_T2], Iterable[_T3], Iterable[_T4], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2, _T3, _T4]]
main.py:1: note:     def [_T_co, _T1, _T2, _T3, _T4, _T5] zip(Iterable[_T1], Iterable[_T2], Iterable[_T3], Iterable[_T4], Iterable[_T5], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]
main.py:1: note:     def [_T_co] zip(Iterable[Any], Iterable[Any], Iterable[Any], Iterable[Any], Iterable[Any], Iterable[Any], /, *iterables: Iterable[Any], strict: bool = ...) -> zip[tuple[Any, ...]]
Found 1 error in 1 file (checked 1 source file)
giuliano-macedo commented 12 months ago

Just to add more context: This issue can cause confusion when using mypy-baseline since it wouldn't match the mypy-baseline.txt when running in the CI without cache if the user first create the mypy-baseline.txt in his machine while having the mypy cache