[flake8-pyi] Add autofix for PYI058 (#9355)

## Summary

This PR adds an autofix for the newly added PYI058 rule (added in
#9313). ~~The PR's current implementation is that the fix is only
available if the fully qualified name of `Generator` or `AsyncGenerator`
is being used:~~
- ~~`-> typing.Generator` is converted to `-> typing.Iterator`;~~
- ~~`-> collections.abc.AsyncGenerator[str, Any]` is converted to `->
collections.abc.AsyncIterator[str]`;~~
- ~~but `-> Generator` is _not_ converted to `-> Iterator`. (It would
require more work to figure out if `Iterator` was already imported or
not. And if it wasn't, where should we import it from? `typing`,
`typing_extensions`, or `collections.abc`? It seems much more
complicated.)~~

The fix is marked as always safe for `__iter__` or `__aiter__` methods
in `.pyi` files, but unsafe for all such methods in `.py` files that
have more than one statement in the method body.

This felt slightly fiddly to accomplish, but I couldn't _see_ any
utilities in
https://github.com/astral-sh/ruff/tree/main/crates/ruff_linter/src/fix
that would have made it simpler to implement. Lmk if I'm missing
something, though -- my first time implementing an autofix! :)

## Test Plan

`cargo test` / `cargo insta review`.
This commit is contained in:
Alex Waygood 2024-01-03 16:11:16 +00:00 committed by GitHub
parent dc5094d42a
commit 1ffc738c84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 853 additions and 234 deletions

View file

@ -1,82 +1,174 @@
import collections.abc def scope():
import typing from collections.abc import Generator
from collections.abc import AsyncGenerator, Generator
from typing import Any
class IteratorReturningSimpleGenerator1: class IteratorReturningSimpleGenerator1:
def __iter__(self) -> Generator: # PYI058 (use `Iterator`) def __iter__(self) -> Generator:
return (x for x in range(42)) ... # PYI058 (use `Iterator`)
class IteratorReturningSimpleGenerator2:
def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: # PYI058 (use `Iterator`)
"""Fully documented, because I'm a runtime function!"""
yield from "abcdefg"
return None
class IteratorReturningSimpleGenerator3: def scope():
def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: # PYI058 (use `Iterator`) import typing
yield "a"
yield "b"
yield "c"
return
class AsyncIteratorReturningSimpleAsyncGenerator1: class IteratorReturningSimpleGenerator2:
def __aiter__(self) -> typing.AsyncGenerator: pass # PYI058 (Use `AsyncIterator`) def __iter__(self) -> typing.Generator:
... # PYI058 (use `Iterator`)
class AsyncIteratorReturningSimpleAsyncGenerator2:
def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`)
class AsyncIteratorReturningSimpleAsyncGenerator3: def scope():
def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: pass # PYI058 (Use `AsyncIterator`) import collections.abc
class CorrectIterator: class IteratorReturningSimpleGenerator3:
def __iter__(self) -> Iterator[str]: ... # OK def __iter__(self) -> collections.abc.Generator:
... # PYI058 (use `Iterator`)
class CorrectAsyncIterator:
def __aiter__(self) -> collections.abc.AsyncIterator[int]: ... # OK
class Fine: def scope():
def __iter__(self) -> typing.Self: ... # OK import collections.abc
from typing import Any
class StrangeButWeWontComplainHere: class IteratorReturningSimpleGenerator4:
def __aiter__(self) -> list[bytes]: ... # OK def __iter__(self, /) -> collections.abc.Generator[str, Any, None]:
... # PYI058 (use `Iterator`)
def __iter__(self) -> Generator: ... # OK (not in class scope)
def __aiter__(self) -> AsyncGenerator: ... # OK (not in class scope)
class IteratorReturningComplexGenerator: def scope():
def __iter__(self) -> Generator[str, int, bytes]: ... # OK import collections.abc
import typing
class AsyncIteratorReturningComplexAsyncGenerator: class IteratorReturningSimpleGenerator5:
def __aiter__(self) -> AsyncGenerator[str, int]: ... # OK def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]:
... # PYI058 (use `Iterator`)
class ClassWithInvalidAsyncAiterMethod:
async def __aiter__(self) -> AsyncGenerator: ... # OK
class IteratorWithUnusualParameters1: def scope():
def __iter__(self, foo) -> Generator: ... # OK from collections.abc import Generator
class IteratorWithUnusualParameters2: class IteratorReturningSimpleGenerator6:
def __iter__(self, *, bar) -> Generator: ... # OK def __iter__(self, /) -> Generator[str, None, None]:
... # PYI058 (use `Iterator`)
class IteratorWithUnusualParameters3:
def __iter__(self, *args) -> Generator: ... # OK
class IteratorWithUnusualParameters4: def scope():
def __iter__(self, **kwargs) -> Generator: ... # OK import typing_extensions
class IteratorWithIterMethodThatReturnsThings: class AsyncIteratorReturningSimpleAsyncGenerator1:
def __iter__(self) -> Generator: # OK def __aiter__(
yield self,
return 42 ) -> typing_extensions.AsyncGenerator:
... # PYI058 (Use `AsyncIterator`)
class IteratorWithIterMethodThatReceivesThingsFromSend:
def __iter__(self) -> Generator: # OK
x = yield 42
class IteratorWithNonTrivialIterBody: def scope():
def __iter__(self) -> Generator: # OK import collections.abc
foo, bar, baz = (1, 2, 3)
yield foo class AsyncIteratorReturningSimpleAsyncGenerator2:
yield bar def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]:
yield baz ... # PYI058 (Use `AsyncIterator`)
def scope():
import collections.abc
class AsyncIteratorReturningSimpleAsyncGenerator3:
def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]:
... # PYI058 (Use `AsyncIterator`)
def scope():
from typing import Iterator
class CorrectIterator:
def __iter__(self) -> Iterator[str]:
... # OK
def scope():
import collections.abc
class CorrectAsyncIterator:
def __aiter__(self) -> collections.abc.AsyncIterator[int]:
... # OK
def scope():
import typing
class Fine:
def __iter__(self) -> typing.Self:
... # OK
def scope():
class StrangeButWeWontComplainHere:
def __aiter__(self) -> list[bytes]:
... # OK
def scope():
from collections.abc import Generator
def __iter__(self) -> Generator:
... # OK (not in class scope)
def scope():
from collections.abc import AsyncGenerator
def __aiter__(self) -> AsyncGenerator:
... # OK (not in class scope)
def scope():
from collections.abc import Generator
class IteratorReturningComplexGenerator:
def __iter__(self) -> Generator[str, int, bytes]:
... # OK
def scope():
from collections.abc import AsyncGenerator
class AsyncIteratorReturningComplexAsyncGenerator:
def __aiter__(self) -> AsyncGenerator[str, int]:
... # OK
def scope():
from collections.abc import AsyncGenerator
class ClassWithInvalidAsyncAiterMethod:
async def __aiter__(self) -> AsyncGenerator:
... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters1:
def __iter__(self, foo) -> Generator:
... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters2:
def __iter__(self, *, bar) -> Generator:
... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters3:
def __iter__(self, *args) -> Generator:
... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters4:
def __iter__(self, **kwargs) -> Generator:
... # OK

View file

@ -1,58 +1,128 @@
import collections.abc def scope():
import typing from collections.abc import Generator
from collections.abc import AsyncGenerator, Generator
from typing import Any
class IteratorReturningSimpleGenerator1: class IteratorReturningSimpleGenerator1:
def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`) def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`)
class IteratorReturningSimpleGenerator2: def scope():
def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`) import typing
class IteratorReturningSimpleGenerator3: class IteratorReturningSimpleGenerator2:
def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`) def __iter__(self) -> typing.Generator: ... # PYI058 (use `Iterator`)
class AsyncIteratorReturningSimpleAsyncGenerator1: def scope():
def __aiter__(self) -> typing.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`) import collections.abc
class AsyncIteratorReturningSimpleAsyncGenerator2: class IteratorReturningSimpleGenerator3:
def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) def __iter__(self) -> collections.abc.Generator: ... # PYI058 (use `Iterator`)
class AsyncIteratorReturningSimpleAsyncGenerator3: def scope():
def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`) import collections.abc
from typing import Any
class CorrectIterator: class IteratorReturningSimpleGenerator4:
def __iter__(self) -> Iterator[str]: ... # OK def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`)
class CorrectAsyncIterator: def scope():
def __aiter__(self) -> collections.abc.AsyncIterator[int]: ... # OK import collections.abc
import typing
class Fine: class IteratorReturningSimpleGenerator5:
def __iter__(self) -> typing.Self: ... # OK def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`)
class StrangeButWeWontComplainHere: def scope():
def __aiter__(self) -> list[bytes]: ... # OK from collections.abc import Generator
def __iter__(self) -> Generator: ... # OK (not in class scope) class IteratorReturningSimpleGenerator6:
def __aiter__(self) -> AsyncGenerator: ... # OK (not in class scope) def __iter__(self, /) -> Generator[str, None, None]: ... # PYI058 (use `Iterator`)
class IteratorReturningComplexGenerator: def scope():
def __iter__(self) -> Generator[str, int, bytes]: ... # OK import typing_extensions
class AsyncIteratorReturningComplexAsyncGenerator: class AsyncIteratorReturningSimpleAsyncGenerator1:
def __aiter__(self) -> AsyncGenerator[str, int]: ... # OK def __aiter__(self,) -> typing_extensions.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`)
class ClassWithInvalidAsyncAiterMethod: def scope():
async def __aiter__(self) -> AsyncGenerator: ... # OK import collections.abc
class IteratorWithUnusualParameters1: class AsyncIteratorReturningSimpleAsyncGenerator3:
def __iter__(self, foo) -> Generator: ... # OK def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]:
... # PYI058 (Use `AsyncIterator`)
class IteratorWithUnusualParameters2: def scope():
def __iter__(self, *, bar) -> Generator: ... # OK import collections.abc
class IteratorWithUnusualParameters3: class AsyncIteratorReturningSimpleAsyncGenerator3:
def __iter__(self, *args) -> Generator: ... # OK def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`)
class IteratorWithUnusualParameters4: def scope():
def __iter__(self, **kwargs) -> Generator: ... # OK from typing import Iterator
class CorrectIterator:
def __iter__(self) -> Iterator[str]: ... # OK
def scope():
import collections.abc
class CorrectAsyncIterator:
def __aiter__(self) -> collections.abc.AsyncIterator[int]: ... # OK
def scope():
import typing
class Fine:
def __iter__(self) -> typing.Self: ... # OK
def scope():
class StrangeButWeWontComplainHere:
def __aiter__(self) -> list[bytes]: ... # OK
def scope():
from collections.abc import Generator
def __iter__(self) -> Generator: ... # OK (not in class scope)
def scope():
from collections.abc import AsyncGenerator
def __aiter__(self) -> AsyncGenerator: ... # OK (not in class scope)
def scope():
from collections.abc import Generator
class IteratorReturningComplexGenerator:
def __iter__(self) -> Generator[str, int, bytes]: ... # OK
def scope():
from collections.abc import AsyncGenerator
class AsyncIteratorReturningComplexAsyncGenerator:
def __aiter__(self) -> AsyncGenerator[str, int]: ... # OK
def scope():
from collections.abc import AsyncGenerator
class ClassWithInvalidAsyncAiterMethod:
async def __aiter__(self) -> AsyncGenerator: ... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters1:
def __iter__(self, foo) -> Generator: ... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters2:
def __iter__(self, *, bar) -> Generator: ... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters3:
def __iter__(self, *args) -> Generator: ... # OK
def scope():
from collections.abc import Generator
class IteratorWithUnusualParameters4:
def __iter__(self, **kwargs) -> Generator: ... # OK

