red_knot_python_semantic: improve not-iterable diagnostic

This cleans up one particular TODO by splitting the "because" part of
the `not-iterable` diagnostic out into an info sub-diagnostic.
This commit is contained in:
Andrew Gallant 2025-04-25 11:42:00 -04:00 committed by Andrew Gallant
parent 07718f4788
commit 9a8f3cf247
20 changed files with 310 additions and 211 deletions

View file

@ -286,7 +286,7 @@ class Test:
def __iter__(self) -> TestIter | int:
return TestIter()
# 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"
# error: [not-iterable] "Object of type `Test` may not be iterable"
for x in Test():
reveal_type(x) # revealed: int
```
@ -316,12 +316,12 @@ def _(flag: bool):
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"
# error: [not-iterable] "Object of type `Iterable1` may not be iterable"
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 `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable"
# error: [not-iterable] "Object of type `Iterable2` may not be iterable"
for y in Iterable2():
# TODO... `int` might be ideal here?
reveal_type(y) # revealed: int | Unknown
@ -376,7 +376,7 @@ def _(flag: bool):
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"
# error: [not-iterable] "Object of type `Iterable` may not be iterable"
for x in Iterable():
reveal_type(x) # revealed: int
```
@ -461,7 +461,7 @@ def _(flag: bool):
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"
# error: [not-iterable] "Object of type `Iterable` may not be iterable"
for x in Iterable():
reveal_type(x) # revealed: int
```

View file

@ -28,7 +28,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-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): ...`)
error: lint:not-iterable: Object of type `Iterable` is not iterable
--> /src/mdtest_snippet.py:10:10
|
9 | # error: [not-iterable]
@ -36,6 +36,8 @@ error: lint:not-iterable: Object of type `Iterable` is not iterable because it h
| ^^^^^^^^^^
11 | reveal_type(x) # revealed: int
|
info: It has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
```

View file

@ -20,7 +20,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Literal[123]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
error: lint:not-iterable: Object of type `Literal[123]` is not iterable
--> /src/mdtest_snippet.py:2:10
|
1 | nonsense = 123
@ -28,5 +28,6 @@ error: lint:not-iterable: Object of type `Literal[123]` is not iterable because
| ^^^^^^^^
3 | pass
|
info: It doesn't have an `__iter__` method or a `__getitem__` method
```

View file

@ -24,7 +24,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `None`, which is not callable
error: lint:not-iterable: Object of type `NotIterable` is not iterable
--> /src/mdtest_snippet.py:6:10
|
4 | __iter__: None = None
@ -33,5 +33,6 @@ error: lint:not-iterable: Object of type `NotIterable` is not iterable because i
| ^^^^^^^^^^^^^
7 | pass
|
info: Its `__iter__` attribute has type `None`, which is not callable
```

View file

@ -25,7 +25,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Bad` is not iterable because it has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
error: lint:not-iterable: Object of type `Bad` is not iterable
--> /src/mdtest_snippet.py:7:10
|
6 | # error: [not-iterable]
@ -33,6 +33,7 @@ error: lint:not-iterable: Object of type `Bad` is not iterable because it has no
| ^^^^^
8 | reveal_type(x) # revealed: Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
```

View file

