From aa6b812a736ad585c9c2dc54033e6a3e9e33d168 Mon Sep 17 00:00:00 2001 From: InSync Date: Mon, 9 Dec 2024 21:59:12 +0700 Subject: [PATCH] [`flake8-pyi`] Also remove `self` and `cls`'s annotation (`PYI034`) (#14801) Co-authored-by: Alex Waygood --- .../test/fixtures/flake8_pyi/PYI034.py | 40 ++- .../test/fixtures/flake8_pyi/PYI034.pyi | 39 ++- .../flake8_pyi/rules/non_self_return_type.rs | 119 +++++--- .../rules/flake8_pyi/rules/simple_defaults.rs | 2 + ...__flake8_pyi__tests__PYI034_PYI034.py.snap | 276 +++++++++++++++++- ..._flake8_pyi__tests__PYI034_PYI034.pyi.snap | 270 ++++++++++++++++- .../ruff_python_semantic/src/analyze/class.rs | 63 +++- .../src/analyze/typing.rs | 39 +++ 8 files changed, 780 insertions(+), 68 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py index 179c9db90c4c57..52a612a79e3c0a 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py @@ -8,7 +8,7 @@ from abc import ABCMeta, abstractmethod from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator from enum import EnumMeta -from typing import Any, overload +from typing import Any, Generic, ParamSpec, Type, TypeVar, TypeVarTuple, overload import typing_extensions from _typeshed import Self @@ -321,3 +321,41 @@ def __imul__(self, other: Any) -> list[str]: class UsesStringizedAnnotations: def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self": return self + + +class NonGeneric1(tuple): + def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... + def __enter__(self: NonGeneric1) -> NonGeneric1: ... + +class NonGeneric2(tuple): + def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + +class Generic1[T](list): + def __new__(cls: type[Generic1]) -> Generic1: ... + def __enter__(self: Generic1) -> Generic1: ... + + +### Correctness of typevar-likes are not verified. + +T = TypeVar('T') +P = ParamSpec() +Ts = TypeVarTuple('foo') + +class Generic2(Generic[T]): + def __new__(cls: type[Generic2]) -> Generic2: ... + def __enter__(self: Generic2) -> Generic2: ... + +class Generic3(tuple[*Ts]): + def __new__(cls: type[Generic3]) -> Generic3: ... + def __enter__(self: Generic3) -> Generic3: ... + +class Generic4(collections.abc.Callable[P, ...]): + def __new__(cls: type[Generic4]) -> Generic4: ... + def __enter__(self: Generic4) -> Generic4: ... + +from some_module import PotentialTypeVar + +class Generic5(list[PotentialTypeVar]): + def __new__(cls: type[Generic5]) -> Generic5: ... + def __enter__(self: Generic5) -> Generic5: ... + diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.pyi index ebecadfbd50cca..9567b343ac7cf0 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.pyi @@ -8,7 +8,7 @@ import typing from abc import ABCMeta, abstractmethod from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator from enum import EnumMeta -from typing import Any, overload +from typing import Any, Generic, ParamSpec, Type, TypeVar, TypeVarTuple, overload import typing_extensions from _typeshed import Self @@ -215,3 +215,40 @@ def __imul__(self, other: Any) -> list[str]: ... class UsesStringizedAnnotations: def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self": ... + + +class NonGeneric1(tuple): + def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... + def __enter__(self: NonGeneric1) -> NonGeneric1: ... + +class NonGeneric2(tuple): + def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + +class Generic1[T](list): + def __new__(cls: type[Generic1]) -> Generic1: ... + def __enter__(self: Generic1) -> Generic1: ... + + +### Correctness of typevar-likes are not verified. + +T = TypeVar('T') +P = ParamSpec() +Ts = TypeVarTuple('foo') + +class Generic2(Generic[T]): + def __new__(cls: type[Generic2]) -> Generic2: ... + def __enter__(self: Generic2) -> Generic2: ... + +class Generic3(tuple[*Ts]): + def __new__(cls: type[Generic3]) -> Generic3: ... + def __enter__(self: Generic3) -> Generic3: ... + +class Generic4(collections.abc.Callable[P, ...]): + def __new__(cls: type[Generic4]) -> Generic4: ... + def __enter__(self: Generic4) -> Generic4: ... + +from some_module import PotentialTypeVar + +class Generic5(list[PotentialTypeVar]): + def __new__(cls: type[Generic5]) -> Generic5: ... + def __enter__(self: Generic5) -> Generic5: ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs index d8c79dacc8a2d0..3d4155af837673 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs @@ -1,15 +1,16 @@ -use ruff_python_ast::{self as ast, Decorator, Expr, Parameters, Stmt}; - use crate::checkers::ast::Checker; use crate::importer::ImportRequest; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::settings::types::PythonVersion; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast as ast; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze; +use ruff_python_semantic::analyze::class::might_be_generic; use ruff_python_semantic::analyze::visibility::{is_abstract, is_final, is_overload}; use ruff_python_semantic::{ScopeKind, SemanticModel}; -use ruff_text_size::{Ranged, TextRange}; +use ruff_text_size::Ranged; /// ## What it does /// Checks for methods that are annotated with a fixed return type which @@ -71,6 +72,10 @@ use ruff_text_size::{Ranged, TextRange}; /// async def __aenter__(self) -> Self: ... /// def __iadd__(self, other: Foo) -> Self: ... /// ``` +/// +/// ## Fix safety +/// This rule's fix is marked as unsafe as it changes the meaning of your type annotations. +/// /// ## References /// - [Python documentation: `typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self) #[derive(ViolationMetadata)] @@ -104,12 +109,12 @@ impl Violation for NonSelfReturnType { /// PYI034 pub(crate) fn non_self_return_type( checker: &mut Checker, - stmt: &Stmt, + stmt: &ast::Stmt, is_async: bool, name: &str, - decorator_list: &[Decorator], - returns: Option<&Expr>, - parameters: &Parameters, + decorator_list: &[ast::Decorator], + returns: Option<&ast::Expr>, + parameters: &ast::Parameters, ) { let semantic = checker.semantic(); @@ -126,7 +131,7 @@ pub(crate) fn non_self_return_type( }; // PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses. - if analyze::class::is_metaclass(class_def, semantic).into() { + if analyze::class::is_metaclass(class_def, semantic).is_yes() { return; } @@ -183,15 +188,34 @@ pub(crate) fn non_self_return_type( /// Add a diagnostic for the given method. fn add_diagnostic( checker: &mut Checker, - stmt: &Stmt, - returns: &Expr, + stmt: &ast::Stmt, + returns: &ast::Expr, class_def: &ast::StmtClassDef, method_name: &str, ) { - /// Return an [`Edit`] that imports `typing.Self` from `typing` or `typing_extensions`. - fn import_self(checker: &Checker, range: TextRange) -> Option { - let target_version = checker.settings.target_version.as_tuple(); - let source_module = if target_version >= (3, 11) { + let mut diagnostic = Diagnostic::new( + NonSelfReturnType { + class_name: class_def.name.to_string(), + method_name: method_name.to_string(), + }, + stmt.identifier(), + ); + + diagnostic.try_set_fix(|| replace_with_self_fix(checker, stmt, returns, class_def)); + + checker.diagnostics.push(diagnostic); +} + +fn replace_with_self_fix( + checker: &mut Checker, + stmt: &ast::Stmt, + returns: &ast::Expr, + class_def: &ast::StmtClassDef, +) -> anyhow::Result { + let semantic = checker.semantic(); + + let (self_import, self_binding) = { + let source_module = if checker.settings.target_version >= PythonVersion::Py311 { "typing" } else { "typing_extensions" @@ -199,32 +223,47 @@ fn add_diagnostic( let (importer, semantic) = (checker.importer(), checker.semantic()); let request = ImportRequest::import_from(source_module, "Self"); + importer.get_or_import_symbol(&request, returns.start(), semantic)? + }; - let (edit, ..) = importer - .get_or_import_symbol(&request, range.start(), semantic) - .ok()?; + let mut others = Vec::with_capacity(2); - Some(edit) - } + let remove_first_argument_type_hint = || -> Option { + let ast::StmtFunctionDef { parameters, .. } = stmt.as_function_def_stmt()?; + let first = parameters.iter().next()?; + let annotation = first.annotation()?; - /// Generate a [`Fix`] that replaces the return type with `Self`. - fn replace_with_self(checker: &mut Checker, range: TextRange) -> Option { - let import_self = import_self(checker, range)?; - let replace_with_self = Edit::range_replacement("Self".to_string(), range); - Some(Fix::unsafe_edits(import_self, [replace_with_self])) + is_class_reference(semantic, annotation, &class_def.name) + .then(|| Edit::deletion(first.name().end(), annotation.end())) + }; + + others.extend(remove_first_argument_type_hint()); + others.push(Edit::range_replacement(self_binding, returns.range())); + + let applicability = if might_be_generic(class_def, checker.semantic()) { + Applicability::DisplayOnly + } else { + Applicability::Unsafe + }; + + Ok(Fix::applicable_edits(self_import, others, applicability)) +} + +/// Return true if `annotation` is either `ClassName` or `type[ClassName]` +fn is_class_reference(semantic: &SemanticModel, annotation: &ast::Expr, expected: &str) -> bool { + if is_name(annotation, expected) { + return true; } - let mut diagnostic = Diagnostic::new( - NonSelfReturnType { - class_name: class_def.name.to_string(), - method_name: method_name.to_string(), - }, - stmt.identifier(), - ); - if let Some(fix) = replace_with_self(checker, returns.range()) { - diagnostic.set_fix(fix); + let ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = annotation else { + return false; + }; + + if !semantic.match_builtin_expr(value, "type") && !semantic.match_typing_expr(value, "Type") { + return false; } - checker.diagnostics.push(diagnostic); + + is_name(slice, expected) } /// Returns `true` if the method is an in-place binary operator. @@ -248,15 +287,15 @@ fn is_inplace_bin_op(name: &str) -> bool { } /// Return `true` if the given expression resolves to the given name. -fn is_name(expr: &Expr, name: &str) -> bool { - let Expr::Name(ast::ExprName { id, .. }) = expr else { +fn is_name(expr: &ast::Expr, name: &str) -> bool { + let ast::Expr::Name(ast::ExprName { id, .. }) = expr else { return false; }; id.as_str() == name } /// Return `true` if the given expression resolves to `typing.Self`. -fn is_self(expr: &Expr, checker: &Checker) -> bool { +fn is_self(expr: &ast::Expr, checker: &Checker) -> bool { checker.match_maybe_stringized_annotation(expr, |expr| { checker.semantic().match_typing_expr(expr, "Self") }) @@ -273,7 +312,7 @@ fn subclasses_iterator(class_def: &ast::StmtClassDef, semantic: &SemanticModel) } /// Return `true` if the given expression resolves to `collections.abc.Iterable` or `collections.abc.Iterator`. -fn is_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool { +fn is_iterable_or_iterator(expr: &ast::Expr, semantic: &SemanticModel) -> bool { semantic .resolve_qualified_name(map_subscript(expr)) .is_some_and(|qualified_name| { @@ -296,7 +335,7 @@ fn subclasses_async_iterator(class_def: &ast::StmtClassDef, semantic: &SemanticM } /// Return `true` if the given expression resolves to `collections.abc.AsyncIterable` or `collections.abc.AsyncIterator`. -fn is_async_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool { +fn is_async_iterable_or_iterator(expr: &ast::Expr, semantic: &SemanticModel) -> bool { semantic .resolve_qualified_name(map_subscript(expr)) .is_some_and(|qualified_name| { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index d8d918bd72781f..1ae0b94fa66177 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -425,6 +425,8 @@ fn is_valid_default_value_without_annotation(default: &Expr) -> bool { /// Returns `true` if an [`Expr`] appears to be `TypeVar`, `TypeVarTuple`, `NewType`, or `ParamSpec` /// call. +/// +/// See also [`ruff_python_semantic::analyze::typing::TypeVarLikeChecker::is_type_var_like_call`]. fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool { let Expr::Call(ast::ExprCall { func, .. }) = expr else { return false; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap index 74a638f7ff5691..d2b3356ff6077f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- PYI034.py:21:9: PYI034 [*] `__new__` methods in classes like `Bad` usually return `self` at runtime | @@ -17,7 +16,7 @@ PYI034.py:21:9: PYI034 [*] `__new__` methods in classes like `Bad` usually retur 19 19 | object 20 20 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 21 |- def __new__(cls, *args: Any, **kwargs: Any) -> Bad: - 21 |+ def __new__(cls, *args: Any, **kwargs: Any) -> Self: + 21 |+ def __new__(cls, *args: Any, **kwargs: Any) -> typing.Self: 22 22 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." 23 23 | 24 24 | def __repr__(self) -> str: @@ -37,7 +36,7 @@ PYI034.py:36:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually ret 34 34 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods 35 35 | 36 |- def __enter__(self) -> Bad: - 36 |+ def __enter__(self) -> Self: + 36 |+ def __enter__(self) -> typing.Self: 37 37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..." 38 38 | 39 39 | async def __aenter__(self) -> Bad: @@ -57,7 +56,7 @@ PYI034.py:39:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually r 37 37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..." 38 38 | 39 |- async def __aenter__(self) -> Bad: - 39 |+ async def __aenter__(self) -> Self: + 39 |+ async def __aenter__(self) -> typing.Self: 40 40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." 41 41 | 42 42 | def __iadd__(self, other: Bad) -> Bad: @@ -77,7 +76,7 @@ PYI034.py:42:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually retu 40 40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." 41 41 | 42 |- def __iadd__(self, other: Bad) -> Bad: - 42 |+ def __iadd__(self, other: Bad) -> Self: + 42 |+ def __iadd__(self, other: Bad) -> typing.Self: 43 43 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..." 44 44 | 45 45 | @@ -96,7 +95,7 @@ PYI034.py:165:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` us 163 163 | 164 164 | class BadIterator1(Iterator[int]): 165 |- def __iter__(self) -> Iterator[int]: - 165 |+ def __iter__(self) -> Self: + 165 |+ def __iter__(self) -> typing.Self: 166 166 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..." 167 167 | 168 168 | @@ -116,7 +115,7 @@ PYI034.py:172:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` us 170 170 | typing.Iterator[int] 171 171 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax) 172 |- def __iter__(self) -> Iterator[int]: - 172 |+ def __iter__(self) -> Self: + 172 |+ def __iter__(self) -> typing.Self: 173 173 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..." 174 174 | 175 175 | @@ -136,7 +135,7 @@ PYI034.py:179:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` us 177 177 | typing.Iterator[int] 178 178 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax) 179 |- def __iter__(self) -> collections.abc.Iterator[int]: - 179 |+ def __iter__(self) -> Self: + 179 |+ def __iter__(self) -> typing.Self: 180 180 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..." 181 181 | 182 182 | @@ -156,7 +155,7 @@ PYI034.py:185:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` us 183 183 | class BadIterator4(Iterator[int]): 184 184 | # Note: *Iterable*, not *Iterator*, returned! 185 |- def __iter__(self) -> Iterable[int]: - 185 |+ def __iter__(self) -> Self: + 185 |+ def __iter__(self) -> typing.Self: 186 186 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..." 187 187 | 188 188 | @@ -175,7 +174,7 @@ PYI034.py:195:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterato 193 193 | 194 194 | class BadAsyncIterator(collections.abc.AsyncIterator[str]): 195 |- def __aiter__(self) -> typing.AsyncIterator[str]: - 195 |+ def __aiter__(self) -> Self: + 195 |+ def __aiter__(self) -> typing.Self: 196 196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) 197 197 | 198 198 | class SubclassOfBadIterator3(BadIterator3): @@ -194,7 +193,7 @@ PYI034.py:199:9: PYI034 [*] `__iter__` methods in classes like `SubclassOfBadIte 197 197 | 198 198 | class SubclassOfBadIterator3(BadIterator3): 199 |- def __iter__(self) -> Iterator[int]: # Y034 - 199 |+ def __iter__(self) -> Self: # Y034 + 199 |+ def __iter__(self) -> typing.Self: # Y034 200 200 | ... 201 201 | 202 202 | class SubclassOfBadAsyncIterator(BadAsyncIterator): @@ -213,7 +212,260 @@ PYI034.py:203:9: PYI034 [*] `__aiter__` methods in classes like `SubclassOfBadAs 201 201 | 202 202 | class SubclassOfBadAsyncIterator(BadAsyncIterator): 203 |- def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034 - 203 |+ def __aiter__(self) -> Self: # Y034 + 203 |+ def __aiter__(self) -> typing.Self: # Y034 204 204 | ... 205 205 | 206 206 | class AsyncIteratorReturningAsyncIterable: + +PYI034.py:327:9: PYI034 [*] `__new__` methods in classes like `NonGeneric1` usually return `self` at runtime + | +326 | class NonGeneric1(tuple): +327 | def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... + | ^^^^^^^ PYI034 +328 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... + | + = help: Use `Self` as return type + +ℹ Unsafe fix +324 324 | +325 325 | +326 326 | class NonGeneric1(tuple): +327 |- def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... + 327 |+ def __new__(cls, *args, **kwargs) -> typing.Self: ... +328 328 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... +329 329 | +330 330 | class NonGeneric2(tuple): + +PYI034.py:328:9: PYI034 [*] `__enter__` methods in classes like `NonGeneric1` usually return `self` at runtime + | +326 | class NonGeneric1(tuple): +327 | def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... +328 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... + | ^^^^^^^^^ PYI034 +329 | +330 | class NonGeneric2(tuple): + | + = help: Use `Self` as return type + +ℹ Unsafe fix +325 325 | +326 326 | class NonGeneric1(tuple): +327 327 | def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... +328 |- def __enter__(self: NonGeneric1) -> NonGeneric1: ... + 328 |+ def __enter__(self) -> typing.Self: ... +329 329 | +330 330 | class NonGeneric2(tuple): +331 331 | def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + +PYI034.py:331:9: PYI034 [*] `__new__` methods in classes like `NonGeneric2` usually return `self` at runtime + | +330 | class NonGeneric2(tuple): +331 | def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + | ^^^^^^^ PYI034 +332 | +333 | class Generic1[T](list): + | + = help: Use `Self` as return type + +ℹ Unsafe fix +328 328 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... +329 329 | +330 330 | class NonGeneric2(tuple): +331 |- def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + 331 |+ def __new__(cls) -> typing.Self: ... +332 332 | +333 333 | class Generic1[T](list): +334 334 | def __new__(cls: type[Generic1]) -> Generic1: ... + +PYI034.py:334:9: PYI034 `__new__` methods in classes like `Generic1` usually return `self` at runtime + | +333 | class Generic1[T](list): +334 | def __new__(cls: type[Generic1]) -> Generic1: ... + | ^^^^^^^ PYI034 +335 | def __enter__(self: Generic1) -> Generic1: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +331 331 | def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... +332 332 | +333 333 | class Generic1[T](list): +334 |- def __new__(cls: type[Generic1]) -> Generic1: ... + 334 |+ def __new__(cls) -> typing.Self: ... +335 335 | def __enter__(self: Generic1) -> Generic1: ... +336 336 | +337 337 | + +PYI034.py:335:9: PYI034 `__enter__` methods in classes like `Generic1` usually return `self` at runtime + | +333 | class Generic1[T](list): +334 | def __new__(cls: type[Generic1]) -> Generic1: ... +335 | def __enter__(self: Generic1) -> Generic1: ... + | ^^^^^^^^^ PYI034 + | + = help: Use `Self` as return type + +ℹ Display-only fix +332 332 | +333 333 | class Generic1[T](list): +334 334 | def __new__(cls: type[Generic1]) -> Generic1: ... +335 |- def __enter__(self: Generic1) -> Generic1: ... + 335 |+ def __enter__(self) -> typing.Self: ... +336 336 | +337 337 | +338 338 | ### Correctness of typevar-likes are not verified. + +PYI034.py:345:9: PYI034 `__new__` methods in classes like `Generic2` usually return `self` at runtime + | +344 | class Generic2(Generic[T]): +345 | def __new__(cls: type[Generic2]) -> Generic2: ... + | ^^^^^^^ PYI034 +346 | def __enter__(self: Generic2) -> Generic2: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +342 342 | Ts = TypeVarTuple('foo') +343 343 | +344 344 | class Generic2(Generic[T]): +345 |- def __new__(cls: type[Generic2]) -> Generic2: ... + 345 |+ def __new__(cls) -> typing.Self: ... +346 346 | def __enter__(self: Generic2) -> Generic2: ... +347 347 | +348 348 | class Generic3(tuple[*Ts]): + +PYI034.py:346:9: PYI034 `__enter__` methods in classes like `Generic2` usually return `self` at runtime + | +344 | class Generic2(Generic[T]): +345 | def __new__(cls: type[Generic2]) -> Generic2: ... +346 | def __enter__(self: Generic2) -> Generic2: ... + | ^^^^^^^^^ PYI034 +347 | +348 | class Generic3(tuple[*Ts]): + | + = help: Use `Self` as return type + +ℹ Display-only fix +343 343 | +344 344 | class Generic2(Generic[T]): +345 345 | def __new__(cls: type[Generic2]) -> Generic2: ... +346 |- def __enter__(self: Generic2) -> Generic2: ... + 346 |+ def __enter__(self) -> typing.Self: ... +347 347 | +348 348 | class Generic3(tuple[*Ts]): +349 349 | def __new__(cls: type[Generic3]) -> Generic3: ... + +PYI034.py:349:9: PYI034 `__new__` methods in classes like `Generic3` usually return `self` at runtime + | +348 | class Generic3(tuple[*Ts]): +349 | def __new__(cls: type[Generic3]) -> Generic3: ... + | ^^^^^^^ PYI034 +350 | def __enter__(self: Generic3) -> Generic3: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +346 346 | def __enter__(self: Generic2) -> Generic2: ... +347 347 | +348 348 | class Generic3(tuple[*Ts]): +349 |- def __new__(cls: type[Generic3]) -> Generic3: ... + 349 |+ def __new__(cls) -> typing.Self: ... +350 350 | def __enter__(self: Generic3) -> Generic3: ... +351 351 | +352 352 | class Generic4(collections.abc.Callable[P, ...]): + +PYI034.py:350:9: PYI034 `__enter__` methods in classes like `Generic3` usually return `self` at runtime + | +348 | class Generic3(tuple[*Ts]): +349 | def __new__(cls: type[Generic3]) -> Generic3: ... +350 | def __enter__(self: Generic3) -> Generic3: ... + | ^^^^^^^^^ PYI034 +351 | +352 | class Generic4(collections.abc.Callable[P, ...]): + | + = help: Use `Self` as return type + +ℹ Display-only fix +347 347 | +348 348 | class Generic3(tuple[*Ts]): +349 349 | def __new__(cls: type[Generic3]) -> Generic3: ... +350 |- def __enter__(self: Generic3) -> Generic3: ... + 350 |+ def __enter__(self) -> typing.Self: ... +351 351 | +352 352 | class Generic4(collections.abc.Callable[P, ...]): +353 353 | def __new__(cls: type[Generic4]) -> Generic4: ... + +PYI034.py:353:9: PYI034 `__new__` methods in classes like `Generic4` usually return `self` at runtime + | +352 | class Generic4(collections.abc.Callable[P, ...]): +353 | def __new__(cls: type[Generic4]) -> Generic4: ... + | ^^^^^^^ PYI034 +354 | def __enter__(self: Generic4) -> Generic4: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +350 350 | def __enter__(self: Generic3) -> Generic3: ... +351 351 | +352 352 | class Generic4(collections.abc.Callable[P, ...]): +353 |- def __new__(cls: type[Generic4]) -> Generic4: ... + 353 |+ def __new__(cls) -> typing.Self: ... +354 354 | def __enter__(self: Generic4) -> Generic4: ... +355 355 | +356 356 | from some_module import PotentialTypeVar + +PYI034.py:354:9: PYI034 `__enter__` methods in classes like `Generic4` usually return `self` at runtime + | +352 | class Generic4(collections.abc.Callable[P, ...]): +353 | def __new__(cls: type[Generic4]) -> Generic4: ... +354 | def __enter__(self: Generic4) -> Generic4: ... + | ^^^^^^^^^ PYI034 +355 | +356 | from some_module import PotentialTypeVar + | + = help: Use `Self` as return type + +ℹ Display-only fix +351 351 | +352 352 | class Generic4(collections.abc.Callable[P, ...]): +353 353 | def __new__(cls: type[Generic4]) -> Generic4: ... +354 |- def __enter__(self: Generic4) -> Generic4: ... + 354 |+ def __enter__(self) -> typing.Self: ... +355 355 | +356 356 | from some_module import PotentialTypeVar +357 357 | + +PYI034.py:359:9: PYI034 [*] `__new__` methods in classes like `Generic5` usually return `self` at runtime + | +358 | class Generic5(list[PotentialTypeVar]): +359 | def __new__(cls: type[Generic5]) -> Generic5: ... + | ^^^^^^^ PYI034 +360 | def __enter__(self: Generic5) -> Generic5: ... + | + = help: Use `Self` as return type + +ℹ Unsafe fix +356 356 | from some_module import PotentialTypeVar +357 357 | +358 358 | class Generic5(list[PotentialTypeVar]): +359 |- def __new__(cls: type[Generic5]) -> Generic5: ... + 359 |+ def __new__(cls) -> typing.Self: ... +360 360 | def __enter__(self: Generic5) -> Generic5: ... +361 361 | + +PYI034.py:360:9: PYI034 [*] `__enter__` methods in classes like `Generic5` usually return `self` at runtime + | +358 | class Generic5(list[PotentialTypeVar]): +359 | def __new__(cls: type[Generic5]) -> Generic5: ... +360 | def __enter__(self: Generic5) -> Generic5: ... + | ^^^^^^^^^ PYI034 + | + = help: Use `Self` as return type + +ℹ Unsafe fix +357 357 | +358 358 | class Generic5(list[PotentialTypeVar]): +359 359 | def __new__(cls: type[Generic5]) -> Generic5: ... +360 |- def __enter__(self: Generic5) -> Generic5: ... + 360 |+ def __enter__(self) -> typing.Self: ... +361 361 | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.pyi.snap index 11c9ca99c6a956..941c11fe991734 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.pyi.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- PYI034.pyi:20:9: PYI034 [*] `__new__` methods in classes like `Bad` usually return `self` at runtime | @@ -18,7 +17,7 @@ PYI034.pyi:20:9: PYI034 [*] `__new__` methods in classes like `Bad` usually retu 20 20 | def __new__( 21 21 | cls, *args: Any, **kwargs: Any 22 |- ) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." - 22 |+ ) -> Self: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." + 22 |+ ) -> typing.Self: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." 23 23 | def __repr__( 24 24 | self, 25 25 | ) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant @@ -39,7 +38,7 @@ PYI034.pyi:35:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually re 35 35 | def __enter__( 36 36 | self, 37 |- ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..." - 37 |+ ) -> Self: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..." + 37 |+ ) -> typing.Self: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..." 38 38 | async def __aenter__( 39 39 | self, 40 40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." @@ -60,7 +59,7 @@ PYI034.pyi:38:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually 38 38 | async def __aenter__( 39 39 | self, 40 |- ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." - 40 |+ ) -> Self: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." + 40 |+ ) -> typing.Self: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." 41 41 | def __iadd__( 42 42 | self, other: Bad 43 43 | ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..." @@ -81,7 +80,7 @@ PYI034.pyi:41:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually ret 41 41 | def __iadd__( 42 42 | self, other: Bad 43 |- ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..." - 43 |+ ) -> Self: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..." + 43 |+ ) -> typing.Self: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..." 44 44 | 45 45 | class AlsoBad( 46 46 | int, builtins.object @@ -103,7 +102,7 @@ PYI034.pyi:104:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` u 106 |- ) -> Iterator[ 107 |- int 108 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..." - 106 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..." + 106 |+ ) -> typing.Self: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..." 109 107 | 110 108 | class BadIterator2( 111 109 | typing.Iterator[int] @@ -126,7 +125,7 @@ PYI034.pyi:113:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` u 115 |- ) -> Iterator[ 116 |- int 117 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..." - 115 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..." + 115 |+ ) -> typing.Self: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..." 118 116 | 119 117 | class BadIterator3( 120 118 | typing.Iterator[int] @@ -149,7 +148,7 @@ PYI034.pyi:122:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` u 124 |- ) -> collections.abc.Iterator[ 125 |- int 126 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..." - 124 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..." + 124 |+ ) -> typing.Self: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..." 127 125 | 128 126 | class BadIterator4(Iterator[int]): 129 127 | # Note: *Iterable*, not *Iterator*, returned! @@ -172,7 +171,7 @@ PYI034.pyi:130:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` u 132 |- ) -> Iterable[ 133 |- int 134 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..." - 132 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..." + 132 |+ ) -> typing.Self: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..." 135 133 | 136 134 | class IteratorReturningIterable: 137 135 | def __iter__( @@ -194,7 +193,258 @@ PYI034.pyi:144:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterat 146 |- ) -> typing.AsyncIterator[ 147 |- str 148 |- ]: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) - 146 |+ ) -> Self: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) + 146 |+ ) -> typing.Self: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) 149 147 | 150 148 | class AsyncIteratorReturningAsyncIterable: 151 149 | def __aiter__( + +PYI034.pyi:221:9: PYI034 [*] `__new__` methods in classes like `NonGeneric1` usually return `self` at runtime + | +220 | class NonGeneric1(tuple): +221 | def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... + | ^^^^^^^ PYI034 +222 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... + | + = help: Use `Self` as return type + +ℹ Unsafe fix +218 218 | +219 219 | +220 220 | class NonGeneric1(tuple): +221 |- def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... + 221 |+ def __new__(cls, *args, **kwargs) -> typing.Self: ... +222 222 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... +223 223 | +224 224 | class NonGeneric2(tuple): + +PYI034.pyi:222:9: PYI034 [*] `__enter__` methods in classes like `NonGeneric1` usually return `self` at runtime + | +220 | class NonGeneric1(tuple): +221 | def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... +222 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... + | ^^^^^^^^^ PYI034 +223 | +224 | class NonGeneric2(tuple): + | + = help: Use `Self` as return type + +ℹ Unsafe fix +219 219 | +220 220 | class NonGeneric1(tuple): +221 221 | def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ... +222 |- def __enter__(self: NonGeneric1) -> NonGeneric1: ... + 222 |+ def __enter__(self) -> typing.Self: ... +223 223 | +224 224 | class NonGeneric2(tuple): +225 225 | def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + +PYI034.pyi:225:9: PYI034 [*] `__new__` methods in classes like `NonGeneric2` usually return `self` at runtime + | +224 | class NonGeneric2(tuple): +225 | def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + | ^^^^^^^ PYI034 +226 | +227 | class Generic1[T](list): + | + = help: Use `Self` as return type + +ℹ Unsafe fix +222 222 | def __enter__(self: NonGeneric1) -> NonGeneric1: ... +223 223 | +224 224 | class NonGeneric2(tuple): +225 |- def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... + 225 |+ def __new__(cls) -> typing.Self: ... +226 226 | +227 227 | class Generic1[T](list): +228 228 | def __new__(cls: type[Generic1]) -> Generic1: ... + +PYI034.pyi:228:9: PYI034 `__new__` methods in classes like `Generic1` usually return `self` at runtime + | +227 | class Generic1[T](list): +228 | def __new__(cls: type[Generic1]) -> Generic1: ... + | ^^^^^^^ PYI034 +229 | def __enter__(self: Generic1) -> Generic1: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +225 225 | def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ... +226 226 | +227 227 | class Generic1[T](list): +228 |- def __new__(cls: type[Generic1]) -> Generic1: ... + 228 |+ def __new__(cls) -> typing.Self: ... +229 229 | def __enter__(self: Generic1) -> Generic1: ... +230 230 | +231 231 | + +PYI034.pyi:229:9: PYI034 `__enter__` methods in classes like `Generic1` usually return `self` at runtime + | +227 | class Generic1[T](list): +228 | def __new__(cls: type[Generic1]) -> Generic1: ... +229 | def __enter__(self: Generic1) -> Generic1: ... + | ^^^^^^^^^ PYI034 + | + = help: Use `Self` as return type + +ℹ Display-only fix +226 226 | +227 227 | class Generic1[T](list): +228 228 | def __new__(cls: type[Generic1]) -> Generic1: ... +229 |- def __enter__(self: Generic1) -> Generic1: ... + 229 |+ def __enter__(self) -> typing.Self: ... +230 230 | +231 231 | +232 232 | ### Correctness of typevar-likes are not verified. + +PYI034.pyi:239:9: PYI034 `__new__` methods in classes like `Generic2` usually return `self` at runtime + | +238 | class Generic2(Generic[T]): +239 | def __new__(cls: type[Generic2]) -> Generic2: ... + | ^^^^^^^ PYI034 +240 | def __enter__(self: Generic2) -> Generic2: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +236 236 | Ts = TypeVarTuple('foo') +237 237 | +238 238 | class Generic2(Generic[T]): +239 |- def __new__(cls: type[Generic2]) -> Generic2: ... + 239 |+ def __new__(cls) -> typing.Self: ... +240 240 | def __enter__(self: Generic2) -> Generic2: ... +241 241 | +242 242 | class Generic3(tuple[*Ts]): + +PYI034.pyi:240:9: PYI034 `__enter__` methods in classes like `Generic2` usually return `self` at runtime + | +238 | class Generic2(Generic[T]): +239 | def __new__(cls: type[Generic2]) -> Generic2: ... +240 | def __enter__(self: Generic2) -> Generic2: ... + | ^^^^^^^^^ PYI034 +241 | +242 | class Generic3(tuple[*Ts]): + | + = help: Use `Self` as return type + +ℹ Display-only fix +237 237 | +238 238 | class Generic2(Generic[T]): +239 239 | def __new__(cls: type[Generic2]) -> Generic2: ... +240 |- def __enter__(self: Generic2) -> Generic2: ... + 240 |+ def __enter__(self) -> typing.Self: ... +241 241 | +242 242 | class Generic3(tuple[*Ts]): +243 243 | def __new__(cls: type[Generic3]) -> Generic3: ... + +PYI034.pyi:243:9: PYI034 `__new__` methods in classes like `Generic3` usually return `self` at runtime + | +242 | class Generic3(tuple[*Ts]): +243 | def __new__(cls: type[Generic3]) -> Generic3: ... + | ^^^^^^^ PYI034 +244 | def __enter__(self: Generic3) -> Generic3: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +240 240 | def __enter__(self: Generic2) -> Generic2: ... +241 241 | +242 242 | class Generic3(tuple[*Ts]): +243 |- def __new__(cls: type[Generic3]) -> Generic3: ... + 243 |+ def __new__(cls) -> typing.Self: ... +244 244 | def __enter__(self: Generic3) -> Generic3: ... +245 245 | +246 246 | class Generic4(collections.abc.Callable[P, ...]): + +PYI034.pyi:244:9: PYI034 `__enter__` methods in classes like `Generic3` usually return `self` at runtime + | +242 | class Generic3(tuple[*Ts]): +243 | def __new__(cls: type[Generic3]) -> Generic3: ... +244 | def __enter__(self: Generic3) -> Generic3: ... + | ^^^^^^^^^ PYI034 +245 | +246 | class Generic4(collections.abc.Callable[P, ...]): + | + = help: Use `Self` as return type + +ℹ Display-only fix +241 241 | +242 242 | class Generic3(tuple[*Ts]): +243 243 | def __new__(cls: type[Generic3]) -> Generic3: ... +244 |- def __enter__(self: Generic3) -> Generic3: ... + 244 |+ def __enter__(self) -> typing.Self: ... +245 245 | +246 246 | class Generic4(collections.abc.Callable[P, ...]): +247 247 | def __new__(cls: type[Generic4]) -> Generic4: ... + +PYI034.pyi:247:9: PYI034 `__new__` methods in classes like `Generic4` usually return `self` at runtime + | +246 | class Generic4(collections.abc.Callable[P, ...]): +247 | def __new__(cls: type[Generic4]) -> Generic4: ... + | ^^^^^^^ PYI034 +248 | def __enter__(self: Generic4) -> Generic4: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +244 244 | def __enter__(self: Generic3) -> Generic3: ... +245 245 | +246 246 | class Generic4(collections.abc.Callable[P, ...]): +247 |- def __new__(cls: type[Generic4]) -> Generic4: ... + 247 |+ def __new__(cls) -> typing.Self: ... +248 248 | def __enter__(self: Generic4) -> Generic4: ... +249 249 | +250 250 | from some_module import PotentialTypeVar + +PYI034.pyi:248:9: PYI034 `__enter__` methods in classes like `Generic4` usually return `self` at runtime + | +246 | class Generic4(collections.abc.Callable[P, ...]): +247 | def __new__(cls: type[Generic4]) -> Generic4: ... +248 | def __enter__(self: Generic4) -> Generic4: ... + | ^^^^^^^^^ PYI034 +249 | +250 | from some_module import PotentialTypeVar + | + = help: Use `Self` as return type + +ℹ Display-only fix +245 245 | +246 246 | class Generic4(collections.abc.Callable[P, ...]): +247 247 | def __new__(cls: type[Generic4]) -> Generic4: ... +248 |- def __enter__(self: Generic4) -> Generic4: ... + 248 |+ def __enter__(self) -> typing.Self: ... +249 249 | +250 250 | from some_module import PotentialTypeVar +251 251 | + +PYI034.pyi:253:9: PYI034 `__new__` methods in classes like `Generic5` usually return `self` at runtime + | +252 | class Generic5(list[PotentialTypeVar]): +253 | def __new__(cls: type[Generic5]) -> Generic5: ... + | ^^^^^^^ PYI034 +254 | def __enter__(self: Generic5) -> Generic5: ... + | + = help: Use `Self` as return type + +ℹ Display-only fix +250 250 | from some_module import PotentialTypeVar +251 251 | +252 252 | class Generic5(list[PotentialTypeVar]): +253 |- def __new__(cls: type[Generic5]) -> Generic5: ... + 253 |+ def __new__(cls) -> typing.Self: ... +254 254 | def __enter__(self: Generic5) -> Generic5: ... + +PYI034.pyi:254:9: PYI034 `__enter__` methods in classes like `Generic5` usually return `self` at runtime + | +252 | class Generic5(list[PotentialTypeVar]): +253 | def __new__(cls: type[Generic5]) -> Generic5: ... +254 | def __enter__(self: Generic5) -> Generic5: ... + | ^^^^^^^^^ PYI034 + | + = help: Use `Self` as return type + +ℹ Display-only fix +251 251 | +252 252 | class Generic5(list[PotentialTypeVar]): +253 253 | def __new__(cls: type[Generic5]) -> Generic5: ... +254 |- def __enter__(self: Generic5) -> Generic5: ... + 254 |+ def __enter__(self) -> typing.Self: ... diff --git a/crates/ruff_python_semantic/src/analyze/class.rs b/crates/ruff_python_semantic/src/analyze/class.rs index e4419031d040a0..64e49421a73308 100644 --- a/crates/ruff_python_semantic/src/analyze/class.rs +++ b/crates/ruff_python_semantic/src/analyze/class.rs @@ -1,10 +1,11 @@ use rustc_hash::FxHashSet; +use crate::analyze::typing; use crate::{BindingId, SemanticModel}; use ruff_python_ast as ast; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::name::QualifiedName; -use ruff_python_ast::Expr; +use ruff_python_ast::{Expr, ExprName, ExprStarred, ExprSubscript, ExprTuple}; /// Return `true` if any base class matches a [`QualifiedName`] predicate. pub fn any_qualified_base_class( @@ -129,9 +130,9 @@ pub enum IsMetaclass { Maybe, } -impl From for bool { - fn from(value: IsMetaclass) -> Self { - matches!(value, IsMetaclass::Yes) +impl IsMetaclass { + pub const fn is_yes(self) -> bool { + matches!(self, IsMetaclass::Yes) } } @@ -170,3 +171,57 @@ pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> (false, _) => IsMetaclass::No, } } + +/// Returns true if a class might generic. +/// +/// A class is considered generic if at least one of its direct bases +/// is subscripted with a `TypeVar`-like, +/// or if it is defined using PEP 695 syntax. +/// +/// Therefore, a class *might* be generic if it uses PEP-695 syntax +/// or at least one of its direct bases is a subscript expression that +/// is subscripted with an object that *might* be a `TypeVar`-like. +pub fn might_be_generic(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { + if class_def.type_params.is_some() { + return true; + } + + class_def.bases().iter().any(|base| { + let Expr::Subscript(ExprSubscript { slice, .. }) = base else { + return false; + }; + + let Expr::Tuple(ExprTuple { elts, .. }) = slice.as_ref() else { + return expr_might_be_typevar_like(slice, semantic); + }; + + elts.iter() + .any(|elt| expr_might_be_typevar_like(elt, semantic)) + }) +} + +fn expr_might_be_typevar_like(expr: &Expr, semantic: &SemanticModel) -> bool { + is_known_typevar(expr, semantic) || expr_might_be_old_style_typevar_like(expr, semantic) +} + +fn is_known_typevar(expr: &Expr, semantic: &SemanticModel) -> bool { + semantic.match_typing_expr(expr, "AnyStr") +} + +fn expr_might_be_old_style_typevar_like(expr: &Expr, semantic: &SemanticModel) -> bool { + match expr { + Expr::Attribute(..) => true, + Expr::Name(name) => might_be_old_style_typevar_like(name, semantic), + Expr::Starred(ExprStarred { value, .. }) => { + expr_might_be_old_style_typevar_like(value, semantic) + } + _ => false, + } +} + +fn might_be_old_style_typevar_like(name: &ExprName, semantic: &SemanticModel) -> bool { + let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else { + return !semantic.has_builtin_binding(&name.id); + }; + typing::is_type_var_like(binding, semantic) +} diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 3620a11822e8e4..1dc71ab7005048 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -778,6 +778,40 @@ impl TypeChecker for PathlibPathChecker { } } +pub struct TypeVarLikeChecker; + +impl TypeVarLikeChecker { + /// Returns `true` if an [`Expr`] is a `TypeVar`, `TypeVarTuple`, or `ParamSpec` call. + /// + /// See also [`ruff_linter::rules::flake8_pyi::rules::simple_defaults::is_type_var_like_call`]. + fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool { + let Expr::Call(ast::ExprCall { func, .. }) = expr else { + return false; + }; + let Some(qualified_name) = semantic.resolve_qualified_name(func) else { + return false; + }; + + matches!( + qualified_name.segments(), + [ + "typing" | "typing_extensions", + "TypeVar" | "TypeVarTuple" | "ParamSpec" + ] + ) + } +} + +impl TypeChecker for TypeVarLikeChecker { + fn match_annotation(_annotation: &Expr, _semantic: &SemanticModel) -> bool { + false + } + + fn match_initializer(initializer: &Expr, semantic: &SemanticModel) -> bool { + Self::is_type_var_like_call(initializer, semantic) + } +} + /// Test whether the given binding can be considered a list. /// /// For this, we check what value might be associated with it through it's initialization and @@ -867,6 +901,11 @@ pub fn is_pathlib_path(binding: &Binding, semantic: &SemanticModel) -> bool { check_type::(binding, semantic) } +/// Test whether the given binding is for an old-style `TypeVar`, `TypeVarTuple` or a `ParamSpec`. +pub fn is_type_var_like(binding: &Binding, semantic: &SemanticModel) -> bool { + check_type::(binding, semantic) +} + /// Find the [`ParameterWithDefault`] corresponding to the given [`Binding`]. #[inline] fn find_parameter<'a>(