pylint-dev/astroid

false not-an-iterable for class method with @overload (2.5.7 regression)

Closed this issue · 8 comments

belm0 commented

Steps to reproduce

Starting from astroid 2.5.7, I'm seeing false not-an-iterable when generator class methods are annotated with @overload.

from typing import overload, Iterator

class MyClass:
    @overload
    def transitions(self, foo: int, bar: int) -> Iterator[int]: ...
    @overload
    def transitions(self, baz: str) -> Iterator[str]: ...
    def transitions(self, foo_or_baz, bar=None):
        yield

for _ in MyClass().transitions('hello'):
    pass

If @overload is removed, or the function is moved to the module level, or I switch to astroid 2.5.6, the problem goes away.

It happens with pylint-2.8.3 or pylint-3.0.0a3.

Current behavior

E1133: Non-iterable value MyClass().transitions('hello') is used in an iterating context (not-an-iterable)

Expected behavior

no error

belm0 commented

I can work around it by replacing ... with yield, but it doesn't seem that this should be necessary (and it wasn't in prior versions of Astroid). And it is hard to satisfy mypy with this workaround, because it expects yield [value] etc.

@belm0 thanks for the report.

The issue seems to have started with #934, the previous commit is fine: 2e8417f. (Tested with pylint 2.8.3.)
@nelfin Would you like to take a look at this?

Sure, happy to take this one

This doesn't seem to have anything to do with @overload. Here's a minimum reproducing example:

class A:
    def foo(self): ...
    def foo(self):
        yield

for _ in A().foo():
    pass

I think this has to do with the name resolution order, Attribute.foo is inferred as the foo(): ... instead of foo(): yield and the lack of a function body means infer_call_result infers None (arguably correct, just given incorrect inputs). If you switch the order of the functions in the class body then there's no pylint warning (which is a false-negative). I'm chasing down the root cause and a fix.

pylint-dev/pylint#7624 have a use case related to this in pylint

Getting a similar problem with pylint on torch.nn.Module.to():

from __future__ import annotations

from typing import Any, Union, overload


class device:
    ...


class Tensor:
    ...


class dtype:
    ...


class Module:
    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        ...

    @overload
    def to(self, dtype: Union[dtype, str]) -> Module:
        ...

    @overload
    def to(self, tensor: Tensor) -> Module:
        ...

    def to(self, *args, **kwargs):
        return self


tensor = Tensor()

module = Module()
_ = module(tensor)  # OK

module = module.to("cpu")
_ = module(tensor)  # a.py:46:4: E1102: module is not callable (not-callable)
astroid==2.15.4
pylint==2.17.4

THANKS @jacobtylerwalls -- this was a major bug for pre-typing code that returned different things depending on settings (would not write new code for that today) and needed @overload to specify the proper return values.