@ -46,7 +46,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: 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
error: lint:not-iterable: Object of type `Iterable1` may not be iterable
--> /src/mdtest_snippet.py:22:14
|
21 | # error: [not-iterable]
@ -55,6 +55,8 @@ error: lint:not-iterable: Object of type `Iterable1` may not be iterable because
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `CustomCallable`, which is not callable
```
@ -73,7 +75,7 @@ info: revealed-type: Revealed type
```
```
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable2.__getitem__(key: int) -> int) | None`) may not be callable
error: lint:not-iterable: Object of type `Iterable2` may not be iterable
--> /src/mdtest_snippet.py:27:14
|
26 | # error: [not-iterable]
@ -82,6 +84,8 @@ error: lint:not-iterable: Object of type `Iterable2` may not be iterable because
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `(bound method Iterable2.__getitem__(key: int) -> int) | None`, which is not callable
```

View file

@ -43,7 +43,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
error: lint:not-iterable: Object of type `Iterable1` may not be iterable
--> /src/mdtest_snippet.py:20:14
|
19 | # error: [not-iterable]
@ -52,6 +52,8 @@ error: lint:not-iterable: Object of type `Iterable1` may not be iterable because
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable
```
@ -70,7 +72,7 @@ info: revealed-type: Revealed type
```
```
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
error: lint:not-iterable: Object of type `Iterable2` may not be iterable
--> /src/mdtest_snippet.py:25:14
|
24 | # error: [not-iterable]
@ -78,6 +80,8 @@ error: lint:not-iterable: Object of type `Iterable2` may not be iterable because
| ^^^^^^^^^^^
26 | reveal_type(y) # revealed: str | int
|
info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
```

View file

@ -47,7 +47,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because its `__iter__` method (with type `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`) may have an invalid signature (expected `def __iter__(self): ...`)
error: lint:not-iterable: Object of type `Iterable1` may not be iterable
--> /src/mdtest_snippet.py:17:14
|
16 | # error: [not-iterable]
@ -55,6 +55,9 @@ error: lint:not-iterable: Object of type `Iterable1` may not be iterable because
| ^^^^^^^^^^^
18 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method may have an invalid signature
info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`
info: Expected signature for `__iter__` is `def __iter__(self): ...`
```
@ -73,7 +76,7 @@ info: revealed-type: Revealed type
```
```
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
error: lint:not-iterable: Object of type `Iterable2` may not be iterable
--> /src/mdtest_snippet.py:28:14
|
27 | # error: [not-iterable]
@ -82,6 +85,7 @@ error: lint:not-iterable: Object of type `Iterable2` may not be iterable because
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
|
info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
```

View file

@ -51,7 +51,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: 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): ...`)
error: lint:not-iterable: Object of type `Iterable1` may not be iterable
--> /src/mdtest_snippet.py:28:14
|
27 | # error: [not-iterable]
@ -59,6 +59,8 @@ error: lint:not-iterable: Object of type `Iterable1` may not be iterable because
| ^^^^^^^^^^^
29 | reveal_type(x) # revealed: int | str
|
info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method
info: Expected signature for `__next__` is `def __next__(self): ...`)
```
@ -77,7 +79,7 @@ info: revealed-type: Revealed type
```
```
error: lint:not-iterable: 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
error: lint:not-iterable: Object of type `Iterable2` may not be iterable
--> /src/mdtest_snippet.py:32:14
|
31 | # error: [not-iterable]
@ -86,6 +88,7 @@ error: lint:not-iterable: Object of type `Iterable2` may not be iterable because
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
|
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
```

View file

@ -36,7 +36,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-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): ...`)
error: lint:not-iterable: Object of type `Iterable` may not be iterable
--> /src/mdtest_snippet.py:18:14
|
17 | # error: [not-iterable]
@ -44,6 +44,8 @@ error: lint:not-iterable: Object of type `Iterable` may not be iterable because
| ^^^^^^^^^^
19 | reveal_type(x) # revealed: int | bytes
|
info: It may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
```

View file

@ -54,7 +54,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
error: lint:not-iterable: Object of type `Iterable1` may not be iterable
--> /src/mdtest_snippet.py:31:14
|
30 | # error: [not-iterable]
@ -63,6 +63,7 @@ error: lint:not-iterable: Object of type `Iterable1` may not be iterable because
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
```
@ -81,7 +82,7 @@ info: revealed-type: Revealed type
```
```
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
error: lint:not-iterable: Object of type `Iterable2` may not be iterable
--> /src/mdtest_snippet.py:36:14
|
35 | # error: [not-iterable]
@ -89,6 +90,8 @@ error: lint:not-iterable: Object of type `Iterable2` may not be iterable because
| ^^^^^^^^^^^
37 | reveal_type(y) # revealed: bytes | str | int
|
info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
```

View file

@ -35,7 +35,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Iterable` may not be iterable because it may not have an `__iter__` method or a `__getitem__` method
error: lint:not-iterable: Object of type `Iterable` may not be iterable
--> /src/mdtest_snippet.py:17:14
|
16 | # error: [not-iterable]
@ -43,6 +43,7 @@ error: lint:not-iterable: Object of type `Iterable` may not be iterable because
| ^^^^^^^^^^
18 | reveal_type(x) # revealed: int | bytes
|
info: It may not have an `__iter__` method or a `__getitem__` method
```

View file

@ -36,7 +36,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: 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
error: lint:not-iterable: Object of type `Test | Test2` may not be 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)
@ -45,6 +45,7 @@ error: lint:not-iterable: Object of type `Test | Test2` may not be iterable beca
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
```

