From 780dc4d7afdcaf4b7b7d488fccc33b0134e192a9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 2 Aug 2023 15:18:45 +0300 Subject: [PATCH 1/2] Permit `ClassVar` and `Final` without subscript in RUF012 Fix #6267. --- .../resources/test/fixtures/ruff/RUF012.py | 2 ++ crates/ruff/src/rules/ruff/rules/helpers.rs | 20 +++++++++++++------ ..._rules__ruff__tests__RUF012_RUF012.py.snap | 12 +++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF012.py b/crates/ruff/resources/test/fixtures/ruff/RUF012.py index 9be4b88c76b7d..9f7349f740ae4 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF012.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF012.py @@ -11,6 +11,8 @@ class A: without_annotation = [] class_variable: ClassVar[list[int]] = [] final_variable: Final[list[int]] = [] + class_variable_without_subscript: ClassVar = [] + final_variable_without_subscript: Final = [] from dataclasses import dataclass, field diff --git a/crates/ruff/src/rules/ruff/rules/helpers.rs b/crates/ruff/src/rules/ruff/rules/helpers.rs index 189c111e021b1..69ac4efc00a40 100644 --- a/crates/ruff/src/rules/ruff/rules/helpers.rs +++ b/crates/ruff/src/rules/ruff/rules/helpers.rs @@ -28,18 +28,26 @@ pub(super) fn is_dataclass_field(func: &Expr, semantic: &SemanticModel) -> bool /// Returns `true` if the given [`Expr`] is a `typing.ClassVar` annotation. pub(super) fn is_class_var_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool { - let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation else { - return false; + // ClassVar can be used either with a subscript `ClassVar[...]` or without (the type is + // inferred). + let expr = if let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation { + value + } else { + annotation }; - semantic.match_typing_expr(value, "ClassVar") + semantic.match_typing_expr(expr, "ClassVar") } /// Returns `true` if the given [`Expr`] is a `typing.Final` annotation. pub(super) fn is_final_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool { - let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation else { - return false; + // Final can be used either with a subscript `Final[...]` or without (the type is + // inferred). + let expr = if let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation { + value + } else { + annotation }; - semantic.match_typing_expr(value, "Final") + semantic.match_typing_expr(expr, "Final") } /// Returns `true` if the given class is a dataclass. diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap index 676e2a1a03805..81b1adfed7a2c 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap @@ -21,14 +21,14 @@ RUF012.py:11:26: RUF012 Mutable class attributes should be annotated with `typin 13 | final_variable: Final[list[int]] = [] | -RUF012.py:23:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:25:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -21 | mutable_default: list[int] = [] -22 | immutable_annotation: Sequence[int] = [] -23 | without_annotation = [] +23 | mutable_default: list[int] = [] +24 | immutable_annotation: Sequence[int] = [] +25 | without_annotation = [] | ^^ RUF012 -24 | perfectly_fine: list[int] = field(default_factory=list) -25 | class_variable: ClassVar[list[int]] = [] +26 | perfectly_fine: list[int] = field(default_factory=list) +27 | class_variable: ClassVar[list[int]] = [] | From 50d1d8f18bdb07f52ea7fb78e6b6d0e9c8b2a752 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 2 Aug 2023 08:49:42 -0400 Subject: [PATCH 2/2] Use map_subscript --- crates/ruff/src/rules/ruff/rules/helpers.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/ruff/src/rules/ruff/rules/helpers.rs b/crates/ruff/src/rules/ruff/rules/helpers.rs index 69ac4efc00a40..9b9669c1a8653 100644 --- a/crates/ruff/src/rules/ruff/rules/helpers.rs +++ b/crates/ruff/src/rules/ruff/rules/helpers.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_python_ast::helpers::map_callable; +use ruff_python_ast::helpers::{map_callable, map_subscript}; use ruff_python_semantic::{BindingKind, SemanticModel}; /// Return `true` if the given [`Expr`] is a special class attribute, like `__slots__`. @@ -30,24 +30,14 @@ pub(super) fn is_dataclass_field(func: &Expr, semantic: &SemanticModel) -> bool pub(super) fn is_class_var_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool { // ClassVar can be used either with a subscript `ClassVar[...]` or without (the type is // inferred). - let expr = if let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation { - value - } else { - annotation - }; - semantic.match_typing_expr(expr, "ClassVar") + semantic.match_typing_expr(map_subscript(annotation), "ClassVar") } /// Returns `true` if the given [`Expr`] is a `typing.Final` annotation. pub(super) fn is_final_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool { // Final can be used either with a subscript `Final[...]` or without (the type is // inferred). - let expr = if let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation { - value - } else { - annotation - }; - semantic.match_typing_expr(expr, "Final") + semantic.match_typing_expr(map_subscript(annotation), "Final") } /// Returns `true` if the given class is a dataclass.