View file

@ -1,11 +1,13 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast; use ruff_python_ast as ast;
use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::identifier::Identifier; use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
/// ## What it does /// ## What it does
/// Checks for simple `__iter__` methods that return `Generator`, and for /// Checks for simple `__iter__` methods that return `Generator`, and for
@ -48,20 +50,40 @@ use crate::checkers::ast::Checker;
/// def __iter__(self) -> Iterator[str]: /// def __iter__(self) -> Iterator[str]:
/// yield from "abdefg" /// yield from "abdefg"
/// ``` /// ```
///
/// ## Fix safety
/// This rule tries hard to avoid false-positive errors, and the rule's fix
/// should always be safe for `.pyi` stub files. However, there is a slightly
/// higher chance that a false positive might be emitted by this rule when
/// applied to runtime Python (`.py` files). As such, the fix is marked as
/// unsafe for any `__iter__` or `__aiter__` method in a `.py` file that has
/// more than two statements (including docstrings) in its body.
#[violation] #[violation]
pub struct GeneratorReturnFromIterMethod { pub struct GeneratorReturnFromIterMethod {
better_return_type: String, return_type: Iterator,
method_name: String, method: Method,
} }
impl Violation for GeneratorReturnFromIterMethod { impl Violation for GeneratorReturnFromIterMethod {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let GeneratorReturnFromIterMethod { let GeneratorReturnFromIterMethod {
better_return_type, return_type,
method_name, method,
} = self; } = self;
format!("Use `{better_return_type}` as the return value for simple `{method_name}` methods") format!("Use `{return_type}` as the return value for simple `{method}` methods")
}
fn fix_title(&self) -> Option<String> {
let GeneratorReturnFromIterMethod {
return_type,
method,
} = self;
Some(format!(
"Convert the return annotation of your `{method}` method to `{return_type}`"
))
} }
} }
@ -76,12 +98,6 @@ pub(crate) fn bad_generator_return_type(
let name = function_def.name.as_str(); let name = function_def.name.as_str();
let better_return_type = match name {
"__iter__" => "Iterator",
"__aiter__" => "AsyncIterator",
_ => return,
};
let semantic = checker.semantic(); let semantic = checker.semantic();
if !semantic.current_scope().kind.is_class() { if !semantic.current_scope().kind.is_class() {
@ -106,44 +122,68 @@ pub(crate) fn bad_generator_return_type(
_ => return, _ => return,
}; };
if !semantic // Determine the module from which the existing annotation is imported (e.g., `typing` or
.resolve_call_path(map_subscript(returns)) // `collections.abc`)
.is_some_and(|call_path| { let (method, module, member) = {
matches!( let Some(call_path) = semantic.resolve_call_path(map_subscript(returns)) else {
(name, call_path.as_slice()), return;
( };
"__iter__", match (name, call_path.as_slice()) {
["typing" | "typing_extensions", "Generator"] ("__iter__", ["typing", "Generator"]) => {
| ["collections", "abc", "Generator"] (Method::Iter, Module::Typing, Generator::Generator)
) | ( }
"__aiter__", ("__aiter__", ["typing", "AsyncGenerator"]) => {
["typing" | "typing_extensions", "AsyncGenerator"] (Method::AIter, Module::Typing, Generator::AsyncGenerator)
| ["collections", "abc", "AsyncGenerator"] }
) ("__iter__", ["typing_extensions", "Generator"]) => {
) (Method::Iter, Module::TypingExtensions, Generator::Generator)
}) }
{ ("__aiter__", ["typing_extensions", "AsyncGenerator"]) => (
return; Method::AIter,
Module::TypingExtensions,
Generator::AsyncGenerator,
),
("__iter__", ["collections", "abc", "Generator"]) => {
(Method::Iter, Module::CollectionsAbc, Generator::Generator)
}
("__aiter__", ["collections", "abc", "AsyncGenerator"]) => (
Method::AIter,
Module::CollectionsAbc,
Generator::AsyncGenerator,
),
_ => return,
}
}; };
// `Generator` allows three type parameters; `AsyncGenerator` allows two. // `Generator` allows three type parameters; `AsyncGenerator` allows two.
// If type parameters are present, // If type parameters are present,
// Check that all parameters except the first one are either `typing.Any` or `None`; // check that all parameters except the first one are either `typing.Any` or `None`:
// if not, don't emit the diagnostic // - if so, collect information on the first parameter for use in the rule's autofix;
if let ast::Expr::Subscript(ast::ExprSubscript { slice, .. }) = returns { // - if not, don't emit the diagnostic
let ast::Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() else { let yield_type_info = match returns {
return; ast::Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
}; ast::Expr::Tuple(slice_tuple @ ast::ExprTuple { .. }) => {
if matches!( if !slice_tuple
(name, &elts[..]), .elts
("__iter__", [_, _, _]) | ("__aiter__", [_, _]) .iter()
) { .skip(1)
if !&elts.iter().skip(1).all(|elt| is_any_or_none(elt, semantic)) { .all(|elt| is_any_or_none(elt, semantic))
return; {
return;
}
let yield_type = match (name, slice_tuple.elts.as_slice()) {
("__iter__", [yield_type, _, _]) => yield_type,
("__aiter__", [yield_type, _]) => yield_type,
_ => return,
};
Some(YieldTypeInfo {
expr: yield_type,
range: slice_tuple.range,
})
} }
} else { _ => return,
return; },
} _ => None,
}; };
// For .py files (runtime Python!), // For .py files (runtime Python!),
@ -157,9 +197,7 @@ pub(crate) fn bad_generator_return_type(
ast::Stmt::Pass(_) => continue, ast::Stmt::Pass(_) => continue,
ast::Stmt::Return(ast::StmtReturn { value, .. }) => { ast::Stmt::Return(ast::StmtReturn { value, .. }) => {
if let Some(ret_val) = value { if let Some(ret_val) = value {
if yield_encountered if yield_encountered && !ret_val.is_none_literal_expr() {
&& !matches!(ret_val.as_ref(), ast::Expr::NoneLiteral(_))
{
return; return;
} }
} }
@ -176,16 +214,151 @@ pub(crate) fn bad_generator_return_type(
} }
} }
}; };
let mut diagnostic = Diagnostic::new(
checker.diagnostics.push(Diagnostic::new(
GeneratorReturnFromIterMethod { GeneratorReturnFromIterMethod {
better_return_type: better_return_type.to_string(), return_type: member.to_iter(),
method_name: name.to_string(), method,
}, },
function_def.identifier(), function_def.identifier(),
)); );
if let Some(fix) = generate_fix(
function_def,
returns,
yield_type_info,
module,
member,
checker,
) {
diagnostic.set_fix(fix);
};
checker.diagnostics.push(diagnostic);
} }
/// Returns `true` if the [`ast::Expr`] is a `None` literal or a `typing.Any` expression.
fn is_any_or_none(expr: &ast::Expr, semantic: &SemanticModel) -> bool { fn is_any_or_none(expr: &ast::Expr, semantic: &SemanticModel) -> bool {
semantic.match_typing_expr(expr, "Any") || matches!(expr, ast::Expr::NoneLiteral(_)) expr.is_none_literal_expr() || semantic.match_typing_expr(expr, "Any")
}
/// Generate a [`Fix`] to convert the return type annotation to `Iterator` or `AsyncIterator`.
fn generate_fix(
function_def: &ast::StmtFunctionDef,
returns: &ast::Expr,
yield_type_info: Option<YieldTypeInfo>,
module: Module,
member: Generator,
checker: &Checker,
) -> Option<Fix> {
let expr = map_subscript(returns);
let (import_edit, binding) = checker
.importer()
.get_or_import_symbol(
&ImportRequest::import_from(&module.to_string(), &member.to_iter().to_string()),
expr.start(),
checker.semantic(),
)
.ok()?;
let binding_edit = Edit::range_replacement(binding, expr.range());
let yield_edit = yield_type_info.map(|yield_type_info| {
Edit::range_replacement(
checker.generator().expr(yield_type_info.expr),
yield_type_info.range(),
)
});
// Mark as unsafe if it's a runtime Python file and the body has more than one statement in it.
let applicability = if checker.source_type.is_stub() || function_def.body.len() == 1 {
Applicability::Safe
} else {
Applicability::Unsafe
};
Some(Fix::applicable_edits(
import_edit,
std::iter::once(binding_edit).chain(yield_edit),
applicability,
))
}
#[derive(Debug)]
struct YieldTypeInfo<'a> {
expr: &'a ast::Expr,
range: TextRange,
}
impl Ranged for YieldTypeInfo<'_> {
fn range(&self) -> TextRange {
self.range
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Module {
Typing,
TypingExtensions,
CollectionsAbc,
}
impl std::fmt::Display for Module {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Module::Typing => write!(f, "typing"),
Module::TypingExtensions => write!(f, "typing_extensions"),
Module::CollectionsAbc => write!(f, "collections.abc"),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Method {
Iter,
AIter,
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Method::Iter => write!(f, "__iter__"),
Method::AIter => write!(f, "__aiter__"),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Generator {
Generator,
AsyncGenerator,
}
impl std::fmt::Display for Generator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Generator::Generator => write!(f, "Generator"),
Generator::AsyncGenerator => write!(f, "AsyncGenerator"),
}
}
}
impl Generator {
fn to_iter(self) -> Iterator {
match self {
Generator::Generator => Iterator::Iterator,
Generator::AsyncGenerator => Iterator::AsyncIterator,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Iterator {
Iterator,
AsyncIterator,
}
impl std::fmt::Display for Iterator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Iterator::Iterator => write!(f, "Iterator"),
Iterator::AsyncIterator => write!(f, "AsyncIterator"),
}
}
} }

