From 7915f1098c46a929e1bdba70f18007e0a8b06333 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Fri, 31 Jan 2025 21:09:56 +0000 Subject: [PATCH] [`ruff`] Classes with mixed type variable style (`RUF060`) --- .../resources/test/fixtures/ruff/RUF060.py | 87 ++++ .../src/checkers/ast/analyze/statement.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + .../src/rules/pyupgrade/rules/mod.rs | 2 +- .../src/rules/pyupgrade/rules/pep695/mod.rs | 69 ++- .../rules/pep695/non_pep695_generic_class.rs | 22 +- crates/ruff_linter/src/rules/ruff/mod.rs | 1 + .../ruff/rules/class_with_mixed_type_vars.rs | 239 +++++++++++ .../ruff_linter/src/rules/ruff/rules/mod.rs | 2 + ...uff__tests__preview__RUF060_RUF060.py.snap | 400 ++++++++++++++++++ ruff.schema.json | 2 + 11 files changed, 796 insertions(+), 32 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py create mode 100644 crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF060_RUF060.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py new file mode 100644 index 00000000000000..0d3bf6f15bb658 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py @@ -0,0 +1,87 @@ +from typing import Generic, ParamSpec, TypeVar, TypeVarTuple, Unpack + + +_A = TypeVar('_A') +_B = TypeVar('_B', bound=int) +_C = TypeVar('_C', str, bytes) +_D = TypeVar('_D', default=int) +_E = TypeVar('_E', bound=int, default=int) +_F = TypeVar('_F', str, bytes, default=str) + +_As = TypeVarTuple('_As') +_Bs = TypeVarTuple('_Bs', bound=tuple[int, str]) +_Cs = TypeVarTuple('_Cs', default=tuple[int, str]) + + +_P1 = ParamSpec('_P1') +_P2 = ParamSpec('_P2', bound=[bytes, bool]) +_P3 = ParamSpec('_P3', default=[int, str]) + + +### Errors + +class C[T](Generic[_A]): ... +class C[T](Generic[_B], str): ... +class C[T](int, Generic[_C]): ... +class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults +class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults +class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults + +class C[*Ts](Generic[*_As]): ... +class C[*Ts](Generic[Unpack[_As]]): ... +class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... +class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults + + +class C[**P](Generic[_P1]): ... +class C[**P](Generic[_P2]): ... +class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults + + +class C[T](Generic[T, _A]): ... + + +# See `is_existing_param_of_same_class`. +# `expr_name_to_type_var` doesn't handle named expressions, +# only simple assignments, so there is no fix. +class C[T: (_Z := TypeVar('_Z'))](Generic[_Z]): ... + + +class C(Generic[_B]): + class D[T](Generic[_B, T]): ... + + +class C[T]: + class D[U](Generic[T, U]): ... + + +# In a single run, only the first is reported. +# Others will be reported/fixed in following iterations. +class C[T](Generic[_C], Generic[_D]): ... +class C[T, _C: (str, bytes)](Generic[_D]): ... # TODO: Type parameter defaults + + +class C[ +T # Comment +](Generic[_E]): ... # TODO: Type parameter defaults + + +class C[T](Generic[Generic[_F]]): ... +class C[T](Generic[Unpack[_A]]): ... +class C[T](Generic[Unpack[_P1]]): ... +class C[T](Generic[Unpack[Unpack[_P2]]]): ... +class C[T](Generic[Unpack[*_As]]): ... +class C[T](Generic[Unpack[_As, _Bs]]): ... + + +class C[T](Generic[_A, _A]): ... +class C[T](Generic[_A, Unpack[_As]]): ... +class C[T](Generic[*_As, _A]): ... + + +### No errors + +class C(Generic[_A]): ... +class C[_A]: ... +class C[_A](list[_A]): ... +class C[_A](list[Generic[_A]]): ... diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 292be0169e6b39..d72d9565faed61 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -563,6 +563,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::NonPEP695GenericClass) { pyupgrade::rules::non_pep695_generic_class(checker, class_def); } + if checker.enabled(Rule::ClassWithMixedTypeVars) { + ruff::rules::class_with_mixed_type_vars(checker, class_def); + } } Stmt::Import(ast::StmtImport { names, range: _ }) => { if checker.enabled(Rule::MultipleImportsOnOneLine) { diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 3041fcbf8bad01..129b1d3c65b1f5 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1008,6 +1008,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback), (Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound), (Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip), + (Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::ClassWithMixedTypeVars), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs index d15801edc36df5..e37e0d9bb7c5b0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs @@ -57,7 +57,7 @@ mod native_literals; mod open_alias; mod os_error_alias; mod outdated_version_block; -mod pep695; +pub(crate) mod pep695; mod printf_string_formatting; mod quoted_annotation; mod redundant_open_modes; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs index 273c0867d432d5..cc89e21079739b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs @@ -9,7 +9,7 @@ use ruff_python_ast::{ self as ast, name::Name, visitor::{self, Visitor}, - Expr, ExprCall, ExprName, ExprSubscript, Identifier, Stmt, StmtAssign, TypeParam, + Arguments, Expr, ExprCall, ExprName, ExprSubscript, Identifier, Stmt, StmtAssign, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, }; use ruff_python_semantic::SemanticModel; @@ -26,7 +26,7 @@ mod non_pep695_generic_function; mod non_pep695_type_alias; #[derive(Debug)] -enum TypeVarRestriction<'a> { +pub(crate) enum TypeVarRestriction<'a> { /// A type variable with a bound, e.g., `TypeVar("T", bound=int)`. Bound(&'a Expr), /// A type variable with constraints, e.g., `TypeVar("T", int, str)`. @@ -37,25 +37,25 @@ enum TypeVarRestriction<'a> { } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum TypeParamKind { +pub(crate) enum TypeParamKind { TypeVar, TypeVarTuple, ParamSpec, } #[derive(Debug)] -struct TypeVar<'a> { - name: &'a str, - restriction: Option>, - kind: TypeParamKind, - default: Option<&'a Expr>, +pub(crate) struct TypeVar<'a> { + pub(crate) name: &'a str, + pub(crate) restriction: Option>, + pub(crate) kind: TypeParamKind, + pub(crate) default: Option<&'a Expr>, } /// Wrapper for formatting a sequence of [`TypeVar`]s for use as a generic type parameter (e.g. `[T, /// *Ts, **P]`). See [`DisplayTypeVar`] for further details. -struct DisplayTypeVars<'a> { - type_vars: &'a [TypeVar<'a>], - source: &'a str, +pub(crate) struct DisplayTypeVars<'a> { + pub(crate) type_vars: &'a [TypeVar<'a>], + pub(crate) source: &'a str, } impl Display for DisplayTypeVars<'_> { @@ -79,7 +79,7 @@ impl Display for DisplayTypeVars<'_> { /// Used for displaying `type_var`. `source` is the whole file, which will be sliced to recover the /// `TypeVarRestriction` values for generic bounds and constraints. -struct DisplayTypeVar<'a> { +pub(crate) struct DisplayTypeVar<'a> { type_var: &'a TypeVar<'a>, source: &'a str, } @@ -190,6 +190,34 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam { } } +impl<'a> From<&'a TypeParam> for TypeVar<'a> { + fn from(param: &'a TypeParam) -> Self { + let (kind, restriction) = match param { + TypeParam::TypeVarTuple(_) => (TypeParamKind::TypeVarTuple, None), + TypeParam::ParamSpec(_) => (TypeParamKind::ParamSpec, None), + + TypeParam::TypeVar(param) => { + let restriction = match param.bound.as_deref() { + None => None, + Some(Expr::Tuple(constraints)) => Some(TypeVarRestriction::Constraint( + constraints.elts.iter().collect::>(), + )), + Some(bound) => Some(TypeVarRestriction::Bound(bound)), + }; + + (TypeParamKind::TypeVar, restriction) + } + }; + + Self { + name: param.name(), + kind, + restriction, + default: param.default(), + } + } +} + struct TypeVarReferenceVisitor<'a> { vars: Vec>, semantic: &'a SemanticModel<'a>, @@ -240,7 +268,7 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> { } } -fn expr_name_to_type_var<'a>( +pub(crate) fn expr_name_to_type_var<'a>( semantic: &'a SemanticModel, name: &'a ExprName, ) -> Option> { @@ -347,3 +375,18 @@ fn check_type_vars(vars: Vec>) -> Option>> { == vars.len()) .then_some(vars) } + +/// Search `class_bases` for a `typing.Generic` base class. Returns the `Generic` expression (if +/// any), along with its index in the class's bases tuple. +pub(crate) fn find_generic<'a>( + class_bases: &'a Arguments, + semantic: &SemanticModel, +) -> Option<(usize, &'a ExprSubscript)> { + class_bases.args.iter().enumerate().find_map(|(idx, expr)| { + expr.as_subscript_expr().and_then(|sub_expr| { + semantic + .match_typing_expr(&sub_expr.value, "Generic") + .then_some((idx, sub_expr)) + }) + }) +} diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs index 5c29f31cfc163e..b43aaaea4fcf68 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs @@ -1,15 +1,16 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::visitor::Visitor; -use ruff_python_ast::{Arguments, ExprSubscript, StmtClassDef}; -use ruff_python_semantic::SemanticModel; +use ruff_python_ast::{ExprSubscript, StmtClassDef}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{remove_argument, Parentheses}; use crate::settings::types::PythonVersion; -use super::{check_type_vars, in_nested_context, DisplayTypeVars, TypeVarReferenceVisitor}; +use super::{ + check_type_vars, find_generic, in_nested_context, DisplayTypeVars, TypeVarReferenceVisitor, +}; /// ## What it does /// @@ -203,18 +204,3 @@ pub(crate) fn non_pep695_generic_class(checker: &mut Checker, class_def: &StmtCl checker.diagnostics.push(diagnostic); } - -/// Search `class_bases` for a `typing.Generic` base class. Returns the `Generic` expression (if -/// any), along with its index in the class's bases tuple. -fn find_generic<'a>( - class_bases: &'a Arguments, - semantic: &SemanticModel, -) -> Option<(usize, &'a ExprSubscript)> { - class_bases.args.iter().enumerate().find_map(|(idx, expr)| { - expr.as_subscript_expr().and_then(|sub_expr| { - semantic - .match_typing_expr(&sub_expr.value, "Generic") - .then_some((idx, sub_expr)) - }) - }) -} diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 5ed899c945f254..939cd86b159d4b 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -429,6 +429,7 @@ mod tests { #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] #[test_case(Rule::StarmapZip, Path::new("RUF058_0.py"))] #[test_case(Rule::StarmapZip, Path::new("RUF058_1.py"))] + #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF060.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs new file mode 100644 index 00000000000000..0c790506427627 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs @@ -0,0 +1,239 @@ +use std::iter; + +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::{ + Arguments, Expr, ExprStarred, ExprSubscript, ExprTuple, StmtClassDef, TypeParams, +}; +use ruff_python_semantic::{Binding, BindingKind, SemanticModel}; + +use crate::checkers::ast::Checker; +use crate::fix::edits::{remove_argument, Parentheses}; +use crate::rules::pyupgrade::rules::pep695::{ + expr_name_to_type_var, find_generic, DisplayTypeVars, TypeParamKind, TypeVar, +}; +use crate::settings::types::PythonVersion; + +/// ## What it does +/// Checks for classes that have [PEP 695] [type parameter lists] +/// while also inheriting from `typing.Generic` or `typing_extensions.Generic`. +/// +/// ## Why is this bad? +/// Such classes cause errors at runtime: +/// +/// ```python +/// from typing import Generic, TypeVar +/// +/// U = TypeVar("U") +/// +/// # TypeError: Cannot inherit from Generic[...] multiple times. +/// class C[T](Generic[U]): ... +/// ``` +/// +/// ## Example +/// +/// ```python +/// from typing import Generic, ParamSpec, TypeVar, TypeVarTuple +/// +/// U = TypeVar("U") +/// P = ParamSpec("P") +/// Ts = TypeVarTuple("Ts") +/// +/// +/// class C[T](Generic[U, P, *Ts]): ... +/// ``` +/// +/// Use instead: +/// +/// ```python +/// class C[T, U, **P, *Ts]: ... +/// ``` +/// +/// ## Fix safety +/// As the fix changes runtime behaviour, it is always marked as unsafe. +/// Additionally, it might remove comments. +/// +/// ## References +/// - [Python documentation: User-defined generic types](https://docs.python.org/3/library/typing.html#user-defined-generic-types) +/// - [Python documentation: type parameter lists](https://docs.python.org/3/reference/compound_stmts.html#type-params) +/// - [PEP 695 - Type Parameter Syntax](https://peps.python.org/pep-0695/) +/// +/// [PEP 695]: https://peps.python.org/pep-0695/ +/// [type parameter lists]: https://docs.python.org/3/reference/compound_stmts.html#type-params +#[derive(ViolationMetadata)] +pub(crate) struct ClassWithMixedTypeVars; + +impl Violation for ClassWithMixedTypeVars { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + "Class with type parameter list inherits from `Generic`".to_string() + } + + fn fix_title(&self) -> Option { + Some("Remove `Generic` base class".to_string()) + } +} + +/// UP050 +pub(crate) fn class_with_mixed_type_vars(checker: &mut Checker, class_def: &StmtClassDef) { + if checker.settings.target_version < PythonVersion::Py312 { + return; + } + + let semantic = checker.semantic(); + let StmtClassDef { + type_params, + arguments, + .. + } = class_def; + + let Some(type_params) = type_params.as_deref() else { + return; + }; + + let Some(arguments) = arguments else { + return; + }; + + let Some((generic_base, old_style_type_vars)) = + typing_generic_base_and_arguments(arguments, semantic) + else { + return; + }; + + let mut diagnostic = Diagnostic::new(ClassWithMixedTypeVars, generic_base.range); + + if let Some(fix) = convert_type_vars( + generic_base, + old_style_type_vars, + type_params, + arguments, + checker, + ) { + diagnostic.set_fix(fix); + } + + checker.diagnostics.push(diagnostic); +} + +fn typing_generic_base_and_arguments<'a>( + class_arguments: &'a Arguments, + semantic: &SemanticModel, +) -> Option<(&'a ExprSubscript, &'a Expr)> { + let (_, base @ ExprSubscript { slice, .. }) = find_generic(class_arguments, semantic)?; + + Some((base, slice.as_ref())) +} + +fn convert_type_vars( + generic_base: &ExprSubscript, + old_style_type_vars: &Expr, + type_params: &TypeParams, + class_arguments: &Arguments, + checker: &Checker, +) -> Option { + let mut type_vars = type_params + .type_params + .iter() + .map(TypeVar::from) + .collect::>(); + + let mut converted_type_vars = match old_style_type_vars { + Expr::Tuple(ExprTuple { elts, .. }) => { + generic_arguments_to_type_vars(elts.iter(), type_params, checker)? + } + expr @ (Expr::Subscript(_) | Expr::Name(_)) => { + generic_arguments_to_type_vars(iter::once(expr), type_params, checker)? + } + _ => return None, + }; + + type_vars.append(&mut converted_type_vars); + + let source = checker.source(); + let new_type_params = DisplayTypeVars { + type_vars: &type_vars, + source, + }; + + let remove_generic_base = + remove_argument(generic_base, class_arguments, Parentheses::Remove, source).ok()?; + let replace_type_params = + Edit::range_replacement(new_type_params.to_string(), type_params.range); + + Some(Fix::unsafe_edits( + remove_generic_base, + [replace_type_params], + )) +} + +fn generic_arguments_to_type_vars<'a>( + exprs: impl Iterator, + existing_type_params: &TypeParams, + checker: &'a Checker, +) -> Option>> { + let is_existing_param_of_same_class = |binding: &Binding| { + // This first check should have been unnecessary, + // as a type parameter list can only contain type-parameter bindings. + // Named expressions, for example, are syntax errors. + // However, Ruff doesn't know that yet (#11118), + // so here it shall remain. + matches!(binding.kind, BindingKind::TypeParam) + && existing_type_params.range.contains_range(binding.range) + }; + + let semantic = checker.semantic(); + + let mut type_vars = vec![]; + let mut encountered: Vec<&str> = vec![]; + + for expr in exprs { + let (name, unpacked) = match expr { + Expr::Name(name) => (name, false), + Expr::Starred(ExprStarred { value, .. }) => (value.as_name_expr()?, true), + + Expr::Subscript(ExprSubscript { value, slice, .. }) => { + if !semantic.match_typing_expr(value, "Unpack") { + return None; + } + + (slice.as_name_expr()?, true) + } + + _ => return None, + }; + + let binding = semantic.only_binding(name).map(|id| semantic.binding(id))?; + let name_as_str = name.id.as_str(); + + if is_existing_param_of_same_class(binding) || encountered.contains(&name_as_str) { + continue; + } + + encountered.push(name_as_str); + + let type_var = expr_name_to_type_var(semantic, name)?; + + match (&type_var.kind, unpacked, &type_var.restriction) { + (TypeParamKind::TypeVarTuple, false, _) => return None, + (TypeParamKind::TypeVar, true, _) => return None, + (TypeParamKind::ParamSpec, true, _) => return None, + + (TypeParamKind::TypeVarTuple, _, Some(_)) => return None, + (TypeParamKind::ParamSpec, _, Some(_)) => return None, + + _ => {} + } + + // TODO: Type parameter defaults + if type_var.default.is_some() { + return None; + } + + type_vars.push(type_var); + } + + Some(type_vars) +} diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 834709136b75bb..9113049213d596 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -2,6 +2,7 @@ pub(crate) use ambiguous_unicode_character::*; pub(crate) use assert_with_print_message::*; pub(crate) use assignment_in_assert::*; pub(crate) use asyncio_dangling_task::*; +pub(crate) use class_with_mixed_type_vars::*; pub(crate) use collection_literal_concatenation::*; pub(crate) use dataclass_enum::*; pub(crate) use decimal_from_float_literal::*; @@ -55,6 +56,7 @@ mod ambiguous_unicode_character; mod assert_with_print_message; mod assignment_in_assert; mod asyncio_dangling_task; +mod class_with_mixed_type_vars; mod collection_literal_concatenation; mod confusables; mod dataclass_enum; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF060_RUF060.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF060_RUF060.py.snap new file mode 100644 index 00000000000000..49d842cf6d6107 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF060_RUF060.py.snap @@ -0,0 +1,400 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF060.py:23:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +21 | ### Errors +22 | +23 | class C[T](Generic[_A]): ... + | ^^^^^^^^^^^ RUF060 +24 | class C[T](Generic[_B], str): ... +25 | class C[T](int, Generic[_C]): ... + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +20 20 | +21 21 | ### Errors +22 22 | +23 |-class C[T](Generic[_A]): ... + 23 |+class C[T, _A]: ... +24 24 | class C[T](Generic[_B], str): ... +25 25 | class C[T](int, Generic[_C]): ... +26 26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults + +RUF060.py:24:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +23 | class C[T](Generic[_A]): ... +24 | class C[T](Generic[_B], str): ... + | ^^^^^^^^^^^ RUF060 +25 | class C[T](int, Generic[_C]): ... +26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +21 21 | ### Errors +22 22 | +23 23 | class C[T](Generic[_A]): ... +24 |-class C[T](Generic[_B], str): ... + 24 |+class C[T, _B: int](str): ... +25 25 | class C[T](int, Generic[_C]): ... +26 26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults +27 27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults + +RUF060.py:25:17: RUF060 [*] Class with type parameter list inherits from `Generic` + | +23 | class C[T](Generic[_A]): ... +24 | class C[T](Generic[_B], str): ... +25 | class C[T](int, Generic[_C]): ... + | ^^^^^^^^^^^ RUF060 +26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults +27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +22 22 | +23 23 | class C[T](Generic[_A]): ... +24 24 | class C[T](Generic[_B], str): ... +25 |-class C[T](int, Generic[_C]): ... + 25 |+class C[T, _C: (str, bytes)](int): ... +26 26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults +27 27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults +28 28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults + +RUF060.py:26:19: RUF060 Class with type parameter list inherits from `Generic` + | +24 | class C[T](Generic[_B], str): ... +25 | class C[T](int, Generic[_C]): ... +26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^ RUF060 +27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults +28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF060.py:27:12: RUF060 Class with type parameter list inherits from `Generic` + | +25 | class C[T](int, Generic[_C]): ... +26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults +27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^ RUF060 +28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF060.py:28:22: RUF060 Class with type parameter list inherits from `Generic` + | +26 | class C[T](bytes, Generic[_D], bool): ... # TODO: Type parameter defaults +27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults +28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^ RUF060 +29 | +30 | class C[*Ts](Generic[*_As]): ... + | + = help: Remove `Generic` base class + +RUF060.py:30:14: RUF060 [*] Class with type parameter list inherits from `Generic` + | +28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults +29 | +30 | class C[*Ts](Generic[*_As]): ... + | ^^^^^^^^^^^^^ RUF060 +31 | class C[*Ts](Generic[Unpack[_As]]): ... +32 | class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +27 27 | class C[T](Generic[_E], list[_E]): ... # TODO: Type parameter defaults +28 28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults +29 29 | +30 |-class C[*Ts](Generic[*_As]): ... + 30 |+class C[*Ts, *_As]: ... +31 31 | class C[*Ts](Generic[Unpack[_As]]): ... +32 32 | class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... +33 33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults + +RUF060.py:31:14: RUF060 [*] Class with type parameter list inherits from `Generic` + | +30 | class C[*Ts](Generic[*_As]): ... +31 | class C[*Ts](Generic[Unpack[_As]]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF060 +32 | class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... +33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +28 28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults +29 29 | +30 30 | class C[*Ts](Generic[*_As]): ... +31 |-class C[*Ts](Generic[Unpack[_As]]): ... + 31 |+class C[*Ts, *_As]: ... +32 32 | class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... +33 33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults +34 34 | + +RUF060.py:32:14: RUF060 Class with type parameter list inherits from `Generic` + | +30 | class C[*Ts](Generic[*_As]): ... +31 | class C[*Ts](Generic[Unpack[_As]]): ... +32 | class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF060 +33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF060.py:33:44: RUF060 Class with type parameter list inherits from `Generic` + | +31 | class C[*Ts](Generic[Unpack[_As]]): ... +32 | class C[*Ts](Generic[Unpack[_Bs]], tuple[*Bs]): ... +33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:36:14: RUF060 [*] Class with type parameter list inherits from `Generic` + | +36 | class C[**P](Generic[_P1]): ... + | ^^^^^^^^^^^^ RUF060 +37 | class C[**P](Generic[_P2]): ... +38 | class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +33 33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults +34 34 | +35 35 | +36 |-class C[**P](Generic[_P1]): ... + 36 |+class C[**P, **_P1]: ... +37 37 | class C[**P](Generic[_P2]): ... +38 38 | class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults +39 39 | + +RUF060.py:37:14: RUF060 Class with type parameter list inherits from `Generic` + | +36 | class C[**P](Generic[_P1]): ... +37 | class C[**P](Generic[_P2]): ... + | ^^^^^^^^^^^^ RUF060 +38 | class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF060.py:38:14: RUF060 Class with type parameter list inherits from `Generic` + | +36 | class C[**P](Generic[_P1]): ... +37 | class C[**P](Generic[_P2]): ... +38 | class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:41:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +41 | class C[T](Generic[T, _A]): ... + | ^^^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +38 38 | class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults +39 39 | +40 40 | +41 |-class C[T](Generic[T, _A]): ... + 41 |+class C[T, _A]: ... +42 42 | +43 43 | +44 44 | # See `is_existing_param_of_same_class`. + +RUF060.py:47:35: RUF060 Class with type parameter list inherits from `Generic` + | +45 | # `expr_name_to_type_var` doesn't handle named expressions, +46 | # only simple assignments, so there is no fix. +47 | class C[T: (_Z := TypeVar('_Z'))](Generic[_Z]): ... + | ^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:51:16: RUF060 [*] Class with type parameter list inherits from `Generic` + | +50 | class C(Generic[_B]): +51 | class D[T](Generic[_B, T]): ... + | ^^^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +48 48 | +49 49 | +50 50 | class C(Generic[_B]): +51 |- class D[T](Generic[_B, T]): ... + 51 |+ class D[T, _B: int]: ... +52 52 | +53 53 | +54 54 | class C[T]: + +RUF060.py:55:16: RUF060 Class with type parameter list inherits from `Generic` + | +54 | class C[T]: +55 | class D[U](Generic[T, U]): ... + | ^^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:60:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +58 | # In a single run, only the first is reported. +59 | # Others will be reported/fixed in following iterations. +60 | class C[T](Generic[_C], Generic[_D]): ... + | ^^^^^^^^^^^ RUF060 +61 | class C[T, _C: (str, bytes)](Generic[_D]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +57 57 | +58 58 | # In a single run, only the first is reported. +59 59 | # Others will be reported/fixed in following iterations. +60 |-class C[T](Generic[_C], Generic[_D]): ... + 60 |+class C[T, _C: (str, bytes)](Generic[_D]): ... +61 61 | class C[T, _C: (str, bytes)](Generic[_D]): ... # TODO: Type parameter defaults +62 62 | +63 63 | + +RUF060.py:61:30: RUF060 Class with type parameter list inherits from `Generic` + | +59 | # Others will be reported/fixed in following iterations. +60 | class C[T](Generic[_C], Generic[_D]): ... +61 | class C[T, _C: (str, bytes)](Generic[_D]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:66:3: RUF060 Class with type parameter list inherits from `Generic` + | +64 | class C[ +65 | T # Comment +66 | ](Generic[_E]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:69:12: RUF060 Class with type parameter list inherits from `Generic` + | +69 | class C[T](Generic[Generic[_F]]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF060 +70 | class C[T](Generic[Unpack[_A]]): ... +71 | class C[T](Generic[Unpack[_P1]]): ... + | + = help: Remove `Generic` base class + +RUF060.py:70:12: RUF060 Class with type parameter list inherits from `Generic` + | +69 | class C[T](Generic[Generic[_F]]): ... +70 | class C[T](Generic[Unpack[_A]]): ... + | ^^^^^^^^^^^^^^^^^^^ RUF060 +71 | class C[T](Generic[Unpack[_P1]]): ... +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... + | + = help: Remove `Generic` base class + +RUF060.py:71:12: RUF060 Class with type parameter list inherits from `Generic` + | +69 | class C[T](Generic[Generic[_F]]): ... +70 | class C[T](Generic[Unpack[_A]]): ... +71 | class C[T](Generic[Unpack[_P1]]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF060 +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... +73 | class C[T](Generic[Unpack[*_As]]): ... + | + = help: Remove `Generic` base class + +RUF060.py:72:12: RUF060 Class with type parameter list inherits from `Generic` + | +70 | class C[T](Generic[Unpack[_A]]): ... +71 | class C[T](Generic[Unpack[_P1]]): ... +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF060 +73 | class C[T](Generic[Unpack[*_As]]): ... +74 | class C[T](Generic[Unpack[_As, _Bs]]): ... + | + = help: Remove `Generic` base class + +RUF060.py:73:12: RUF060 Class with type parameter list inherits from `Generic` + | +71 | class C[T](Generic[Unpack[_P1]]): ... +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... +73 | class C[T](Generic[Unpack[*_As]]): ... + | ^^^^^^^^^^^^^^^^^^^^^ RUF060 +74 | class C[T](Generic[Unpack[_As, _Bs]]): ... + | + = help: Remove `Generic` base class + +RUF060.py:74:12: RUF060 Class with type parameter list inherits from `Generic` + | +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... +73 | class C[T](Generic[Unpack[*_As]]): ... +74 | class C[T](Generic[Unpack[_As, _Bs]]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +RUF060.py:77:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +77 | class C[T](Generic[_A, _A]): ... + | ^^^^^^^^^^^^^^^ RUF060 +78 | class C[T](Generic[_A, Unpack[_As]]): ... +79 | class C[T](Generic[*_As, _A]): ... + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +74 74 | class C[T](Generic[Unpack[_As, _Bs]]): ... +75 75 | +76 76 | +77 |-class C[T](Generic[_A, _A]): ... + 77 |+class C[T, _A]: ... +78 78 | class C[T](Generic[_A, Unpack[_As]]): ... +79 79 | class C[T](Generic[*_As, _A]): ... +80 80 | + +RUF060.py:78:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +77 | class C[T](Generic[_A, _A]): ... +78 | class C[T](Generic[_A, Unpack[_As]]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^ RUF060 +79 | class C[T](Generic[*_As, _A]): ... + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +75 75 | +76 76 | +77 77 | class C[T](Generic[_A, _A]): ... +78 |-class C[T](Generic[_A, Unpack[_As]]): ... + 78 |+class C[T, _A, *_As]: ... +79 79 | class C[T](Generic[*_As, _A]): ... +80 80 | +81 81 | + +RUF060.py:79:12: RUF060 [*] Class with type parameter list inherits from `Generic` + | +77 | class C[T](Generic[_A, _A]): ... +78 | class C[T](Generic[_A, Unpack[_As]]): ... +79 | class C[T](Generic[*_As, _A]): ... + | ^^^^^^^^^^^^^^^^^ RUF060 + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +76 76 | +77 77 | class C[T](Generic[_A, _A]): ... +78 78 | class C[T](Generic[_A, Unpack[_As]]): ... +79 |-class C[T](Generic[*_As, _A]): ... + 79 |+class C[T, *_As, _A]: ... +80 80 | +81 81 | +82 82 | ### No errors diff --git a/ruff.schema.json b/ruff.schema.json index 50af727c32d745..0a84d643cdc833 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3957,6 +3957,8 @@ "RUF056", "RUF057", "RUF058", + "RUF06", + "RUF060", "RUF1", "RUF10", "RUF100",