From 2a52075739a2781fc930ba348a8b824670f80833 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Sun, 10 Nov 2024 00:31:48 +0000 Subject: [PATCH 1/9] [`flake8-pyi`] Add "replace with `Self`" fix (`PYI019`) --- .../test/fixtures/flake8_pyi/PYI019.py | 31 ++ .../test/fixtures/flake8_pyi/PYI019.pyi | 31 ++ .../src/checkers/ast/analyze/statement.rs | 4 +- .../rules/custom_type_var_return_type.rs | 231 ++++++++++-- ...__flake8_pyi__tests__PYI019_PYI019.py.snap | 141 ++++++- ..._flake8_pyi__tests__PYI019_PYI019.pyi.snap | 357 +++++++++++++++++- 6 files changed, 750 insertions(+), 45 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py index 8e9338574e0ae..c279a8d05cf40 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py @@ -52,3 +52,34 @@ class CustomClassMethod: # in the settings for this test: @foo_classmethod def foo[S](cls: type[S]) -> S: ... # PYI019 + + +# No fixes for .py +class PEP695Fix: + def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + + def __init_subclass__[S](cls: type[S]) -> S: ... + + def __neg__[S: PEP695Fix](self: S) -> S: ... + + def __pos__[S](self: S) -> S: ... + + def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + + def __sub__[S](self: S, other: S) -> S: ... + + @classmethod + def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + + @classmethod + def class_method_unbound[S](cls: type[S]) -> S: ... + + def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + + def instance_method_unbound[S](self: S) -> S: ... + + def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + + def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + + def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi index 8e9338574e0ae..e323fe2613eb1 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi @@ -52,3 +52,34 @@ class CustomClassMethod: # in the settings for this test: @foo_classmethod def foo[S](cls: type[S]) -> S: ... # PYI019 + + +# Only .pyi gets fixes +class PEP695Fix: + def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + + def __init_subclass__[S](cls: type[S]) -> S: ... + + def __neg__[S: PEP695Fix](self: S) -> S: ... + + def __pos__[S](self: S) -> S: ... + + def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + + def __sub__[S](self: S, other: S) -> S: ... + + @classmethod + def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + + @classmethod + def class_method_unbound[S](cls: type[S]) -> S: ... + + def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + + def instance_method_unbound[S](self: S) -> S: ... + + def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + + def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + + def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 6624dd8d14479..e71962cb496af 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -164,9 +164,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { checker, name, decorator_list, - returns.as_ref().map(AsRef::as_ref), - parameters, type_params.as_deref(), + parameters, + returns.as_ref().map(AsRef::as_ref), ); } if checker.source_type.is_stub() { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 34eb68943f693..c2cb57acd6b7e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -1,26 +1,27 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use itertools::Itertools; +use ruff_diagnostics::{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::{Decorator, Expr, Parameters, TypeParam, TypeParams}; use ruff_python_semantic::analyze::function_type::{self, FunctionType}; use ruff_python_semantic::analyze::visibility::{is_abstract, is_overload}; -use ruff_text_size::Ranged; - -use crate::checkers::ast::Checker; +use ruff_text_size::{Ranged, TextRange}; /// ## What it does /// Checks for methods that define a custom `TypeVar` for their return type -/// annotation instead of using `typing_extensions.Self`. +/// annotation instead of using `Self`. /// /// ## Why is this bad? -/// While the semantics are often identical, using `typing_extensions.Self` is -/// more intuitive and succinct (per [PEP 673]) than a custom `TypeVar`. For -/// example, the use of `Self` will typically allow for the omission of type -/// parameters on the `self` and `cls` arguments. +/// While the semantics are often identical, using `Self` is more intuitive +/// and succinct (per [PEP 673]) than a custom `TypeVar`. For example, the +/// use of `Self` will typically allow for the omission of type parameters +/// on the `self` and `cls` arguments. /// -/// This check currently applies to instance methods that return `self`, class -/// methods that return an instance of `cls`, and `__new__` methods. +/// This check currently applies to instance methods that return `self`, +/// class methods that return an instance of `cls`, and `__new__` methods. /// /// ## Example /// @@ -48,15 +49,25 @@ use crate::checkers::ast::Checker; #[violation] pub struct CustomTypeVarReturnType { method_name: String, + in_stub: bool, } impl Violation for CustomTypeVarReturnType { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { - let CustomTypeVarReturnType { method_name } = self; - format!( - "Methods like `{method_name}` should return `typing.Self` instead of a custom `TypeVar`" - ) + let method_name = &self.method_name; + format!("Methods like `{method_name}` should return `Self` instead of a custom `TypeVar`") + } + + fn fix_title(&self) -> Option { + // See `replace_custom_typevar_with_self`'s doc comment + if self.in_stub { + Some("Replace with `Self`".to_string()) + } else { + None + } } } @@ -65,9 +76,9 @@ pub(crate) fn custom_type_var_return_type( checker: &mut Checker, name: &str, decorator_list: &[Decorator], - returns: Option<&Expr>, - args: &Parameters, type_params: Option<&TypeParams>, + parameters: &Parameters, + returns: Option<&Expr>, ) { // Given, e.g., `def foo(self: _S, arg: bytes) -> _T`, extract `_T`. let Some(returns) = returns else { @@ -75,10 +86,10 @@ pub(crate) fn custom_type_var_return_type( }; // Given, e.g., `def foo(self: _S, arg: bytes)`, extract `_S`. - let Some(self_or_cls_annotation) = args + let Some(self_or_cls_annotation) = parameters .posonlyargs .iter() - .chain(&args.args) + .chain(¶meters.args) .next() .and_then(|parameter_with_default| parameter_with_default.parameter.annotation.as_ref()) else { @@ -115,12 +126,7 @@ pub(crate) fn custom_type_var_return_type( }; if method.uses_custom_var() { - checker.diagnostics.push(Diagnostic::new( - CustomTypeVarReturnType { - method_name: name.to_string(), - }, - returns.range(), - )); + add_diagnostic(checker, name.to_string(), type_params, parameters, returns); } } @@ -147,8 +153,8 @@ struct ClassMethod<'a> { } impl<'a> ClassMethod<'a> { - /// Returns `true` if the class method is annotated with a custom `TypeVar` that is likely - /// private. + /// Returns `true` if the class method is annotated with + /// a custom `TypeVar` that is likely private. fn uses_custom_var(&self) -> bool { let Expr::Subscript(ast::ExprSubscript { slice, value, .. }) = self.cls_annotation else { return false; @@ -188,8 +194,8 @@ struct InstanceMethod<'a> { } impl<'a> InstanceMethod<'a> { - /// Returns `true` if the instance method is annotated with a custom `TypeVar` that is likely - /// private. + /// Returns `true` if the instance method is annotated with + /// a custom `TypeVar` that is likely private. fn uses_custom_var(&self) -> bool { let Expr::Name(ast::ExprName { id: first_arg_type, .. @@ -230,3 +236,170 @@ fn is_likely_private_typevar(type_var_name: &str, type_params: Option<&TypeParam }) }) } + +fn add_diagnostic( + checker: &mut Checker, + method_name: String, + type_params: Option<&TypeParams>, + parameters: &Parameters, + returns: &Expr, +) { + let in_stub = checker.source_type.is_stub(); + + let mut diagnostic = Diagnostic::new( + CustomTypeVarReturnType { + method_name, + in_stub, + }, + returns.range(), + ); + + // See `replace_custom_typevar_with_self`'s doc comment + if in_stub { + let fix = replace_custom_typevar_with_self(checker, type_params, parameters, returns); + diagnostic.set_fix(fix); + } + + checker.diagnostics.push(diagnostic); +} + +/// Add a "Replace with `Self`" fix that does the following: +/// +/// * Import `Self` if necessary +/// * Remove the first parameter's annotation +/// * Replace the return annotation with `Self` +/// * Replace other uses of the original `TypeVar` elsewhere in the signature with `Self` +/// * Remove that `TypeVar` from the PEP 695 type parameter list. +/// +/// This fix cannot be suggested for non-stubs, +/// as a non-stub fix would have to deal with +/// references in body/at runtime as well, which is +/// substantially harder and requires a type-aware backend. +fn replace_custom_typevar_with_self( + checker: &Checker, + type_params: Option<&TypeParams>, + parameters: &Parameters, + returns: &Expr, +) -> Fix { + let typevar_name = returns.as_name_expr().unwrap().id(); + + let mut all_edits = vec![ + replace_return_annotation_with_self(returns), + remove_first_parameter_annotation(parameters) + ]; + + if let Some(edit) = import_self(checker, returns.range()) { + all_edits.push(edit); + } + + if let Some(edit) = remove_typevar_declaration(type_params, typevar_name) { + all_edits.push(edit); + } + + if let Ok(mut edits) = replace_typevar_usages_with_self(parameters, typevar_name) { + all_edits.append(&mut edits); + } + + let first = all_edits.remove(0); + let rest = all_edits; + + Fix::unsafe_edits(first, rest) +} + +fn import_self(checker: &Checker, return_range: TextRange) -> Option { + // From PYI034's fix + let target_version = checker.settings.target_version.as_tuple(); + let source_module = if target_version >= (3, 11) { + "typing" + } else { + "typing_extensions" + }; + + let (importer, semantic) = (checker.importer(), checker.semantic()); + let request = ImportRequest::import_from(source_module, "Self"); + + let position = return_range.start(); + let Ok((edit, ..)) = importer.get_or_import_symbol(&request, position, semantic) else { + return None; + }; + + Some(edit) +} + +fn remove_first_parameter_annotation(parameters: &Parameters) -> Edit { + let mut non_variadic_positional = parameters.posonlyargs.iter().chain(¶meters.args); + let first = &non_variadic_positional.next().unwrap().parameter; + + let name_end = first.name.range.end(); + let annotation_end = first.range.end(); + + Edit::deletion(name_end, annotation_end) +} + +fn replace_return_annotation_with_self(returns: &Expr) -> Edit { + Edit::range_replacement("Self".to_string(), returns.range()) +} + +fn replace_typevar_usages_with_self( + parameters: &Parameters, + typevar_name: &str, +) -> Result, ()> { + let mut edits = vec![]; + + for parameter in parameters.iter().skip(1) { + let Some(annotation) = parameter.annotation() else { + continue; + }; + let Expr::Name(name) = annotation else { + continue; + }; + + if name.id.as_str() == typevar_name { + let edit = Edit::range_replacement("Self".to_string(), annotation.range()); + edits.push(edit); + } else { + // FIXME: Handle complex annotations. + return Err(()); + } + } + + Ok(edits) +} + +fn remove_typevar_declaration(type_params: Option<&TypeParams>, name: &str) -> Option { + let is_declaration_in_question = |type_param: &&TypeParam| -> bool { + if let TypeParam::TypeVar(typevar) = type_param { + return typevar.name.as_str() == name; + }; + + return false; + }; + + let parameter_list = type_params?; + let parameters = ¶meter_list.type_params; + let first = parameters.first()?; + + if parameter_list.len() == 1 && is_declaration_in_question(&first) { + return Some(Edit::range_deletion(parameter_list.range)); + } + + let Some((index, ..)) = parameters.iter().find_position(is_declaration_in_question) else { + return None; + }; + + let typevar_range = parameters[index].as_type_var().unwrap().range(); + let last_index = parameters.len() - 1; + + let range = match (0..last_index).contains(&index) { + true => { + let next_range = parameters[index + 1].range(); + TextRange::new(typevar_range.start(), next_range.start()) + } + false => { + let previous_range = parameters[index - 1].range(); + TextRange::new(previous_range.end(), typevar_range.start()) + } + }; + + Some(Edit::range_deletion(range)) +} diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap index eaa6a4bcc533b..99cd93ba852c5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap @@ -1,34 +1,35 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +snapshot_kind: text --- -PYI019.py:7:62: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:7:62: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 6 | class BadClass: 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 | ^^ PYI019 | -PYI019.py:10:54: PYI019 Methods like `bad_instance_method` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:10:54: PYI019 Methods like `bad_instance_method` should return `Self` instead of a custom `TypeVar` | 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 | ^^ PYI019 | -PYI019.py:14:54: PYI019 Methods like `bad_class_method` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:14:54: PYI019 Methods like `bad_class_method` should return `Self` instead of a custom `TypeVar` | 13 | @classmethod 14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 | ^^ PYI019 | -PYI019.py:18:55: PYI019 Methods like `bad_posonly_class_method` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:18:55: PYI019 Methods like `bad_posonly_class_method` should return `Self` instead of a custom `TypeVar` | 17 | @classmethod 18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 | ^^ PYI019 | -PYI019.py:39:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:39:63: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 37 | # Python > 3.12 38 | class PEP695BadDunderNew[T]: @@ -36,16 +37,142 @@ PYI019.py:39:63: PYI019 Methods like `__new__` should return `typing.Self` inste | ^ PYI019 | -PYI019.py:42:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:42:46: PYI019 Methods like `generic_instance_method` should return `Self` instead of a custom `TypeVar` | 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 | ^ PYI019 | -PYI019.py:54:32: PYI019 Methods like `foo` should return `typing.Self` instead of a custom `TypeVar` +PYI019.py:54:32: PYI019 Methods like `foo` should return `Self` instead of a custom `TypeVar` | 52 | # in the settings for this test: 53 | @foo_classmethod 54 | def foo[S](cls: type[S]) -> S: ... # PYI019 | ^ PYI019 | + +PYI019.py:59:48: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` + | +57 | # No fixes for .py +58 | class PEP695Fix: +59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +60 | +61 | def __init_subclass__[S](cls: type[S]) -> S: ... + | + +PYI019.py:61:47: PYI019 Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` + | +59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +60 | +61 | def __init_subclass__[S](cls: type[S]) -> S: ... + | ^ PYI019 +62 | +63 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | + +PYI019.py:63:43: PYI019 Methods like `__neg__` should return `Self` instead of a custom `TypeVar` + | +61 | def __init_subclass__[S](cls: type[S]) -> S: ... +62 | +63 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 +64 | +65 | def __pos__[S](self: S) -> S: ... + | + +PYI019.py:65:32: PYI019 Methods like `__pos__` should return `Self` instead of a custom `TypeVar` + | +63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +64 | +65 | def __pos__[S](self: S) -> S: ... + | ^ PYI019 +66 | +67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | + +PYI019.py:67:53: PYI019 Methods like `__add__` should return `Self` instead of a custom `TypeVar` + | +65 | def __pos__[S](self: S) -> S: ... +66 | +67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 +68 | +69 | def __sub__[S](self: S, other: S) -> S: ... + | + +PYI019.py:69:42: PYI019 Methods like `__sub__` should return `Self` instead of a custom `TypeVar` + | +67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +68 | +69 | def __sub__[S](self: S, other: S) -> S: ... + | ^ PYI019 +70 | +71 | @classmethod + | + +PYI019.py:72:59: PYI019 Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` + | +71 | @classmethod +72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +73 | +74 | @classmethod + | + +PYI019.py:75:50: PYI019 Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` + | +74 | @classmethod +75 | def class_method_unbound[S](cls: type[S]) -> S: ... + | ^ PYI019 +76 | +77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | + +PYI019.py:77:57: PYI019 Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` + | +75 | def class_method_unbound[S](cls: type[S]) -> S: ... +76 | +77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 +78 | +79 | def instance_method_unbound[S](self: S) -> S: ... + | + +PYI019.py:79:48: PYI019 Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` + | +77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +78 | +79 | def instance_method_unbound[S](self: S) -> S: ... + | ^ PYI019 +80 | +81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | + +PYI019.py:81:90: PYI019 Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` + | +79 | def instance_method_unbound[S](self: S) -> S: ... +80 | +81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 +82 | +83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | + +PYI019.py:83:81: PYI019 Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` + | +81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +82 | +83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | ^ PYI019 +84 | +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + | + +PYI019.py:85:88: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` + | +83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +84 | +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + | ^ PYI019 + | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap index b3ddd0801d1f1..5ac75733f6283 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap @@ -1,51 +1,394 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +snapshot_kind: text --- -PYI019.pyi:7:62: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar` +PYI019.pyi:7:62: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 6 | class BadClass: 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 | ^^ PYI019 | + = help: Replace with `Self` -PYI019.pyi:10:54: PYI019 Methods like `bad_instance_method` should return `typing.Self` instead of a custom `TypeVar` +ℹ Unsafe fix +4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) +5 5 | +6 6 | class BadClass: +7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 + 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 +8 8 | +9 9 | +10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + +PYI019.pyi:10:54: PYI019 [*] Methods like `bad_instance_method` should return `Self` instead of a custom `TypeVar` | 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 | ^^ PYI019 | + = help: Replace with `Self` + +ℹ Unsafe fix +7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 +8 8 | +9 9 | +10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 +11 11 | +12 12 | +13 13 | @classmethod -PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `typing.Self` instead of a custom `TypeVar` +PYI019.pyi:14:54: PYI019 [*] Methods like `bad_class_method` should return `Self` instead of a custom `TypeVar` | 13 | @classmethod 14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 | ^^ PYI019 | + = help: Replace with `Self` -PYI019.pyi:18:55: PYI019 Methods like `bad_posonly_class_method` should return `typing.Self` instead of a custom `TypeVar` +ℹ Unsafe fix +11 11 | +12 12 | +13 13 | @classmethod +14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 + 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 +15 15 | +16 16 | +17 17 | @classmethod + +PYI019.pyi:18:55: PYI019 [*] Methods like `bad_posonly_class_method` should return `Self` instead of a custom `TypeVar` | 17 | @classmethod 18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 | ^^ PYI019 | + = help: Replace with `Self` + +ℹ Unsafe fix +15 15 | +16 16 | +17 17 | @classmethod +18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 + 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 +19 19 | +20 20 | +21 21 | @classmethod -PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar` +PYI019.pyi:39:63: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 37 | # Python > 3.12 38 | class PEP695BadDunderNew[T]: 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 | ^ PYI019 | + = help: Replace with `Self` -PYI019.pyi:42:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar` +ℹ Unsafe fix +36 36 | +37 37 | # Python > 3.12 +38 38 | class PEP695BadDunderNew[T]: +39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 + 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 +40 40 | +41 41 | +42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 + +PYI019.pyi:42:46: PYI019 [*] Methods like `generic_instance_method` should return `Self` instead of a custom `TypeVar` | 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 | ^ PYI019 | + = help: Replace with `Self` + +ℹ Unsafe fix +39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 +40 40 | +41 41 | +42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 + 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 +43 43 | +44 44 | +45 45 | class PEP695GoodDunderNew[T]: -PYI019.pyi:54:32: PYI019 Methods like `foo` should return `typing.Self` instead of a custom `TypeVar` +PYI019.pyi:54:32: PYI019 [*] Methods like `foo` should return `Self` instead of a custom `TypeVar` | 52 | # in the settings for this test: 53 | @foo_classmethod 54 | def foo[S](cls: type[S]) -> S: ... # PYI019 | ^ PYI019 | + = help: Replace with `Self` + +ℹ Unsafe fix +51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` +52 52 | # in the settings for this test: +53 53 | @foo_classmethod +54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 + 54 |+ def foo(cls) -> Self: ... # PYI019 +55 55 | +56 56 | +57 57 | # Only .pyi gets fixes + +PYI019.pyi:59:48: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` + | +57 | # Only .pyi gets fixes +58 | class PEP695Fix: +59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +60 | +61 | def __init_subclass__[S](cls: type[S]) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +56 56 | +57 57 | # Only .pyi gets fixes +58 58 | class PEP695Fix: +59 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + 59 |+ def __new__(cls) -> Self: ... +60 60 | +61 61 | def __init_subclass__[S](cls: type[S]) -> S: ... +62 62 | + +PYI019.pyi:61:47: PYI019 [*] Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` + | +59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +60 | +61 | def __init_subclass__[S](cls: type[S]) -> S: ... + | ^ PYI019 +62 | +63 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +58 58 | class PEP695Fix: +59 59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +60 60 | +61 |- def __init_subclass__[S](cls: type[S]) -> S: ... + 61 |+ def __init_subclass__(cls) -> Self: ... +62 62 | +63 63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +64 64 | + +PYI019.pyi:63:43: PYI019 [*] Methods like `__neg__` should return `Self` instead of a custom `TypeVar` + | +61 | def __init_subclass__[S](cls: type[S]) -> S: ... +62 | +63 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 +64 | +65 | def __pos__[S](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +60 60 | +61 61 | def __init_subclass__[S](cls: type[S]) -> S: ... +62 62 | +63 |- def __neg__[S: PEP695Fix](self: S) -> S: ... + 63 |+ def __neg__(self) -> Self: ... +64 64 | +65 65 | def __pos__[S](self: S) -> S: ... +66 66 | + +PYI019.pyi:65:32: PYI019 [*] Methods like `__pos__` should return `Self` instead of a custom `TypeVar` + | +63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +64 | +65 | def __pos__[S](self: S) -> S: ... + | ^ PYI019 +66 | +67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +62 62 | +63 63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +64 64 | +65 |- def __pos__[S](self: S) -> S: ... + 65 |+ def __pos__(self) -> Self: ... +66 66 | +67 67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +68 68 | + +PYI019.pyi:67:53: PYI019 [*] Methods like `__add__` should return `Self` instead of a custom `TypeVar` + | +65 | def __pos__[S](self: S) -> S: ... +66 | +67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 +68 | +69 | def __sub__[S](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +64 64 | +65 65 | def __pos__[S](self: S) -> S: ... +66 66 | +67 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + 67 |+ def __add__(self, other: Self) -> Self: ... +68 68 | +69 69 | def __sub__[S](self: S, other: S) -> S: ... +70 70 | + +PYI019.pyi:69:42: PYI019 [*] Methods like `__sub__` should return `Self` instead of a custom `TypeVar` + | +67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +68 | +69 | def __sub__[S](self: S, other: S) -> S: ... + | ^ PYI019 +70 | +71 | @classmethod + | + = help: Replace with `Self` + +ℹ Unsafe fix +66 66 | +67 67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +68 68 | +69 |- def __sub__[S](self: S, other: S) -> S: ... + 69 |+ def __sub__(self, other: Self) -> Self: ... +70 70 | +71 71 | @classmethod +72 72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + +PYI019.pyi:72:59: PYI019 [*] Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` + | +71 | @classmethod +72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +73 | +74 | @classmethod + | + = help: Replace with `Self` + +ℹ Unsafe fix +69 69 | def __sub__[S](self: S, other: S) -> S: ... +70 70 | +71 71 | @classmethod +72 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + 72 |+ def class_method_bound(cls) -> Self: ... +73 73 | +74 74 | @classmethod +75 75 | def class_method_unbound[S](cls: type[S]) -> S: ... + +PYI019.pyi:75:50: PYI019 [*] Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` + | +74 | @classmethod +75 | def class_method_unbound[S](cls: type[S]) -> S: ... + | ^ PYI019 +76 | +77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +72 72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... +73 73 | +74 74 | @classmethod +75 |- def class_method_unbound[S](cls: type[S]) -> S: ... + 75 |+ def class_method_unbound(cls) -> Self: ... +76 76 | +77 77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +78 78 | + +PYI019.pyi:77:57: PYI019 [*] Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` + | +75 | def class_method_unbound[S](cls: type[S]) -> S: ... +76 | +77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 +78 | +79 | def instance_method_unbound[S](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +74 74 | @classmethod +75 75 | def class_method_unbound[S](cls: type[S]) -> S: ... +76 76 | +77 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + 77 |+ def instance_method_bound(self) -> Self: ... +78 78 | +79 79 | def instance_method_unbound[S](self: S) -> S: ... +80 80 | + +PYI019.pyi:79:48: PYI019 [*] Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` + | +77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +78 | +79 | def instance_method_unbound[S](self: S) -> S: ... + | ^ PYI019 +80 | +81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +76 76 | +77 77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +78 78 | +79 |- def instance_method_unbound[S](self: S) -> S: ... + 79 |+ def instance_method_unbound(self) -> Self: ... +80 80 | +81 81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +82 82 | + +PYI019.pyi:81:90: PYI019 [*] Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` + | +79 | def instance_method_unbound[S](self: S) -> S: ... +80 | +81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 +82 | +83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +78 78 | +79 79 | def instance_method_unbound[S](self: S) -> S: ... +80 80 | +81 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + 81 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... +82 82 | +83 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +84 84 | + +PYI019.pyi:83:81: PYI019 [*] Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` + | +81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +82 | +83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | ^ PYI019 +84 | +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + | + = help: Replace with `Self` + +ℹ Unsafe fix +80 80 | +81 81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +82 82 | +83 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + 83 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... +84 84 | +85 85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + +PYI019.pyi:85:88: PYI019 [*] Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` + | +83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +84 | +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + | ^ PYI019 + | + = help: Replace with `Self` + +ℹ Unsafe fix +82 82 | +83 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +84 84 | +85 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + 85 |+ def multiple_type_vars[*Ts, T](self, other: S, /, *args: *Ts, a: T, b: T) -> Self: ... From 4e1b5e7fb39dfbfb7e122c9a2bee51d61babca00 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Sun, 10 Nov 2024 03:07:22 +0000 Subject: [PATCH 2/9] `rustfmt` --- .../src/rules/flake8_pyi/rules/custom_type_var_return_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index c2cb57acd6b7e..654df3caf4d69 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -285,7 +285,7 @@ fn replace_custom_typevar_with_self( let mut all_edits = vec![ replace_return_annotation_with_self(returns), - remove_first_parameter_annotation(parameters) + remove_first_parameter_annotation(parameters), ]; if let Some(edit) = import_self(checker, returns.range()) { From ab7bf2223dbd6f397f762b8bb0b83c03bf9a1d5c Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Sun, 10 Nov 2024 03:13:01 +0000 Subject: [PATCH 3/9] Clippy --- .../rules/custom_type_var_return_type.rs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 654df3caf4d69..665a34c221f74 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -372,7 +372,7 @@ fn remove_typevar_declaration(type_params: Option<&TypeParams>, name: &str) -> O return typevar.name.as_str() == name; }; - return false; + false }; let parameter_list = type_params?; @@ -383,22 +383,19 @@ fn remove_typevar_declaration(type_params: Option<&TypeParams>, name: &str) -> O return Some(Edit::range_deletion(parameter_list.range)); } - let Some((index, ..)) = parameters.iter().find_position(is_declaration_in_question) else { - return None; - }; + let (index, ..) = parameters + .iter() + .find_position(is_declaration_in_question)?; let typevar_range = parameters[index].as_type_var().unwrap().range(); let last_index = parameters.len() - 1; - let range = match (0..last_index).contains(&index) { - true => { - let next_range = parameters[index + 1].range(); - TextRange::new(typevar_range.start(), next_range.start()) - } - false => { - let previous_range = parameters[index - 1].range(); - TextRange::new(previous_range.end(), typevar_range.start()) - } + let range = if (0..last_index).contains(&index) { + let next_range = parameters[index + 1].range(); + TextRange::new(typevar_range.start(), next_range.start()) + } else { + let previous_range = parameters[index - 1].range(); + TextRange::new(previous_range.end(), typevar_range.start()) }; Some(Edit::range_deletion(range)) From 3adf07207a15e66e6acfdc46f47941de2e99c528 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Mon, 11 Nov 2024 12:59:20 +0000 Subject: [PATCH 4/9] Address review --- .../test/fixtures/flake8_pyi/PYI019.py | 2 +- .../test/fixtures/flake8_pyi/PYI019.pyi | 2 +- .../rules/custom_type_var_return_type.rs | 52 ++++++++++++------- ...__flake8_pyi__tests__PYI019_PYI019.py.snap | 8 +-- ..._flake8_pyi__tests__PYI019_PYI019.pyi.snap | 44 ++++++++-------- 5 files changed, 62 insertions(+), 46 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py index c279a8d05cf40..b009f8f8fe57b 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py @@ -82,4 +82,4 @@ def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi index e323fe2613eb1..790b20a204a85 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi @@ -82,4 +82,4 @@ class PEP695Fix: def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... + def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 665a34c221f74..5e190afbed249 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -1,7 +1,7 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, 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; @@ -268,13 +268,16 @@ fn add_diagnostic( /// * Import `Self` if necessary /// * Remove the first parameter's annotation /// * Replace the return annotation with `Self` -/// * Replace other uses of the original `TypeVar` elsewhere in the signature with `Self` -/// * Remove that `TypeVar` from the PEP 695 type parameter list. +/// * Replace other uses of the original type variable elsewhere in the signature with `Self` +/// * Remove that type variable from the PEP 695 type parameter list /// /// This fix cannot be suggested for non-stubs, -/// as a non-stub fix would have to deal with -/// references in body/at runtime as well, which is -/// substantially harder and requires a type-aware backend. +/// as a non-stub fix would have to deal with references in body/at runtime as well, +/// which is substantially harder and requires a type-aware backend. +/// +/// The fourth step above has the same problem. +/// This function thus only does replacements for the simplest of cases +/// and will mark the fix as unsafe if an annotation cannot be handled. fn replace_custom_typevar_with_self( checker: &Checker, type_params: Option<&TypeParams>, @@ -282,6 +285,7 @@ fn replace_custom_typevar_with_self( returns: &Expr, ) -> Fix { let typevar_name = returns.as_name_expr().unwrap().id(); + let mut fix_applicability = Applicability::Safe; let mut all_edits = vec![ replace_return_annotation_with_self(returns), @@ -296,14 +300,23 @@ fn replace_custom_typevar_with_self( all_edits.push(edit); } - if let Ok(mut edits) = replace_typevar_usages_with_self(parameters, typevar_name) { - all_edits.append(&mut edits); + match replace_typevar_usages_with_self(parameters, typevar_name) { + Ok(mut edits) => { + all_edits.append(&mut edits) + } + Err(mut edits) => { + fix_applicability = Applicability::Unsafe; + all_edits.append(&mut edits) + } } - let first = all_edits.remove(0); - let rest = all_edits; + let (first, rest) = (all_edits.remove(0), all_edits); - Fix::unsafe_edits(first, rest) + if fix_applicability == Applicability::Safe { + Fix::safe_edits(first, rest) + } else { + Fix::unsafe_edits(first, rest) + } } fn import_self(checker: &Checker, return_range: TextRange) -> Option { @@ -319,9 +332,7 @@ fn import_self(checker: &Checker, return_range: TextRange) -> Option { let request = ImportRequest::import_from(source_module, "Self"); let position = return_range.start(); - let Ok((edit, ..)) = importer.get_or_import_symbol(&request, position, semantic) else { - return None; - }; + let (edit, ..) = importer.get_or_import_symbol(&request, position, semantic).ok()?; Some(edit) } @@ -343,8 +354,9 @@ fn replace_return_annotation_with_self(returns: &Expr) -> Edit { fn replace_typevar_usages_with_self( parameters: &Parameters, typevar_name: &str, -) -> Result, ()> { +) -> Result, Vec> { let mut edits = vec![]; + let mut could_not_handle_all_usages = false; for parameter in parameters.iter().skip(1) { let Some(annotation) = parameter.annotation() else { @@ -358,12 +370,16 @@ fn replace_typevar_usages_with_self( let edit = Edit::range_replacement("Self".to_string(), annotation.range()); edits.push(edit); } else { - // FIXME: Handle complex annotations. - return Err(()); + // Bail for complex cases + could_not_handle_all_usages = true; } } - Ok(edits) + if could_not_handle_all_usages { + Err(edits) + } else { + Ok(edits) + } } fn remove_typevar_declaration(type_params: Option<&TypeParams>, name: &str) -> Option { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap index 99cd93ba852c5..a9f4fc72650aa 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap @@ -166,13 +166,13 @@ PYI019.py:83:81: PYI019 Methods like `instance_method_unbound_with_another_param 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... | ^ PYI019 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... | -PYI019.py:85:88: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` +PYI019.py:85:94: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` | 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... - | ^ PYI019 +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + | ^ PYI019 | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap index 5ac75733f6283..76b32ba369b07 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap @@ -63,7 +63,7 @@ PYI019.pyi:18:55: PYI019 [*] Methods like `bad_posonly_class_method` should retu | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 15 15 | 16 16 | 17 17 | @classmethod @@ -99,7 +99,7 @@ PYI019.pyi:42:46: PYI019 [*] Methods like `generic_instance_method` should retur | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 40 40 | 41 41 | @@ -118,7 +118,7 @@ PYI019.pyi:54:32: PYI019 [*] Methods like `foo` should return `Self` instead of | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` 52 52 | # in the settings for this test: 53 53 | @foo_classmethod @@ -139,7 +139,7 @@ PYI019.pyi:59:48: PYI019 [*] Methods like `__new__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 56 56 | 57 57 | # Only .pyi gets fixes 58 58 | class PEP695Fix: @@ -160,7 +160,7 @@ PYI019.pyi:61:47: PYI019 [*] Methods like `__init_subclass__` should return `Sel | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 58 58 | class PEP695Fix: 59 59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 60 60 | @@ -181,7 +181,7 @@ PYI019.pyi:63:43: PYI019 [*] Methods like `__neg__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 60 60 | 61 61 | def __init_subclass__[S](cls: type[S]) -> S: ... 62 62 | @@ -202,7 +202,7 @@ PYI019.pyi:65:32: PYI019 [*] Methods like `__pos__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 62 62 | 63 63 | def __neg__[S: PEP695Fix](self: S) -> S: ... 64 64 | @@ -223,7 +223,7 @@ PYI019.pyi:67:53: PYI019 [*] Methods like `__add__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 64 64 | 65 65 | def __pos__[S](self: S) -> S: ... 66 66 | @@ -244,7 +244,7 @@ PYI019.pyi:69:42: PYI019 [*] Methods like `__sub__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 66 66 | 67 67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... 68 68 | @@ -264,7 +264,7 @@ PYI019.pyi:72:59: PYI019 [*] Methods like `class_method_bound` should return `Se | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 69 69 | def __sub__[S](self: S, other: S) -> S: ... 70 70 | 71 71 | @classmethod @@ -284,7 +284,7 @@ PYI019.pyi:75:50: PYI019 [*] Methods like `class_method_unbound` should return ` | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 72 72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... 73 73 | 74 74 | @classmethod @@ -305,7 +305,7 @@ PYI019.pyi:77:57: PYI019 [*] Methods like `instance_method_bound` should return | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 74 74 | @classmethod 75 75 | def class_method_unbound[S](cls: type[S]) -> S: ... 76 76 | @@ -326,7 +326,7 @@ PYI019.pyi:79:48: PYI019 [*] Methods like `instance_method_unbound` should retur | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 76 76 | 77 77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 78 78 | @@ -347,7 +347,7 @@ PYI019.pyi:81:90: PYI019 [*] Methods like `instance_method_bound_with_another_pa | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 78 78 | 79 79 | def instance_method_unbound[S](self: S) -> S: ... 80 80 | @@ -364,25 +364,25 @@ PYI019.pyi:83:81: PYI019 [*] Methods like `instance_method_unbound_with_another_ 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... | ^ PYI019 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 80 80 | 81 81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 82 82 | 83 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... 83 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... 84 84 | -85 85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... +85 85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -PYI019.pyi:85:88: PYI019 [*] Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:85:94: PYI019 [*] Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` | 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... - | ^ PYI019 +85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + | ^ PYI019 | = help: Replace with `Self` @@ -390,5 +390,5 @@ PYI019.pyi:85:88: PYI019 [*] Methods like `multiple_type_vars` should return `Se 82 82 | 83 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... 84 84 | -85 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: T) -> S: ... - 85 |+ def multiple_type_vars[*Ts, T](self, other: S, /, *args: *Ts, a: T, b: T) -> Self: ... +85 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + 85 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... From fe2a7776386eaa2aa17677b49f3310d79af73fc6 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Mon, 11 Nov 2024 13:05:04 +0000 Subject: [PATCH 5/9] Fix `PYI034`'s `import_self` too --- .../rules/flake8_pyi/rules/custom_type_var_return_type.rs | 8 ++++---- .../src/rules/flake8_pyi/rules/non_self_return_type.rs | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 5e190afbed249..38a847a3a2843 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -301,9 +301,7 @@ fn replace_custom_typevar_with_self( } match replace_typevar_usages_with_self(parameters, typevar_name) { - Ok(mut edits) => { - all_edits.append(&mut edits) - } + Ok(mut edits) => all_edits.append(&mut edits), Err(mut edits) => { fix_applicability = Applicability::Unsafe; all_edits.append(&mut edits) @@ -332,7 +330,9 @@ fn import_self(checker: &Checker, return_range: TextRange) -> Option { let request = ImportRequest::import_from(source_module, "Self"); let position = return_range.start(); - let (edit, ..) = importer.get_or_import_symbol(&request, position, semantic).ok()?; + let (edit, ..) = importer + .get_or_import_symbol(&request, position, semantic) + .ok()?; Some(edit) } 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 a0b772e8b51e3..eb67537f77e25 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 @@ -200,10 +200,9 @@ fn add_diagnostic( let (importer, semantic) = (checker.importer(), checker.semantic()); let request = ImportRequest::import_from(source_module, "Self"); - let Ok((edit, ..)) = importer.get_or_import_symbol(&request, range.start(), semantic) - else { - return None; - }; + let (edit, ..) = importer + .get_or_import_symbol(&request, range.start(), semantic) + .ok()?; Some(edit) } From fff3265fbfaa65109e497249e9fce7a55f13fbb8 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Mon, 11 Nov 2024 14:19:32 +0000 Subject: [PATCH 6/9] Fix problems per review --- .../test/fixtures/flake8_pyi/PYI019.py | 6 +- .../test/fixtures/flake8_pyi/PYI019.pyi | 6 +- .../rules/custom_type_var_return_type.rs | 56 +-- ...__flake8_pyi__tests__PYI019_PYI019.py.snap | 152 ++++---- ..._flake8_pyi__tests__PYI019_PYI019.pyi.snap | 333 ++++++++++-------- 5 files changed, 298 insertions(+), 255 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py index b009f8f8fe57b..e6fd279b14605 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py @@ -54,7 +54,9 @@ class CustomClassMethod: def foo[S](cls: type[S]) -> S: ... # PYI019 -# No fixes for .py +_S695 = TypeVar("_S695", bound="PEP695Fix") + +# Only .pyi gets fixes, no fixes for .py class PEP695Fix: def __new__[S: PEP695Fix](cls: type[S]) -> S: ... @@ -83,3 +85,5 @@ def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + + def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi index 790b20a204a85..e6fd279b14605 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi @@ -54,7 +54,9 @@ class CustomClassMethod: def foo[S](cls: type[S]) -> S: ... # PYI019 -# Only .pyi gets fixes +_S695 = TypeVar("_S695", bound="PEP695Fix") + +# Only .pyi gets fixes, no fixes for .py class PEP695Fix: def __new__[S: PEP695Fix](cls: type[S]) -> S: ... @@ -83,3 +85,5 @@ class PEP695Fix: def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + + def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 38a847a3a2843..6c844ac88b750 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -256,8 +256,11 @@ fn add_diagnostic( // See `replace_custom_typevar_with_self`'s doc comment if in_stub { - let fix = replace_custom_typevar_with_self(checker, type_params, parameters, returns); - diagnostic.set_fix(fix); + if let Some(fix) = + replace_custom_typevar_with_self(checker, type_params, parameters, returns) + { + diagnostic.set_fix(fix); + } } checker.diagnostics.push(diagnostic); @@ -283,37 +286,32 @@ fn replace_custom_typevar_with_self( type_params: Option<&TypeParams>, parameters: &Parameters, returns: &Expr, -) -> Fix { +) -> Option { + // The return annotation is guaranteed to be a name, + // as verified by `uses_custom_var()`. let typevar_name = returns.as_name_expr().unwrap().id(); - let mut fix_applicability = Applicability::Safe; let mut all_edits = vec![ replace_return_annotation_with_self(returns), remove_first_parameter_annotation(parameters), ]; - if let Some(edit) = import_self(checker, returns.range()) { - all_edits.push(edit); - } + let edit = import_self(checker, returns.range())?; + all_edits.push(edit); if let Some(edit) = remove_typevar_declaration(type_params, typevar_name) { all_edits.push(edit); } - match replace_typevar_usages_with_self(parameters, typevar_name) { - Ok(mut edits) => all_edits.append(&mut edits), - Err(mut edits) => { - fix_applicability = Applicability::Unsafe; - all_edits.append(&mut edits) - } - } + let (mut edits, fix_applicability) = replace_typevar_usages_with_self(parameters, typevar_name); + all_edits.append(&mut edits); - let (first, rest) = (all_edits.remove(0), all_edits); + let (first, rest) = (all_edits.swap_remove(0), all_edits); - if fix_applicability == Applicability::Safe { - Fix::safe_edits(first, rest) - } else { - Fix::unsafe_edits(first, rest) + match fix_applicability { + Applicability::DisplayOnly => Some(Fix::display_only_edits(first, rest)), + Applicability::Unsafe => Some(Fix::unsafe_edits(first, rest)), + Applicability::Safe => Some(Fix::safe_edits(first, rest)), } } @@ -338,6 +336,8 @@ fn import_self(checker: &Checker, return_range: TextRange) -> Option { } fn remove_first_parameter_annotation(parameters: &Parameters) -> Edit { + // The first parameter is guaranteed to be `self`/`cls`, + // as verified by `uses_custom_var()`. let mut non_variadic_positional = parameters.posonlyargs.iter().chain(¶meters.args); let first = &non_variadic_positional.next().unwrap().parameter; @@ -354,7 +354,7 @@ fn replace_return_annotation_with_self(returns: &Expr) -> Edit { fn replace_typevar_usages_with_self( parameters: &Parameters, typevar_name: &str, -) -> Result, Vec> { +) -> (Vec, Applicability) { let mut edits = vec![]; let mut could_not_handle_all_usages = false; @@ -363,6 +363,7 @@ fn replace_typevar_usages_with_self( continue; }; let Expr::Name(name) = annotation else { + could_not_handle_all_usages = true; continue; }; @@ -370,15 +371,14 @@ fn replace_typevar_usages_with_self( let edit = Edit::range_replacement("Self".to_string(), annotation.range()); edits.push(edit); } else { - // Bail for complex cases could_not_handle_all_usages = true; } } if could_not_handle_all_usages { - Err(edits) + (edits, Applicability::DisplayOnly) } else { - Ok(edits) + (edits, Applicability::Safe) } } @@ -399,17 +399,21 @@ fn remove_typevar_declaration(type_params: Option<&TypeParams>, name: &str) -> O return Some(Edit::range_deletion(parameter_list.range)); } - let (index, ..) = parameters + let (index, declaration) = parameters .iter() .find_position(is_declaration_in_question)?; - let typevar_range = parameters[index].as_type_var().unwrap().range(); + let typevar_range = declaration.range(); let last_index = parameters.len() - 1; - let range = if (0..last_index).contains(&index) { + let range = if index < last_index { + // [A, B, C] + // ^^^ Remove this let next_range = parameters[index + 1].range(); TextRange::new(typevar_range.start(), next_range.start()) } else { + // [A, B, C] + // ^^^ Remove this let previous_range = parameters[index - 1].range(); TextRange::new(previous_range.end(), typevar_range.start()) }; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap index a9f4fc72650aa..d073dea248930 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.py.snap @@ -51,128 +51,138 @@ PYI019.py:54:32: PYI019 Methods like `foo` should return `Self` instead of a cus | ^ PYI019 | -PYI019.py:59:48: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` +PYI019.py:61:48: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | -57 | # No fixes for .py -58 | class PEP695Fix: -59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +59 | # Only .pyi gets fixes, no fixes for .py +60 | class PEP695Fix: +61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... | ^ PYI019 -60 | -61 | def __init_subclass__[S](cls: type[S]) -> S: ... - | - -PYI019.py:61:47: PYI019 Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` - | -59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -60 | -61 | def __init_subclass__[S](cls: type[S]) -> S: ... - | ^ PYI019 62 | -63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +63 | def __init_subclass__[S](cls: type[S]) -> S: ... | -PYI019.py:63:43: PYI019 Methods like `__neg__` should return `Self` instead of a custom `TypeVar` +PYI019.py:63:47: PYI019 Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` | -61 | def __init_subclass__[S](cls: type[S]) -> S: ... +61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 62 | -63 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 +63 | def __init_subclass__[S](cls: type[S]) -> S: ... + | ^ PYI019 64 | -65 | def __pos__[S](self: S) -> S: ... +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... | -PYI019.py:65:32: PYI019 Methods like `__pos__` should return `Self` instead of a custom `TypeVar` +PYI019.py:65:43: PYI019 Methods like `__neg__` should return `Self` instead of a custom `TypeVar` | -63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 | -65 | def __pos__[S](self: S) -> S: ... - | ^ PYI019 +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 66 | -67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +67 | def __pos__[S](self: S) -> S: ... | -PYI019.py:67:53: PYI019 Methods like `__add__` should return `Self` instead of a custom `TypeVar` +PYI019.py:67:32: PYI019 Methods like `__pos__` should return `Self` instead of a custom `TypeVar` | -65 | def __pos__[S](self: S) -> S: ... +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 | -67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 +67 | def __pos__[S](self: S) -> S: ... + | ^ PYI019 68 | -69 | def __sub__[S](self: S, other: S) -> S: ... +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... | -PYI019.py:69:42: PYI019 Methods like `__sub__` should return `Self` instead of a custom `TypeVar` +PYI019.py:69:53: PYI019 Methods like `__add__` should return `Self` instead of a custom `TypeVar` | -67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +67 | def __pos__[S](self: S) -> S: ... 68 | -69 | def __sub__[S](self: S, other: S) -> S: ... - | ^ PYI019 +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 70 | -71 | @classmethod +71 | def __sub__[S](self: S, other: S) -> S: ... | -PYI019.py:72:59: PYI019 Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` +PYI019.py:71:42: PYI019 Methods like `__sub__` should return `Self` instead of a custom `TypeVar` | -71 | @classmethod -72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - | ^ PYI019 -73 | -74 | @classmethod +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 | +71 | def __sub__[S](self: S, other: S) -> S: ... + | ^ PYI019 +72 | +73 | @classmethod | -PYI019.py:75:50: PYI019 Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` +PYI019.py:74:59: PYI019 Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` | -74 | @classmethod -75 | def class_method_unbound[S](cls: type[S]) -> S: ... - | ^ PYI019 -76 | -77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +73 | @classmethod +74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +75 | +76 | @classmethod | -PYI019.py:77:57: PYI019 Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` +PYI019.py:77:50: PYI019 Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` | -75 | def class_method_unbound[S](cls: type[S]) -> S: ... -76 | -77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 +76 | @classmethod +77 | def class_method_unbound[S](cls: type[S]) -> S: ... + | ^ PYI019 78 | -79 | def instance_method_unbound[S](self: S) -> S: ... +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... | -PYI019.py:79:48: PYI019 Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` +PYI019.py:79:57: PYI019 Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` | -77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +77 | def class_method_unbound[S](cls: type[S]) -> S: ... 78 | -79 | def instance_method_unbound[S](self: S) -> S: ... - | ^ PYI019 +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 80 | -81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +81 | def instance_method_unbound[S](self: S) -> S: ... | -PYI019.py:81:90: PYI019 Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` +PYI019.py:81:48: PYI019 Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` | -79 | def instance_method_unbound[S](self: S) -> S: ... +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 | -81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 +81 | def instance_method_unbound[S](self: S) -> S: ... + | ^ PYI019 82 | -83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... | -PYI019.py:83:81: PYI019 Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` +PYI019.py:83:90: PYI019 Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` | -81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +81 | def instance_method_unbound[S](self: S) -> S: ... 82 | -83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | ^ PYI019 +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... | -PYI019.py:85:94: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` +PYI019.py:85:81: PYI019 Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` | -83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | ^ PYI019 +86 | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + | + +PYI019.py:87:94: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` + | +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... | ^ PYI019 +88 | +89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + | + +PYI019.py:89:75: PYI019 Methods like `mixing_old_and_new_style_type_vars` should return `Self` instead of a custom `TypeVar` + | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 | +89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + | ^^^^^ PYI019 | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap index 76b32ba369b07..57ed22c125195 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap @@ -2,7 +2,7 @@ source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs snapshot_kind: text --- -PYI019.pyi:7:62: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:7:62: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 6 | class BadClass: 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 @@ -10,7 +10,7 @@ PYI019.pyi:7:62: PYI019 [*] Methods like `__new__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Display-only fix 4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) 5 5 | 6 6 | class BadClass: @@ -20,14 +20,14 @@ PYI019.pyi:7:62: PYI019 [*] Methods like `__new__` should return `Self` instead 9 9 | 10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 -PYI019.pyi:10:54: PYI019 [*] Methods like `bad_instance_method` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:10:54: PYI019 Methods like `bad_instance_method` should return `Self` instead of a custom `TypeVar` | 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 | ^^ PYI019 | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Display-only fix 7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 8 8 | 9 9 | @@ -37,7 +37,7 @@ PYI019.pyi:10:54: PYI019 [*] Methods like `bad_instance_method` should return `S 12 12 | 13 13 | @classmethod -PYI019.pyi:14:54: PYI019 [*] Methods like `bad_class_method` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `Self` instead of a custom `TypeVar` | 13 | @classmethod 14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 @@ -45,7 +45,7 @@ PYI019.pyi:14:54: PYI019 [*] Methods like `bad_class_method` should return `Self | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Display-only fix 11 11 | 12 12 | 13 13 | @classmethod @@ -73,7 +73,7 @@ PYI019.pyi:18:55: PYI019 [*] Methods like `bad_posonly_class_method` should retu 20 20 | 21 21 | @classmethod -PYI019.pyi:39:63: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 37 | # Python > 3.12 38 | class PEP695BadDunderNew[T]: @@ -82,7 +82,7 @@ PYI019.pyi:39:63: PYI019 [*] Methods like `__new__` should return `Self` instead | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Display-only fix 36 36 | 37 37 | # Python > 3.12 38 38 | class PEP695BadDunderNew[T]: @@ -126,269 +126,290 @@ PYI019.pyi:54:32: PYI019 [*] Methods like `foo` should return `Self` instead of 54 |+ def foo(cls) -> Self: ... # PYI019 55 55 | 56 56 | -57 57 | # Only .pyi gets fixes +57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") -PYI019.pyi:59:48: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:61:48: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` | -57 | # Only .pyi gets fixes -58 | class PEP695Fix: -59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +59 | # Only .pyi gets fixes, no fixes for .py +60 | class PEP695Fix: +61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... | ^ PYI019 -60 | -61 | def __init_subclass__[S](cls: type[S]) -> S: ... - | - = help: Replace with `Self` - -ℹ Safe fix -56 56 | -57 57 | # Only .pyi gets fixes -58 58 | class PEP695Fix: -59 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - 59 |+ def __new__(cls) -> Self: ... -60 60 | -61 61 | def __init_subclass__[S](cls: type[S]) -> S: ... -62 62 | - -PYI019.pyi:61:47: PYI019 [*] Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` - | -59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -60 | -61 | def __init_subclass__[S](cls: type[S]) -> S: ... - | ^ PYI019 62 | -63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +63 | def __init_subclass__[S](cls: type[S]) -> S: ... | = help: Replace with `Self` ℹ Safe fix -58 58 | class PEP695Fix: -59 59 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -60 60 | -61 |- def __init_subclass__[S](cls: type[S]) -> S: ... - 61 |+ def __init_subclass__(cls) -> Self: ... +58 58 | +59 59 | # Only .pyi gets fixes, no fixes for .py +60 60 | class PEP695Fix: +61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + 61 |+ def __new__(cls) -> Self: ... 62 62 | -63 63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 64 | -PYI019.pyi:63:43: PYI019 [*] Methods like `__neg__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:63:47: PYI019 [*] Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` | -61 | def __init_subclass__[S](cls: type[S]) -> S: ... +61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 62 | -63 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 +63 | def __init_subclass__[S](cls: type[S]) -> S: ... + | ^ PYI019 64 | -65 | def __pos__[S](self: S) -> S: ... +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix -60 60 | -61 61 | def __init_subclass__[S](cls: type[S]) -> S: ... +60 60 | class PEP695Fix: +61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 62 62 | -63 |- def __neg__[S: PEP695Fix](self: S) -> S: ... - 63 |+ def __neg__(self) -> Self: ... +63 |- def __init_subclass__[S](cls: type[S]) -> S: ... + 63 |+ def __init_subclass__(cls) -> Self: ... 64 64 | -65 65 | def __pos__[S](self: S) -> S: ... +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 66 | -PYI019.pyi:65:32: PYI019 [*] Methods like `__pos__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:65:43: PYI019 [*] Methods like `__neg__` should return `Self` instead of a custom `TypeVar` | -63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 | -65 | def __pos__[S](self: S) -> S: ... - | ^ PYI019 +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 66 | -67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +67 | def __pos__[S](self: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix 62 62 | -63 63 | def __neg__[S: PEP695Fix](self: S) -> S: ... +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 64 | -65 |- def __pos__[S](self: S) -> S: ... - 65 |+ def __pos__(self) -> Self: ... +65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... + 65 |+ def __neg__(self) -> Self: ... 66 66 | -67 67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +67 67 | def __pos__[S](self: S) -> S: ... 68 68 | -PYI019.pyi:67:53: PYI019 [*] Methods like `__add__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:67:32: PYI019 [*] Methods like `__pos__` should return `Self` instead of a custom `TypeVar` | -65 | def __pos__[S](self: S) -> S: ... +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 | -67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 +67 | def __pos__[S](self: S) -> S: ... + | ^ PYI019 68 | -69 | def __sub__[S](self: S, other: S) -> S: ... +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix 64 64 | -65 65 | def __pos__[S](self: S) -> S: ... +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 66 | -67 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - 67 |+ def __add__(self, other: Self) -> Self: ... +67 |- def __pos__[S](self: S) -> S: ... + 67 |+ def __pos__(self) -> Self: ... 68 68 | -69 69 | def __sub__[S](self: S, other: S) -> S: ... +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... 70 70 | -PYI019.pyi:69:42: PYI019 [*] Methods like `__sub__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:69:53: PYI019 [*] Methods like `__add__` should return `Self` instead of a custom `TypeVar` | -67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +67 | def __pos__[S](self: S) -> S: ... 68 | -69 | def __sub__[S](self: S, other: S) -> S: ... - | ^ PYI019 +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 70 | -71 | @classmethod +71 | def __sub__[S](self: S, other: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix 66 66 | -67 67 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +67 67 | def __pos__[S](self: S) -> S: ... 68 68 | -69 |- def __sub__[S](self: S, other: S) -> S: ... - 69 |+ def __sub__(self, other: Self) -> Self: ... +69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + 69 |+ def __add__(self, other: Self) -> Self: ... 70 70 | -71 71 | @classmethod -72 72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | -PYI019.pyi:72:59: PYI019 [*] Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:71:42: PYI019 [*] Methods like `__sub__` should return `Self` instead of a custom `TypeVar` | -71 | @classmethod -72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - | ^ PYI019 -73 | -74 | @classmethod +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 | +71 | def __sub__[S](self: S, other: S) -> S: ... + | ^ PYI019 +72 | +73 | @classmethod | = help: Replace with `Self` ℹ Safe fix -69 69 | def __sub__[S](self: S, other: S) -> S: ... +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... 70 70 | -71 71 | @classmethod -72 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - 72 |+ def class_method_bound(cls) -> Self: ... -73 73 | -74 74 | @classmethod -75 75 | def class_method_unbound[S](cls: type[S]) -> S: ... +71 |- def __sub__[S](self: S, other: S) -> S: ... + 71 |+ def __sub__(self, other: Self) -> Self: ... +72 72 | +73 73 | @classmethod +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... -PYI019.pyi:75:50: PYI019 [*] Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:74:59: PYI019 [*] Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` | -74 | @classmethod -75 | def class_method_unbound[S](cls: type[S]) -> S: ... - | ^ PYI019 -76 | -77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +73 | @classmethod +74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +75 | +76 | @classmethod | = help: Replace with `Self` ℹ Safe fix -72 72 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... -73 73 | -74 74 | @classmethod -75 |- def class_method_unbound[S](cls: type[S]) -> S: ... - 75 |+ def class_method_unbound(cls) -> Self: ... -76 76 | -77 77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -78 78 | - -PYI019.pyi:77:57: PYI019 [*] Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` - | -75 | def class_method_unbound[S](cls: type[S]) -> S: ... -76 | -77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | +73 73 | @classmethod +74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + 74 |+ def class_method_bound(cls) -> Self: ... +75 75 | +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... + +PYI019.pyi:77:50: PYI019 [*] Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` + | +76 | @classmethod +77 | def class_method_unbound[S](cls: type[S]) -> S: ... + | ^ PYI019 78 | -79 | def instance_method_unbound[S](self: S) -> S: ... +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix -74 74 | @classmethod -75 75 | def class_method_unbound[S](cls: type[S]) -> S: ... -76 76 | -77 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - 77 |+ def instance_method_bound(self) -> Self: ... +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... +75 75 | +76 76 | @classmethod +77 |- def class_method_unbound[S](cls: type[S]) -> S: ... + 77 |+ def class_method_unbound(cls) -> Self: ... 78 78 | -79 79 | def instance_method_unbound[S](self: S) -> S: ... +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 80 | -PYI019.pyi:79:48: PYI019 [*] Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:79:57: PYI019 [*] Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` | -77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +77 | def class_method_unbound[S](cls: type[S]) -> S: ... 78 | -79 | def instance_method_unbound[S](self: S) -> S: ... - | ^ PYI019 +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 80 | -81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +81 | def instance_method_unbound[S](self: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix -76 76 | -77 77 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... 78 78 | -79 |- def instance_method_unbound[S](self: S) -> S: ... - 79 |+ def instance_method_unbound(self) -> Self: ... +79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + 79 |+ def instance_method_bound(self) -> Self: ... 80 80 | -81 81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +81 81 | def instance_method_unbound[S](self: S) -> S: ... 82 82 | -PYI019.pyi:81:90: PYI019 [*] Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:81:48: PYI019 [*] Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` | -79 | def instance_method_unbound[S](self: S) -> S: ... +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 | -81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 +81 | def instance_method_unbound[S](self: S) -> S: ... + | ^ PYI019 82 | -83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix 78 78 | -79 79 | def instance_method_unbound[S](self: S) -> S: ... +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 80 | -81 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - 81 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... +81 |- def instance_method_unbound[S](self: S) -> S: ... + 81 |+ def instance_method_unbound(self) -> Self: ... 82 82 | -83 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 84 | -PYI019.pyi:83:81: PYI019 [*] Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:83:90: PYI019 [*] Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` | -81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +81 | def instance_method_unbound[S](self: S) -> S: ... 82 | -83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | ^ PYI019 +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... | = help: Replace with `Self` ℹ Safe fix 80 80 | -81 81 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +81 81 | def instance_method_unbound[S](self: S) -> S: ... 82 82 | -83 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - 83 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... +83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... 84 84 | -85 85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | -PYI019.pyi:85:94: PYI019 [*] Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:85:81: PYI019 [*] Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` | -83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 | -85 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | ^ PYI019 +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | ^ PYI019 +86 | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... | = help: Replace with `Self` -ℹ Unsafe fix +ℹ Safe fix 82 82 | -83 83 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 84 | -85 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - 85 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... +85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | + +PYI019.pyi:87:94: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` + | +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + | ^ PYI019 +88 | +89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + | + = help: Replace with `Self` + +ℹ Display-only fix +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | +87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... +88 88 | +89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + +PYI019.pyi:89:75: PYI019 Methods like `mixing_old_and_new_style_type_vars` should return `Self` instead of a custom `TypeVar` + | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 | +89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + | ^^^^^ PYI019 + | + = help: Replace with `Self` + +ℹ Display-only fix +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | +89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ... From 58e2a85f606dd8be1738a8b7dcf9faf9909f9cbf Mon Sep 17 00:00:00 2001 From: InSync Date: Tue, 12 Nov 2024 01:19:18 +0700 Subject: [PATCH 7/9] Add a "Fix safety" section --- .../flake8_pyi/rules/custom_type_var_return_type.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 6c844ac88b750..c29b19ffc6a3c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -45,7 +45,17 @@ use ruff_text_size::{Ranged, TextRange}; /// def bar(cls, arg: int) -> Self: ... /// ``` /// +/// ## Fix safety +/// The fix is only available in stub files. +/// It will try to remove all usages and declarations of the custom type variable. +/// Pre-[PEP-695]-style declarations will not be removed. +/// +/// If a variable's annotation is too complex to handle, +/// the fix will be marked as display only. +/// Otherwise, it will be marked as safe. +/// /// [PEP 673]: https://peps.python.org/pep-0673/#motivation +/// [PEP 695]: https://peps.python.org/pep-0695/ #[violation] pub struct CustomTypeVarReturnType { method_name: String, From f13969e4ad659f92754883a10ce765617791ff2e Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 12 Nov 2024 10:28:31 +0000 Subject: [PATCH 8/9] Update crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs --- .../rules/flake8_pyi/rules/custom_type_var_return_type.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index c29b19ffc6a3c..08410adb9cca5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -318,11 +318,7 @@ fn replace_custom_typevar_with_self( let (first, rest) = (all_edits.swap_remove(0), all_edits); - match fix_applicability { - Applicability::DisplayOnly => Some(Fix::display_only_edits(first, rest)), - Applicability::Unsafe => Some(Fix::unsafe_edits(first, rest)), - Applicability::Safe => Some(Fix::safe_edits(first, rest)), - } + Fix::applicable_edits(first, rest, fix_applicability) } fn import_self(checker: &Checker, return_range: TextRange) -> Option { From 347bd25332df5d0c108edb20782f00374d61c23e Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 12 Nov 2024 11:08:23 +0000 Subject: [PATCH 9/9] Gate behind preview, pass `FunctionDef` --- .../src/checkers/ast/analyze/statement.rs | 11 +- .../ruff_linter/src/rules/flake8_pyi/mod.rs | 17 + .../rules/custom_type_var_return_type.rs | 54 ++- ..._flake8_pyi__tests__PYI019_PYI019.pyi.snap | 237 +--------- ...sts__custom_classmethod_rules_preview.snap | 414 ++++++++++++++++++ 5 files changed, 474 insertions(+), 259 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__custom_classmethod_rules_preview.snap diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index e71962cb496af..1883fab5f4ada 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -81,7 +81,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { returns, parameters, body, - type_params, + type_params: _, range: _, }, ) => { @@ -160,14 +160,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { flake8_pyi::rules::bad_generator_return_type(function_def, checker); } if checker.enabled(Rule::CustomTypeVarReturnType) { - flake8_pyi::rules::custom_type_var_return_type( - checker, - name, - decorator_list, - type_params.as_deref(), - parameters, - returns.as_ref().map(AsRef::as_ref), - ); + flake8_pyi::rules::custom_type_var_return_type(checker, function_def); } if checker.source_type.is_stub() { if checker.enabled(Rule::StrOrReprDefinedInStub) { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index 61e396aa21128..9c666432ab8b1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -152,6 +152,23 @@ mod tests { Ok(()) } + #[test] + fn custom_classmethod_rules_preview() -> Result<()> { + let diagnostics = test_path( + Path::new("flake8_pyi/PYI019.pyi"), + &settings::LinterSettings { + pep8_naming: pep8_naming::settings::Settings { + classmethod_decorators: vec!["foo_classmethod".to_string()], + ..pep8_naming::settings::Settings::default() + }, + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(Rule::CustomTypeVarReturnType) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.py"))] #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.pyi"))] fn py38(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs index 08410adb9cca5..5baf7907d2ad6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_return_type.rs @@ -5,7 +5,7 @@ use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Vi use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::helpers::map_subscript; -use ruff_python_ast::{Decorator, Expr, Parameters, TypeParam, TypeParams}; +use ruff_python_ast::{Expr, Parameters, TypeParam, TypeParams}; use ruff_python_semantic::analyze::function_type::{self, FunctionType}; use ruff_python_semantic::analyze::visibility::{is_abstract, is_overload}; use ruff_text_size::{Ranged, TextRange}; @@ -84,17 +84,15 @@ impl Violation for CustomTypeVarReturnType { /// PYI019 pub(crate) fn custom_type_var_return_type( checker: &mut Checker, - name: &str, - decorator_list: &[Decorator], - type_params: Option<&TypeParams>, - parameters: &Parameters, - returns: Option<&Expr>, + function_def: &ast::StmtFunctionDef, ) { // Given, e.g., `def foo(self: _S, arg: bytes) -> _T`, extract `_T`. - let Some(returns) = returns else { + let Some(returns) = function_def.returns.as_ref() else { return; }; + let parameters = &*function_def.parameters; + // Given, e.g., `def foo(self: _S, arg: bytes)`, extract `_S`. let Some(self_or_cls_annotation) = parameters .posonlyargs @@ -106,6 +104,8 @@ pub(crate) fn custom_type_var_return_type( return; }; + let decorator_list = &*function_def.decorator_list; + let semantic = checker.semantic(); // Skip any abstract, static, and overloaded methods. @@ -114,7 +114,7 @@ pub(crate) fn custom_type_var_return_type( } let method = match function_type::classify( - name, + &function_def.name, decorator_list, semantic.current_scope(), semantic, @@ -126,17 +126,17 @@ pub(crate) fn custom_type_var_return_type( FunctionType::ClassMethod => Method::Class(ClassMethod { cls_annotation: self_or_cls_annotation, returns, - type_params, + type_params: function_def.type_params.as_deref(), }), FunctionType::Method => Method::Instance(InstanceMethod { self_annotation: self_or_cls_annotation, returns, - type_params, + type_params: function_def.type_params.as_deref(), }), }; if method.uses_custom_var() { - add_diagnostic(checker, name.to_string(), type_params, parameters, returns); + add_diagnostic(checker, function_def, returns); } } @@ -247,18 +247,12 @@ fn is_likely_private_typevar(type_var_name: &str, type_params: Option<&TypeParam }) } -fn add_diagnostic( - checker: &mut Checker, - method_name: String, - type_params: Option<&TypeParams>, - parameters: &Parameters, - returns: &Expr, -) { +fn add_diagnostic(checker: &mut Checker, function_def: &ast::StmtFunctionDef, returns: &Expr) { let in_stub = checker.source_type.is_stub(); let mut diagnostic = Diagnostic::new( CustomTypeVarReturnType { - method_name, + method_name: function_def.name.to_string(), in_stub, }, returns.range(), @@ -266,9 +260,7 @@ fn add_diagnostic( // See `replace_custom_typevar_with_self`'s doc comment if in_stub { - if let Some(fix) = - replace_custom_typevar_with_self(checker, type_params, parameters, returns) - { + if let Some(fix) = replace_custom_typevar_with_self(checker, function_def, returns) { diagnostic.set_fix(fix); } } @@ -293,32 +285,38 @@ fn add_diagnostic( /// and will mark the fix as unsafe if an annotation cannot be handled. fn replace_custom_typevar_with_self( checker: &Checker, - type_params: Option<&TypeParams>, - parameters: &Parameters, + function_def: &ast::StmtFunctionDef, returns: &Expr, ) -> Option { + if checker.settings.preview.is_disabled() { + return None; + } + // The return annotation is guaranteed to be a name, // as verified by `uses_custom_var()`. let typevar_name = returns.as_name_expr().unwrap().id(); let mut all_edits = vec![ replace_return_annotation_with_self(returns), - remove_first_parameter_annotation(parameters), + remove_first_parameter_annotation(&function_def.parameters), ]; let edit = import_self(checker, returns.range())?; all_edits.push(edit); - if let Some(edit) = remove_typevar_declaration(type_params, typevar_name) { + if let Some(edit) = + remove_typevar_declaration(function_def.type_params.as_deref(), typevar_name) + { all_edits.push(edit); } - let (mut edits, fix_applicability) = replace_typevar_usages_with_self(parameters, typevar_name); + let (mut edits, fix_applicability) = + replace_typevar_usages_with_self(&function_def.parameters, typevar_name); all_edits.append(&mut edits); let (first, rest) = (all_edits.swap_remove(0), all_edits); - Fix::applicable_edits(first, rest, fix_applicability) + Some(Fix::applicable_edits(first, rest, fix_applicability)) } fn import_self(checker: &Checker, return_range: TextRange) -> Option { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap index 57ed22c125195..0fba8ea76bacf 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019.pyi.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- PYI019.pyi:7:62: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | @@ -10,16 +9,6 @@ PYI019.pyi:7:62: PYI019 Methods like `__new__` should return `Self` instead of a | = help: Replace with `Self` -ℹ Display-only fix -4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) -5 5 | -6 6 | class BadClass: -7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 -8 8 | -9 9 | -10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - PYI019.pyi:10:54: PYI019 Methods like `bad_instance_method` should return `Self` instead of a custom `TypeVar` | 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 @@ -27,16 +16,6 @@ PYI019.pyi:10:54: PYI019 Methods like `bad_instance_method` should return `Self` | = help: Replace with `Self` -ℹ Display-only fix -7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 -8 8 | -9 9 | -10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 -11 11 | -12 12 | -13 13 | @classmethod - PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `Self` instead of a custom `TypeVar` | 13 | @classmethod @@ -45,17 +24,7 @@ PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `Self` in | = help: Replace with `Self` -ℹ Display-only fix -11 11 | -12 12 | -13 13 | @classmethod -14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 -15 15 | -16 16 | -17 17 | @classmethod - -PYI019.pyi:18:55: PYI019 [*] Methods like `bad_posonly_class_method` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:18:55: PYI019 Methods like `bad_posonly_class_method` should return `Self` instead of a custom `TypeVar` | 17 | @classmethod 18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 @@ -63,16 +32,6 @@ PYI019.pyi:18:55: PYI019 [*] Methods like `bad_posonly_class_method` should retu | = help: Replace with `Self` -ℹ Safe fix -15 15 | -16 16 | -17 17 | @classmethod -18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 -19 19 | -20 20 | -21 21 | @classmethod - PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 37 | # Python > 3.12 @@ -82,34 +41,14 @@ PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `Self` instead of | = help: Replace with `Self` -ℹ Display-only fix -36 36 | -37 37 | # Python > 3.12 -38 38 | class PEP695BadDunderNew[T]: -39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 -40 40 | -41 41 | -42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - -PYI019.pyi:42:46: PYI019 [*] Methods like `generic_instance_method` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:42:46: PYI019 Methods like `generic_instance_method` should return `Self` instead of a custom `TypeVar` | 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 | ^ PYI019 | = help: Replace with `Self` -ℹ Safe fix -39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 -40 40 | -41 41 | -42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 - 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 -43 43 | -44 44 | -45 45 | class PEP695GoodDunderNew[T]: - -PYI019.pyi:54:32: PYI019 [*] Methods like `foo` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:54:32: PYI019 Methods like `foo` should return `Self` instead of a custom `TypeVar` | 52 | # in the settings for this test: 53 | @foo_classmethod @@ -118,17 +57,7 @@ PYI019.pyi:54:32: PYI019 [*] Methods like `foo` should return `Self` instead of | = help: Replace with `Self` -ℹ Safe fix -51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` -52 52 | # in the settings for this test: -53 53 | @foo_classmethod -54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 - 54 |+ def foo(cls) -> Self: ... # PYI019 -55 55 | -56 56 | -57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") - -PYI019.pyi:61:48: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:61:48: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` | 59 | # Only .pyi gets fixes, no fixes for .py 60 | class PEP695Fix: @@ -139,17 +68,7 @@ PYI019.pyi:61:48: PYI019 [*] Methods like `__new__` should return `Self` instead | = help: Replace with `Self` -ℹ Safe fix -58 58 | -59 59 | # Only .pyi gets fixes, no fixes for .py -60 60 | class PEP695Fix: -61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - 61 |+ def __new__(cls) -> Self: ... -62 62 | -63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 64 | - -PYI019.pyi:63:47: PYI019 [*] Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:63:47: PYI019 Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` | 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 62 | @@ -160,17 +79,7 @@ PYI019.pyi:63:47: PYI019 [*] Methods like `__init_subclass__` should return `Sel | = help: Replace with `Self` -ℹ Safe fix -60 60 | class PEP695Fix: -61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -62 62 | -63 |- def __init_subclass__[S](cls: type[S]) -> S: ... - 63 |+ def __init_subclass__(cls) -> Self: ... -64 64 | -65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 66 | - -PYI019.pyi:65:43: PYI019 [*] Methods like `__neg__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:65:43: PYI019 Methods like `__neg__` should return `Self` instead of a custom `TypeVar` | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 | @@ -181,17 +90,7 @@ PYI019.pyi:65:43: PYI019 [*] Methods like `__neg__` should return `Self` instead | = help: Replace with `Self` -ℹ Safe fix -62 62 | -63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 64 | -65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... - 65 |+ def __neg__(self) -> Self: ... -66 66 | -67 67 | def __pos__[S](self: S) -> S: ... -68 68 | - -PYI019.pyi:67:32: PYI019 [*] Methods like `__pos__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:67:32: PYI019 Methods like `__pos__` should return `Self` instead of a custom `TypeVar` | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 | @@ -202,17 +101,7 @@ PYI019.pyi:67:32: PYI019 [*] Methods like `__pos__` should return `Self` instead | = help: Replace with `Self` -ℹ Safe fix -64 64 | -65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 66 | -67 |- def __pos__[S](self: S) -> S: ... - 67 |+ def __pos__(self) -> Self: ... -68 68 | -69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 70 | - -PYI019.pyi:69:53: PYI019 [*] Methods like `__add__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:69:53: PYI019 Methods like `__add__` should return `Self` instead of a custom `TypeVar` | 67 | def __pos__[S](self: S) -> S: ... 68 | @@ -223,17 +112,7 @@ PYI019.pyi:69:53: PYI019 [*] Methods like `__add__` should return `Self` instead | = help: Replace with `Self` -ℹ Safe fix -66 66 | -67 67 | def __pos__[S](self: S) -> S: ... -68 68 | -69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - 69 |+ def __add__(self, other: Self) -> Self: ... -70 70 | -71 71 | def __sub__[S](self: S, other: S) -> S: ... -72 72 | - -PYI019.pyi:71:42: PYI019 [*] Methods like `__sub__` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:71:42: PYI019 Methods like `__sub__` should return `Self` instead of a custom `TypeVar` | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... 70 | @@ -244,17 +123,7 @@ PYI019.pyi:71:42: PYI019 [*] Methods like `__sub__` should return `Self` instead | = help: Replace with `Self` -ℹ Safe fix -68 68 | -69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 70 | -71 |- def __sub__[S](self: S, other: S) -> S: ... - 71 |+ def __sub__(self, other: Self) -> Self: ... -72 72 | -73 73 | @classmethod -74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - -PYI019.pyi:74:59: PYI019 [*] Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:74:59: PYI019 Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` | 73 | @classmethod 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... @@ -264,17 +133,7 @@ PYI019.pyi:74:59: PYI019 [*] Methods like `class_method_bound` should return `Se | = help: Replace with `Self` -ℹ Safe fix -71 71 | def __sub__[S](self: S, other: S) -> S: ... -72 72 | -73 73 | @classmethod -74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - 74 |+ def class_method_bound(cls) -> Self: ... -75 75 | -76 76 | @classmethod -77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... - -PYI019.pyi:77:50: PYI019 [*] Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:77:50: PYI019 Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` | 76 | @classmethod 77 | def class_method_unbound[S](cls: type[S]) -> S: ... @@ -284,17 +143,7 @@ PYI019.pyi:77:50: PYI019 [*] Methods like `class_method_unbound` should return ` | = help: Replace with `Self` -ℹ Safe fix -74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... -75 75 | -76 76 | @classmethod -77 |- def class_method_unbound[S](cls: type[S]) -> S: ... - 77 |+ def class_method_unbound(cls) -> Self: ... -78 78 | -79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 80 | - -PYI019.pyi:79:57: PYI019 [*] Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:79:57: PYI019 Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` | 77 | def class_method_unbound[S](cls: type[S]) -> S: ... 78 | @@ -305,17 +154,7 @@ PYI019.pyi:79:57: PYI019 [*] Methods like `instance_method_bound` should return | = help: Replace with `Self` -ℹ Safe fix -76 76 | @classmethod -77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... -78 78 | -79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - 79 |+ def instance_method_bound(self) -> Self: ... -80 80 | -81 81 | def instance_method_unbound[S](self: S) -> S: ... -82 82 | - -PYI019.pyi:81:48: PYI019 [*] Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:81:48: PYI019 Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 | @@ -326,17 +165,7 @@ PYI019.pyi:81:48: PYI019 [*] Methods like `instance_method_unbound` should retur | = help: Replace with `Self` -ℹ Safe fix -78 78 | -79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 80 | -81 |- def instance_method_unbound[S](self: S) -> S: ... - 81 |+ def instance_method_unbound(self) -> Self: ... -82 82 | -83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 84 | - -PYI019.pyi:83:90: PYI019 [*] Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:83:90: PYI019 Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` | 81 | def instance_method_unbound[S](self: S) -> S: ... 82 | @@ -347,17 +176,7 @@ PYI019.pyi:83:90: PYI019 [*] Methods like `instance_method_bound_with_another_pa | = help: Replace with `Self` -ℹ Safe fix -80 80 | -81 81 | def instance_method_unbound[S](self: S) -> S: ... -82 82 | -83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... -84 84 | -85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 86 | - -PYI019.pyi:85:81: PYI019 [*] Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` +PYI019.pyi:85:81: PYI019 Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 | @@ -368,16 +187,6 @@ PYI019.pyi:85:81: PYI019 [*] Methods like `instance_method_unbound_with_another_ | = help: Replace with `Self` -ℹ Safe fix -82 82 | -83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 84 | -85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... -86 86 | -87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 88 | - PYI019.pyi:87:94: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... @@ -389,15 +198,6 @@ PYI019.pyi:87:94: PYI019 Methods like `multiple_type_vars` should return `Self` | = help: Replace with `Self` -ℹ Display-only fix -84 84 | -85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 86 | -87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... -88 88 | -89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - PYI019.pyi:89:75: PYI019 Methods like `mixing_old_and_new_style_type_vars` should return `Self` instead of a custom `TypeVar` | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... @@ -406,10 +206,3 @@ PYI019.pyi:89:75: PYI019 Methods like `mixing_old_and_new_style_type_vars` shoul | ^^^^^ PYI019 | = help: Replace with `Self` - -ℹ Display-only fix -86 86 | -87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 88 | -89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__custom_classmethod_rules_preview.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__custom_classmethod_rules_preview.snap new file mode 100644 index 0000000000000..db2592f12e34c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__custom_classmethod_rules_preview.snap @@ -0,0 +1,414 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI019.pyi:7:62: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` + | +6 | class BadClass: +7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 + | ^^ PYI019 + | + = help: Replace with `Self` + +ℹ Display-only fix +4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) +5 5 | +6 6 | class BadClass: +7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 + 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 +8 8 | +9 9 | +10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + +PYI019.pyi:10:54: PYI019 Methods like `bad_instance_method` should return `Self` instead of a custom `TypeVar` + | +10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + | ^^ PYI019 + | + = help: Replace with `Self` + +ℹ Display-only fix +7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 +8 8 | +9 9 | +10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 +11 11 | +12 12 | +13 13 | @classmethod + +PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `Self` instead of a custom `TypeVar` + | +13 | @classmethod +14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 + | ^^ PYI019 + | + = help: Replace with `Self` + +ℹ Display-only fix +11 11 | +12 12 | +13 13 | @classmethod +14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 + 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 +15 15 | +16 16 | +17 17 | @classmethod + +PYI019.pyi:18:55: PYI019 [*] Methods like `bad_posonly_class_method` should return `Self` instead of a custom `TypeVar` + | +17 | @classmethod +18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 + | ^^ PYI019 + | + = help: Replace with `Self` + +ℹ Safe fix +15 15 | +16 16 | +17 17 | @classmethod +18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 + 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 +19 19 | +20 20 | +21 21 | @classmethod + +PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `Self` instead of a custom `TypeVar` + | +37 | # Python > 3.12 +38 | class PEP695BadDunderNew[T]: +39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 + | ^ PYI019 + | + = help: Replace with `Self` + +ℹ Display-only fix +36 36 | +37 37 | # Python > 3.12 +38 38 | class PEP695BadDunderNew[T]: +39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 + 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 +40 40 | +41 41 | +42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 + +PYI019.pyi:42:46: PYI019 [*] Methods like `generic_instance_method` should return `Self` instead of a custom `TypeVar` + | +42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 + | ^ PYI019 + | + = help: Replace with `Self` + +ℹ Safe fix +39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 +40 40 | +41 41 | +42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 + 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 +43 43 | +44 44 | +45 45 | class PEP695GoodDunderNew[T]: + +PYI019.pyi:54:32: PYI019 [*] Methods like `foo` should return `Self` instead of a custom `TypeVar` + | +52 | # in the settings for this test: +53 | @foo_classmethod +54 | def foo[S](cls: type[S]) -> S: ... # PYI019 + | ^ PYI019 + | + = help: Replace with `Self` + +ℹ Safe fix +51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` +52 52 | # in the settings for this test: +53 53 | @foo_classmethod +54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 + 54 |+ def foo(cls) -> Self: ... # PYI019 +55 55 | +56 56 | +57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") + +PYI019.pyi:61:48: PYI019 [*] Methods like `__new__` should return `Self` instead of a custom `TypeVar` + | +59 | # Only .pyi gets fixes, no fixes for .py +60 | class PEP695Fix: +61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +62 | +63 | def __init_subclass__[S](cls: type[S]) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +58 58 | +59 59 | # Only .pyi gets fixes, no fixes for .py +60 60 | class PEP695Fix: +61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + 61 |+ def __new__(cls) -> Self: ... +62 62 | +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 64 | + +PYI019.pyi:63:47: PYI019 [*] Methods like `__init_subclass__` should return `Self` instead of a custom `TypeVar` + | +61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +62 | +63 | def __init_subclass__[S](cls: type[S]) -> S: ... + | ^ PYI019 +64 | +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +60 60 | class PEP695Fix: +61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +62 62 | +63 |- def __init_subclass__[S](cls: type[S]) -> S: ... + 63 |+ def __init_subclass__(cls) -> Self: ... +64 64 | +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 66 | + +PYI019.pyi:65:43: PYI019 [*] Methods like `__neg__` should return `Self` instead of a custom `TypeVar` + | +63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 | +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 +66 | +67 | def __pos__[S](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +62 62 | +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 64 | +65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... + 65 |+ def __neg__(self) -> Self: ... +66 66 | +67 67 | def __pos__[S](self: S) -> S: ... +68 68 | + +PYI019.pyi:67:32: PYI019 [*] Methods like `__pos__` should return `Self` instead of a custom `TypeVar` + | +65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 | +67 | def __pos__[S](self: S) -> S: ... + | ^ PYI019 +68 | +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +64 64 | +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 66 | +67 |- def __pos__[S](self: S) -> S: ... + 67 |+ def __pos__(self) -> Self: ... +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 70 | + +PYI019.pyi:69:53: PYI019 [*] Methods like `__add__` should return `Self` instead of a custom `TypeVar` + | +67 | def __pos__[S](self: S) -> S: ... +68 | +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 +70 | +71 | def __sub__[S](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +66 66 | +67 67 | def __pos__[S](self: S) -> S: ... +68 68 | +69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + 69 |+ def __add__(self, other: Self) -> Self: ... +70 70 | +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | + +PYI019.pyi:71:42: PYI019 [*] Methods like `__sub__` should return `Self` instead of a custom `TypeVar` + | +69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 | +71 | def __sub__[S](self: S, other: S) -> S: ... + | ^ PYI019 +72 | +73 | @classmethod + | + = help: Replace with `Self` + +ℹ Safe fix +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 70 | +71 |- def __sub__[S](self: S, other: S) -> S: ... + 71 |+ def __sub__(self, other: Self) -> Self: ... +72 72 | +73 73 | @classmethod +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + +PYI019.pyi:74:59: PYI019 [*] Methods like `class_method_bound` should return `Self` instead of a custom `TypeVar` + | +73 | @classmethod +74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + | ^ PYI019 +75 | +76 | @classmethod + | + = help: Replace with `Self` + +ℹ Safe fix +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | +73 73 | @classmethod +74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + 74 |+ def class_method_bound(cls) -> Self: ... +75 75 | +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... + +PYI019.pyi:77:50: PYI019 [*] Methods like `class_method_unbound` should return `Self` instead of a custom `TypeVar` + | +76 | @classmethod +77 | def class_method_unbound[S](cls: type[S]) -> S: ... + | ^ PYI019 +78 | +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... +75 75 | +76 76 | @classmethod +77 |- def class_method_unbound[S](cls: type[S]) -> S: ... + 77 |+ def class_method_unbound(cls) -> Self: ... +78 78 | +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 80 | + +PYI019.pyi:79:57: PYI019 [*] Methods like `instance_method_bound` should return `Self` instead of a custom `TypeVar` + | +77 | def class_method_unbound[S](cls: type[S]) -> S: ... +78 | +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + | ^ PYI019 +80 | +81 | def instance_method_unbound[S](self: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... +78 78 | +79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + 79 |+ def instance_method_bound(self) -> Self: ... +80 80 | +81 81 | def instance_method_unbound[S](self: S) -> S: ... +82 82 | + +PYI019.pyi:81:48: PYI019 [*] Methods like `instance_method_unbound` should return `Self` instead of a custom `TypeVar` + | +79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 | +81 | def instance_method_unbound[S](self: S) -> S: ... + | ^ PYI019 +82 | +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +78 78 | +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 80 | +81 |- def instance_method_unbound[S](self: S) -> S: ... + 81 |+ def instance_method_unbound(self) -> Self: ... +82 82 | +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 84 | + +PYI019.pyi:83:90: PYI019 [*] Methods like `instance_method_bound_with_another_parameter` should return `Self` instead of a custom `TypeVar` + | +81 | def instance_method_unbound[S](self: S) -> S: ... +82 | +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + | ^ PYI019 +84 | +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +80 80 | +81 81 | def instance_method_unbound[S](self: S) -> S: ... +82 82 | +83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | + +PYI019.pyi:85:81: PYI019 [*] Methods like `instance_method_unbound_with_another_parameter` should return `Self` instead of a custom `TypeVar` + | +83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 | +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + | ^ PYI019 +86 | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + | + = help: Replace with `Self` + +ℹ Safe fix +82 82 | +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 84 | +85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | + +PYI019.pyi:87:94: PYI019 Methods like `multiple_type_vars` should return `Self` instead of a custom `TypeVar` + | +85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + | ^ PYI019 +88 | +89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + | + = help: Replace with `Self` + +ℹ Display-only fix +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | +87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... +88 88 | +89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + +PYI019.pyi:89:75: PYI019 Methods like `mixing_old_and_new_style_type_vars` should return `Self` instead of a custom `TypeVar` + | +87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 | +89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + | ^^^^^ PYI019 + | + = help: Replace with `Self` + +ℹ Display-only fix +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | +89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ...