mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[red-knot] Rewrite Type::try_iterate()
to improve type inference and diagnostic messages (#16321)
This commit is contained in:
parent
1be0dc6885
commit
5c007db7e2
25 changed files with 2107 additions and 162 deletions
|
@ -105,7 +105,11 @@ reveal_type(x)
|
|||
|
||||
## With non-callable iterator
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class NotIterable:
|
||||
if flag:
|
||||
|
@ -113,7 +117,8 @@ def _(flag: bool):
|
|||
else:
|
||||
__iter__: None = None
|
||||
|
||||
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
|
||||
# error: [not-iterable]
|
||||
for x in NotIterable():
|
||||
pass
|
||||
|
||||
# revealed: Unknown
|
||||
|
@ -123,21 +128,25 @@ def _(flag: bool):
|
|||
|
||||
## Invalid iterable
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
nonsense = 123
|
||||
for x in nonsense: # error: "Object of type `Literal[123]` is not iterable"
|
||||
for x in nonsense: # error: [not-iterable]
|
||||
pass
|
||||
```
|
||||
|
||||
## New over old style iteration protocol
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
class NotIterable:
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 42
|
||||
__iter__: None = None
|
||||
|
||||
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
|
||||
for x in NotIterable(): # error: [not-iterable]
|
||||
pass
|
||||
```
|
||||
|
||||
|
@ -221,7 +230,11 @@ def _(flag: bool):
|
|||
|
||||
## Union type as iterable where one union element has no `__iter__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
@ -231,14 +244,18 @@ class Test:
|
|||
return TestIter()
|
||||
|
||||
def _(flag: bool):
|
||||
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
|
||||
# error: [not-iterable]
|
||||
for x in Test() if flag else 42:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has invalid `__iter__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
@ -253,7 +270,7 @@ class Test2:
|
|||
|
||||
def _(flag: bool):
|
||||
# TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
# error: "Object of type `Test | Test2` is not iterable"
|
||||
# error: [not-iterable]
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
@ -269,7 +286,454 @@ class Test:
|
|||
def __iter__(self) -> TestIter | int:
|
||||
return TestIter()
|
||||
|
||||
# error: [not-iterable] "Object of type `Test` is not iterable"
|
||||
# error: [not-iterable] "Object of type `Test` may not be iterable because its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method"
|
||||
for x in Test():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly-not-callable `__iter__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class CustomCallable:
|
||||
if flag:
|
||||
def __call__(self, *args, **kwargs) -> Iterator:
|
||||
return Iterator()
|
||||
else:
|
||||
__call__: None = None
|
||||
|
||||
class Iterable1:
|
||||
__iter__: CustomCallable = CustomCallable()
|
||||
|
||||
class Iterable2:
|
||||
if flag:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
else:
|
||||
__iter__: None = None
|
||||
|
||||
# error: [not-iterable] "Object of type `Iterable1` may not be iterable because its `__iter__` attribute (with type `CustomCallable`) may not be callable"
|
||||
for x in Iterable1():
|
||||
# TODO... `int` might be ideal here?
|
||||
reveal_type(x) # revealed: int | Unknown
|
||||
|
||||
# error: [not-iterable] "Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `Literal[__iter__] | None`) may not be callable"
|
||||
for y in Iterable2():
|
||||
# TODO... `int` might be ideal here?
|
||||
reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
## `__iter__` method with a bad signature
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self, extra_arg) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## `__iter__` does not return an iterator
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Bad:
|
||||
def __iter__(self) -> int:
|
||||
return 42
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Bad():
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `__iter__` returns an object with a possibly unbound `__next__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Iterator:
|
||||
if flag:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
# error: [not-iterable] "Object of type `Iterable` may not be iterable because its `__iter__` method returns an object of type `Iterator`, which may not have a `__next__` method"
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## `__iter__` returns an iterator with an invalid `__next__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator1:
|
||||
def __next__(self, extra_arg) -> int:
|
||||
return 42
|
||||
|
||||
class Iterator2:
|
||||
__next__: None = None
|
||||
|
||||
class Iterable1:
|
||||
def __iter__(self) -> Iterator1:
|
||||
return Iterator1()
|
||||
|
||||
class Iterable2:
|
||||
def __iter__(self) -> Iterator2:
|
||||
return Iterator2()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
# error: [not-iterable]
|
||||
for y in Iterable2():
|
||||
reveal_type(y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly unbound `__iter__` and bad `__getitem__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
if flag:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
# invalid signature because it only accepts a `str`,
|
||||
# but the old-style iteration protocol will pass it an `int`
|
||||
def __getitem__(self, key: str) -> bytes:
|
||||
return 42
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: int | bytes
|
||||
```
|
||||
|
||||
## Possibly unbound `__iter__` and not-callable `__getitem__`
|
||||
|
||||
This snippet tests that we infer the element type correctly in the following edge case:
|
||||
|
||||
- `__iter__` is a method with the correct parameter spec that returns a valid iterator; BUT
|
||||
- `__iter__` is possibly unbound; AND
|
||||
- `__getitem__` is set to a non-callable type
|
||||
|
||||
It's important that we emit a diagnostic here, but it's also important that we still use the return
|
||||
type of the iterator's `__next__` method as the inferred type of `x` in the `for` loop:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
if flag:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
__getitem__: None = None
|
||||
|
||||
# error: [not-iterable] "Object of type `Iterable` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable"
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly unbound `__iter__` and possibly unbound `__getitem__`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
def _(flag1: bool, flag2: bool):
|
||||
class Iterable:
|
||||
if flag1:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
if flag2:
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return 42
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: int | bytes
|
||||
```
|
||||
|
||||
## No `__iter__` method and `__getitem__` is not callable
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Bad:
|
||||
__getitem__: None = None
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Bad():
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly-not-callable `__getitem__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class CustomCallable:
|
||||
if flag:
|
||||
def __call__(self, *args, **kwargs) -> int:
|
||||
return 42
|
||||
else:
|
||||
__call__: None = None
|
||||
|
||||
class Iterable1:
|
||||
__getitem__: CustomCallable = CustomCallable()
|
||||
|
||||
class Iterable2:
|
||||
if flag:
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 42
|
||||
else:
|
||||
__getitem__: None = None
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
# TODO... `int` might be ideal here?
|
||||
reveal_type(x) # revealed: int | Unknown
|
||||
|
||||
# error: [not-iterable]
|
||||
for y in Iterable2():
|
||||
# TODO... `int` might be ideal here?
|
||||
reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
## Bad `__getitem__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterable:
|
||||
# invalid because it will implicitly be passed an `int`
|
||||
# by the interpreter
|
||||
def __getitem__(self, key: str) -> int:
|
||||
return 42
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly unbound `__iter__` but definitely bound `__getitem__`
|
||||
|
||||
Here, we should not emit a diagnostic: if `__iter__` is unbound, we should fallback to
|
||||
`__getitem__`:
|
||||
|
||||
```py
|
||||
class Iterator:
|
||||
def __next__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterable:
|
||||
if flag:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return b"foo"
|
||||
|
||||
for x in Iterable():
|
||||
reveal_type(x) # revealed: str | bytes
|
||||
```
|
||||
|
||||
## Possibly invalid `__iter__` methods
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterable1:
|
||||
if flag:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
else:
|
||||
def __iter__(self, invalid_extra_arg) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
class Iterable2:
|
||||
if flag:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
else:
|
||||
__iter__: None = None
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable2():
|
||||
# TODO: `int` would probably be better here:
|
||||
reveal_type(x) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
## Possibly invalid `__next__` method
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterator1:
|
||||
if flag:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
else:
|
||||
def __next__(self, invalid_extra_arg) -> str:
|
||||
return "foo"
|
||||
|
||||
class Iterator2:
|
||||
if flag:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
else:
|
||||
__next__: None = None
|
||||
|
||||
class Iterable1:
|
||||
def __iter__(self) -> Iterator1:
|
||||
return Iterator1()
|
||||
|
||||
class Iterable2:
|
||||
def __iter__(self) -> Iterator2:
|
||||
return Iterator2()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
reveal_type(x) # revealed: int | str
|
||||
|
||||
# error: [not-iterable]
|
||||
for y in Iterable2():
|
||||
# TODO: `int` would probably be better here:
|
||||
reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
## Possibly invalid `__getitem__` methods
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterable1:
|
||||
if flag:
|
||||
def __getitem__(self, item: int) -> str:
|
||||
return "foo"
|
||||
else:
|
||||
__getitem__: None = None
|
||||
|
||||
class Iterable2:
|
||||
if flag:
|
||||
def __getitem__(self, item: int) -> str:
|
||||
return "foo"
|
||||
else:
|
||||
def __getitem__(self, item: str) -> int:
|
||||
return "foo"
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
# TODO: `str` might be better
|
||||
reveal_type(x) # revealed: str | Unknown
|
||||
|
||||
# error: [not-iterable]
|
||||
for y in Iterable2():
|
||||
reveal_type(y) # revealed: str | int
|
||||
```
|
||||
|
||||
## Possibly unbound `__iter__` and possibly invalid `__getitem__`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> bytes:
|
||||
return b"foo"
|
||||
|
||||
def _(flag: bool, flag2: bool):
|
||||
class Iterable1:
|
||||
if flag:
|
||||
def __getitem__(self, item: int) -> str:
|
||||
return "foo"
|
||||
else:
|
||||
__getitem__: None = None
|
||||
|
||||
if flag2:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
class Iterable2:
|
||||
if flag:
|
||||
def __getitem__(self, item: int) -> str:
|
||||
return "foo"
|
||||
else:
|
||||
def __getitem__(self, item: str) -> int:
|
||||
return "foo"
|
||||
if flag2:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
# TODO: `bytes | str` might be better
|
||||
reveal_type(x) # revealed: bytes | str | Unknown
|
||||
|
||||
# error: [not-iterable]
|
||||
for y in Iterable2():
|
||||
reveal_type(y) # revealed: bytes | str | int
|
||||
```
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Bad `__getitem__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterable:
|
||||
4 | # invalid because it will implicitly be passed an `int`
|
||||
5 | # by the interpreter
|
||||
6 | def __getitem__(self, key: str) -> int:
|
||||
7 | return 42
|
||||
8 |
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
11 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:10:10
|
||||
|
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` is not iterable because it has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
11 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:11:5
|
||||
|
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
11 | reveal_type(x) # revealed: int
|
||||
| -------------- info: Revealed type is `int`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Invalid iterable
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | nonsense = 123
|
||||
2 | for x in nonsense: # error: [not-iterable]
|
||||
3 | pass
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:2:10
|
||||
|
|
||||
1 | nonsense = 123
|
||||
2 | for x in nonsense: # error: [not-iterable]
|
||||
| ^^^^^^^^ Object of type `Literal[123]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
||||
3 | pass
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - New over old style iteration protocol
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class NotIterable:
|
||||
2 | def __getitem__(self, key: int) -> int:
|
||||
3 | return 42
|
||||
4 | __iter__: None = None
|
||||
5 |
|
||||
6 | for x in NotIterable(): # error: [not-iterable]
|
||||
7 | pass
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:6:10
|
||||
|
|
||||
4 | __iter__: None = None
|
||||
5 |
|
||||
6 | for x in NotIterable(): # error: [not-iterable]
|
||||
| ^^^^^^^^^^^^^ Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `None`, which is not callable
|
||||
7 | pass
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - No `__iter__` method and `__getitem__` is not callable
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Bad:
|
||||
4 | __getitem__: None = None
|
||||
5 |
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:7:10
|
||||
|
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
| ^^^^^ Object of type `Bad` is not iterable because it has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:8:5
|
||||
|
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
| -------------- info: Revealed type is `Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly-not-callable `__getitem__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class CustomCallable:
|
||||
5 | if flag:
|
||||
6 | def __call__(self, *args, **kwargs) -> int:
|
||||
7 | return 42
|
||||
8 | else:
|
||||
9 | __call__: None = None
|
||||
10 |
|
||||
11 | class Iterable1:
|
||||
12 | __getitem__: CustomCallable = CustomCallable()
|
||||
13 |
|
||||
14 | class Iterable2:
|
||||
15 | if flag:
|
||||
16 | def __getitem__(self, key: int) -> int:
|
||||
17 | return 42
|
||||
18 | else:
|
||||
19 | __getitem__: None = None
|
||||
20 |
|
||||
21 | # error: [not-iterable]
|
||||
22 | for x in Iterable1():
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
25 |
|
||||
26 | # error: [not-iterable]
|
||||
27 | for y in Iterable2():
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:22:14
|
||||
|
|
||||
21 | # error: [not-iterable]
|
||||
22 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `CustomCallable`) may not be callable
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:24:9
|
||||
|
|
||||
22 | for x in Iterable1():
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
| -------------- info: Revealed type is `int | Unknown`
|
||||
25 |
|
||||
26 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:27:14
|
||||
|
|
||||
26 | # error: [not-iterable]
|
||||
27 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `Literal[__getitem__] | None`) may not be callable
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:29:9
|
||||
|
|
||||
27 | for y in Iterable2():
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
| -------------- info: Revealed type is `int | Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,94 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly invalid `__getitem__` methods
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class Iterable1:
|
||||
5 | if flag:
|
||||
6 | def __getitem__(self, item: int) -> str:
|
||||
7 | return "foo"
|
||||
8 | else:
|
||||
9 | __getitem__: None = None
|
||||
10 |
|
||||
11 | class Iterable2:
|
||||
12 | if flag:
|
||||
13 | def __getitem__(self, item: int) -> str:
|
||||
14 | return "foo"
|
||||
15 | else:
|
||||
16 | def __getitem__(self, item: str) -> int:
|
||||
17 | return "foo"
|
||||
18 |
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
23 |
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:20:14
|
||||
|
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `Literal[__getitem__] | None`) may not be callable
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:22:9
|
||||
|
|
||||
20 | for x in Iterable1():
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
| -------------- info: Revealed type is `str | Unknown`
|
||||
23 |
|
||||
24 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:25:14
|
||||
|
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` method (with type `Literal[__getitem__, __getitem__]`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:26:9
|
||||
|
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
| -------------- info: Revealed type is `str | int`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly invalid `__iter__` methods
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | def _(flag: bool):
|
||||
8 | class Iterable1:
|
||||
9 | if flag:
|
||||
10 | def __iter__(self) -> Iterator:
|
||||
11 | return Iterator()
|
||||
12 | else:
|
||||
13 | def __iter__(self, invalid_extra_arg) -> Iterator:
|
||||
14 | return Iterator()
|
||||
15 |
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
18 | reveal_type(x) # revealed: int
|
||||
19 |
|
||||
20 | class Iterable2:
|
||||
21 | if flag:
|
||||
22 | def __iter__(self) -> Iterator:
|
||||
23 | return Iterator()
|
||||
24 | else:
|
||||
25 | __iter__: None = None
|
||||
26 |
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable2():
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:17:14
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because its `__iter__` method (with type `Literal[__iter__, __iter__]`) may have an invalid signature (expected `def __iter__(self): ...`)
|
||||
18 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:18:9
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
18 | reveal_type(x) # revealed: int
|
||||
| -------------- info: Revealed type is `int`
|
||||
19 |
|
||||
20 | class Iterable2:
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:28:14
|
||||
|
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `Literal[__iter__] | None`) may not be callable
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:30:9
|
||||
|
|
||||
28 | for x in Iterable2():
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
| -------------- info: Revealed type is `int | Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,102 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly invalid `__next__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class Iterator1:
|
||||
5 | if flag:
|
||||
6 | def __next__(self) -> int:
|
||||
7 | return 42
|
||||
8 | else:
|
||||
9 | def __next__(self, invalid_extra_arg) -> str:
|
||||
10 | return "foo"
|
||||
11 |
|
||||
12 | class Iterator2:
|
||||
13 | if flag:
|
||||
14 | def __next__(self) -> int:
|
||||
15 | return 42
|
||||
16 | else:
|
||||
17 | __next__: None = None
|
||||
18 |
|
||||
19 | class Iterable1:
|
||||
20 | def __iter__(self) -> Iterator1:
|
||||
21 | return Iterator1()
|
||||
22 |
|
||||
23 | class Iterable2:
|
||||
24 | def __iter__(self) -> Iterator2:
|
||||
25 | return Iterator2()
|
||||
26 |
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
30 |
|
||||
31 | # error: [not-iterable]
|
||||
32 | for y in Iterable2():
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:28:14
|
||||
|
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method (expected `def __next__(self): ...`)
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:29:9
|
||||
|
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
| -------------- info: Revealed type is `int | str`
|
||||
30 |
|
||||
31 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:32:14
|
||||
|
|
||||
31 | # error: [not-iterable]
|
||||
32 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:34:9
|
||||
|
|
||||
32 | for y in Iterable2():
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
| -------------- info: Revealed type is `int | Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly unbound `__iter__` and bad `__getitem__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class Iterator:
|
||||
5 | def __next__(self) -> int:
|
||||
6 | return 42
|
||||
7 |
|
||||
8 | class Iterable:
|
||||
9 | if flag:
|
||||
10 | def __iter__(self) -> Iterator:
|
||||
11 | return Iterator()
|
||||
12 | # invalid signature because it only accepts a `str`,
|
||||
13 | # but the old-style iteration protocol will pass it an `int`
|
||||
14 | def __getitem__(self, key: str) -> bytes:
|
||||
15 | return 42
|
||||
16 |
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:18:14
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:19:9
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
| -------------- info: Revealed type is `int | bytes`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly unbound `__iter__` and possibly invalid `__getitem__`
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> bytes:
|
||||
5 | return b"foo"
|
||||
6 |
|
||||
7 | def _(flag: bool, flag2: bool):
|
||||
8 | class Iterable1:
|
||||
9 | if flag:
|
||||
10 | def __getitem__(self, item: int) -> str:
|
||||
11 | return "foo"
|
||||
12 | else:
|
||||
13 | __getitem__: None = None
|
||||
14 |
|
||||
15 | if flag2:
|
||||
16 | def __iter__(self) -> Iterator:
|
||||
17 | return Iterator()
|
||||
18 |
|
||||
19 | class Iterable2:
|
||||
20 | if flag:
|
||||
21 | def __getitem__(self, item: int) -> str:
|
||||
22 | return "foo"
|
||||
23 | else:
|
||||
24 | def __getitem__(self, item: str) -> int:
|
||||
25 | return "foo"
|
||||
26 | if flag2:
|
||||
27 | def __iter__(self) -> Iterator:
|
||||
28 | return Iterator()
|
||||
29 |
|
||||
30 | # error: [not-iterable]
|
||||
31 | for x in Iterable1():
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
34 |
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:31:14
|
||||
|
|
||||
30 | # error: [not-iterable]
|
||||
31 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute (with type `Literal[__getitem__] | None`) may not be callable
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:33:9
|
||||
|
|
||||
31 | for x in Iterable1():
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
| -------------- info: Revealed type is `bytes | str | Unknown`
|
||||
34 |
|
||||
35 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:36:14
|
||||
|
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type `Literal[__getitem__, __getitem__]`)
|
||||
may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:37:9
|
||||
|
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
| -------------- info: Revealed type is `bytes | str | int`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Possibly unbound `__iter__` and possibly unbound `__getitem__`
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | def _(flag1: bool, flag2: bool):
|
||||
8 | class Iterable:
|
||||
9 | if flag1:
|
||||
10 | def __iter__(self) -> Iterator:
|
||||
11 | return Iterator()
|
||||
12 | if flag2:
|
||||
13 | def __getitem__(self, key: int) -> bytes:
|
||||
14 | return 42
|
||||
15 |
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:17:14
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` may not be iterable because it may not have an `__iter__` method or a `__getitem__` method
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:18:9
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
| -------------- info: Revealed type is `int | bytes`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Union type as iterable where one union element has invalid `__iter__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class TestIter:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Test:
|
||||
8 | def __iter__(self) -> TestIter:
|
||||
9 | return TestIter()
|
||||
10 |
|
||||
11 | class Test2:
|
||||
12 | def __iter__(self) -> int:
|
||||
13 | return 42
|
||||
14 |
|
||||
15 | def _(flag: bool):
|
||||
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
19 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:18:14
|
||||
|
|
||||
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Object of type `Test | Test2` may not be iterable because its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
|
||||
19 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:19:9
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
19 | reveal_type(x) # revealed: int
|
||||
| -------------- info: Revealed type is `int`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - Union type as iterable where one union element has no `__iter__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class TestIter:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Test:
|
||||
8 | def __iter__(self) -> TestIter:
|
||||
9 | return TestIter()
|
||||
10 |
|
||||
11 | def _(flag: bool):
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
14 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:13:14
|
||||
|
|
||||
11 | def _(flag: bool):
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ Object of type `Test | Literal[42]` may not be iterable because it may not have an `__iter__` method and it doesn't have a `__getitem__` method
|
||||
14 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:14:9
|
||||
|
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
14 | reveal_type(x) # revealed: int
|
||||
| -------------- info: Revealed type is `int`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - With non-callable iterator
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class NotIterable:
|
||||
5 | if flag:
|
||||
6 | __iter__: int = 1
|
||||
7 | else:
|
||||
8 | __iter__: None = None
|
||||
9 |
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in NotIterable():
|
||||
12 | pass
|
||||
13 |
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:11:14
|
||||
|
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in NotIterable():
|
||||
| ^^^^^^^^^^^^^ Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `int | None`, which is not callable
|
||||
12 | pass
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning: lint:possibly-unresolved-reference
|
||||
--> /src/mdtest_snippet.py:16:17
|
||||
|
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
| - Name `x` used when possibly not defined
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:16:5
|
||||
|
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
| -------------- info: Revealed type is `Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - `__iter__` does not return an iterator
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Bad:
|
||||
4 | def __iter__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:8:10
|
||||
|
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
| ^^^^^ Object of type `Bad` is not iterable because its `__iter__` method returns an object of type `int`, which has no `__next__` method
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:9:5
|
||||
|
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
| -------------- info: Revealed type is `Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - `__iter__` method with a bad signature
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Iterable:
|
||||
8 | def __iter__(self, extra_arg) -> Iterator:
|
||||
9 | return Iterator()
|
||||
10 |
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
13 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:12:10
|
||||
|
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` is not iterable because its `__iter__` method has an invalid signature (expected `def __iter__(self): ...`)
|
||||
13 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:13:5
|
||||
|
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
13 | reveal_type(x) # revealed: int
|
||||
| -------------- info: Revealed type is `int`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: for.md - For loops - `__iter__` returns an iterator with an invalid `__next__` method
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator1:
|
||||
4 | def __next__(self, extra_arg) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Iterator2:
|
||||
8 | __next__: None = None
|
||||
9 |
|
||||
10 | class Iterable1:
|
||||
11 | def __iter__(self) -> Iterator1:
|
||||
12 | return Iterator1()
|
||||
13 |
|
||||
14 | class Iterable2:
|
||||
15 | def __iter__(self) -> Iterator2:
|
||||
16 | return Iterator2()
|
||||
17 |
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
20 | reveal_type(x) # revealed: int
|
||||
21 |
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:19:10
|
||||
|
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` is not iterable because its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method (expected `def __next__(self): ...`)
|
||||
20 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:20:5
|
||||
|
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
20 | reveal_type(x) # revealed: int
|
||||
| -------------- info: Revealed type is `int`
|
||||
21 |
|
||||
22 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
--> /src/mdtest_snippet.py:23:10
|
||||
|
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` is not iterable because its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type
|
||||
--> /src/mdtest_snippet.py:24:5
|
||||
|
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
| -------------- info: Revealed type is `Unknown`
|
||||
|
|
||||
|
||||
```
|
|
@ -22,7 +22,7 @@ error: lint:not-iterable
|
|||
--> /src/mdtest_snippet.py:1:8
|
||||
|
|
||||
1 | a, b = 1 # error: [not-iterable]
|
||||
| ^ Object of type `Literal[1]` is not iterable
|
||||
| ^ Object of type `Literal[1]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
||||
|
|
||||
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::str::FromStr;
|
|||
use bitflags::bitflags;
|
||||
use call::{CallDunderError, CallError};
|
||||
use context::InferContext;
|
||||
use diagnostic::{report_not_iterable, report_not_iterable_possibly_unbound};
|
||||
use diagnostic::NOT_ITERABLE;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
@ -1737,7 +1737,7 @@ impl<'db> Type<'db> {
|
|||
// it still results in loosing information. Or should the information
|
||||
// be recomputed when rendering the diagnostic?
|
||||
CallError::Union(union_error) => {
|
||||
if let Type::Union(_) = union_error.called_ty {
|
||||
if let Type::Union(_) = union_error.called_type {
|
||||
if union_error.errors.len() == 1 {
|
||||
union_error.errors.into_vec().pop().unwrap()
|
||||
} else {
|
||||
|
@ -2237,15 +2237,15 @@ impl<'db> Type<'db> {
|
|||
// Turn "`<type of illegal '__call__'>` not callable" into
|
||||
// "`X` not callable"
|
||||
CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
not_callable_type: self,
|
||||
}
|
||||
}
|
||||
CallDunderError::Call(CallError::Union(UnionCallError {
|
||||
called_ty: _,
|
||||
called_type: _,
|
||||
bindings,
|
||||
errors,
|
||||
})) => CallError::Union(UnionCallError {
|
||||
called_ty: self,
|
||||
called_type: self,
|
||||
bindings,
|
||||
errors,
|
||||
}),
|
||||
|
@ -2261,7 +2261,7 @@ impl<'db> Type<'db> {
|
|||
CallDunderError::MethodNotAvailable => {
|
||||
// Turn "`X.__call__` unbound" into "`X` not callable"
|
||||
CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
not_callable_type: self,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -2279,7 +2279,7 @@ impl<'db> Type<'db> {
|
|||
))),
|
||||
|
||||
_ => Err(CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
not_callable_type: self,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -2317,7 +2317,7 @@ impl<'db> Type<'db> {
|
|||
Type::Dynamic(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(self))),
|
||||
|
||||
_ => Err(CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
not_callable_type: self,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -2350,7 +2350,7 @@ impl<'db> Type<'db> {
|
|||
/// For type checking, use [`try_iterate`](Self::try_iterate) instead.
|
||||
fn iterate(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.try_iterate(db)
|
||||
.unwrap_or_else(|err| err.fallback_element_type())
|
||||
.unwrap_or_else(|err| err.fallback_element_type(db))
|
||||
}
|
||||
|
||||
/// Given the type of an object that is iterated over in some way,
|
||||
|
@ -2361,73 +2361,96 @@ impl<'db> Type<'db> {
|
|||
/// for y in x:
|
||||
/// pass
|
||||
/// ```
|
||||
fn try_iterate(self, db: &'db dyn Db) -> Result<Type<'db>, IterateError<'db>> {
|
||||
fn try_iterate(self, db: &'db dyn Db) -> Result<Type<'db>, IterationError<'db>> {
|
||||
if let Type::Tuple(tuple_type) = self {
|
||||
return Ok(UnionType::from_elements(db, tuple_type.elements(db)));
|
||||
}
|
||||
|
||||
let dunder_iter_result = self.try_call_dunder(db, "__iter__", &CallArguments::none());
|
||||
match &dunder_iter_result {
|
||||
Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => {
|
||||
let iterator_ty = outcome.return_type(db);
|
||||
let try_call_dunder_getitem = || {
|
||||
self.try_call_dunder(
|
||||
db,
|
||||
"__getitem__",
|
||||
&CallArguments::positional([KnownClass::Int.to_instance(db)]),
|
||||
)
|
||||
.map(|dunder_getitem_outcome| dunder_getitem_outcome.return_type(db))
|
||||
};
|
||||
|
||||
return match iterator_ty.try_call_dunder(db, "__next__", &CallArguments::none()) {
|
||||
Ok(outcome) => {
|
||||
if matches!(
|
||||
dunder_iter_result,
|
||||
Err(CallDunderError::PossiblyUnbound { .. })
|
||||
) {
|
||||
Err(IterateError::PossiblyUnbound {
|
||||
iterable_ty: self,
|
||||
element_ty: outcome.return_type(db),
|
||||
})
|
||||
} else {
|
||||
Ok(outcome.return_type(db))
|
||||
}
|
||||
let try_call_dunder_next_on_iterator = |iterator: Type<'db>| {
|
||||
iterator
|
||||
.try_call_dunder(db, "__next__", &CallArguments::none())
|
||||
.map(|dunder_next_outcome| dunder_next_outcome.return_type(db))
|
||||
};
|
||||
|
||||
let dunder_iter_result = self
|
||||
.try_call_dunder(db, "__iter__", &CallArguments::none())
|
||||
.map(|dunder_iter_outcome| dunder_iter_outcome.return_type(db));
|
||||
|
||||
let iteration_result = match dunder_iter_result {
|
||||
Ok(iterator) => {
|
||||
// `__iter__` is definitely bound and calling it succeeds.
|
||||
// See what calling `__next__` on the object returned by `__iter__` gives us...
|
||||
try_call_dunder_next_on_iterator(iterator).map_err(|dunder_next_error| {
|
||||
IterationErrorKind::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_next_error,
|
||||
}
|
||||
Err(CallDunderError::PossiblyUnbound(outcome)) => {
|
||||
Err(IterateError::PossiblyUnbound {
|
||||
iterable_ty: self,
|
||||
element_ty: outcome.return_type(db),
|
||||
})
|
||||
}
|
||||
Err(_) => Err(IterateError::NotIterable {
|
||||
not_iterable_ty: self,
|
||||
}),
|
||||
};
|
||||
}
|
||||
// If `__iter__` exists but can't be called or doesn't have the expected signature,
|
||||
// return not iterable over falling back to `__getitem__`.
|
||||
Err(CallDunderError::Call(_)) => {
|
||||
return Err(IterateError::NotIterable {
|
||||
not_iterable_ty: self,
|
||||
})
|
||||
}
|
||||
Err(CallDunderError::MethodNotAvailable) => {
|
||||
// No `__iter__` attribute, try `__getitem__` next.
|
||||
}
|
||||
}
|
||||
|
||||
// Although it's not considered great practice,
|
||||
// classes that define `__getitem__` are also iterable,
|
||||
// even if they do not define `__iter__`.
|
||||
//
|
||||
// TODO(Alex) this is only valid if the `__getitem__` method is annotated as
|
||||
// accepting `int` or `SupportsIndex`
|
||||
match self.try_call_dunder(
|
||||
db,
|
||||
"__getitem__",
|
||||
&CallArguments::positional([KnownClass::Int.to_instance(db)]),
|
||||
) {
|
||||
Ok(outcome) => Ok(outcome.return_type(db)),
|
||||
Err(CallDunderError::PossiblyUnbound(outcome)) => Err(IterateError::PossiblyUnbound {
|
||||
iterable_ty: self,
|
||||
element_ty: outcome.return_type(db),
|
||||
}),
|
||||
Err(_) => Err(IterateError::NotIterable {
|
||||
not_iterable_ty: self,
|
||||
}),
|
||||
}
|
||||
// `__iter__` is possibly unbound...
|
||||
Err(CallDunderError::PossiblyUnbound(dunder_iter_outcome)) => {
|
||||
let iterator = dunder_iter_outcome.return_type(db);
|
||||
|
||||
match try_call_dunder_next_on_iterator(iterator) {
|
||||
Ok(dunder_next_return) => {
|
||||
try_call_dunder_getitem()
|
||||
.map(|dunder_getitem_return_type| {
|
||||
// If `__iter__` is possibly unbound,
|
||||
// but it returns an object that has a bound and valid `__next__` method,
|
||||
// *and* the object has a bound and valid `__getitem__` method,
|
||||
// we infer a union of the type returned by the `__next__` method
|
||||
// and the type returned by the `__getitem__` method.
|
||||
//
|
||||
// No diagnostic is emitted; iteration will always succeed!
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
[dunder_next_return, dunder_getitem_return_type],
|
||||
)
|
||||
})
|
||||
.map_err(|dunder_getitem_error| {
|
||||
IterationErrorKind::PossiblyUnboundIterAndGetitemError {
|
||||
dunder_next_return,
|
||||
dunder_getitem_error,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Err(dunder_next_error) => Err(IterationErrorKind::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_next_error,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// `__iter__` is definitely bound but it can't be called with the expected arguments
|
||||
Err(CallDunderError::Call(dunder_iter_call_error)) => {
|
||||
Err(IterationErrorKind::IterCallError(dunder_iter_call_error))
|
||||
}
|
||||
|
||||
// There's no `__iter__` method. Try `__getitem__` instead...
|
||||
Err(CallDunderError::MethodNotAvailable) => {
|
||||
try_call_dunder_getitem().map_err(|dunder_getitem_error| {
|
||||
IterationErrorKind::UnboundIterAndGetitemError {
|
||||
dunder_getitem_error,
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
iteration_result.map_err(|error_kind| IterationError {
|
||||
iterable_type: self,
|
||||
error_kind,
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
@ -2909,51 +2932,348 @@ pub enum TypeVarBoundOrConstraints<'db> {
|
|||
Constraints(TupleType<'db>),
|
||||
}
|
||||
|
||||
/// Error returned if a type isn't iterable.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum IterateError<'db> {
|
||||
/// The type isn't iterable because it doesn't implement the new-style or old-style iteration protocol
|
||||
///
|
||||
/// The new-style iteration protocol requires a type being iterated over to have an `__iter__`
|
||||
/// method that returns something with a `__next__` method. The old-style iteration
|
||||
/// protocol requires a type being iterated over to have a `__getitem__` method that accepts
|
||||
/// a positive-integer argument.
|
||||
NotIterable { not_iterable_ty: Type<'db> },
|
||||
/// Error returned if a type is not (or may not be) iterable.
|
||||
#[derive(Debug)]
|
||||
struct IterationError<'db> {
|
||||
/// The type of the object that the analysed code attempted to iterate over.
|
||||
iterable_type: Type<'db>,
|
||||
|
||||
/// The type is iterable but the methods aren't always bound.
|
||||
PossiblyUnbound {
|
||||
iterable_ty: Type<'db>,
|
||||
element_ty: Type<'db>,
|
||||
},
|
||||
/// The precise kind of error encountered when trying to iterate over the type.
|
||||
error_kind: IterationErrorKind<'db>,
|
||||
}
|
||||
|
||||
impl<'db> IterateError<'db> {
|
||||
/// Reports the diagnostic for this error.
|
||||
fn report_diagnostic(&self, context: &InferContext<'db>, iterable_node: ast::AnyNodeRef) {
|
||||
match self {
|
||||
Self::NotIterable { not_iterable_ty } => {
|
||||
report_not_iterable(context, iterable_node, *not_iterable_ty);
|
||||
}
|
||||
Self::PossiblyUnbound {
|
||||
iterable_ty,
|
||||
element_ty: _,
|
||||
} => {
|
||||
report_not_iterable_possibly_unbound(context, iterable_node, *iterable_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> IterationError<'db> {
|
||||
/// Returns the element type if it is known, or `None` if the type is never iterable.
|
||||
fn element_type(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
IterateError::NotIterable { .. } => None,
|
||||
IterateError::PossiblyUnbound { element_ty, .. } => Some(*element_ty),
|
||||
}
|
||||
fn element_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
self.error_kind.element_type(db)
|
||||
}
|
||||
|
||||
/// Returns the element type if it is known, or `Type::unknown()` if it is not.
|
||||
fn fallback_element_type(&self) -> Type<'db> {
|
||||
self.element_type().unwrap_or(Type::unknown())
|
||||
fn fallback_element_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.element_type(db).unwrap_or(Type::unknown())
|
||||
}
|
||||
|
||||
/// Reports the diagnostic for this error.
|
||||
fn report_diagnostic(&self, context: &InferContext<'db>, iterable_node: ast::AnyNodeRef) {
|
||||
self.error_kind
|
||||
.report_diagnostic(context, self.iterable_type, iterable_node);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum IterationErrorKind<'db> {
|
||||
/// The object being iterated over has a bound `__iter__` method,
|
||||
/// but calling it with the expected arguments results in an error.
|
||||
IterCallError(CallError<'db>),
|
||||
|
||||
/// The object being iterated over has a bound `__iter__` method that can be called
|
||||
/// with the expected types, but it returns an object that is not a valid iterator.
|
||||
IterReturnsInvalidIterator {
|
||||
/// The type of the object returned by the `__iter__` method.
|
||||
iterator: Type<'db>,
|
||||
/// The error we encountered when we tried to call `__next__` on the type
|
||||
/// returned by `__iter__`
|
||||
dunder_next_error: CallDunderError<'db>,
|
||||
},
|
||||
|
||||
/// The object being iterated over has a bound `__iter__` method that returns a
|
||||
/// valid iterator. However, the `__iter__` method is possibly unbound, and there
|
||||
/// either isn't a `__getitem__` method to fall back to, or calling the `__getitem__`
|
||||
/// method returns some kind of error.
|
||||
PossiblyUnboundIterAndGetitemError {
|
||||
/// The type of the object returned by the `__next__` method on the iterator.
|
||||
/// (The iterator being the type returned by the `__iter__` method on the iterable.)
|
||||
dunder_next_return: Type<'db>,
|
||||
/// The error we encountered when we tried to call `__getitem__` on the iterable.
|
||||
dunder_getitem_error: CallDunderError<'db>,
|
||||
},
|
||||
|
||||
/// The object being iterated over doesn't have an `__iter__` method.
|
||||
/// It also either doesn't have a `__getitem__` method to fall back to,
|
||||
/// or calling the `__getitem__` method returns some kind of error.
|
||||
UnboundIterAndGetitemError {
|
||||
dunder_getitem_error: CallDunderError<'db>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'db> IterationErrorKind<'db> {
|
||||
/// Returns the element type if it is known, or `None` if the type is never iterable.
|
||||
fn element_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::IterReturnsInvalidIterator {
|
||||
dunder_next_error, ..
|
||||
} => dunder_next_error.return_type(db),
|
||||
|
||||
Self::IterCallError(dunder_iter_call_error) => dunder_iter_call_error
|
||||
.fallback_return_type(db)
|
||||
.try_call_dunder(db, "__next__", &CallArguments::none())
|
||||
.map(|dunder_next_outcome| Some(dunder_next_outcome.return_type(db)))
|
||||
.unwrap_or_else(|dunder_next_call_error| dunder_next_call_error.return_type(db)),
|
||||
|
||||
Self::PossiblyUnboundIterAndGetitemError {
|
||||
dunder_next_return,
|
||||
dunder_getitem_error,
|
||||
} => match dunder_getitem_error {
|
||||
CallDunderError::MethodNotAvailable => Some(*dunder_next_return),
|
||||
CallDunderError::PossiblyUnbound(dunder_getitem_outcome) => {
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[*dunder_next_return, dunder_getitem_outcome.return_type(db)],
|
||||
))
|
||||
}
|
||||
CallDunderError::Call(dunder_getitem_call_error) => Some(
|
||||
dunder_getitem_call_error
|
||||
.return_type(db)
|
||||
.map(|dunder_getitem_return| {
|
||||
let elements = [*dunder_next_return, dunder_getitem_return];
|
||||
UnionType::from_elements(db, elements)
|
||||
})
|
||||
.unwrap_or(*dunder_next_return),
|
||||
),
|
||||
},
|
||||
|
||||
Self::UnboundIterAndGetitemError {
|
||||
dunder_getitem_error,
|
||||
} => dunder_getitem_error.return_type(db),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports the diagnostic for this error.
|
||||
fn report_diagnostic(
|
||||
&self,
|
||||
context: &InferContext<'db>,
|
||||
iterable_type: Type<'db>,
|
||||
iterable_node: ast::AnyNodeRef,
|
||||
) {
|
||||
let db = context.db();
|
||||
|
||||
let report_not_iterable = |arguments: std::fmt::Arguments| {
|
||||
context.report_lint(&NOT_ITERABLE, iterable_node, arguments);
|
||||
};
|
||||
|
||||
// TODO: for all of these error variant, the "explanation" for the diagnostic
|
||||
// (everything after the "because") should really be presented as a "help:", "note",
|
||||
// or similar, rather than as part of the same sentence as the error message.
|
||||
|
||||
match self {
|
||||
Self::IterCallError(dunder_iter_call_error) => match dunder_iter_call_error {
|
||||
CallError::NotCallable { not_callable_type } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because its `__iter__` attribute has type `{dunder_iter_type}`, \
|
||||
which is not callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_iter_type = not_callable_type.display(db),
|
||||
)),
|
||||
CallError::PossiblyUnboundDunderCall { called_type, .. } => {
|
||||
report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` attribute (with type `{dunder_iter_type}`) \
|
||||
may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_iter_type = called_type.display(db),
|
||||
));
|
||||
}
|
||||
CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => {
|
||||
report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` attribute (with type `{dunder_iter_type}`) \
|
||||
may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_iter_type = union_call_error.called_type.display(db),
|
||||
));
|
||||
}
|
||||
CallError::BindingError { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because its `__iter__` method has an invalid signature \
|
||||
(expected `def __iter__(self): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
)),
|
||||
CallError::Union(UnionCallError { called_type, .. }) => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` method (with type `{dunder_iter_type}`) \
|
||||
may have an invalid signature (expected `def __iter__(self): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_iter_type = called_type.display(db),
|
||||
)),
|
||||
}
|
||||
|
||||
Self::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_next_error
|
||||
} => match dunder_next_error {
|
||||
CallDunderError::MethodNotAvailable => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has no `__next__` method",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
)),
|
||||
CallDunderError::PossiblyUnbound(_) => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which may not have a `__next__` method",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
)),
|
||||
CallDunderError::Call(dunder_next_call_error) => match dunder_next_call_error {
|
||||
CallError::NotCallable { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has a `__next__` attribute that is not callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
)),
|
||||
CallError::PossiblyUnboundDunderCall { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has a `__next__` attribute that may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
)),
|
||||
CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => {
|
||||
report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has a `__next__` attribute that may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
));
|
||||
}
|
||||
CallError::BindingError { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has an invalid `__next__` method (expected `def __next__(self): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
)),
|
||||
CallError::Union(_) => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which may have an invalid `__next__` method (expected `def __next__(self): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
iterator_type = iterator.display(db),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Self::PossiblyUnboundIterAndGetitemError {
|
||||
dunder_getitem_error, ..
|
||||
} => match dunder_getitem_error {
|
||||
CallDunderError::MethodNotAvailable => report_not_iterable(format_args!(
|
||||
"Object of type `{}` may not be iterable \
|
||||
because it may not have an `__iter__` method \
|
||||
and it doesn't have a `__getitem__` method",
|
||||
iterable_type.display(db)
|
||||
)),
|
||||
CallDunderError::PossiblyUnbound(_) => report_not_iterable(format_args!(
|
||||
"Object of type `{}` may not be iterable \
|
||||
because it may not have an `__iter__` method or a `__getitem__` method",
|
||||
iterable_type.display(db)
|
||||
)),
|
||||
CallDunderError::Call(dunder_getitem_call_error) => match dunder_getitem_call_error {
|
||||
CallError::NotCallable { not_callable_type } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it may not have an `__iter__` method \
|
||||
and its `__getitem__` attribute has type `{dunder_getitem_type}`, \
|
||||
which is not callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_getitem_type = not_callable_type.display(db),
|
||||
)),
|
||||
CallError::PossiblyUnboundDunderCall { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it may not have an `__iter__` method \
|
||||
and its `__getitem__` attribute may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
)),
|
||||
CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => {
|
||||
report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it may not have an `__iter__` method \
|
||||
and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \
|
||||
may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_getitem_type = union_call_error.called_type.display(db),
|
||||
));
|
||||
}
|
||||
CallError::BindingError { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it may not have an `__iter__` method \
|
||||
and its `__getitem__` method has an incorrect signature \
|
||||
for the old-style iteration protocol \
|
||||
(expected a signature at least as permissive as \
|
||||
`def __getitem__(self, key: int): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
)),
|
||||
CallError::Union(UnionCallError {called_type, ..})=> report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it may not have an `__iter__` method \
|
||||
and its `__getitem__` method (with type `{dunder_getitem_type}`)
|
||||
may have an incorrect signature for the old-style iteration protocol \
|
||||
(expected a signature at least as permissive as \
|
||||
`def __getitem__(self, key: int): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_getitem_type = called_type.display(db),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Self::UnboundIterAndGetitemError { dunder_getitem_error } => match dunder_getitem_error {
|
||||
CallDunderError::MethodNotAvailable => report_not_iterable(format_args!(
|
||||
"Object of type `{}` is not iterable because it doesn't have \
|
||||
an `__iter__` method or a `__getitem__` method",
|
||||
iterable_type.display(db)
|
||||
)),
|
||||
CallDunderError::PossiblyUnbound(_) => report_not_iterable(format_args!(
|
||||
"Object of type `{}` may not be iterable because it has no `__iter__` method \
|
||||
and it may not have a `__getitem__` method",
|
||||
iterable_type.display(db)
|
||||
)),
|
||||
CallDunderError::Call(dunder_getitem_call_error) => match dunder_getitem_call_error {
|
||||
CallError::NotCallable { not_callable_type } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because it has no `__iter__` method and \
|
||||
its `__getitem__` attribute has type `{dunder_getitem_type}`, \
|
||||
which is not callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_getitem_type = not_callable_type.display(db),
|
||||
)),
|
||||
CallError::PossiblyUnboundDunderCall { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it has no `__iter__` method and its `__getitem__` attribute \
|
||||
may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
)),
|
||||
CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => {
|
||||
report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it has no `__iter__` method and its `__getitem__` attribute \
|
||||
(with type `{dunder_getitem_type}`) may not be callable",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_getitem_type = union_call_error.called_type.display(db),
|
||||
));
|
||||
}
|
||||
CallError::BindingError { .. } => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable \
|
||||
because it has no `__iter__` method and \
|
||||
its `__getitem__` method has an incorrect signature \
|
||||
for the old-style iteration protocol \
|
||||
(expected a signature at least as permissive as \
|
||||
`def __getitem__(self, key: int): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
)),
|
||||
CallError::Union(UnionCallError { called_type, .. }) => report_not_iterable(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable \
|
||||
because it has no `__iter__` method and \
|
||||
its `__getitem__` method (with type `{dunder_getitem_type}`) \
|
||||
may have an incorrect signature for the old-style iteration protocol \
|
||||
(expected a signature at least as permissive as \
|
||||
`def __getitem__(self, key: int): ...`)",
|
||||
iterable_type = iterable_type.display(db),
|
||||
dunder_getitem_type = called_type.display(db),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,13 +54,13 @@ impl<'db> CallOutcome<'db> {
|
|||
Ok(CallOutcome::Union(bindings.into()))
|
||||
} else if bindings.is_empty() && not_callable {
|
||||
Err(CallError::NotCallable {
|
||||
not_callable_ty: Type::Union(union),
|
||||
not_callable_type: Type::Union(union),
|
||||
})
|
||||
} else {
|
||||
Err(CallError::Union(UnionCallError {
|
||||
errors: errors.into(),
|
||||
bindings: bindings.into(),
|
||||
called_ty: Type::Union(union),
|
||||
called_type: Type::Union(union),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ pub(super) enum CallError<'db> {
|
|||
/// The type is not callable.
|
||||
NotCallable {
|
||||
/// The type that can't be called.
|
||||
not_callable_ty: Type<'db>,
|
||||
not_callable_type: Type<'db>,
|
||||
},
|
||||
|
||||
/// A call to a union failed because at least one variant
|
||||
|
@ -147,10 +147,10 @@ impl<'db> CallError<'db> {
|
|||
pub(super) fn called_type(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::NotCallable {
|
||||
not_callable_ty, ..
|
||||
} => *not_callable_ty,
|
||||
Self::Union(UnionCallError { called_ty, .. }) => *called_ty,
|
||||
Self::PossiblyUnboundDunderCall { called_type, .. } => *called_type,
|
||||
not_callable_type, ..
|
||||
} => *not_callable_type,
|
||||
Self::Union(UnionCallError { called_type, .. })
|
||||
| Self::PossiblyUnboundDunderCall { called_type, .. } => *called_type,
|
||||
Self::BindingError { binding } => binding.callable_type(),
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +169,29 @@ pub(super) struct UnionCallError<'db> {
|
|||
pub(super) bindings: Box<[CallBinding<'db>]>,
|
||||
|
||||
/// The union type that we tried calling.
|
||||
pub(super) called_ty: Type<'db>,
|
||||
pub(super) called_type: Type<'db>,
|
||||
}
|
||||
|
||||
impl UnionCallError<'_> {
|
||||
/// Return `true` if this `UnionCallError` indicates that the union might not be callable at all.
|
||||
/// Otherwise, return `false`.
|
||||
///
|
||||
/// For example, the union type `Callable[[int], int] | None` may not be callable at all,
|
||||
/// because the `None` element in this union has no `__call__` method. Calling an object that
|
||||
/// inhabited this union type would lead to a `UnionCallError` that would indicate that the
|
||||
/// union might not be callable at all.
|
||||
///
|
||||
/// On the other hand, the union type `Callable[[int], int] | Callable[[str], str]` is always
|
||||
/// *callable*, but it would still lead to a `UnionCallError` if an inhabitant of this type was
|
||||
/// called with a single `int` argument passed in. That's because the second element in the
|
||||
/// union doesn't accept an `int` when it's called: it only accepts a `str`.
|
||||
pub(crate) fn indicates_type_possibly_not_callable(&self) -> bool {
|
||||
self.errors.iter().any(|error| match error {
|
||||
CallError::BindingError { .. } => false,
|
||||
CallError::NotCallable { .. } | CallError::PossiblyUnboundDunderCall { .. } => true,
|
||||
CallError::Union(union_error) => union_error.indicates_type_possibly_not_callable(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -192,7 +214,7 @@ impl<'db> CallDunderError<'db> {
|
|||
pub(super) fn return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::Call(error) => error.return_type(db),
|
||||
Self::PossiblyUnbound(_) => None,
|
||||
Self::PossiblyUnbound(call_outcome) => Some(call_outcome.return_type(db)),
|
||||
Self::MethodNotAvailable => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,12 +224,12 @@ impl<'db> Class<'db> {
|
|||
let return_ty_result = match metaclass.try_call(db, &arguments) {
|
||||
Ok(outcome) => Ok(outcome.return_type(db)),
|
||||
|
||||
Err(CallError::NotCallable { not_callable_ty }) => Err(MetaclassError {
|
||||
kind: MetaclassErrorKind::NotCallable(not_callable_ty),
|
||||
Err(CallError::NotCallable { not_callable_type }) => Err(MetaclassError {
|
||||
kind: MetaclassErrorKind::NotCallable(not_callable_type),
|
||||
}),
|
||||
|
||||
Err(CallError::Union(UnionCallError {
|
||||
called_ty,
|
||||
called_type,
|
||||
errors,
|
||||
bindings,
|
||||
})) => {
|
||||
|
@ -259,7 +259,7 @@ impl<'db> Class<'db> {
|
|||
|
||||
if partly_not_callable {
|
||||
Err(MetaclassError {
|
||||
kind: MetaclassErrorKind::PartlyNotCallable(called_ty),
|
||||
kind: MetaclassErrorKind::PartlyNotCallable(called_type),
|
||||
})
|
||||
} else {
|
||||
Ok(return_ty.unwrap_or(Type::unknown()))
|
||||
|
|
|
@ -921,35 +921,6 @@ impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
|
|||
}
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn report_not_iterable(context: &InferContext, node: AnyNodeRef, not_iterable_ty: Type) {
|
||||
context.report_lint(
|
||||
&NOT_ITERABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(context.db())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
/// because its `__iter__` method is possibly unbound.
|
||||
pub(super) fn report_not_iterable_possibly_unbound(
|
||||
context: &InferContext,
|
||||
node: AnyNodeRef,
|
||||
element_ty: Type,
|
||||
) {
|
||||
context.report_lint(
|
||||
&NOT_ITERABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
|
||||
element_ty.display(context.db())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
|
||||
pub(super) fn report_index_out_of_bounds(
|
||||
context: &InferContext,
|
||||
|
|
|
@ -2414,7 +2414,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
TargetKind::Name => iterable_ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable.into());
|
||||
err.fallback_element_type()
|
||||
err.fallback_element_type(self.db())
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
@ -3209,7 +3209,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} else {
|
||||
iterable_ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable.into());
|
||||
err.fallback_element_type()
|
||||
err.fallback_element_type(self.db())
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -3436,13 +3436,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
call_expression: &ast::ExprCall,
|
||||
) {
|
||||
match err {
|
||||
CallError::NotCallable { not_callable_ty } => {
|
||||
CallError::NotCallable { not_callable_type } => {
|
||||
context.report_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
call_expression,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable",
|
||||
not_callable_ty.display(context.db())
|
||||
not_callable_type.display(context.db())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -3492,7 +3492,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let iterable_ty = self.infer_expression(value);
|
||||
iterable_ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, value.as_ref().into());
|
||||
err.fallback_element_type()
|
||||
err.fallback_element_type(self.db())
|
||||
});
|
||||
|
||||
// TODO
|
||||
|
@ -3511,7 +3511,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let iterable_ty = self.infer_expression(value);
|
||||
iterable_ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, value.as_ref().into());
|
||||
err.fallback_element_type()
|
||||
err.fallback_element_type(self.db())
|
||||
});
|
||||
|
||||
// TODO get type from `ReturnType` of generator
|
||||
|
|
|
@ -59,7 +59,7 @@ impl<'db> Unpacker<'db> {
|
|||
// type.
|
||||
value_ty = value_ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, value.as_any_node_ref(self.db()));
|
||||
err.fallback_element_type()
|
||||
err.fallback_element_type(self.db())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ impl<'db> Unpacker<'db> {
|
|||
} else {
|
||||
ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, value_expr);
|
||||
err.fallback_element_type()
|
||||
err.fallback_element_type(self.db())
|
||||
})
|
||||
};
|
||||
for target_type in &mut target_types {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue