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

Wrong line number for invalid iterable that uses __getattr__ #14892

Closed
JukkaL opened this issue Mar 13, 2023 · 3 comments
Closed

Wrong line number for invalid iterable that uses __getattr__ #14892

JukkaL opened this issue Mar 13, 2023 · 3 comments
Assignees
Labels
bug mypy got something wrong topic-error-reporting How we report errors

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 13, 2023

This generates an error false positive which is reported on the wrong line so there are likely two bugs:

from typing import Union, Tuple

class C:
    def __getattr__(self, name):
        pass

class D:
    def f(self) -> C: # "C" has no attribute "__iter__" (not iterable)
        return C()

    def g(self) -> None:
        a, b = self.f()  # This is where the error originates from

C should be treated as iterable, since it defines __getattr__.

@JukkaL JukkaL added bug mypy got something wrong false-positive mypy gave an error on correct code labels Mar 13, 2023
@JukkaL JukkaL self-assigned this Mar 13, 2023
@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 13, 2023

C should be treated as iterable, since it defines __getattr__.

Is that correct? At runtime, dunders are looked up on the class object rather than the instance for many operations (including the iterator protocol), so __getattr__ doesn't actually work for dunders like __iter__:

>>> class Foo:
...     def __getattr__(self, name):
...         if name == "__iter__":
...             return lambda self: iter(range(10))
...
>>> Foo().__iter__
<range_iterator object at 0x00000194BCD3BB50>
>>> list(Foo().__iter__)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(Foo())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Foo' object is not iterable

iter(Foo()) at runtime results in a lookup equivalent to _tmp = Foo(); type(_tmp).__iter__(_tmp).

@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 13, 2023

Using the original example:

>>> class C:
...     def __getattr__(self, name):
...         if name == "__iter__":
...             return lambda self: iter(range(2))
...
>>> class D:
...     def f(self):
...         return C()
...     def g(self):
...         a, b = self.f()
...
>>> D().g()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in g
TypeError: cannot unpack non-iterable C object

JukkaL added a commit that referenced this issue Mar 13, 2023
A type was used as error context, but types don't reliably have valid
line numbers during type checking. Pass context explicitly instead.

The error in the test case is actually a false positive, but I'm first
fixing the line number of the error, since it seems plausible that the
wrong line number could cause other problems.

Work on #14892.
@JukkaL
Copy link
Collaborator Author

JukkaL commented Mar 13, 2023

Yes, you are quite right. So the issue is only about the incorrect line number.

@JukkaL JukkaL changed the title Wrong line number and false positive for iterable that uses __getattr__ Wrong line number for invalid iterable that uses __getattr__ Mar 13, 2023
@AlexWaygood AlexWaygood added topic-error-reporting How we report errors and removed false-positive mypy gave an error on correct code labels Mar 13, 2023
@JukkaL JukkaL closed this as completed Mar 13, 2023
JelleZijlstra pushed a commit that referenced this issue Mar 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-error-reporting How we report errors
Projects
None yet
Development

No branches or pull requests

2 participants