View file

@ -1,57 +1,184 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
--- ---
PYI058.py:7:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods PYI058.py:5:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
6 | class IteratorReturningSimpleGenerator1: 4 | class IteratorReturningSimpleGenerator1:
7 | def __iter__(self) -> Generator: # PYI058 (use `Iterator`) 5 | def __iter__(self) -> Generator:
| ^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
8 | return (x for x in range(42)) 6 | ... # PYI058 (use `Iterator`)
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
PYI058.py:11:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods Safe fix
| 1 |+from collections.abc import Iterator
10 | class IteratorReturningSimpleGenerator2: 1 2 | def scope():
11 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: # PYI058 (use `Iterator`) 2 3 | from collections.abc import Generator
| ^^^^^^^^ PYI058 3 4 |
12 | """Fully documented, because I'm a runtime function!""" 4 5 | class IteratorReturningSimpleGenerator1:
13 | yield from "abcdefg" 5 |- def __iter__(self) -> Generator:
| 6 |+ def __iter__(self) -> Iterator:
6 7 | ... # PYI058 (use `Iterator`)
7 8 |
8 9 |
PYI058.py:17:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods PYI058.py:13:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
16 | class IteratorReturningSimpleGenerator3: 12 | class IteratorReturningSimpleGenerator2:
17 | def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: # PYI058 (use `Iterator`) 13 | def __iter__(self) -> typing.Generator:
| ^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
18 | yield "a" 14 | ... # PYI058 (use `Iterator`)
19 | yield "b"
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
PYI058.py:24:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods Safe fix
| 10 10 | import typing
23 | class AsyncIteratorReturningSimpleAsyncGenerator1: 11 11 |
24 | def __aiter__(self) -> typing.AsyncGenerator: pass # PYI058 (Use `AsyncIterator`) 12 12 | class IteratorReturningSimpleGenerator2:
| ^^^^^^^^^ PYI058 13 |- def __iter__(self) -> typing.Generator:
25 | 13 |+ def __iter__(self) -> typing.Iterator:
26 | class AsyncIteratorReturningSimpleAsyncGenerator2: 14 14 | ... # PYI058 (use `Iterator`)
| 15 15 |
16 16 |
PYI058.py:27:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods PYI058.py:21:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
26 | class AsyncIteratorReturningSimpleAsyncGenerator2: 20 | class IteratorReturningSimpleGenerator3:
27 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) 21 | def __iter__(self) -> collections.abc.Generator:
| ^^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
28 | 22 | ... # PYI058 (use `Iterator`)
29 | class AsyncIteratorReturningSimpleAsyncGenerator3:
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
PYI058.py:30:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
18 19 | import collections.abc
19 20 |
20 21 | class IteratorReturningSimpleGenerator3:
21 |- def __iter__(self) -> collections.abc.Generator:
22 |+ def __iter__(self) -> Iterator:
22 23 | ... # PYI058 (use `Iterator`)
23 24 |
24 25 |
PYI058.py:30:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
29 | class AsyncIteratorReturningSimpleAsyncGenerator3: 29 | class IteratorReturningSimpleGenerator4:
30 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: pass # PYI058 (Use `AsyncIterator`) 30 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]:
| ^^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
31 | 31 | ... # PYI058 (use `Iterator`)
32 | class CorrectIterator:
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
27 28 | from typing import Any
28 29 |
29 30 | class IteratorReturningSimpleGenerator4:
30 |- def __iter__(self, /) -> collections.abc.Generator[str, Any, None]:
31 |+ def __iter__(self, /) -> Iterator[str]:
31 32 | ... # PYI058 (use `Iterator`)
32 33 |
33 34 |
PYI058.py:39:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
|
38 | class IteratorReturningSimpleGenerator5:
39 | def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]:
| ^^^^^^^^ PYI058
40 | ... # PYI058 (use `Iterator`)
|
= help: Convert the return annotation of your `__iter__` method to `Iterator`
Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
36 37 | import typing
37 38 |
38 39 | class IteratorReturningSimpleGenerator5:
39 |- def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]:
40 |+ def __iter__(self, /) -> Iterator[str]:
40 41 | ... # PYI058 (use `Iterator`)
41 42 |
42 43 |
PYI058.py:47:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
|
46 | class IteratorReturningSimpleGenerator6:
47 | def __iter__(self, /) -> Generator[str, None, None]:
| ^^^^^^^^ PYI058
48 | ... # PYI058 (use `Iterator`)
|
= help: Convert the return annotation of your `__iter__` method to `Iterator`
Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
44 45 | from collections.abc import Generator
45 46 |
46 47 | class IteratorReturningSimpleGenerator6:
47 |- def __iter__(self, /) -> Generator[str, None, None]:
48 |+ def __iter__(self, /) -> Iterator[str]:
48 49 | ... # PYI058 (use `Iterator`)
49 50 |
50 51 |
PYI058.py:55:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods
|
54 | class AsyncIteratorReturningSimpleAsyncGenerator1:
55 | def __aiter__(
| ^^^^^^^^^ PYI058
56 | self,
57 | ) -> typing_extensions.AsyncGenerator:
|
= help: Convert the return annotation of your `__aiter__` method to `AsyncIterator`
Safe fix
54 54 | class AsyncIteratorReturningSimpleAsyncGenerator1:
55 55 | def __aiter__(
56 56 | self,
57 |- ) -> typing_extensions.AsyncGenerator:
57 |+ ) -> typing_extensions.AsyncIterator:
58 58 | ... # PYI058 (Use `AsyncIterator`)
59 59 |
60 60 |
PYI058.py:73:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods
|
72 | class AsyncIteratorReturningSimpleAsyncGenerator3:
73 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]:
| ^^^^^^^^^ PYI058
74 | ... # PYI058 (Use `AsyncIterator`)
|
= help: Convert the return annotation of your `__aiter__` method to `AsyncIterator`
Safe fix
1 |+from collections.abc import AsyncIterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
70 71 | import collections.abc
71 72 |
72 73 | class AsyncIteratorReturningSimpleAsyncGenerator3:
73 |- def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]:
74 |+ def __aiter__(self, /) -> AsyncIterator[str]:
74 75 | ... # PYI058 (Use `AsyncIterator`)
75 76 |
76 77 |

