diff --git a/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs b/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs index 541f5e7f8f2c8..9af30526aeee9 100644 --- a/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs +++ b/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs @@ -46,27 +46,34 @@ use crate::rules::pylint::helpers::type_param_name; pub struct TypeNameIncorrectVariance { kind: VarKind, param_name: String, + variance: VarVariance, + replacement_name: String, } impl Violation for TypeNameIncorrectVariance { #[derive_message_formats] fn message(&self) -> String { - let TypeNameIncorrectVariance { kind, param_name } = self; - format!("`{kind}` name \"{param_name}\" does not match variance") + let TypeNameIncorrectVariance { + kind, + param_name, + variance, + replacement_name, + } = self; + format!("`{kind}` name \"{param_name}\" does not reflect its {variance}; consider renaming it to \"{replacement_name}\"") } } /// PLC0105 pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) { let Expr::Call(ast::ExprCall { - func, - args, - keywords, - .. - }) = value - else { - return; - }; + func, + args, + keywords, + .. + }) = value + else { + return; + }; let Some(param_name) = type_param_name(args, keywords) else { return; @@ -114,14 +121,26 @@ pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) None } }) - else { - return; + else { + return; + }; + + let variance = variance(covariant, contravariant); + let name_root = param_name + .trim_end_matches("_co") + .trim_end_matches("_contra"); + let replacement_name: String = match variance { + VarVariance::Covariance => format!("{name_root}_co"), + VarVariance::Contravariance => format!("{name_root}_contra"), + VarVariance::Invariance => name_root.to_string(), }; checker.diagnostics.push(Diagnostic::new( TypeNameIncorrectVariance { kind, param_name: param_name.to_string(), + variance, + replacement_name, }, func.range(), )); @@ -138,6 +157,18 @@ fn mismatch(param_name: &str, covariant: Option<&Expr>, contravariant: Option<&E } } +/// Return the variance of the type parameter. +fn variance(covariant: Option<&Expr>, contravariant: Option<&Expr>) -> VarVariance { + match ( + covariant.map(is_const_true), + contravariant.map(is_const_true), + ) { + (Some(true), _) => VarVariance::Covariance, + (_, Some(true)) => VarVariance::Contravariance, + _ => VarVariance::Invariance, + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] enum VarKind { TypeVar, @@ -152,3 +183,20 @@ impl fmt::Display for VarKind { } } } + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +enum VarVariance { + Covariance, + Contravariance, + Invariance, +} + +impl fmt::Display for VarVariance { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + VarVariance::Covariance => fmt.write_str("covariance"), + VarVariance::Contravariance => fmt.write_str("contravariance"), + VarVariance::Invariance => fmt.write_str("invariance"), + } + } +} diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0105_type_name_incorrect_variance.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0105_type_name_incorrect_variance.py.snap index 8e54cce037f96..675130c6eae07 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0105_type_name_incorrect_variance.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0105_type_name_incorrect_variance.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff/src/rules/pylint/mod.rs --- -type_name_incorrect_variance.py:5:5: PLC0105 `TypeVar` name "T" does not match variance +type_name_incorrect_variance.py:5:5: PLC0105 `TypeVar` name "T" does not reflect its covariance; consider renaming it to "T_co" | 3 | # Errors. 4 | @@ -11,7 +11,7 @@ type_name_incorrect_variance.py:5:5: PLC0105 `TypeVar` name "T" does not match v 7 | T = TypeVar("T", contravariant=True) | -type_name_incorrect_variance.py:6:5: PLC0105 `TypeVar` name "T" does not match variance +type_name_incorrect_variance.py:6:5: PLC0105 `TypeVar` name "T" does not reflect its covariance; consider renaming it to "T_co" | 5 | T = TypeVar("T", covariant=True) 6 | T = TypeVar("T", covariant=True, contravariant=False) @@ -20,7 +20,7 @@ type_name_incorrect_variance.py:6:5: PLC0105 `TypeVar` name "T" does not match v 8 | T = TypeVar("T", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:7:5: PLC0105 `TypeVar` name "T" does not match variance +type_name_incorrect_variance.py:7:5: PLC0105 `TypeVar` name "T" does not reflect its contravariance; consider renaming it to "T_contra" | 5 | T = TypeVar("T", covariant=True) 6 | T = TypeVar("T", covariant=True, contravariant=False) @@ -30,7 +30,7 @@ type_name_incorrect_variance.py:7:5: PLC0105 `TypeVar` name "T" does not match v 9 | P = ParamSpec("P", covariant=True) | -type_name_incorrect_variance.py:8:5: PLC0105 `TypeVar` name "T" does not match variance +type_name_incorrect_variance.py:8:5: PLC0105 `TypeVar` name "T" does not reflect its contravariance; consider renaming it to "T_contra" | 6 | T = TypeVar("T", covariant=True, contravariant=False) 7 | T = TypeVar("T", contravariant=True) @@ -40,7 +40,7 @@ type_name_incorrect_variance.py:8:5: PLC0105 `TypeVar` name "T" does not match v 10 | P = ParamSpec("P", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:9:5: PLC0105 `ParamSpec` name "P" does not match variance +type_name_incorrect_variance.py:9:5: PLC0105 `ParamSpec` name "P" does not reflect its covariance; consider renaming it to "P_co" | 7 | T = TypeVar("T", contravariant=True) 8 | T = TypeVar("T", covariant=False, contravariant=True) @@ -50,7 +50,7 @@ type_name_incorrect_variance.py:9:5: PLC0105 `ParamSpec` name "P" does not match 11 | P = ParamSpec("P", contravariant=True) | -type_name_incorrect_variance.py:10:5: PLC0105 `ParamSpec` name "P" does not match variance +type_name_incorrect_variance.py:10:5: PLC0105 `ParamSpec` name "P" does not reflect its covariance; consider renaming it to "P_co" | 8 | T = TypeVar("T", covariant=False, contravariant=True) 9 | P = ParamSpec("P", covariant=True) @@ -60,7 +60,7 @@ type_name_incorrect_variance.py:10:5: PLC0105 `ParamSpec` name "P" does not matc 12 | P = ParamSpec("P", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:11:5: PLC0105 `ParamSpec` name "P" does not match variance +type_name_incorrect_variance.py:11:5: PLC0105 `ParamSpec` name "P" does not reflect its contravariance; consider renaming it to "P_contra" | 9 | P = ParamSpec("P", covariant=True) 10 | P = ParamSpec("P", covariant=True, contravariant=False) @@ -69,7 +69,7 @@ type_name_incorrect_variance.py:11:5: PLC0105 `ParamSpec` name "P" does not matc 12 | P = ParamSpec("P", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "P" does not match variance +type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "P" does not reflect its contravariance; consider renaming it to "P_contra" | 10 | P = ParamSpec("P", covariant=True, contravariant=False) 11 | P = ParamSpec("P", contravariant=True) @@ -79,7 +79,7 @@ type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "P" does not matc 14 | T_co = TypeVar("T_co") | -type_name_incorrect_variance.py:14:8: PLC0105 `TypeVar` name "T_co" does not match variance +type_name_incorrect_variance.py:14:8: PLC0105 `TypeVar` name "T_co" does not reflect its invariance; consider renaming it to "T" | 12 | P = ParamSpec("P", covariant=False, contravariant=True) 13 | @@ -89,7 +89,7 @@ type_name_incorrect_variance.py:14:8: PLC0105 `TypeVar` name "T_co" does not mat 16 | T_co = TypeVar("T_co", contravariant=False) | -type_name_incorrect_variance.py:15:8: PLC0105 `TypeVar` name "T_co" does not match variance +type_name_incorrect_variance.py:15:8: PLC0105 `TypeVar` name "T_co" does not reflect its invariance; consider renaming it to "T" | 14 | T_co = TypeVar("T_co") 15 | T_co = TypeVar("T_co", covariant=False) @@ -98,7 +98,7 @@ type_name_incorrect_variance.py:15:8: PLC0105 `TypeVar` name "T_co" does not mat 17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) | -type_name_incorrect_variance.py:16:8: PLC0105 `TypeVar` name "T_co" does not match variance +type_name_incorrect_variance.py:16:8: PLC0105 `TypeVar` name "T_co" does not reflect its invariance; consider renaming it to "T" | 14 | T_co = TypeVar("T_co") 15 | T_co = TypeVar("T_co", covariant=False) @@ -108,7 +108,7 @@ type_name_incorrect_variance.py:16:8: PLC0105 `TypeVar` name "T_co" does not mat 18 | T_co = TypeVar("T_co", contravariant=True) | -type_name_incorrect_variance.py:17:8: PLC0105 `TypeVar` name "T_co" does not match variance +type_name_incorrect_variance.py:17:8: PLC0105 `TypeVar` name "T_co" does not reflect its invariance; consider renaming it to "T" | 15 | T_co = TypeVar("T_co", covariant=False) 16 | T_co = TypeVar("T_co", contravariant=False) @@ -118,7 +118,7 @@ type_name_incorrect_variance.py:17:8: PLC0105 `TypeVar` name "T_co" does not mat 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:18:8: PLC0105 `TypeVar` name "T_co" does not match variance +type_name_incorrect_variance.py:18:8: PLC0105 `TypeVar` name "T_co" does not reflect its contravariance; consider renaming it to "T_contra" | 16 | T_co = TypeVar("T_co", contravariant=False) 17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) @@ -128,7 +128,7 @@ type_name_incorrect_variance.py:18:8: PLC0105 `TypeVar` name "T_co" does not mat 20 | P_co = ParamSpec("P_co") | -type_name_incorrect_variance.py:19:8: PLC0105 `TypeVar` name "T_co" does not match variance +type_name_incorrect_variance.py:19:8: PLC0105 `TypeVar` name "T_co" does not reflect its contravariance; consider renaming it to "T_contra" | 17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) 18 | T_co = TypeVar("T_co", contravariant=True) @@ -138,7 +138,7 @@ type_name_incorrect_variance.py:19:8: PLC0105 `TypeVar` name "T_co" does not mat 21 | P_co = ParamSpec("P_co", covariant=False) | -type_name_incorrect_variance.py:20:8: PLC0105 `ParamSpec` name "P_co" does not match variance +type_name_incorrect_variance.py:20:8: PLC0105 `ParamSpec` name "P_co" does not reflect its invariance; consider renaming it to "P" | 18 | T_co = TypeVar("T_co", contravariant=True) 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) @@ -148,7 +148,7 @@ type_name_incorrect_variance.py:20:8: PLC0105 `ParamSpec` name "P_co" does not m 22 | P_co = ParamSpec("P_co", contravariant=False) | -type_name_incorrect_variance.py:21:8: PLC0105 `ParamSpec` name "P_co" does not match variance +type_name_incorrect_variance.py:21:8: PLC0105 `ParamSpec` name "P_co" does not reflect its invariance; consider renaming it to "P" | 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) 20 | P_co = ParamSpec("P_co") @@ -158,7 +158,7 @@ type_name_incorrect_variance.py:21:8: PLC0105 `ParamSpec` name "P_co" does not m 23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) | -type_name_incorrect_variance.py:22:8: PLC0105 `ParamSpec` name "P_co" does not match variance +type_name_incorrect_variance.py:22:8: PLC0105 `ParamSpec` name "P_co" does not reflect its invariance; consider renaming it to "P" | 20 | P_co = ParamSpec("P_co") 21 | P_co = ParamSpec("P_co", covariant=False) @@ -168,7 +168,7 @@ type_name_incorrect_variance.py:22:8: PLC0105 `ParamSpec` name "P_co" does not m 24 | P_co = ParamSpec("P_co", contravariant=True) | -type_name_incorrect_variance.py:23:8: PLC0105 `ParamSpec` name "P_co" does not match variance +type_name_incorrect_variance.py:23:8: PLC0105 `ParamSpec` name "P_co" does not reflect its invariance; consider renaming it to "P" | 21 | P_co = ParamSpec("P_co", covariant=False) 22 | P_co = ParamSpec("P_co", contravariant=False) @@ -178,7 +178,7 @@ type_name_incorrect_variance.py:23:8: PLC0105 `ParamSpec` name "P_co" does not m 25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:24:8: PLC0105 `ParamSpec` name "P_co" does not match variance +type_name_incorrect_variance.py:24:8: PLC0105 `ParamSpec` name "P_co" does not reflect its contravariance; consider renaming it to "P_contra" | 22 | P_co = ParamSpec("P_co", contravariant=False) 23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) @@ -187,7 +187,7 @@ type_name_incorrect_variance.py:24:8: PLC0105 `ParamSpec` name "P_co" does not m 25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "P_co" does not match variance +type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "P_co" does not reflect its contravariance; consider renaming it to "P_contra" | 23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) 24 | P_co = ParamSpec("P_co", contravariant=True) @@ -197,7 +197,7 @@ type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "P_co" does not m 27 | T_contra = TypeVar("T_contra") | -type_name_incorrect_variance.py:27:12: PLC0105 `TypeVar` name "T_contra" does not match variance +type_name_incorrect_variance.py:27:12: PLC0105 `TypeVar` name "T_contra" does not reflect its invariance; consider renaming it to "T" | 25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) 26 | @@ -207,7 +207,7 @@ type_name_incorrect_variance.py:27:12: PLC0105 `TypeVar` name "T_contra" does no 29 | T_contra = TypeVar("T_contra", contravariant=False) | -type_name_incorrect_variance.py:28:12: PLC0105 `TypeVar` name "T_contra" does not match variance +type_name_incorrect_variance.py:28:12: PLC0105 `TypeVar` name "T_contra" does not reflect its invariance; consider renaming it to "T" | 27 | T_contra = TypeVar("T_contra") 28 | T_contra = TypeVar("T_contra", covariant=False) @@ -216,7 +216,7 @@ type_name_incorrect_variance.py:28:12: PLC0105 `TypeVar` name "T_contra" does no 30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) | -type_name_incorrect_variance.py:29:12: PLC0105 `TypeVar` name "T_contra" does not match variance +type_name_incorrect_variance.py:29:12: PLC0105 `TypeVar` name "T_contra" does not reflect its invariance; consider renaming it to "T" | 27 | T_contra = TypeVar("T_contra") 28 | T_contra = TypeVar("T_contra", covariant=False) @@ -226,7 +226,7 @@ type_name_incorrect_variance.py:29:12: PLC0105 `TypeVar` name "T_contra" does no 31 | T_contra = TypeVar("T_contra", covariant=True) | -type_name_incorrect_variance.py:30:12: PLC0105 `TypeVar` name "T_contra" does not match variance +type_name_incorrect_variance.py:30:12: PLC0105 `TypeVar` name "T_contra" does not reflect its invariance; consider renaming it to "T" | 28 | T_contra = TypeVar("T_contra", covariant=False) 29 | T_contra = TypeVar("T_contra", contravariant=False) @@ -236,7 +236,7 @@ type_name_incorrect_variance.py:30:12: PLC0105 `TypeVar` name "T_contra" does no 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:31:12: PLC0105 `TypeVar` name "T_contra" does not match variance +type_name_incorrect_variance.py:31:12: PLC0105 `TypeVar` name "T_contra" does not reflect its covariance; consider renaming it to "T_co" | 29 | T_contra = TypeVar("T_contra", contravariant=False) 30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) @@ -246,7 +246,7 @@ type_name_incorrect_variance.py:31:12: PLC0105 `TypeVar` name "T_contra" does no 33 | P_contra = ParamSpec("P_contra") | -type_name_incorrect_variance.py:32:12: PLC0105 `TypeVar` name "T_contra" does not match variance +type_name_incorrect_variance.py:32:12: PLC0105 `TypeVar` name "T_contra" does not reflect its covariance; consider renaming it to "T_co" | 30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) 31 | T_contra = TypeVar("T_contra", covariant=True) @@ -256,7 +256,7 @@ type_name_incorrect_variance.py:32:12: PLC0105 `TypeVar` name "T_contra" does no 34 | P_contra = ParamSpec("P_contra", covariant=False) | -type_name_incorrect_variance.py:33:12: PLC0105 `ParamSpec` name "P_contra" does not match variance +type_name_incorrect_variance.py:33:12: PLC0105 `ParamSpec` name "P_contra" does not reflect its invariance; consider renaming it to "P" | 31 | T_contra = TypeVar("T_contra", covariant=True) 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) @@ -266,7 +266,7 @@ type_name_incorrect_variance.py:33:12: PLC0105 `ParamSpec` name "P_contra" does 35 | P_contra = ParamSpec("P_contra", contravariant=False) | -type_name_incorrect_variance.py:34:12: PLC0105 `ParamSpec` name "P_contra" does not match variance +type_name_incorrect_variance.py:34:12: PLC0105 `ParamSpec` name "P_contra" does not reflect its invariance; consider renaming it to "P" | 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) 33 | P_contra = ParamSpec("P_contra") @@ -276,7 +276,7 @@ type_name_incorrect_variance.py:34:12: PLC0105 `ParamSpec` name "P_contra" does 36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) | -type_name_incorrect_variance.py:35:12: PLC0105 `ParamSpec` name "P_contra" does not match variance +type_name_incorrect_variance.py:35:12: PLC0105 `ParamSpec` name "P_contra" does not reflect its invariance; consider renaming it to "P" | 33 | P_contra = ParamSpec("P_contra") 34 | P_contra = ParamSpec("P_contra", covariant=False) @@ -286,7 +286,7 @@ type_name_incorrect_variance.py:35:12: PLC0105 `ParamSpec` name "P_contra" does 37 | P_contra = ParamSpec("P_contra", covariant=True) | -type_name_incorrect_variance.py:36:12: PLC0105 `ParamSpec` name "P_contra" does not match variance +type_name_incorrect_variance.py:36:12: PLC0105 `ParamSpec` name "P_contra" does not reflect its invariance; consider renaming it to "P" | 34 | P_contra = ParamSpec("P_contra", covariant=False) 35 | P_contra = ParamSpec("P_contra", contravariant=False) @@ -296,7 +296,7 @@ type_name_incorrect_variance.py:36:12: PLC0105 `ParamSpec` name "P_contra" does 38 | P_contra = ParamSpec("P_contra", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:37:12: PLC0105 `ParamSpec` name "P_contra" does not match variance +type_name_incorrect_variance.py:37:12: PLC0105 `ParamSpec` name "P_contra" does not reflect its covariance; consider renaming it to "P_co" | 35 | P_contra = ParamSpec("P_contra", contravariant=False) 36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) @@ -305,7 +305,7 @@ type_name_incorrect_variance.py:37:12: PLC0105 `ParamSpec` name "P_contra" does 38 | P_contra = ParamSpec("P_contra", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:38:12: PLC0105 `ParamSpec` name "P_contra" does not match variance +type_name_incorrect_variance.py:38:12: PLC0105 `ParamSpec` name "P_contra" does not reflect its covariance; consider renaming it to "P_co" | 36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) 37 | P_contra = ParamSpec("P_contra", covariant=True)