diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.py index 810a084c62d00..2add612f4b857 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.py @@ -1,82 +1,174 @@ -import collections.abc -import typing -from collections.abc import AsyncGenerator, Generator -from typing import Any +def scope(): + from collections.abc import Generator -class IteratorReturningSimpleGenerator1: - def __iter__(self) -> Generator: # PYI058 (use `Iterator`) - return (x for x in range(42)) + class IteratorReturningSimpleGenerator1: + def __iter__(self) -> Generator: + ... # 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 __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: # PYI058 (use `Iterator`) - yield "a" - yield "b" - yield "c" - return +def scope(): + import typing -class AsyncIteratorReturningSimpleAsyncGenerator1: - def __aiter__(self) -> typing.AsyncGenerator: pass # PYI058 (Use `AsyncIterator`) + class IteratorReturningSimpleGenerator2: + def __iter__(self) -> typing.Generator: + ... # PYI058 (use `Iterator`) -class AsyncIteratorReturningSimpleAsyncGenerator2: - def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) -class AsyncIteratorReturningSimpleAsyncGenerator3: - def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: pass # PYI058 (Use `AsyncIterator`) +def scope(): + import collections.abc -class CorrectIterator: - def __iter__(self) -> Iterator[str]: ... # OK + class IteratorReturningSimpleGenerator3: + def __iter__(self) -> collections.abc.Generator: + ... # PYI058 (use `Iterator`) -class CorrectAsyncIterator: - def __aiter__(self) -> collections.abc.AsyncIterator[int]: ... # OK -class Fine: - def __iter__(self) -> typing.Self: ... # OK +def scope(): + import collections.abc + from typing import Any -class StrangeButWeWontComplainHere: - def __aiter__(self) -> list[bytes]: ... # OK + class IteratorReturningSimpleGenerator4: + 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 __iter__(self) -> Generator[str, int, bytes]: ... # OK +def scope(): + import collections.abc + import typing -class AsyncIteratorReturningComplexAsyncGenerator: - def __aiter__(self) -> AsyncGenerator[str, int]: ... # OK + class IteratorReturningSimpleGenerator5: + def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: + ... # PYI058 (use `Iterator`) -class ClassWithInvalidAsyncAiterMethod: - async def __aiter__(self) -> AsyncGenerator: ... # OK -class IteratorWithUnusualParameters1: - def __iter__(self, foo) -> Generator: ... # OK +def scope(): + from collections.abc import Generator -class IteratorWithUnusualParameters2: - def __iter__(self, *, bar) -> Generator: ... # OK + class IteratorReturningSimpleGenerator6: + def __iter__(self, /) -> Generator[str, None, None]: + ... # PYI058 (use `Iterator`) -class IteratorWithUnusualParameters3: - def __iter__(self, *args) -> Generator: ... # OK -class IteratorWithUnusualParameters4: - def __iter__(self, **kwargs) -> Generator: ... # OK +def scope(): + import typing_extensions -class IteratorWithIterMethodThatReturnsThings: - def __iter__(self) -> Generator: # OK - yield - return 42 + class AsyncIteratorReturningSimpleAsyncGenerator1: + def __aiter__( + self, + ) -> typing_extensions.AsyncGenerator: + ... # PYI058 (Use `AsyncIterator`) -class IteratorWithIterMethodThatReceivesThingsFromSend: - def __iter__(self) -> Generator: # OK - x = yield 42 -class IteratorWithNonTrivialIterBody: - def __iter__(self) -> Generator: # OK - foo, bar, baz = (1, 2, 3) - yield foo - yield bar - yield baz +def scope(): + import collections.abc + + class AsyncIteratorReturningSimpleAsyncGenerator2: + def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: + ... # 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 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.pyi index ce6e78a68b311..14a904a4e9839 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI058.pyi @@ -1,58 +1,128 @@ -import collections.abc -import typing -from collections.abc import AsyncGenerator, Generator -from typing import Any +def scope(): + from collections.abc import Generator -class IteratorReturningSimpleGenerator1: - def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`) + class IteratorReturningSimpleGenerator1: + def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`) -class IteratorReturningSimpleGenerator2: - def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`) +def scope(): + import typing -class IteratorReturningSimpleGenerator3: - def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`) + class IteratorReturningSimpleGenerator2: + def __iter__(self) -> typing.Generator: ... # PYI058 (use `Iterator`) -class AsyncIteratorReturningSimpleAsyncGenerator1: - def __aiter__(self) -> typing.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`) +def scope(): + import collections.abc -class AsyncIteratorReturningSimpleAsyncGenerator2: - def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) + class IteratorReturningSimpleGenerator3: + def __iter__(self) -> collections.abc.Generator: ... # PYI058 (use `Iterator`) -class AsyncIteratorReturningSimpleAsyncGenerator3: - def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`) +def scope(): + import collections.abc + from typing import Any -class CorrectIterator: - def __iter__(self) -> Iterator[str]: ... # OK + class IteratorReturningSimpleGenerator4: + def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`) -class CorrectAsyncIterator: - def __aiter__(self) -> collections.abc.AsyncIterator[int]: ... # OK +def scope(): + import collections.abc + import typing -class Fine: - def __iter__(self) -> typing.Self: ... # OK + class IteratorReturningSimpleGenerator5: + def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`) -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 __aiter__(self) -> AsyncGenerator: ... # OK (not in class scope) + class IteratorReturningSimpleGenerator6: + def __iter__(self, /) -> Generator[str, None, None]: ... # PYI058 (use `Iterator`) -class IteratorReturningComplexGenerator: - def __iter__(self) -> Generator[str, int, bytes]: ... # OK +def scope(): + import typing_extensions -class AsyncIteratorReturningComplexAsyncGenerator: - def __aiter__(self) -> AsyncGenerator[str, int]: ... # OK + class AsyncIteratorReturningSimpleAsyncGenerator1: + def __aiter__(self,) -> typing_extensions.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`) -class ClassWithInvalidAsyncAiterMethod: - async def __aiter__(self) -> AsyncGenerator: ... # OK +def scope(): + import collections.abc -class IteratorWithUnusualParameters1: - def __iter__(self, foo) -> Generator: ... # OK + class AsyncIteratorReturningSimpleAsyncGenerator3: + def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: + ... # PYI058 (Use `AsyncIterator`) -class IteratorWithUnusualParameters2: - def __iter__(self, *, bar) -> Generator: ... # OK +def scope(): + import collections.abc -class IteratorWithUnusualParameters3: - def __iter__(self, *args) -> Generator: ... # OK + class AsyncIteratorReturningSimpleAsyncGenerator3: + def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`) -class IteratorWithUnusualParameters4: - def __iter__(self, **kwargs) -> Generator: ... # OK +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 diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs index 5dc0de179227f..29a064473fe05 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs @@ -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_python_ast as ast; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::SemanticModel; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; /// ## What it does /// Checks for simple `__iter__` methods that return `Generator`, and for @@ -48,20 +50,40 @@ use crate::checkers::ast::Checker; /// def __iter__(self) -> Iterator[str]: /// 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] pub struct GeneratorReturnFromIterMethod { - better_return_type: String, - method_name: String, + return_type: Iterator, + method: Method, } impl Violation for GeneratorReturnFromIterMethod { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { let GeneratorReturnFromIterMethod { - better_return_type, - method_name, + return_type, + method, } = 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 { + 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 better_return_type = match name { - "__iter__" => "Iterator", - "__aiter__" => "AsyncIterator", - _ => return, - }; - let semantic = checker.semantic(); if !semantic.current_scope().kind.is_class() { @@ -106,44 +122,68 @@ pub(crate) fn bad_generator_return_type( _ => return, }; - if !semantic - .resolve_call_path(map_subscript(returns)) - .is_some_and(|call_path| { - matches!( - (name, call_path.as_slice()), - ( - "__iter__", - ["typing" | "typing_extensions", "Generator"] - | ["collections", "abc", "Generator"] - ) | ( - "__aiter__", - ["typing" | "typing_extensions", "AsyncGenerator"] - | ["collections", "abc", "AsyncGenerator"] - ) - ) - }) - { - return; + // Determine the module from which the existing annotation is imported (e.g., `typing` or + // `collections.abc`) + let (method, module, member) = { + let Some(call_path) = semantic.resolve_call_path(map_subscript(returns)) else { + return; + }; + match (name, call_path.as_slice()) { + ("__iter__", ["typing", "Generator"]) => { + (Method::Iter, Module::Typing, Generator::Generator) + } + ("__aiter__", ["typing", "AsyncGenerator"]) => { + (Method::AIter, Module::Typing, Generator::AsyncGenerator) + } + ("__iter__", ["typing_extensions", "Generator"]) => { + (Method::Iter, Module::TypingExtensions, Generator::Generator) + } + ("__aiter__", ["typing_extensions", "AsyncGenerator"]) => ( + 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. // If type parameters are present, - // Check that all parameters except the first one are either `typing.Any` or `None`; - // if not, don't emit the diagnostic - if let ast::Expr::Subscript(ast::ExprSubscript { slice, .. }) = returns { - let ast::Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() else { - return; - }; - if matches!( - (name, &elts[..]), - ("__iter__", [_, _, _]) | ("__aiter__", [_, _]) - ) { - if !&elts.iter().skip(1).all(|elt| is_any_or_none(elt, semantic)) { - return; + // check that all parameters except the first one are either `typing.Any` or `None`: + // - if so, collect information on the first parameter for use in the rule's autofix; + // - if not, don't emit the diagnostic + let yield_type_info = match returns { + ast::Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() { + ast::Expr::Tuple(slice_tuple @ ast::ExprTuple { .. }) => { + if !slice_tuple + .elts + .iter() + .skip(1) + .all(|elt| is_any_or_none(elt, semantic)) + { + 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!), @@ -157,9 +197,7 @@ pub(crate) fn bad_generator_return_type( ast::Stmt::Pass(_) => continue, ast::Stmt::Return(ast::StmtReturn { value, .. }) => { if let Some(ret_val) = value { - if yield_encountered - && !matches!(ret_val.as_ref(), ast::Expr::NoneLiteral(_)) - { + if yield_encountered && !ret_val.is_none_literal_expr() { return; } } @@ -176,16 +214,151 @@ pub(crate) fn bad_generator_return_type( } } }; - - checker.diagnostics.push(Diagnostic::new( + let mut diagnostic = Diagnostic::new( GeneratorReturnFromIterMethod { - better_return_type: better_return_type.to_string(), - method_name: name.to_string(), + return_type: member.to_iter(), + method, }, 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 { - 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, + module: Module, + member: Generator, + checker: &Checker, +) -> Option { + 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"), + } + } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.py.snap index 41b5c92fca6da..e5e20116dcad9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.py.snap @@ -1,57 +1,184 @@ --- 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: -7 | def __iter__(self) -> Generator: # PYI058 (use `Iterator`) - | ^^^^^^^^ PYI058 -8 | return (x for x in range(42)) +4 | class IteratorReturningSimpleGenerator1: +5 | def __iter__(self) -> Generator: + | ^^^^^^^^ PYI058 +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 +1 2 | def scope(): +2 3 | from collections.abc import Generator +3 4 | +4 5 | class IteratorReturningSimpleGenerator1: +5 |- def __iter__(self) -> Generator: + 6 |+ def __iter__(self) -> Iterator: +6 7 | ... # PYI058 (use `Iterator`) +7 8 | +8 9 | + +PYI058.py:13:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods + | +12 | class IteratorReturningSimpleGenerator2: +13 | def __iter__(self) -> typing.Generator: + | ^^^^^^^^ PYI058 +14 | ... # PYI058 (use `Iterator`) + | + = help: Convert the return annotation of your `__iter__` method to `Iterator` + +ℹ Safe fix +10 10 | import typing +11 11 | +12 12 | class IteratorReturningSimpleGenerator2: +13 |- def __iter__(self) -> typing.Generator: + 13 |+ def __iter__(self) -> typing.Iterator: +14 14 | ... # PYI058 (use `Iterator`) +15 15 | +16 16 | + +PYI058.py:21:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods + | +20 | class IteratorReturningSimpleGenerator3: +21 | def __iter__(self) -> collections.abc.Generator: + | ^^^^^^^^ PYI058 +22 | ... # 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 | +-------------------------------------------------------------------------------- +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 | -10 | class IteratorReturningSimpleGenerator2: -11 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: # PYI058 (use `Iterator`) - | ^^^^^^^^ PYI058 -12 | """Fully documented, because I'm a runtime function!""" -13 | yield from "abcdefg" +29 | class IteratorReturningSimpleGenerator4: +30 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: + | ^^^^^^^^ PYI058 +31 | ... # PYI058 (use `Iterator`) | + = help: Convert the return annotation of your `__iter__` method to `Iterator` -PYI058.py:17:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods +ℹ 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 | -16 | class IteratorReturningSimpleGenerator3: -17 | def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: # PYI058 (use `Iterator`) - | ^^^^^^^^ PYI058 -18 | yield "a" -19 | yield "b" +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:24:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods +PYI058.py:47:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods | -23 | class AsyncIteratorReturningSimpleAsyncGenerator1: -24 | def __aiter__(self) -> typing.AsyncGenerator: pass # PYI058 (Use `AsyncIterator`) - | ^^^^^^^^^ PYI058 -25 | -26 | class AsyncIteratorReturningSimpleAsyncGenerator2: +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` -PYI058.py:27: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 | +-------------------------------------------------------------------------------- +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 | -26 | class AsyncIteratorReturningSimpleAsyncGenerator2: -27 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) - | ^^^^^^^^^ PYI058 -28 | -29 | class AsyncIteratorReturningSimpleAsyncGenerator3: +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:30:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods +PYI058.py:73:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods | -29 | class AsyncIteratorReturningSimpleAsyncGenerator3: -30 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: pass # PYI058 (Use `AsyncIterator`) - | ^^^^^^^^^ PYI058 -31 | -32 | class CorrectIterator: +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 | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.pyi.snap index 444916e1af727..88ed75b49511d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI058_PYI058.pyi.snap @@ -1,58 +1,215 @@ --- 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: -7 | def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`) - | ^^^^^^^^ PYI058 -8 | -9 | class IteratorReturningSimpleGenerator2: +4 | class IteratorReturningSimpleGenerator1: +5 | def __iter__(self) -> Generator: ... # PYI058 (use `Iterator`) + | ^^^^^^^^ PYI058 +6 | +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 +1 2 | def scope(): +2 3 | from collections.abc import Generator +3 4 | +4 5 | class IteratorReturningSimpleGenerator1: +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:11:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods + | +10 | class IteratorReturningSimpleGenerator2: +11 | def __iter__(self) -> typing.Generator: ... # PYI058 (use `Iterator`) + | ^^^^^^^^ PYI058 +12 | +13 | def scope(): + | + = help: Convert the return annotation of your `__iter__` method to `Iterator` + +ℹ Safe fix +8 8 | import typing +9 9 | +10 10 | class IteratorReturningSimpleGenerator2: +11 |- def __iter__(self) -> typing.Generator: ... # PYI058 (use `Iterator`) + 11 |+ def __iter__(self) -> typing.Iterator: ... # PYI058 (use `Iterator`) +12 12 | +13 13 | def scope(): +14 14 | import collections.abc + +PYI058.pyi:17:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods + | +16 | class IteratorReturningSimpleGenerator3: +17 | def __iter__(self) -> collections.abc.Generator: ... # PYI058 (use `Iterator`) + | ^^^^^^^^ PYI058 +18 | +19 | 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 | +-------------------------------------------------------------------------------- +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 | - 9 | class IteratorReturningSimpleGenerator2: -10 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`) - | ^^^^^^^^ PYI058 -11 | -12 | class IteratorReturningSimpleGenerator3: +23 | class IteratorReturningSimpleGenerator4: +24 | def __iter__(self, /) -> collections.abc.Generator[str, Any, None]: ... # PYI058 (use `Iterator`) + | ^^^^^^^^ PYI058 +25 | +26 | def scope(): | + = help: Convert the return annotation of your `__iter__` method to `Iterator` -PYI058.pyi:13:9: PYI058 Use `Iterator` as the return value for simple `__iter__` methods +ℹ 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 | -12 | class IteratorReturningSimpleGenerator3: -13 | def __iter__(self, /) -> collections.abc.Generator[str, None, typing.Any]: ... # PYI058 (use `Iterator`) - | ^^^^^^^^ PYI058 -14 | -15 | class AsyncIteratorReturningSimpleAsyncGenerator1: +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:16:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods +PYI058.pyi:37:13: PYI058 [*] Use `Iterator` as the return value for simple `__iter__` methods | -15 | class AsyncIteratorReturningSimpleAsyncGenerator1: -16 | def __aiter__(self) -> typing.AsyncGenerator: ... # PYI058 (Use `AsyncIterator`) - | ^^^^^^^^^ PYI058 -17 | -18 | class AsyncIteratorReturningSimpleAsyncGenerator2: +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:19:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods +PYI058.pyi:43:13: PYI058 [*] Use `AsyncIterator` as the return value for simple `__aiter__` methods | -18 | class AsyncIteratorReturningSimpleAsyncGenerator2: -19 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, Any]: ... # PYI058 (Use `AsyncIterator`) - | ^^^^^^^^^ PYI058 -20 | -21 | class AsyncIteratorReturningSimpleAsyncGenerator3: +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` -PYI058.pyi:22:9: PYI058 Use `AsyncIterator` as the return value for simple `__aiter__` methods +ℹ 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 | -21 | class AsyncIteratorReturningSimpleAsyncGenerator3: -22 | def __aiter__(self, /) -> collections.abc.AsyncGenerator[str, None]: ... # PYI058 (Use `AsyncIterator`) - | ^^^^^^^^^ PYI058 -23 | -24 | class CorrectIterator: +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