View file

@ -1,58 +1,215 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
--- ---
PYI058.pyi:7:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods PYI058.pyi:5:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
6 | class IteratorReturningSimpleGenerator1: 4 | class IteratorReturningSimpleGenerator1:
7 | def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`) 5 | def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`)
| ^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
8 | 6 |
9 | class IteratorReturningSimpleGenerator2: 7 | def scope():
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
PYI058.pyi:10:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods Safe fix
| 1 |+from collections.abc import Iterator
9 | class IteratorReturningSimpleGenerator2: 1 2 | def scope():
10 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`) 2 3 | from collections.abc import Generator
| ^^^^^^^^ PYI058 3 4 |
11 | 4 5 | class IteratorReturningSimpleGenerator1:
12 | class IteratorReturningSimpleGenerator3: 5 |- def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`)
| 6 |+ def __iter__(self) -> Iterator: ... # PYI058 (use `Iterator`)
6 7 |
7 8 | def scope():
8 9 | import typing
PYI058.pyi:13:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods PYI058.pyi:11:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
12 | class IteratorReturningSimpleGenerator3: 10 | class IteratorReturningSimpleGenerator2:
13 | def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`) 11 | def __iter__(self) -> typing.Generator: ... # PYI058 (use `Iterator`)
| ^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
14 | 12 |
15 | class AsyncIteratorReturningSimpleAsyncGenerator1: 13 | def scope():
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
PYI058.pyi:16:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods Safe fix
| 8 8 | import typing
15 | class AsyncIteratorReturningSimpleAsyncGenerator1: 9 9 |
16 | def __aiter__(self) -> typing.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`) 10 10 | class IteratorReturningSimpleGenerator2:
| ^^^^^^^^^ PYI058 11 |- def __iter__(self) -> typing.Generator: ... # PYI058 (use `Iterator`)
17 | 11 |+ def __iter__(self) -> typing.Iterator: ... # PYI058 (use `Iterator`)
18 | class AsyncIteratorReturningSimpleAsyncGenerator2: 12 12 |
| 13 13 | def scope():
14 14 | import collections.abc
PYI058.pyi:19:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods PYI058.pyi:17:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
18 | class AsyncIteratorReturningSimpleAsyncGenerator2: 16 | class IteratorReturningSimpleGenerator3:
19 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) 17 | def __iter__(self) -> collections.abc.Generator: ... # PYI058 (use `Iterator`)
| ^^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
20 | 18 |
21 | class AsyncIteratorReturningSimpleAsyncGenerator3: 19 | def scope():
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
PYI058.pyi:22:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
14 15 | import collections.abc
15 16 |
16 17 | class IteratorReturningSimpleGenerator3:
17 |- def __iter__(self) -> collections.abc.Generator: ... # PYI058 (use `Iterator`)
18 |+ def __iter__(self) -> Iterator: ... # PYI058 (use `Iterator`)
18 19 |
19 20 | def scope():
20 21 | import collections.abc
PYI058.pyi:24:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
| |
21 | class AsyncIteratorReturningSimpleAsyncGenerator3: 23 | class IteratorReturningSimpleGenerator4:
22 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`) 24 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`)
| ^^^^^^^^^ PYI058 | ^^^^^^^^ PYI058
23 | 25 |
24 | class CorrectIterator: 26 | def scope():
| |
= help: Convert the return annotation of your `__iter__` method to `Iterator`
Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
21 22 | from typing import Any
22 23 |
23 24 | class IteratorReturningSimpleGenerator4:
24 |- def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`)
25 |+ def __iter__(self, /) -> Iterator[str]: ... # PYI058 (use `Iterator`)
25 26 |
26 27 | def scope():
27 28 | import collections.abc
PYI058.pyi:31:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
|
30 | class IteratorReturningSimpleGenerator5:
31 | def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`)
| ^^^^^^^^ PYI058
32 |
33 | def scope():
|
= help: Convert the return annotation of your `__iter__` method to `Iterator`
Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
28 29 | import typing
29 30 |
30 31 | class IteratorReturningSimpleGenerator5:
31 |- def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`)
32 |+ def __iter__(self, /) -> Iterator[str]: ... # PYI058 (use `Iterator`)
32 33 |
33 34 | def scope():
34 35 | from collections.abc import Generator
PYI058.pyi:37:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods
|
36 | class IteratorReturningSimpleGenerator6:
37 | def __iter__(self, /) -> Generator[str, None, None]: ... # PYI058 (use `Iterator`)
| ^^^^^^^^ PYI058
38 |
39 | def scope():
|
= help: Convert the return annotation of your `__iter__` method to `Iterator`
Safe fix
1 |+from collections.abc import Iterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
34 35 | from collections.abc import Generator
35 36 |
36 37 | class IteratorReturningSimpleGenerator6:
37 |- def __iter__(self, /) -> Generator[str, None, None]: ... # PYI058 (use `Iterator`)
38 |+ def __iter__(self, /) -> Iterator[str]: ... # PYI058 (use `Iterator`)
38 39 |
39 40 | def scope():
40 41 | import typing_extensions
PYI058.pyi:43:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods
|
42 | class AsyncIteratorReturningSimpleAsyncGenerator1:
43 | def __aiter__(self,) -> typing_extensions.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`)
| ^^^^^^^^^ PYI058
44 |
45 | def scope():
|
= help: Convert the return annotation of your `__aiter__` method to `AsyncIterator`
Safe fix
40 40 | import typing_extensions
41 41 |
42 42 | class AsyncIteratorReturningSimpleAsyncGenerator1:
43 |- def __aiter__(self,) -> typing_extensions.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`)
43 |+ def __aiter__(self,) -> typing_extensions.AsyncIterator: ... # PYI058 (Use `AsyncIterator`)
44 44 |
45 45 | def scope():
46 46 | import collections.abc
PYI058.pyi:49:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods
|
48 | class AsyncIteratorReturningSimpleAsyncGenerator3:
49 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]:
| ^^^^^^^^^ PYI058
50 | ... # PYI058 (Use `AsyncIterator`)
|
= help: Convert the return annotation of your `__aiter__` method to `AsyncIterator`
Safe fix
1 |+from collections.abc import AsyncIterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
46 47 | import collections.abc
47 48 |
48 49 | class AsyncIteratorReturningSimpleAsyncGenerator3:
49 |- def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]:
50 |+ def __aiter__(self, /) -> AsyncIterator[str]:
50 51 | ... # PYI058 (Use `AsyncIterator`)
51 52 |
52 53 | def scope():
PYI058.pyi:56:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods
|
55 | class AsyncIteratorReturningSimpleAsyncGenerator3:
56 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`)
| ^^^^^^^^^ PYI058
57 |
58 | def scope():
|
= help: Convert the return annotation of your `__aiter__` method to `AsyncIterator`
Safe fix
1 |+from collections.abc import AsyncIterator
1 2 | def scope():
2 3 | from collections.abc import Generator
3 4 |
--------------------------------------------------------------------------------
53 54 | import collections.abc
54 55 |
55 56 | class AsyncIteratorReturningSimpleAsyncGenerator3:
56 |- def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`)
57 |+ def __aiter__(self, /) -> AsyncIterator[str]: ... # PYI058 (Use `AsyncIterator`)
57 58 |
58 59 | def scope():
59 60 | from typing import Iterator