View file

@ -31,7 +31,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: 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
error: lint:not-iterable: Object of type `Test | Literal[42]` may not be iterable
--> /src/mdtest_snippet.py:13:14
|
11 | def _(flag: bool):
@ -40,6 +40,7 @@ error: lint:not-iterable: Object of type `Test | Literal[42]` may not be iterabl
| ^^^^^^^^^^^^^^^^^^^^^^
14 | reveal_type(x) # revealed: int
|
info: It may not have an `__iter__` method and it doesn't have a `__getitem__` method
```

View file

@ -33,7 +33,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `int | None`, which is not callable
error: lint:not-iterable: Object of type `NotIterable` is not iterable
--> /src/mdtest_snippet.py:11:14
|
10 | # error: [not-iterable]
@ -41,6 +41,7 @@ error: lint:not-iterable: Object of type `NotIterable` is not iterable because i
| ^^^^^^^^^^^^^
12 | pass
|
info: Its `__iter__` attribute has type `int | None`, which is not callable
```

View file

@ -26,7 +26,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Bad` is not iterable because its `__iter__` method returns an object of type `int`, which has no `__next__` method
error: lint:not-iterable: Object of type `Bad` is not iterable
--> /src/mdtest_snippet.py:8:10
|
7 | # error: [not-iterable]
@ -34,6 +34,7 @@ error: lint:not-iterable: Object of type `Bad` is not iterable because its `__it
| ^^^^^
9 | reveal_type(x) # revealed: Unknown
|
info: Its `__iter__` method returns an object of type `int`, which has no `__next__` method
```

View file

@ -30,7 +30,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: Object of type `Iterable` is not iterable because its `__iter__` method has an invalid signature (expected `def __iter__(self): ...`)
error: lint:not-iterable: Object of type `Iterable` is not iterable
--> /src/mdtest_snippet.py:12:10
|
11 | # error: [not-iterable]
@ -38,6 +38,8 @@ error: lint:not-iterable: Object of type `Iterable` is not iterable because its
| ^^^^^^^^^^
13 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method has an invalid signature
info: Expected signature `def __iter__(self): ...`
```

View file

@ -41,7 +41,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
# Diagnostics
```
error: lint:not-iterable: 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): ...`)
error: lint:not-iterable: Object of type `Iterable1` is not iterable
--> /src/mdtest_snippet.py:19:10
|
18 | # error: [not-iterable]
@ -49,6 +49,8 @@ error: lint:not-iterable: Object of type `Iterable1` is not iterable because its
| ^^^^^^^^^^^
20 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method
info: Expected signature for `__next__` is `def __next__(self): ...`
```
@ -67,7 +69,7 @@ info: revealed-type: Revealed type
```
```
error: lint:not-iterable: 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
error: lint:not-iterable: Object of type `Iterable2` is not iterable
--> /src/mdtest_snippet.py:23:10
|
22 | # error: [not-iterable]
@ -75,6 +77,7 @@ error: lint:not-iterable: Object of type `Iterable2` is not iterable because its
| ^^^^^^^^^^^
24 | reveal_type(y) # revealed: Unknown
|
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
```

View file

