From c37475d8d05027c8ce88de4ee5e1a9aef1d529a6 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Mon, 10 Jul 2023 15:22:55 +0100 Subject: [PATCH 1/7] Add rule --- .../pylint/type_name_incorrect_variance.py | 59 ++++ crates/ruff/src/checkers/ast/mod.rs | 3 + crates/ruff/src/codes.rs | 1 + crates/ruff/src/rules/pylint/mod.rs | 4 + crates/ruff/src/rules/pylint/rules/mod.rs | 2 + .../rules/type_name_incorrect_variance.rs | 159 +++++++++ ...C0105_type_name_incorrect_variance.py.snap | 318 ++++++++++++++++++ 7 files changed, 546 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/pylint/type_name_incorrect_variance.py create mode 100644 crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0105_type_name_incorrect_variance.py.snap diff --git a/crates/ruff/resources/test/fixtures/pylint/type_name_incorrect_variance.py b/crates/ruff/resources/test/fixtures/pylint/type_name_incorrect_variance.py new file mode 100644 index 0000000000000..33f68d2a04625 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/type_name_incorrect_variance.py @@ -0,0 +1,59 @@ +from typing import ParamSpec, TypeVar + +# Errors. + +T = TypeVar("T", covariant=True) +T = TypeVar("T", covariant=True, contravariant=False) +T = TypeVar("T", contravariant=True) +T = TypeVar("T", covariant=False, contravariant=True) +P = ParamSpec("P", covariant=True) +P = ParamSpec("P", covariant=True, contravariant=False) +P = ParamSpec("P", contravariant=True) +P = ParamSpec("P", covariant=False, contravariant=True) + +T_co = TypeVar("T_co") +T_co = TypeVar("T_co", covariant=False) +T_co = TypeVar("T_co", contravariant=False) +T_co = TypeVar("T_co", covariant=False, contravariant=False) +T_co = TypeVar("T_co", contravariant=True) +T_co = TypeVar("T_co", covariant=False, contravariant=True) +P_co = ParamSpec("P_co") +P_co = ParamSpec("P_co", covariant=False) +P_co = ParamSpec("P_co", contravariant=False) +P_co = ParamSpec("P_co", covariant=False, contravariant=False) +P_co = ParamSpec("P_co", contravariant=True) +P_co = ParamSpec("P_co", covariant=False, contravariant=True) + +T_contra = TypeVar("T_contra") +T_contra = TypeVar("T_contra", covariant=False) +T_contra = TypeVar("T_contra", contravariant=False) +T_contra = TypeVar("T_contra", covariant=False, contravariant=False) +T_contra = TypeVar("T_contra", covariant=True) +T_contra = TypeVar("T_contra", covariant=True, contravariant=False) +P_contra = ParamSpec("P_contra") +P_contra = ParamSpec("P_contra", covariant=False) +P_contra = ParamSpec("P_contra", contravariant=False) +P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) +P_contra = ParamSpec("P_contra", covariant=True) +P_contra = ParamSpec("P_contra", covariant=True, contravariant=False) + +# Non-errors. + +T = TypeVar("T") +T = TypeVar("T", covariant=False) +T = TypeVar("T", contravariant=False) +T = TypeVar("T", covariant=False, contravariant=False) +P = ParamSpec("P") +P = ParamSpec("P", covariant=False) +P = ParamSpec("P", contravariant=False) +P = ParamSpec("P", covariant=False, contravariant=False) + +T_co = TypeVar("T_co", covariant=True) +T_co = TypeVar("T_co", covariant=True, contravariant=False) +P_co = ParamSpec("P_co", covariant=True) +P_co = ParamSpec("P_co", covariant=True, contravariant=False) + +T_contra = TypeVar("T_contra", contravariant=True) +T_contra = TypeVar("T_contra", covariant=False, contravariant=True) +P_contra = ParamSpec("P_contra", contravariant=True) +P_contra = ParamSpec("P_contra", covariant=False, contravariant=True) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index be8aa68af6aff..06a8055b0b2f7 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -1658,6 +1658,9 @@ where if self.settings.rules.enabled(Rule::TypeParamNameMismatch) { pylint::rules::type_param_name_mismatch(self, value, targets); } + if self.settings.rules.enabled(Rule::TypeNameIncorrectVariance) { + pylint::rules::type_name_incorrect_variance(self, value); + } if self.settings.rules.enabled(Rule::TypeBivariance) { pylint::rules::type_bivariance(self, value); } diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index b73d2caa44619..31b80fcff4007 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -168,6 +168,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyflakes, "901") => (RuleGroup::Unspecified, rules::pyflakes::rules::RaiseNotImplemented), // pylint + (Pylint, "C0105") => (RuleGroup::Unspecified, rules::pylint::rules::TypeNameIncorrectVariance), (Pylint, "C0131") => (RuleGroup::Unspecified, rules::pylint::rules::TypeBivariance), (Pylint, "C0132") => (RuleGroup::Unspecified, rules::pylint::rules::TypeParamNameMismatch), (Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots), diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 9129aeb506fd9..f6f5330d06e81 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -86,6 +86,10 @@ mod tests { )] #[test_case(Rule::TooManyStatements, Path::new("too_many_statements.py"))] #[test_case(Rule::TypeBivariance, Path::new("type_bivariance.py"))] + #[test_case( + Rule::TypeNameIncorrectVariance, + Path::new("type_name_incorrect_variance.py") + )] #[test_case(Rule::TypeParamNameMismatch, Path::new("type_param_name_mismatch.py"))] #[test_case( Rule::UnexpectedSpecialMethodSignature, diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index ad2b6d0d6cfa7..551abb1adab46 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -38,6 +38,7 @@ pub(crate) use too_many_branches::*; pub(crate) use too_many_return_statements::*; pub(crate) use too_many_statements::*; pub(crate) use type_bivariance::*; +pub(crate) use type_name_incorrect_variance::*; pub(crate) use type_param_name_mismatch::*; pub(crate) use unexpected_special_method_signature::*; pub(crate) use unnecessary_direct_lambda_call::*; @@ -87,6 +88,7 @@ mod too_many_branches; mod too_many_return_statements; mod too_many_statements; mod type_bivariance; +mod type_name_incorrect_variance; mod type_param_name_mismatch; mod unexpected_special_method_signature; mod unnecessary_direct_lambda_call; 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 new file mode 100644 index 0000000000000..11274fd40c68f --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs @@ -0,0 +1,159 @@ +use std::fmt; + +use rustpython_parser::ast::{self, Expr, Ranged}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::is_const_true; + +use crate::checkers::ast::Checker; +use crate::rules::pylint::helpers::type_param_name; + +/// ## What it does +/// Checks for type names that do not match the variance of the type parameter. +/// +/// ## Why is this bad? +/// [PEP 484](https://www.python.org/dev/peps/pep-0484/) recommends using the +/// suffixes `_co` for covariant type parameters and `_contra` for contravariant +/// type parameters. Invariant type parameters should not have a suffix. This +/// helps to distinguish between invariant, covariant, and contravariant type +/// parameters. +/// +/// ## Example +/// ```python +/// from typing import TypeVar +/// +/// T = TypeVar("T", covariant=True) +/// U = TypeVar("U", contravariant=True) +/// V_co = TypeVar("V_co") +/// ``` +/// +/// Use instead: +/// ```python +/// from typing import TypeVar +/// +/// T_co = TypeVar("T_co", covariant=True) +/// U_contra = TypeVar("U_contra", contravariant=True) +/// V = TypeVar("V") +/// ``` +/// +/// ## References +/// - [Python documentation: `typing` — Support for type hints](https://docs.python.org/3/library/typing.html) +/// - [PEP 483 – The Theory of Type Hints: Covariance and Contravariance](https://peps.python.org/pep-0483/#covariance-and-contravariance) +/// - [PEP 484 – Type Hints: Covariance and contravariance](https://peps.python.org/pep-0484/#covariance-and-contravariance) +#[violation] +pub struct TypeNameIncorrectVariance { + kind: VarKind, + param_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") + } +} + +pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) { + let Expr::Call(ast::ExprCall { + func, + args, + keywords, + .. + }) = value + else { + return; + }; + let Some(param_name) = type_param_name(args, keywords) else { return; }; + let covariant = keywords + .iter() + .find(|keyword| { + keyword + .arg + .as_ref() + .map_or(false, |keyword| keyword.as_str() == "covariant") + }) + .map(|keyword| &keyword.value); + let contravariant = keywords + .iter() + .find(|keyword| { + keyword + .arg + .as_ref() + .map_or(false, |keyword| keyword.as_str() == "contravariant") + }) + .map(|keyword| &keyword.value); + if mismatch(param_name, covariant, contravariant) { + let Some(kind) = checker + .semantic() + .resolve_call_path(func) + .and_then(|call_path| { + if checker + .semantic() + .match_typing_call_path(&call_path, "ParamSpec") + { + Some(VarKind::ParamSpec) + } else if checker + .semantic() + .match_typing_call_path(&call_path, "TypeVar") + { + Some(VarKind::TypeVar) + } else { + None + } + }) + else { + return; + }; + checker.diagnostics.push(Diagnostic::new( + TypeNameIncorrectVariance { + kind, + param_name: param_name.to_string(), + }, + func.range(), + )); + }; +} + +fn mismatch(param_name: &str, covariant: Option<&Expr>, contravariant: Option<&Expr>) -> bool { + if param_name.ends_with("_co") { + match covariant { + Some(covariant) if !is_const_true(covariant) => true, + None => true, + _ => false, + } + } else if param_name.ends_with("_contra") { + match contravariant { + Some(contravariant) if !is_const_true(contravariant) => true, + None => true, + _ => false, + } + } else { + match (covariant, contravariant) { + (Some(covariant), None) if is_const_true(covariant) => true, + (None, Some(contravariant)) if is_const_true(contravariant) => true, + (Some(covariant), Some(contravariant)) + if is_const_true(covariant) || is_const_true(contravariant) => + { + true + } + _ => false, + } + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +enum VarKind { + TypeVar, + ParamSpec, +} + +impl fmt::Display for VarKind { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + VarKind::TypeVar => fmt.write_str("TypeVar"), + VarKind::ParamSpec => fmt.write_str("ParamSpec"), + } + } +} 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 new file mode 100644 index 0000000000000..a6cee08225ce9 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0105_type_name_incorrect_variance.py.snap @@ -0,0 +1,318 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +--- +type_name_incorrect_variance.py:5:5: PLC0105 `TypeVar` name "T" does not match variance + | +3 | # Errors. +4 | +5 | T = TypeVar("T", covariant=True) + | ^^^^^^^ PLC0105 +6 | T = TypeVar("T", covariant=True, contravariant=False) +7 | T = TypeVar("T", contravariant=True) + | + +type_name_incorrect_variance.py:6:5: PLC0105 `TypeVar` name "T" does not match variance + | +5 | T = TypeVar("T", covariant=True) +6 | T = TypeVar("T", covariant=True, contravariant=False) + | ^^^^^^^ PLC0105 +7 | T = TypeVar("T", contravariant=True) +8 | T = TypeVar("T", covariant=False, contravariant=True) + | + +type_name_incorrect_variance.py:7:5: PLC0105 `TypeVar` name "T" does not match variance + | +5 | T = TypeVar("T", covariant=True) +6 | T = TypeVar("T", covariant=True, contravariant=False) +7 | T = TypeVar("T", contravariant=True) + | ^^^^^^^ PLC0105 +8 | T = TypeVar("T", covariant=False, contravariant=True) +9 | U = ParamSpec("U", covariant=True) + | + +type_name_incorrect_variance.py:8:5: PLC0105 `TypeVar` name "T" does not match variance + | + 6 | T = TypeVar("T", covariant=True, contravariant=False) + 7 | T = TypeVar("T", contravariant=True) + 8 | T = TypeVar("T", covariant=False, contravariant=True) + | ^^^^^^^ PLC0105 + 9 | U = ParamSpec("U", covariant=True) +10 | U = ParamSpec("U", covariant=True, contravariant=False) + | + +type_name_incorrect_variance.py:9:5: PLC0105 `ParamSpec` name "U" does not match variance + | + 7 | T = TypeVar("T", contravariant=True) + 8 | T = TypeVar("T", covariant=False, contravariant=True) + 9 | U = ParamSpec("U", covariant=True) + | ^^^^^^^^^ PLC0105 +10 | U = ParamSpec("U", covariant=True, contravariant=False) +11 | U = ParamSpec("U", contravariant=True) + | + +type_name_incorrect_variance.py:10:5: PLC0105 `ParamSpec` name "U" does not match variance + | + 8 | T = TypeVar("T", covariant=False, contravariant=True) + 9 | U = ParamSpec("U", covariant=True) +10 | U = ParamSpec("U", covariant=True, contravariant=False) + | ^^^^^^^^^ PLC0105 +11 | U = ParamSpec("U", contravariant=True) +12 | U = ParamSpec("U", covariant=False, contravariant=True) + | + +type_name_incorrect_variance.py:11:5: PLC0105 `ParamSpec` name "U" does not match variance + | + 9 | U = ParamSpec("U", covariant=True) +10 | U = ParamSpec("U", covariant=True, contravariant=False) +11 | U = ParamSpec("U", contravariant=True) + | ^^^^^^^^^ PLC0105 +12 | U = ParamSpec("U", covariant=False, contravariant=True) + | + +type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "U" does not match variance + | +10 | U = ParamSpec("U", covariant=True, contravariant=False) +11 | U = ParamSpec("U", contravariant=True) +12 | U = ParamSpec("U", covariant=False, contravariant=True) + | ^^^^^^^^^ PLC0105 +13 | +14 | T_co = TypeVar("T_co") + | + +type_name_incorrect_variance.py:14:8: PLC0105 `TypeVar` name "T_co" does not match variance + | +12 | U = ParamSpec("U", covariant=False, contravariant=True) +13 | +14 | T_co = TypeVar("T_co") + | ^^^^^^^ PLC0105 +15 | T_co = TypeVar("T_co", covariant=False) +16 | T_co = TypeVar("T_co", contravariant=False) + | + +type_name_incorrect_variance.py:15:8: PLC0105 `TypeVar` name "T_co" does not match variance + | +14 | T_co = TypeVar("T_co") +15 | T_co = TypeVar("T_co", covariant=False) + | ^^^^^^^ PLC0105 +16 | T_co = TypeVar("T_co", contravariant=False) +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 + | +14 | T_co = TypeVar("T_co") +15 | T_co = TypeVar("T_co", covariant=False) +16 | T_co = TypeVar("T_co", contravariant=False) + | ^^^^^^^ PLC0105 +17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) +18 | T_co = TypeVar("T_co", contravariant=True) + | + +type_name_incorrect_variance.py:17:8: PLC0105 `TypeVar` name "T_co" does not match variance + | +15 | T_co = TypeVar("T_co", covariant=False) +16 | T_co = TypeVar("T_co", contravariant=False) +17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) + | ^^^^^^^ PLC0105 +18 | T_co = TypeVar("T_co", contravariant=True) +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 + | +16 | T_co = TypeVar("T_co", contravariant=False) +17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) +18 | T_co = TypeVar("T_co", contravariant=True) + | ^^^^^^^ PLC0105 +19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) +20 | U_co = ParamSpec("U_co") + | + +type_name_incorrect_variance.py:19:8: PLC0105 `TypeVar` name "T_co" does not match variance + | +17 | T_co = TypeVar("T_co", covariant=False, contravariant=False) +18 | T_co = TypeVar("T_co", contravariant=True) +19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) + | ^^^^^^^ PLC0105 +20 | U_co = ParamSpec("U_co") +21 | U_co = ParamSpec("U_co", covariant=False) + | + +type_name_incorrect_variance.py:20:8: PLC0105 `ParamSpec` name "U_co" does not match variance + | +18 | T_co = TypeVar("T_co", contravariant=True) +19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) +20 | U_co = ParamSpec("U_co") + | ^^^^^^^^^ PLC0105 +21 | U_co = ParamSpec("U_co", covariant=False) +22 | U_co = ParamSpec("U_co", contravariant=False) + | + +type_name_incorrect_variance.py:21:8: PLC0105 `ParamSpec` name "U_co" does not match variance + | +19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) +20 | U_co = ParamSpec("U_co") +21 | U_co = ParamSpec("U_co", covariant=False) + | ^^^^^^^^^ PLC0105 +22 | U_co = ParamSpec("U_co", contravariant=False) +23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) + | + +type_name_incorrect_variance.py:22:8: PLC0105 `ParamSpec` name "U_co" does not match variance + | +20 | U_co = ParamSpec("U_co") +21 | U_co = ParamSpec("U_co", covariant=False) +22 | U_co = ParamSpec("U_co", contravariant=False) + | ^^^^^^^^^ PLC0105 +23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) +24 | U_co = ParamSpec("U_co", contravariant=True) + | + +type_name_incorrect_variance.py:23:8: PLC0105 `ParamSpec` name "U_co" does not match variance + | +21 | U_co = ParamSpec("U_co", covariant=False) +22 | U_co = ParamSpec("U_co", contravariant=False) +23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) + | ^^^^^^^^^ PLC0105 +24 | U_co = ParamSpec("U_co", contravariant=True) +25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) + | + +type_name_incorrect_variance.py:24:8: PLC0105 `ParamSpec` name "U_co" does not match variance + | +22 | U_co = ParamSpec("U_co", contravariant=False) +23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) +24 | U_co = ParamSpec("U_co", contravariant=True) + | ^^^^^^^^^ PLC0105 +25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) + | + +type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "U_co" does not match variance + | +23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) +24 | U_co = ParamSpec("U_co", contravariant=True) +25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) + | ^^^^^^^^^ PLC0105 +26 | +27 | T_contra = TypeVar("T_contra") + | + +type_name_incorrect_variance.py:27:12: PLC0105 `TypeVar` name "T_contra" does not match variance + | +25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) +26 | +27 | T_contra = TypeVar("T_contra") + | ^^^^^^^ PLC0105 +28 | T_contra = TypeVar("T_contra", covariant=False) +29 | T_contra = TypeVar("T_contra", contravariant=False) + | + +type_name_incorrect_variance.py:28:12: PLC0105 `TypeVar` name "T_contra" does not match variance + | +27 | T_contra = TypeVar("T_contra") +28 | T_contra = TypeVar("T_contra", covariant=False) + | ^^^^^^^ PLC0105 +29 | T_contra = TypeVar("T_contra", contravariant=False) +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 + | +27 | T_contra = TypeVar("T_contra") +28 | T_contra = TypeVar("T_contra", covariant=False) +29 | T_contra = TypeVar("T_contra", contravariant=False) + | ^^^^^^^ PLC0105 +30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) +31 | T_contra = TypeVar("T_contra", covariant=True) + | + +type_name_incorrect_variance.py:30:12: PLC0105 `TypeVar` name "T_contra" does not match variance + | +28 | T_contra = TypeVar("T_contra", covariant=False) +29 | T_contra = TypeVar("T_contra", contravariant=False) +30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) + | ^^^^^^^ PLC0105 +31 | T_contra = TypeVar("T_contra", covariant=True) +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 + | +29 | T_contra = TypeVar("T_contra", contravariant=False) +30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) +31 | T_contra = TypeVar("T_contra", covariant=True) + | ^^^^^^^ PLC0105 +32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) +33 | U_contra = ParamSpec("U_contra") + | + +type_name_incorrect_variance.py:32:12: PLC0105 `TypeVar` name "T_contra" does not match variance + | +30 | T_contra = TypeVar("T_contra", covariant=False, contravariant=False) +31 | T_contra = TypeVar("T_contra", covariant=True) +32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) + | ^^^^^^^ PLC0105 +33 | U_contra = ParamSpec("U_contra") +34 | U_contra = ParamSpec("U_contra", covariant=False) + | + +type_name_incorrect_variance.py:33:12: PLC0105 `ParamSpec` name "U_contra" does not match variance + | +31 | T_contra = TypeVar("T_contra", covariant=True) +32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) +33 | U_contra = ParamSpec("U_contra") + | ^^^^^^^^^ PLC0105 +34 | U_contra = ParamSpec("U_contra", covariant=False) +35 | U_contra = ParamSpec("U_contra", contravariant=False) + | + +type_name_incorrect_variance.py:34:12: PLC0105 `ParamSpec` name "U_contra" does not match variance + | +32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) +33 | U_contra = ParamSpec("U_contra") +34 | U_contra = ParamSpec("U_contra", covariant=False) + | ^^^^^^^^^ PLC0105 +35 | U_contra = ParamSpec("U_contra", contravariant=False) +36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) + | + +type_name_incorrect_variance.py:35:12: PLC0105 `ParamSpec` name "U_contra" does not match variance + | +33 | U_contra = ParamSpec("U_contra") +34 | U_contra = ParamSpec("U_contra", covariant=False) +35 | U_contra = ParamSpec("U_contra", contravariant=False) + | ^^^^^^^^^ PLC0105 +36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) +37 | U_contra = ParamSpec("U_contra", covariant=True) + | + +type_name_incorrect_variance.py:36:12: PLC0105 `ParamSpec` name "U_contra" does not match variance + | +34 | U_contra = ParamSpec("U_contra", covariant=False) +35 | U_contra = ParamSpec("U_contra", contravariant=False) +36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) + | ^^^^^^^^^ PLC0105 +37 | U_contra = ParamSpec("U_contra", covariant=True) +38 | U_contra = ParamSpec("U_contra", covariant=True, contravariant=False) + | + +type_name_incorrect_variance.py:37:12: PLC0105 `ParamSpec` name "U_contra" does not match variance + | +35 | U_contra = ParamSpec("U_contra", contravariant=False) +36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) +37 | U_contra = ParamSpec("U_contra", covariant=True) + | ^^^^^^^^^ PLC0105 +38 | U_contra = ParamSpec("U_contra", covariant=True, contravariant=False) + | + +type_name_incorrect_variance.py:38:12: PLC0105 `ParamSpec` name "U_contra" does not match variance + | +36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) +37 | U_contra = ParamSpec("U_contra", covariant=True) +38 | U_contra = ParamSpec("U_contra", covariant=True, contravariant=False) + | ^^^^^^^^^ PLC0105 +39 | +40 | # Non-errors. + | + + From 1e1586666831e0518175daedbe7d7301b88c6f8f Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Mon, 10 Jul 2023 15:30:52 +0100 Subject: [PATCH 2/7] Update snapshot --- ...C0105_type_name_incorrect_variance.py.snap | 178 +++++++++--------- 1 file changed, 89 insertions(+), 89 deletions(-) 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 a6cee08225ce9..8e54cce037f96 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 @@ -27,7 +27,7 @@ type_name_incorrect_variance.py:7:5: PLC0105 `TypeVar` name "T" does not match v 7 | T = TypeVar("T", contravariant=True) | ^^^^^^^ PLC0105 8 | T = TypeVar("T", covariant=False, contravariant=True) -9 | U = ParamSpec("U", covariant=True) +9 | P = ParamSpec("P", covariant=True) | type_name_incorrect_variance.py:8:5: PLC0105 `TypeVar` name "T" does not match variance @@ -36,44 +36,44 @@ type_name_incorrect_variance.py:8:5: PLC0105 `TypeVar` name "T" does not match v 7 | T = TypeVar("T", contravariant=True) 8 | T = TypeVar("T", covariant=False, contravariant=True) | ^^^^^^^ PLC0105 - 9 | U = ParamSpec("U", covariant=True) -10 | U = ParamSpec("U", covariant=True, contravariant=False) + 9 | P = ParamSpec("P", covariant=True) +10 | P = ParamSpec("P", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:9:5: PLC0105 `ParamSpec` name "U" does not match variance +type_name_incorrect_variance.py:9:5: PLC0105 `ParamSpec` name "P" does not match variance | 7 | T = TypeVar("T", contravariant=True) 8 | T = TypeVar("T", covariant=False, contravariant=True) - 9 | U = ParamSpec("U", covariant=True) + 9 | P = ParamSpec("P", covariant=True) | ^^^^^^^^^ PLC0105 -10 | U = ParamSpec("U", covariant=True, contravariant=False) -11 | U = ParamSpec("U", contravariant=True) +10 | P = ParamSpec("P", covariant=True, contravariant=False) +11 | P = ParamSpec("P", contravariant=True) | -type_name_incorrect_variance.py:10:5: PLC0105 `ParamSpec` name "U" does not match variance +type_name_incorrect_variance.py:10:5: PLC0105 `ParamSpec` name "P" does not match variance | 8 | T = TypeVar("T", covariant=False, contravariant=True) - 9 | U = ParamSpec("U", covariant=True) -10 | U = ParamSpec("U", covariant=True, contravariant=False) + 9 | P = ParamSpec("P", covariant=True) +10 | P = ParamSpec("P", covariant=True, contravariant=False) | ^^^^^^^^^ PLC0105 -11 | U = ParamSpec("U", contravariant=True) -12 | U = ParamSpec("U", covariant=False, contravariant=True) +11 | P = ParamSpec("P", contravariant=True) +12 | P = ParamSpec("P", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:11:5: PLC0105 `ParamSpec` name "U" does not match variance +type_name_incorrect_variance.py:11:5: PLC0105 `ParamSpec` name "P" does not match variance | - 9 | U = ParamSpec("U", covariant=True) -10 | U = ParamSpec("U", covariant=True, contravariant=False) -11 | U = ParamSpec("U", contravariant=True) + 9 | P = ParamSpec("P", covariant=True) +10 | P = ParamSpec("P", covariant=True, contravariant=False) +11 | P = ParamSpec("P", contravariant=True) | ^^^^^^^^^ PLC0105 -12 | U = ParamSpec("U", covariant=False, contravariant=True) +12 | P = ParamSpec("P", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "U" does not match variance +type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "P" does not match variance | -10 | U = ParamSpec("U", covariant=True, contravariant=False) -11 | U = ParamSpec("U", contravariant=True) -12 | U = ParamSpec("U", covariant=False, contravariant=True) +10 | P = ParamSpec("P", covariant=True, contravariant=False) +11 | P = ParamSpec("P", contravariant=True) +12 | P = ParamSpec("P", covariant=False, contravariant=True) | ^^^^^^^^^ PLC0105 13 | 14 | T_co = TypeVar("T_co") @@ -81,7 +81,7 @@ type_name_incorrect_variance.py:12:5: PLC0105 `ParamSpec` name "U" does not matc type_name_incorrect_variance.py:14:8: PLC0105 `TypeVar` name "T_co" does not match variance | -12 | U = ParamSpec("U", covariant=False, contravariant=True) +12 | P = ParamSpec("P", covariant=False, contravariant=True) 13 | 14 | T_co = TypeVar("T_co") | ^^^^^^^ PLC0105 @@ -125,7 +125,7 @@ type_name_incorrect_variance.py:18:8: PLC0105 `TypeVar` name "T_co" does not mat 18 | T_co = TypeVar("T_co", contravariant=True) | ^^^^^^^ PLC0105 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) -20 | U_co = ParamSpec("U_co") +20 | P_co = ParamSpec("P_co") | type_name_incorrect_variance.py:19:8: PLC0105 `TypeVar` name "T_co" does not match variance @@ -134,64 +134,64 @@ type_name_incorrect_variance.py:19:8: PLC0105 `TypeVar` name "T_co" does not mat 18 | T_co = TypeVar("T_co", contravariant=True) 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) | ^^^^^^^ PLC0105 -20 | U_co = ParamSpec("U_co") -21 | U_co = ParamSpec("U_co", covariant=False) +20 | P_co = ParamSpec("P_co") +21 | P_co = ParamSpec("P_co", covariant=False) | -type_name_incorrect_variance.py:20:8: PLC0105 `ParamSpec` name "U_co" does not match variance +type_name_incorrect_variance.py:20:8: PLC0105 `ParamSpec` name "P_co" does not match variance | 18 | T_co = TypeVar("T_co", contravariant=True) 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) -20 | U_co = ParamSpec("U_co") +20 | P_co = ParamSpec("P_co") | ^^^^^^^^^ PLC0105 -21 | U_co = ParamSpec("U_co", covariant=False) -22 | U_co = ParamSpec("U_co", contravariant=False) +21 | P_co = ParamSpec("P_co", covariant=False) +22 | P_co = ParamSpec("P_co", contravariant=False) | -type_name_incorrect_variance.py:21:8: PLC0105 `ParamSpec` name "U_co" does not match variance +type_name_incorrect_variance.py:21:8: PLC0105 `ParamSpec` name "P_co" does not match variance | 19 | T_co = TypeVar("T_co", covariant=False, contravariant=True) -20 | U_co = ParamSpec("U_co") -21 | U_co = ParamSpec("U_co", covariant=False) +20 | P_co = ParamSpec("P_co") +21 | P_co = ParamSpec("P_co", covariant=False) | ^^^^^^^^^ PLC0105 -22 | U_co = ParamSpec("U_co", contravariant=False) -23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) +22 | P_co = ParamSpec("P_co", contravariant=False) +23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) | -type_name_incorrect_variance.py:22:8: PLC0105 `ParamSpec` name "U_co" does not match variance +type_name_incorrect_variance.py:22:8: PLC0105 `ParamSpec` name "P_co" does not match variance | -20 | U_co = ParamSpec("U_co") -21 | U_co = ParamSpec("U_co", covariant=False) -22 | U_co = ParamSpec("U_co", contravariant=False) +20 | P_co = ParamSpec("P_co") +21 | P_co = ParamSpec("P_co", covariant=False) +22 | P_co = ParamSpec("P_co", contravariant=False) | ^^^^^^^^^ PLC0105 -23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) -24 | U_co = ParamSpec("U_co", contravariant=True) +23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) +24 | P_co = ParamSpec("P_co", contravariant=True) | -type_name_incorrect_variance.py:23:8: PLC0105 `ParamSpec` name "U_co" does not match variance +type_name_incorrect_variance.py:23:8: PLC0105 `ParamSpec` name "P_co" does not match variance | -21 | U_co = ParamSpec("U_co", covariant=False) -22 | U_co = ParamSpec("U_co", contravariant=False) -23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) +21 | P_co = ParamSpec("P_co", covariant=False) +22 | P_co = ParamSpec("P_co", contravariant=False) +23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) | ^^^^^^^^^ PLC0105 -24 | U_co = ParamSpec("U_co", contravariant=True) -25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) +24 | P_co = ParamSpec("P_co", contravariant=True) +25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:24:8: PLC0105 `ParamSpec` name "U_co" does not match variance +type_name_incorrect_variance.py:24:8: PLC0105 `ParamSpec` name "P_co" does not match variance | -22 | U_co = ParamSpec("U_co", contravariant=False) -23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) -24 | U_co = ParamSpec("U_co", contravariant=True) +22 | P_co = ParamSpec("P_co", contravariant=False) +23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) +24 | P_co = ParamSpec("P_co", contravariant=True) | ^^^^^^^^^ PLC0105 -25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) +25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) | -type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "U_co" does not match variance +type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "P_co" does not match variance | -23 | U_co = ParamSpec("U_co", covariant=False, contravariant=False) -24 | U_co = ParamSpec("U_co", contravariant=True) -25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) +23 | P_co = ParamSpec("P_co", covariant=False, contravariant=False) +24 | P_co = ParamSpec("P_co", contravariant=True) +25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) | ^^^^^^^^^ PLC0105 26 | 27 | T_contra = TypeVar("T_contra") @@ -199,7 +199,7 @@ type_name_incorrect_variance.py:25:8: PLC0105 `ParamSpec` name "U_co" does not m type_name_incorrect_variance.py:27:12: PLC0105 `TypeVar` name "T_contra" does not match variance | -25 | U_co = ParamSpec("U_co", covariant=False, contravariant=True) +25 | P_co = ParamSpec("P_co", covariant=False, contravariant=True) 26 | 27 | T_contra = TypeVar("T_contra") | ^^^^^^^ PLC0105 @@ -243,7 +243,7 @@ type_name_incorrect_variance.py:31:12: PLC0105 `TypeVar` name "T_contra" does no 31 | T_contra = TypeVar("T_contra", covariant=True) | ^^^^^^^ PLC0105 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) -33 | U_contra = ParamSpec("U_contra") +33 | P_contra = ParamSpec("P_contra") | type_name_incorrect_variance.py:32:12: PLC0105 `TypeVar` name "T_contra" does not match variance @@ -252,64 +252,64 @@ type_name_incorrect_variance.py:32:12: PLC0105 `TypeVar` name "T_contra" does no 31 | T_contra = TypeVar("T_contra", covariant=True) 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) | ^^^^^^^ PLC0105 -33 | U_contra = ParamSpec("U_contra") -34 | U_contra = ParamSpec("U_contra", covariant=False) +33 | P_contra = ParamSpec("P_contra") +34 | P_contra = ParamSpec("P_contra", covariant=False) | -type_name_incorrect_variance.py:33:12: PLC0105 `ParamSpec` name "U_contra" does not match variance +type_name_incorrect_variance.py:33:12: PLC0105 `ParamSpec` name "P_contra" does not match variance | 31 | T_contra = TypeVar("T_contra", covariant=True) 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) -33 | U_contra = ParamSpec("U_contra") +33 | P_contra = ParamSpec("P_contra") | ^^^^^^^^^ PLC0105 -34 | U_contra = ParamSpec("U_contra", covariant=False) -35 | U_contra = ParamSpec("U_contra", contravariant=False) +34 | P_contra = ParamSpec("P_contra", covariant=False) +35 | P_contra = ParamSpec("P_contra", contravariant=False) | -type_name_incorrect_variance.py:34:12: PLC0105 `ParamSpec` name "U_contra" does not match variance +type_name_incorrect_variance.py:34:12: PLC0105 `ParamSpec` name "P_contra" does not match variance | 32 | T_contra = TypeVar("T_contra", covariant=True, contravariant=False) -33 | U_contra = ParamSpec("U_contra") -34 | U_contra = ParamSpec("U_contra", covariant=False) +33 | P_contra = ParamSpec("P_contra") +34 | P_contra = ParamSpec("P_contra", covariant=False) | ^^^^^^^^^ PLC0105 -35 | U_contra = ParamSpec("U_contra", contravariant=False) -36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) +35 | P_contra = ParamSpec("P_contra", contravariant=False) +36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) | -type_name_incorrect_variance.py:35:12: PLC0105 `ParamSpec` name "U_contra" does not match variance +type_name_incorrect_variance.py:35:12: PLC0105 `ParamSpec` name "P_contra" does not match variance | -33 | U_contra = ParamSpec("U_contra") -34 | U_contra = ParamSpec("U_contra", covariant=False) -35 | U_contra = ParamSpec("U_contra", contravariant=False) +33 | P_contra = ParamSpec("P_contra") +34 | P_contra = ParamSpec("P_contra", covariant=False) +35 | P_contra = ParamSpec("P_contra", contravariant=False) | ^^^^^^^^^ PLC0105 -36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) -37 | U_contra = ParamSpec("U_contra", covariant=True) +36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) +37 | P_contra = ParamSpec("P_contra", covariant=True) | -type_name_incorrect_variance.py:36:12: PLC0105 `ParamSpec` name "U_contra" does not match variance +type_name_incorrect_variance.py:36:12: PLC0105 `ParamSpec` name "P_contra" does not match variance | -34 | U_contra = ParamSpec("U_contra", covariant=False) -35 | U_contra = ParamSpec("U_contra", contravariant=False) -36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) +34 | P_contra = ParamSpec("P_contra", covariant=False) +35 | P_contra = ParamSpec("P_contra", contravariant=False) +36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) | ^^^^^^^^^ PLC0105 -37 | U_contra = ParamSpec("U_contra", covariant=True) -38 | U_contra = ParamSpec("U_contra", covariant=True, contravariant=False) +37 | P_contra = ParamSpec("P_contra", covariant=True) +38 | P_contra = ParamSpec("P_contra", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:37:12: PLC0105 `ParamSpec` name "U_contra" does not match variance +type_name_incorrect_variance.py:37:12: PLC0105 `ParamSpec` name "P_contra" does not match variance | -35 | U_contra = ParamSpec("U_contra", contravariant=False) -36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) -37 | U_contra = ParamSpec("U_contra", covariant=True) +35 | P_contra = ParamSpec("P_contra", contravariant=False) +36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) +37 | P_contra = ParamSpec("P_contra", covariant=True) | ^^^^^^^^^ PLC0105 -38 | U_contra = ParamSpec("U_contra", covariant=True, contravariant=False) +38 | P_contra = ParamSpec("P_contra", covariant=True, contravariant=False) | -type_name_incorrect_variance.py:38:12: PLC0105 `ParamSpec` name "U_contra" does not match variance +type_name_incorrect_variance.py:38:12: PLC0105 `ParamSpec` name "P_contra" does not match variance | -36 | U_contra = ParamSpec("U_contra", covariant=False, contravariant=False) -37 | U_contra = ParamSpec("U_contra", covariant=True) -38 | U_contra = ParamSpec("U_contra", covariant=True, contravariant=False) +36 | P_contra = ParamSpec("P_contra", covariant=False, contravariant=False) +37 | P_contra = ParamSpec("P_contra", covariant=True) +38 | P_contra = ParamSpec("P_contra", covariant=True, contravariant=False) | ^^^^^^^^^ PLC0105 39 | 40 | # Non-errors. From 64f60565fab131e497a8d3cd0c99a83a9f28d3f5 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Mon, 10 Jul 2023 15:39:11 +0100 Subject: [PATCH 3/7] Add schema --- ruff.schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ruff.schema.json b/ruff.schema.json index f283f253b8865..5cf6c13d06113 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2129,6 +2129,8 @@ "PLC", "PLC0", "PLC01", + "PLC010", + "PLC0105", "PLC013", "PLC0131", "PLC0132", @@ -2400,7 +2402,6 @@ "RUF011", "RUF012", "RUF013", - "RUF014", "RUF1", "RUF10", "RUF100", From 7545af54d4abbb579b819cce71172ad84737f5bd Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 10 Jul 2023 11:13:19 -0400 Subject: [PATCH 4/7] Update schema --- ruff.schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ruff.schema.json b/ruff.schema.json index 5cf6c13d06113..15cc68f75279d 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2402,6 +2402,7 @@ "RUF011", "RUF012", "RUF013", + "RUF014", "RUF1", "RUF10", "RUF100", From 5d6eac5982e096eee7245b8ccd52da257ccb1320 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 10 Jul 2023 11:18:21 -0400 Subject: [PATCH 5/7] Tweak some logic --- .../rules/type_name_incorrect_variance.rs | 108 +++++++++--------- 1 file changed, 52 insertions(+), 56 deletions(-) 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 11274fd40c68f..9c08bf7a87fa3 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 @@ -10,14 +10,13 @@ use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; /// ## What it does -/// Checks for type names that do not match the variance of the type parameter. +/// Checks for type names that do not match the variance of their associated +/// type parameter. /// /// ## Why is this bad? -/// [PEP 484](https://www.python.org/dev/peps/pep-0484/) recommends using the -/// suffixes `_co` for covariant type parameters and `_contra` for contravariant -/// type parameters. Invariant type parameters should not have a suffix. This -/// helps to distinguish between invariant, covariant, and contravariant type -/// parameters. +/// [PEP 484] recommends the use of the `_co` and `_contra` suffixes for +/// covariant and contravariant type parameters, respectively (while invariant +/// type parameters should not have any such suffix). /// /// ## Example /// ```python @@ -41,6 +40,8 @@ use crate::rules::pylint::helpers::type_param_name; /// - [Python documentation: `typing` — Support for type hints](https://docs.python.org/3/library/typing.html) /// - [PEP 483 – The Theory of Type Hints: Covariance and Contravariance](https://peps.python.org/pep-0483/#covariance-and-contravariance) /// - [PEP 484 – Type Hints: Covariance and contravariance](https://peps.python.org/pep-0484/#covariance-and-contravariance) +/// +/// [PEP 484]: https://www.python.org/dev/peps/pep-0484/ #[violation] pub struct TypeNameIncorrectVariance { kind: VarKind, @@ -55,6 +56,7 @@ impl Violation for TypeNameIncorrectVariance { } } +/// PLC0105 pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) { let Expr::Call(ast::ExprCall { func, @@ -65,7 +67,11 @@ pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) else { return; }; - let Some(param_name) = type_param_name(args, keywords) else { return; }; + + let Some(param_name) = type_param_name(args, keywords) else { + return; + }; + let covariant = keywords .iter() .find(|keyword| { @@ -75,6 +81,7 @@ pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) .map_or(false, |keyword| keyword.as_str() == "covariant") }) .map(|keyword| &keyword.value); + let contravariant = keywords .iter() .find(|keyword| { @@ -84,62 +91,51 @@ pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) .map_or(false, |keyword| keyword.as_str() == "contravariant") }) .map(|keyword| &keyword.value); - if mismatch(param_name, covariant, contravariant) { - let Some(kind) = checker - .semantic() - .resolve_call_path(func) - .and_then(|call_path| { - if checker - .semantic() - .match_typing_call_path(&call_path, "ParamSpec") - { - Some(VarKind::ParamSpec) - } else if checker - .semantic() - .match_typing_call_path(&call_path, "TypeVar") - { - Some(VarKind::TypeVar) - } else { - None - } - }) - else { - return; - }; - checker.diagnostics.push(Diagnostic::new( - TypeNameIncorrectVariance { - kind, - param_name: param_name.to_string(), - }, - func.range(), - )); + + if !mismatch(param_name, covariant, contravariant) { + return; + } + + let Some(kind) = checker + .semantic() + .resolve_call_path(func) + .and_then(|call_path| { + if checker + .semantic() + .match_typing_call_path(&call_path, "ParamSpec") + { + Some(VarKind::ParamSpec) + } else if checker + .semantic() + .match_typing_call_path(&call_path, "TypeVar") + { + Some(VarKind::TypeVar) + } else { + None + } + }) + else { + return; }; + + checker.diagnostics.push(Diagnostic::new( + TypeNameIncorrectVariance { + kind, + param_name: param_name.to_string(), + }, + func.range(), + )); } +/// Returns `true` if the parameter name does not match its type variance. fn mismatch(param_name: &str, covariant: Option<&Expr>, contravariant: Option<&Expr>) -> bool { if param_name.ends_with("_co") { - match covariant { - Some(covariant) if !is_const_true(covariant) => true, - None => true, - _ => false, - } + covariant.map_or(true, |covariant| !is_const_true(covariant)) } else if param_name.ends_with("_contra") { - match contravariant { - Some(contravariant) if !is_const_true(contravariant) => true, - None => true, - _ => false, - } + contravariant.map_or(true, |contravariant| !is_const_true(contravariant)) } else { - match (covariant, contravariant) { - (Some(covariant), None) if is_const_true(covariant) => true, - (None, Some(contravariant)) if is_const_true(contravariant) => true, - (Some(covariant), Some(contravariant)) - if is_const_true(covariant) || is_const_true(contravariant) => - { - true - } - _ => false, - } + covariant.map_or(false, |covariant| is_const_true(covariant)) + || contravariant.map_or(false, |contravariant| is_const_true(contravariant)) } } From c83b0bd630b6285e0fc0b187bfb4e9b63e520d67 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 10 Jul 2023 11:29:32 -0400 Subject: [PATCH 6/7] Clippy --- .../src/rules/pylint/rules/type_name_incorrect_variance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9c08bf7a87fa3..69724cd549cfe 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 @@ -134,8 +134,8 @@ fn mismatch(param_name: &str, covariant: Option<&Expr>, contravariant: Option<&E } else if param_name.ends_with("_contra") { contravariant.map_or(true, |contravariant| !is_const_true(contravariant)) } else { - covariant.map_or(false, |covariant| is_const_true(covariant)) - || contravariant.map_or(false, |contravariant| is_const_true(contravariant)) + covariant.map_or(false, is_const_true) + || contravariant.map_or(false, is_const_true) } } From b2b979ab15d4dfb1a1812fd3da2c3941d5f5252f Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Mon, 10 Jul 2023 16:44:16 +0100 Subject: [PATCH 7/7] Format --- .../src/rules/pylint/rules/type_name_incorrect_variance.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 69724cd549cfe..541f5e7f8f2c8 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 @@ -134,8 +134,7 @@ fn mismatch(param_name: &str, covariant: Option<&Expr>, contravariant: Option<&E } else if param_name.ends_with("_contra") { contravariant.map_or(true, |contravariant| !is_const_true(contravariant)) } else { - covariant.map_or(false, is_const_true) - || contravariant.map_or(false, is_const_true) + covariant.map_or(false, is_const_true) || contravariant.map_or(false, is_const_true) } }