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..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__`. @@ -28,18 +28,16 @@ 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; - }; - semantic.match_typing_expr(value, "ClassVar") + // ClassVar can be used either with a subscript `ClassVar[...]` or without (the type is + // inferred). + 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 { - let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation else { - return false; - }; - semantic.match_typing_expr(value, "Final") + // Final can be used either with a subscript `Final[...]` or without (the type is + // inferred). + semantic.match_typing_expr(map_subscript(annotation), "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]] = [] |