@ -18,11 +18,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpack
# Diagnostics
```
error: lint:not-iterable: Object of type `Literal[1]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
error: lint:not-iterable: Object of type `Literal[1]` is not iterable
--> /src/mdtest_snippet.py:1:8
|
1 | a, b = 1 # error: [not-iterable]
| ^
|
info: It doesn't have an `__iter__` method or a `__getitem__` method
```

View file

@ -42,6 +42,7 @@ use crate::symbol::{
};
use crate::types::call::{Bindings, CallArgumentTypes, CallableBinding};
pub(crate) use crate::types::class_base::ClassBase;
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::infer::infer_unpack_types;
@ -5505,219 +5506,281 @@ impl<'db> IterationError<'db> {
iterable_type: Type<'db>,
iterable_node: ast::AnyNodeRef,
) {
/// A little helper type for emitting a diagnostic
/// based on the variant of iteration error.
struct Reporter<'a> {
db: &'a dyn Db,
builder: LintDiagnosticGuardBuilder<'a, 'a>,
iterable_type: Type<'a>,
}
impl<'a> Reporter<'a> {
/// Emit a diagnostic that is certain that `iterable_type` is not iterable.
///
/// `because` should explain why `iterable_type` is not iterable.
#[allow(clippy::wrong_self_convention)]
fn is_not(self, because: impl std::fmt::Display) -> LintDiagnosticGuard<'a, 'a> {
let mut diag = self.builder.into_diagnostic(format_args!(
"Object of type `{iterable_type}` is not iterable",
iterable_type = self.iterable_type.display(self.db),
));
diag.info(because);
diag
}
/// Emit a diagnostic that is uncertain that `iterable_type` is not iterable.
///
/// `because` should explain why `iterable_type` is likely not iterable.
fn may_not(self, because: impl std::fmt::Display) -> LintDiagnosticGuard<'a, 'a> {
let mut diag = self.builder.into_diagnostic(format_args!(
"Object of type `{iterable_type}` may not be iterable",
iterable_type = self.iterable_type.display(self.db),
));
diag.info(because);
diag
}
}
let Some(builder) = context.report_lint(&NOT_ITERABLE, iterable_node) else {
return;
};
let db = context.db();
let report_not_iterable = |arguments: std::fmt::Arguments| {
builder.into_diagnostic(arguments);
let reporter = Reporter {
db,
builder,
iterable_type,
};
// TODO: for all of these error variants, 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(CallErrorKind::NotCallable, bindings) => 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 = bindings.callable_type().display(db),
)),
Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => {
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),
Self::IterCallError(CallErrorKind::NotCallable, bindings) => {
reporter.is_not(format_args!(
"Its `__iter__` attribute has type `{dunder_iter_type}`, which is not callable",
dunder_iter_type = bindings.callable_type().display(db),
));
}
Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings)
if bindings.is_single() =>
{
reporter.may_not(format_args!(
"Its `__iter__` attribute (with type `{dunder_iter_type}`) \
may not be callable",
dunder_iter_type = bindings.callable_type().display(db),
));
}
Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) => {
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),
reporter.may_not(format_args!(
"Its `__iter__` attribute (with type `{dunder_iter_type}`) \
may not be callable",
dunder_iter_type = bindings.callable_type().display(db),
));
}
Self::IterCallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => 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),
)),
Self::IterCallError(CallErrorKind::BindingError, bindings) => 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 = bindings.callable_type().display(db),
)),
Self::IterCallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => {
reporter
.is_not("Its `__iter__` method has an invalid signature")
.info("Expected signature `def __iter__(self): ...`");
}
Self::IterCallError(CallErrorKind::BindingError, bindings) => {
let mut diag =
reporter.may_not("Its `__iter__` method may have an invalid signature");
diag.info(format_args!(
"Type of `__iter__` is `{dunder_iter_type}`",
dunder_iter_type = bindings.callable_type().display(db),
));
diag.info("Expected signature for `__iter__` is `def __iter__(self): ...`");
}
Self::IterReturnsInvalidIterator {
iterator,
dunder_next_error
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::CallError(CallErrorKind::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),
)),
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, _) => 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),
)),
CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => 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),
)),
CallDunderError::CallError(CallErrorKind::BindingError, _) => 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),
)),
}
CallDunderError::MethodNotAvailable => {
reporter.is_not(format_args!(
"Its `__iter__` method returns an object of type `{iterator_type}`, \
which has no `__next__` method",
iterator_type = iterator.display(db),
));
}
CallDunderError::PossiblyUnbound(_) => {
reporter.may_not(format_args!(
"Its `__iter__` method returns an object of type `{iterator_type}`, \
which may not have a `__next__` method",
iterator_type = iterator.display(db),
));
}
CallDunderError::CallError(CallErrorKind::NotCallable, _) => {
reporter.is_not(format_args!(
"Its `__iter__` method returns an object of type `{iterator_type}`, \
which has a `__next__` attribute that is not callable",
iterator_type = iterator.display(db),
));
}
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, _) => {
reporter.may_not(format_args!(
"Its `__iter__` method returns an object of type `{iterator_type}`, \
which has a `__next__` attribute that may not be callable",
iterator_type = iterator.display(db),
));
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings)
if bindings.is_single() =>
{
reporter
.is_not(format_args!(
"Its `__iter__` method returns an object of type `{iterator_type}`, \
which has an invalid `__next__` method",
iterator_type = iterator.display(db),
))
.info("Expected signature for `__next__` is `def __next__(self): ...`");
}
CallDunderError::CallError(CallErrorKind::BindingError, _) => {
reporter
.may_not(format_args!(
"Its `__iter__` method returns an object of type `{iterator_type}`, \
which may have an invalid `__next__` method",
iterator_type = iterator.display(db),
))
.info("Expected signature for `__next__` is `def __next__(self): ...`)");
}
},
Self::PossiblyUnboundIterAndGetitemError {
dunder_getitem_error, ..
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::CallError(CallErrorKind::NotCallable, bindings) => 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 = bindings.callable_type().display(db),
)),
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => 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),
)),
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) => {
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),
CallDunderError::MethodNotAvailable => {
reporter.may_not(
"It may not have an `__iter__` method \
and it doesn't have a `__getitem__` method",
);
}
CallDunderError::PossiblyUnbound(_) => {
reporter
.may_not("It may not have an `__iter__` method or a `__getitem__` method");
}
CallDunderError::CallError(CallErrorKind::NotCallable, bindings) => {
reporter.may_not(format_args!(
"It may not have an `__iter__` method \
and its `__getitem__` attribute has type `{dunder_getitem_type}`, \
which is not callable",
dunder_getitem_type = bindings.callable_type().display(db),
));
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => 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),
)),
CallDunderError::CallError(CallErrorKind::BindingError, bindings) => 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 = bindings.callable_type().display(db),
)),
}
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings)
if bindings.is_single() =>
{
reporter.may_not(
"It may not have an `__iter__` method \
and its `__getitem__` attribute may not be callable",
);
}
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) => {
reporter.may_not(format_args!(
"It may not have an `__iter__` method \
and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \
may not be callable",
dunder_getitem_type = bindings.callable_type().display(db),
));
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings)
if bindings.is_single() =>
{
reporter
.may_not(
"It may not have an `__iter__` method \
and its `__getitem__` method has an incorrect signature \
for the old-style iteration protocol",
)
.info(
"`__getitem__` must be at least as permissive as \
`def __getitem__(self, key: int): ...` \
to satisfy the old-style iteration protocol",
);
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings) => {
reporter
.may_not(format_args!(
"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",
dunder_getitem_type = bindings.callable_type().display(db),
))
.info(
"`__getitem__` must be at least as permissive as \
`def __getitem__(self, key: int): ...` \
to satisfy the old-style iteration protocol",
);
}
},
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::CallError(CallErrorKind::NotCallable, bindings) => 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 = bindings.callable_type().display(db),
)),
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => 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),
)),
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) => {
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),
Self::UnboundIterAndGetitemError {
dunder_getitem_error,
} => match dunder_getitem_error {
CallDunderError::MethodNotAvailable => {
reporter
.is_not("It doesn't have an `__iter__` method or a `__getitem__` method");
}
CallDunderError::PossiblyUnbound(_) => {
reporter.is_not(
"It has no `__iter__` method and it may not have a `__getitem__` method",
);
}
CallDunderError::CallError(CallErrorKind::NotCallable, bindings) => {
reporter.is_not(format_args!(
"It has no `__iter__` method and \
its `__getitem__` attribute has type `{dunder_getitem_type}`, \
which is not callable",
dunder_getitem_type = bindings.callable_type().display(db),
));
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => 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),
)),
CallDunderError::CallError(CallErrorKind::BindingError, bindings) => 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 = bindings.callable_type().display(db),
)),
}
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings)
if bindings.is_single() =>
{
reporter.may_not(
"It has no `__iter__` method and its `__getitem__` attribute \
may not be callable",
);
}
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) => {
reporter.may_not(
"It has no `__iter__` method and its `__getitem__` attribute is invalid",
).info(format_args!(
"`__getitem__` has type `{dunder_getitem_type}`, which is not callable",
dunder_getitem_type = bindings.callable_type().display(db),
));
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings)
if bindings.is_single() =>
{
reporter
.is_not(
"It has no `__iter__` method and \
its `__getitem__` method has an incorrect signature \
for the old-style iteration protocol",
)
.info(
"`__getitem__` must be at least as permissive as \
`def __getitem__(self, key: int): ...` \
to satisfy the old-style iteration protocol",
);
}
CallDunderError::CallError(CallErrorKind::BindingError, bindings) => {
reporter
.may_not(format_args!(
"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",
dunder_getitem_type = bindings.callable_type().display(db),
))
.info(
"`__getitem__` must be at least as permissive as \
`def __getitem__(self, key: int): ...` \
to satisfy the old-style iteration protocol",
);
}
},
}
}
}