diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF053.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF053.py new file mode 100644 index 00000000000000..06b46a8b8a3f31 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF053.py @@ -0,0 +1,109 @@ +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) +_G = TypeVar('_G', str, a := int) + +_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]): ... + + +from somewhere import APublicTypeVar +class C[T](Generic[APublicTypeVar]): ... +class C[T](Generic[APublicTypeVar, _A]): ... + + +# `_G` has two constraints: `str` and `a := int`. +# The latter cannot be used as a PEP 695 constraint, +# as named expressions are forbidden within type parameter lists. +# See also the `_Z` example above. +class C[T](Generic[_G]): ... # Should be moved down below eventually + + +# Single-element constraints should not be converted to a bound. +class C[T: (str,)](Generic[_A]): ... +class C[T: [a]](Generic[_A]): ... + + +# Existing bounds should not be deparenthesized. +# class C[T: (_Y := int)](Generic[_A]): ... # TODO: Uncomment this +# class C[T: (*a,)](Generic[_A]): ... # TODO: Uncomment this + + +### 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 15674895e18a69..87ba76c6550d7f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -554,6 +554,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 b9be03c40a7bde..c3e66c99de634a 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1005,6 +1005,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "049") => (RuleGroup::Preview, rules::ruff::rules::DataclassEnum), (Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel), (Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable), + (Ruff, "053") => (RuleGroup::Preview, rules::ruff::rules::ClassWithMixedTypeVars), (Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback), (Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound), 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 68db219871485e..ec36bfe5997118 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; @@ -28,7 +28,7 @@ mod non_pep695_type_alias; mod private_type_parameter; #[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)`. @@ -39,25 +39,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<'_> { @@ -81,7 +81,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, } @@ -192,6 +192,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>, @@ -242,7 +270,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> { @@ -349,3 +377,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 4c2de26865bfd9..fbf95f7a931cc8 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 /// @@ -211,18 +212,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 e5e353e1cb4ab6..fdb147439f3abf 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -435,6 +435,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("RUF053.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..6cd75352d7b58f --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs @@ -0,0 +1,247 @@ +use rustc_hash::FxHashSet; +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::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, comments within the fix range will not be preserved. +/// +/// ## 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()) + } +} + +/// RUF053 +pub(crate) fn class_with_mixed_type_vars(checker: &mut Checker, class_def: &StmtClassDef) { + if checker.settings.target_version < PythonVersion::Py312 { + return; + } + + let StmtClassDef { + type_params, + arguments, + .. + } = class_def; + + let Some(type_params) = type_params else { + return; + }; + + let Some(arguments) = arguments else { + return; + }; + + let Some((generic_base, old_style_type_vars)) = + typing_generic_base_and_arguments(arguments, checker.semantic()) + else { + return; + }; + + let mut diagnostic = Diagnostic::new(ClassWithMixedTypeVars, generic_base.range); + + diagnostic.try_set_optional_fix(|| { + convert_type_vars( + generic_base, + old_style_type_vars, + type_params, + arguments, + checker, + ) + }); + + 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, +) -> anyhow::Result> { + let mut type_vars: Vec<_> = type_params.type_params.iter().map(TypeVar::from).collect(); + + let semantic = checker.semantic(); + let converted_type_vars = match old_style_type_vars { + Expr::Tuple(ExprTuple { elts, .. }) => { + generic_arguments_to_type_vars(elts.iter(), type_params, semantic) + } + expr @ (Expr::Subscript(_) | Expr::Name(_)) => { + generic_arguments_to_type_vars(iter::once(expr), type_params, semantic) + } + _ => None, + }; + + let Some(converted_type_vars) = converted_type_vars else { + return Ok(None); + }; + + type_vars.extend(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)?; + let replace_type_params = + Edit::range_replacement(new_type_params.to_string(), type_params.range); + + Ok(Some(Fix::unsafe_edits( + remove_generic_base, + [replace_type_params], + ))) +} + +/// Returns the type variables `exprs` represent. +/// +/// If at least one of them cannot be converted to [`TypeVar`], +/// `None` is returned. +fn generic_arguments_to_type_vars<'a>( + exprs: impl Iterator, + existing_type_params: &TypeParams, + semantic: &'a SemanticModel, +) -> Option>> { + let mut type_vars = vec![]; + let mut encountered: FxHashSet<&str> = existing_type_params + .iter() + .map(|tp| tp.name().as_str()) + .collect(); + + 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, + }; + + if !encountered.insert(name.id.as_str()) { + continue; + } + + let type_var = expr_name_to_type_var(semantic, name)?; + + if !type_var_is_valid(&type_var, unpacked) { + return None; + } + + // TODO: Type parameter defaults + if type_var.default.is_some() { + return None; + } + + type_vars.push(type_var); + } + + Some(type_vars) +} + +/// Returns true in the following cases: +/// +/// * If `type_var` is a `TypeVar`: +/// * It must not be unpacked +/// * If `type_var` is a `TypeVarTuple`: +/// * It must be unpacked +/// * It must not have any restrictions +/// * If `type_var` is a `ParamSpec`: +/// * It must not be unpacked +/// * It must not have any restrictions +fn type_var_is_valid(type_var: &TypeVar, unpacked: bool) -> bool { + let is_type_var_tuple = matches!(&type_var.kind, TypeParamKind::TypeVarTuple); + + if is_type_var_tuple && !unpacked || !is_type_var_tuple && unpacked { + return false; + } + + if !matches!(&type_var.kind, TypeParamKind::TypeVar) && type_var.restriction.is_some() { + return false; + } + + true +} 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__RUF053_RUF053.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF053_RUF053.py.snap new file mode 100644 index 00000000000000..8d308ab552d9fe --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF053_RUF053.py.snap @@ -0,0 +1,475 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF053.py:23:12: RUF053 [*] Class with type parameter list inherits from `Generic` + | +21 | ### Errors +22 | +23 | class C[T](Generic[_A]): ... + | ^^^^^^^^^^^ RUF053 +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 + +RUF053.py:24:12: RUF053 [*] Class with type parameter list inherits from `Generic` + | +23 | class C[T](Generic[_A]): ... +24 | class C[T](Generic[_B], str): ... + | ^^^^^^^^^^^ RUF053 +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 + +RUF053.py:25:17: RUF053 [*] 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]): ... + | ^^^^^^^^^^^ RUF053 +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 + +RUF053.py:26:19: RUF053 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 + | ^^^^^^^^^^^ RUF053 +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 + +RUF053.py:27:12: RUF053 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 + | ^^^^^^^^^^^ RUF053 +28 | class C[T](list[_F], Generic[_F]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF053.py:28:22: RUF053 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 + | ^^^^^^^^^^^ RUF053 +29 | +30 | class C[*Ts](Generic[*_As]): ... + | + = help: Remove `Generic` base class + +RUF053.py:30:14: RUF053 [*] 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]): ... + | ^^^^^^^^^^^^^ RUF053 +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 + +RUF053.py:31:14: RUF053 [*] Class with type parameter list inherits from `Generic` + | +30 | class C[*Ts](Generic[*_As]): ... +31 | class C[*Ts](Generic[Unpack[_As]]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF053 +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 | + +RUF053.py:32:14: RUF053 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]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF053 +33 | class C[*Ts](Callable[[*_Cs], tuple[*Ts]], Generic[_Cs]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF053.py:33:44: RUF053 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 + | ^^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:36:14: RUF053 [*] Class with type parameter list inherits from `Generic` + | +36 | class C[**P](Generic[_P1]): ... + | ^^^^^^^^^^^^ RUF053 +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 | + +RUF053.py:37:14: RUF053 Class with type parameter list inherits from `Generic` + | +36 | class C[**P](Generic[_P1]): ... +37 | class C[**P](Generic[_P2]): ... + | ^^^^^^^^^^^^ RUF053 +38 | class C[**P](Generic[_P3]): ... # TODO: Type parameter defaults + | + = help: Remove `Generic` base class + +RUF053.py:38:14: RUF053 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 + | ^^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:41:12: RUF053 [*] Class with type parameter list inherits from `Generic` + | +41 | class C[T](Generic[T, _A]): ... + | ^^^^^^^^^^^^^^ RUF053 + | + = 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`. + +RUF053.py:47:35: RUF053 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]): ... + | ^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:51:16: RUF053 [*] Class with type parameter list inherits from `Generic` + | +50 | class C(Generic[_B]): +51 | class D[T](Generic[_B, T]): ... + | ^^^^^^^^^^^^^^ RUF053 + | + = 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]: + +RUF053.py:55:16: RUF053 Class with type parameter list inherits from `Generic` + | +54 | class C[T]: +55 | class D[U](Generic[T, U]): ... + | ^^^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:60:12: RUF053 [*] 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]): ... + | ^^^^^^^^^^^ RUF053 +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 | + +RUF053.py:61:30: RUF053 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 + | ^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:66:3: RUF053 Class with type parameter list inherits from `Generic` + | +64 | class C[ +65 | T # Comment +66 | ](Generic[_E]): ... # TODO: Type parameter defaults + | ^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:69:12: RUF053 Class with type parameter list inherits from `Generic` + | +69 | class C[T](Generic[Generic[_F]]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF053 +70 | class C[T](Generic[Unpack[_A]]): ... +71 | class C[T](Generic[Unpack[_P1]]): ... + | + = help: Remove `Generic` base class + +RUF053.py:70:12: RUF053 Class with type parameter list inherits from `Generic` + | +69 | class C[T](Generic[Generic[_F]]): ... +70 | class C[T](Generic[Unpack[_A]]): ... + | ^^^^^^^^^^^^^^^^^^^ RUF053 +71 | class C[T](Generic[Unpack[_P1]]): ... +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... + | + = help: Remove `Generic` base class + +RUF053.py:71:12: RUF053 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]]): ... + | ^^^^^^^^^^^^^^^^^^^^ RUF053 +72 | class C[T](Generic[Unpack[Unpack[_P2]]]): ... +73 | class C[T](Generic[Unpack[*_As]]): ... + | + = help: Remove `Generic` base class + +RUF053.py:72:12: RUF053 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]]]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF053 +73 | class C[T](Generic[Unpack[*_As]]): ... +74 | class C[T](Generic[Unpack[_As, _Bs]]): ... + | + = help: Remove `Generic` base class + +RUF053.py:73:12: RUF053 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]]): ... + | ^^^^^^^^^^^^^^^^^^^^^ RUF053 +74 | class C[T](Generic[Unpack[_As, _Bs]]): ... + | + = help: Remove `Generic` base class + +RUF053.py:74:12: RUF053 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]]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:77:12: RUF053 [*] Class with type parameter list inherits from `Generic` + | +77 | class C[T](Generic[_A, _A]): ... + | ^^^^^^^^^^^^^^^ RUF053 +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 | + +RUF053.py:78:12: RUF053 [*] Class with type parameter list inherits from `Generic` + | +77 | class C[T](Generic[_A, _A]): ... +78 | class C[T](Generic[_A, Unpack[_As]]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^ RUF053 +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 | + +RUF053.py:79:12: RUF053 [*] 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]): ... + | ^^^^^^^^^^^^^^^^^ RUF053 + | + = 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 | from somewhere import APublicTypeVar + +RUF053.py:83:12: RUF053 Class with type parameter list inherits from `Generic` + | +82 | from somewhere import APublicTypeVar +83 | class C[T](Generic[APublicTypeVar]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^ RUF053 +84 | class C[T](Generic[APublicTypeVar, _A]): ... + | + = help: Remove `Generic` base class + +RUF053.py:84:12: RUF053 Class with type parameter list inherits from `Generic` + | +82 | from somewhere import APublicTypeVar +83 | class C[T](Generic[APublicTypeVar]): ... +84 | class C[T](Generic[APublicTypeVar, _A]): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +RUF053.py:91:12: RUF053 [*] Class with type parameter list inherits from `Generic` + | +89 | # as named expressions are forbidden within type parameter lists. +90 | # See also the `_Z` example above. +91 | class C[T](Generic[_G]): ... # Should be moved down below eventually + | ^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +88 88 | # The latter cannot be used as a PEP 695 constraint, +89 89 | # as named expressions are forbidden within type parameter lists. +90 90 | # See also the `_Z` example above. +91 |-class C[T](Generic[_G]): ... # Should be moved down below eventually + 91 |+class C[T, _G: (str, a := int)]: ... # Should be moved down below eventually +92 92 | +93 93 | +94 94 | # Single-element constraints should not be converted to a bound. + +RUF053.py:95:20: RUF053 [*] Class with type parameter list inherits from `Generic` + | +94 | # Single-element constraints should not be converted to a bound. +95 | class C[T: (str,)](Generic[_A]): ... + | ^^^^^^^^^^^ RUF053 +96 | class C[T: [a]](Generic[_A]): ... + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +92 92 | +93 93 | +94 94 | # Single-element constraints should not be converted to a bound. +95 |-class C[T: (str,)](Generic[_A]): ... + 95 |+class C[T: (str), _A]: ... +96 96 | class C[T: [a]](Generic[_A]): ... +97 97 | +98 98 | + +RUF053.py:96:17: RUF053 [*] Class with type parameter list inherits from `Generic` + | +94 | # Single-element constraints should not be converted to a bound. +95 | class C[T: (str,)](Generic[_A]): ... +96 | class C[T: [a]](Generic[_A]): ... + | ^^^^^^^^^^^ RUF053 + | + = help: Remove `Generic` base class + +ℹ Unsafe fix +93 93 | +94 94 | # Single-element constraints should not be converted to a bound. +95 95 | class C[T: (str,)](Generic[_A]): ... +96 |-class C[T: [a]](Generic[_A]): ... + 96 |+class C[T: [a], _A]: ... +97 97 | +98 98 | +99 99 | # Existing bounds should not be deparenthesized. diff --git a/ruff.schema.json b/ruff.schema.json index fd005ceced23bd..83815f2b0d3f89 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3947,6 +3947,7 @@ "RUF05", "RUF051", "RUF052", + "RUF053", "RUF055", "RUF056", "RUF057",