From 43dc99bdafc9c8616df4c66dafcda68c447f2baf Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Mon, 24 Apr 2023 21:31:13 -0700 Subject: [PATCH 1/7] RUF008/9 for non-dataclasses as well --- .../resources/test/fixtures/ruff/RUF010.py | 21 +++++ .../resources/test/fixtures/ruff/RUF011.py | 27 +++++++ crates/ruff/src/checkers/ast/mod.rs | 36 +++++++-- crates/ruff/src/codes.rs | 2 + crates/ruff/src/registry.rs | 2 + crates/ruff/src/rules/ruff/mod.rs | 13 ++- crates/ruff/src/rules/ruff/rules/mod.rs | 9 ++- ...rs => mutable_defaults_in_class_fields.rs} | 81 +++++++++++++++---- ..._rules__ruff__tests__RUF010_RUF010.py.snap | 42 ++++++++++ ..._rules__ruff__tests__RUF011_RUF011.py.snap | 48 +++++++++++ ruff.schema.json | 3 + 11 files changed, 259 insertions(+), 25 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/ruff/RUF010.py create mode 100644 crates/ruff/resources/test/fixtures/ruff/RUF011.py rename crates/ruff/src/rules/ruff/rules/{mutable_defaults_in_dataclass_fields.rs => mutable_defaults_in_class_fields.rs} (69%) create mode 100644 crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap create mode 100644 crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF010.py b/crates/ruff/resources/test/fixtures/ruff/RUF010.py new file mode 100644 index 0000000000000..2cf96991fd276 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/ruff/RUF010.py @@ -0,0 +1,21 @@ +import typing +from dataclasses import dataclass, field +from typing import Sequence + +KNOWINGLY_MUTABLE_DEFAULT = [] + + +class A: + mutable_default: list[int] = [] + immutable_annotation: typing.Sequence[int] = [] + without_annotation = [] + ignored_via_comment: list[int] = [] # noqa: RUF010 + correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT + + +class B: + mutable_default: list[int] = [] + immutable_annotation: Sequence[int] = [] + without_annotation = [] + ignored_via_comment: list[int] = [] # noqa: RUF010 + correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF011.py b/crates/ruff/resources/test/fixtures/ruff/RUF011.py new file mode 100644 index 0000000000000..ce604222baa30 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/ruff/RUF011.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass +from typing import NamedTuple + + +def default_function() -> list[int]: + return [] + + +class ImmutableType(NamedTuple): + something: int = 8 + + +class A: + hidden_mutable_default: list[int] = default_function() + + +DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40) +DEFAULT_A_FOR_ALL_DATACLASSES = A([1, 2, 3]) + + +class B: + hidden_mutable_default: list[int] = default_function() + another_dataclass: A = A() + not_optimal: ImmutableType = ImmutableType(20) + good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES + okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES + field: list[int] = field(default_vactory=list) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 536ab26abe383..82ed1da8b30b2 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -843,19 +843,43 @@ where if self.settings.rules.any_enabled(&[ Rule::MutableDataclassDefault, + Rule::MutableClassDefault, Rule::FunctionCallInDataclassDefaultArgument, - ]) && ruff::rules::is_dataclass(self, decorator_list) - { - if self.settings.rules.enabled(Rule::MutableDataclassDefault) { - ruff::rules::mutable_dataclass_default(self, body); + Rule::FunctionCallInClassDefaultArgument, + ]) { + let is_dataclass = ruff::rules::is_dataclass(self, decorator_list); + if is_dataclass && self.settings.rules.enabled(Rule::MutableDataclassDefault) { + ruff::rules::mutable_class_default(self, true, body); + } + + if is_dataclass + && self + .settings + .rules + .enabled(Rule::FunctionCallInDataclassDefaultArgument) + { + ruff::rules::function_call_in_class_defaults( + self, + body, + is_dataclass, + true, + ); + } + if self.settings.rules.enabled(Rule::MutableClassDefault) { + ruff::rules::mutable_class_default(self, false, body); } if self .settings .rules - .enabled(Rule::FunctionCallInDataclassDefaultArgument) + .enabled(Rule::FunctionCallInClassDefaultArgument) { - ruff::rules::function_call_in_dataclass_defaults(self, body); + ruff::rules::function_call_in_class_defaults( + self, + body, + is_dataclass, + false, + ); } } diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 5a1f864cd3c70..72b222bf09f91 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -713,6 +713,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Ruff, "007") => Rule::PairwiseOverZipped, (Ruff, "008") => Rule::MutableDataclassDefault, (Ruff, "009") => Rule::FunctionCallInDataclassDefaultArgument, + (Ruff, "010") => Rule::MutableClassDefault, + (Ruff, "011") => Rule::FunctionCallInClassDefaultArgument, (Ruff, "100") => Rule::UnusedNOQA, // flake8-django diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 22eabf0636658..96e24cc0088db 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -657,6 +657,8 @@ ruff_macros::register_rules!( rules::ruff::rules::PairwiseOverZipped, rules::ruff::rules::MutableDataclassDefault, rules::ruff::rules::FunctionCallInDataclassDefaultArgument, + rules::ruff::rules::MutableClassDefault, + rules::ruff::rules::FunctionCallInClassDefaultArgument, // flake8-django rules::flake8_django::rules::DjangoNullableModelStringField, rules::flake8_django::rules::DjangoLocalsInRenderFunction, diff --git a/crates/ruff/src/rules/ruff/mod.rs b/crates/ruff/src/rules/ruff/mod.rs index 0be811b724722..8a971d6f3513c 100644 --- a/crates/ruff/src/rules/ruff/mod.rs +++ b/crates/ruff/src/rules/ruff/mod.rs @@ -155,7 +155,18 @@ mod tests { #[test_case(Rule::MutableDataclassDefault, Path::new("RUF008.py"); "RUF008")] #[test_case(Rule::FunctionCallInDataclassDefaultArgument, Path::new("RUF009.py"); "RUF009")] - fn mutable_defaults(rule_code: Rule, path: &Path) -> Result<()> { + fn mutable_dataclass_defaults(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("ruff").join(path).as_path(), + &settings::Settings::for_rule(rule_code), + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test_case(Rule::MutableClassDefault, Path::new("RUF010.py"); "RUF010")] + #[test_case(Rule::FunctionCallInClassDefaultArgument, Path::new("RUF011.py"); "RUF011")] + fn mutable_class_defaults(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( Path::new("ruff").join(path).as_path(), diff --git a/crates/ruff/src/rules/ruff/rules/mod.rs b/crates/ruff/src/rules/ruff/rules/mod.rs index 6a063c18dc1f8..ea6aee7307771 100644 --- a/crates/ruff/src/rules/ruff/rules/mod.rs +++ b/crates/ruff/src/rules/ruff/rules/mod.rs @@ -1,7 +1,7 @@ mod ambiguous_unicode_character; mod asyncio_dangling_task; mod collection_literal_concatenation; -mod mutable_defaults_in_dataclass_fields; +mod mutable_defaults_in_class_fields; mod pairwise_over_zipped; mod unused_noqa; @@ -13,9 +13,10 @@ pub use asyncio_dangling_task::{asyncio_dangling_task, AsyncioDanglingTask}; pub use collection_literal_concatenation::{ collection_literal_concatenation, CollectionLiteralConcatenation, }; -pub use mutable_defaults_in_dataclass_fields::{ - function_call_in_dataclass_defaults, is_dataclass, mutable_dataclass_default, - FunctionCallInDataclassDefaultArgument, MutableDataclassDefault, +pub use mutable_defaults_in_class_fields::{ + function_call_in_class_defaults, is_dataclass, mutable_class_default, + FunctionCallInClassDefaultArgument, FunctionCallInDataclassDefaultArgument, + MutableClassDefault, MutableDataclassDefault, }; pub use pairwise_over_zipped::{pairwise_over_zipped, PairwiseOverZipped}; pub use unused_noqa::{UnusedCodes, UnusedNOQA}; diff --git a/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_dataclass_fields.rs b/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs similarity index 69% rename from crates/ruff/src/rules/ruff/rules/mutable_defaults_in_dataclass_fields.rs rename to crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs index 8d1d9839ef248..0d4e9c1fd32a1 100644 --- a/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_dataclass_fields.rs +++ b/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs @@ -57,6 +57,17 @@ impl Violation for MutableDataclassDefault { } } +/// This rule is same as MutableDataclassDefault, but for any class. The same arguments apply. +#[violation] +pub struct MutableClassDefault; + +impl Violation for MutableClassDefault { + #[derive_message_formats] + fn message(&self) -> String { + format!("Do not use mutable default values for class attributes") + } +} + /// ## What it does /// Checks for function calls in dataclass defaults. /// @@ -129,6 +140,25 @@ impl Violation for FunctionCallInDataclassDefaultArgument { } } +/// Same as FunctionCallInDataclassDefaultArgument, but for any class. +/// Importantly, this error will be issued on calls to dataclasses.field +#[violation] +pub struct FunctionCallInClassDefaultArgument { + pub name: Option, +} + +impl Violation for FunctionCallInClassDefaultArgument { + #[derive_message_formats] + fn message(&self) -> String { + let FunctionCallInClassDefaultArgument { name } = self; + if let Some(name) = name { + format!("Do not perform function call `{name}` in non-dataclass attribute defaults") + } else { + format!("Do not perform function call in non-dataclass attribute defaults") + } + } +} + fn is_mutable_expr(expr: &Expr) -> bool { matches!( &expr.node, @@ -143,7 +173,7 @@ fn is_mutable_expr(expr: &Expr) -> bool { const ALLOWED_FUNCS: &[&[&str]] = &[&["dataclasses", "field"]]; -fn is_allowed_func(context: &Context, func: &Expr) -> bool { +fn is_allowed_dataclass_func(context: &Context, func: &Expr) -> bool { context.resolve_call_path(func).map_or(false, |call_path| { ALLOWED_FUNCS .iter() @@ -159,8 +189,13 @@ fn is_class_var_annotation(context: &Context, annotation: &Expr) -> bool { context.match_typing_expr(value, "ClassVar") } -/// RUF009 -pub fn function_call_in_dataclass_defaults(checker: &mut Checker, body: &[Stmt]) { +/// RUF009/RUF011 +pub fn function_call_in_class_defaults( + checker: &mut Checker, + body: &[Stmt], + is_dataclass: bool, + emit_dataclass_error: bool, +) { for statement in body { if let StmtKind::AnnAssign { annotation, @@ -172,21 +207,39 @@ pub fn function_call_in_dataclass_defaults(checker: &mut Checker, body: &[Stmt]) continue; } if let ExprKind::Call { func, .. } = &expr.node { - if !is_allowed_func(&checker.ctx, func) { - checker.diagnostics.push(Diagnostic::new( - FunctionCallInDataclassDefaultArgument { - name: compose_call_path(func), - }, - Range::from(expr), - )); + if !is_dataclass || !is_allowed_dataclass_func(&checker.ctx, func) { + let diagnostic: Diagnostic = if emit_dataclass_error { + Diagnostic::new( + FunctionCallInDataclassDefaultArgument { + name: compose_call_path(func), + }, + Range::from(expr), + ) + } else { + Diagnostic::new( + FunctionCallInClassDefaultArgument { + name: compose_call_path(func), + }, + Range::from(expr), + ) + }; + checker.diagnostics.push(diagnostic); } } } } } -/// RUF008 -pub fn mutable_dataclass_default(checker: &mut Checker, body: &[Stmt]) { +/// RUF008/RUF010 +pub fn mutable_class_default(checker: &mut Checker, emit_dataclass_error: bool, body: &[Stmt]) { + fn diagnostic(emit_dataclass_error: bool, value: &Expr) -> Diagnostic { + if emit_dataclass_error { + Diagnostic::new(MutableDataclassDefault, Range::from(value)) + } else { + Diagnostic::new(MutableClassDefault, Range::from(value)) + } + } + for statement in body { match &statement.node { StmtKind::AnnAssign { @@ -200,14 +253,14 @@ pub fn mutable_dataclass_default(checker: &mut Checker, body: &[Stmt]) { { checker .diagnostics - .push(Diagnostic::new(MutableDataclassDefault, Range::from(value))); + .push(diagnostic(emit_dataclass_error, value)); } } StmtKind::Assign { value, .. } => { if is_mutable_expr(value) { checker .diagnostics - .push(Diagnostic::new(MutableDataclassDefault, Range::from(value))); + .push(diagnostic(emit_dataclass_error, value)); } } _ => (), diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap new file mode 100644 index 0000000000000..44ea3287aeeea --- /dev/null +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap @@ -0,0 +1,42 @@ +--- +source: crates/ruff/src/rules/ruff/mod.rs +--- +RUF010.py:9:34: RUF010 Do not use mutable default values for class attributes + | + 9 | class A: +10 | mutable_default: list[int] = [] + | ^^ RUF010 +11 | immutable_annotation: typing.Sequence[int] = [] +12 | without_annotation = [] + | + +RUF010.py:11:26: RUF010 Do not use mutable default values for class attributes + | +11 | mutable_default: list[int] = [] +12 | immutable_annotation: typing.Sequence[int] = [] +13 | without_annotation = [] + | ^^ RUF010 +14 | ignored_via_comment: list[int] = [] # noqa: RUF010 +15 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT + | + +RUF010.py:17:34: RUF010 Do not use mutable default values for class attributes + | +17 | class B: +18 | mutable_default: list[int] = [] + | ^^ RUF010 +19 | immutable_annotation: Sequence[int] = [] +20 | without_annotation = [] + | + +RUF010.py:19:26: RUF010 Do not use mutable default values for class attributes + | +19 | mutable_default: list[int] = [] +20 | immutable_annotation: Sequence[int] = [] +21 | without_annotation = [] + | ^^ RUF010 +22 | ignored_via_comment: list[int] = [] # noqa: RUF010 +23 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT + | + + diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap new file mode 100644 index 0000000000000..95673fe38df85 --- /dev/null +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap @@ -0,0 +1,48 @@ +--- +source: crates/ruff/src/rules/ruff/mod.rs +--- +RUF011.py:14:41: RUF011 Do not perform function call `default_function` in non-dataclass attribute defaults + | +14 | class A: +15 | hidden_mutable_default: list[int] = default_function() + | ^^^^^^^^^^^^^^^^^^ RUF011 + | + +RUF011.py:22:41: RUF011 Do not perform function call `default_function` in non-dataclass attribute defaults + | +22 | class B: +23 | hidden_mutable_default: list[int] = default_function() + | ^^^^^^^^^^^^^^^^^^ RUF011 +24 | another_dataclass: A = A() +25 | not_optimal: ImmutableType = ImmutableType(20) + | + +RUF011.py:23:28: RUF011 Do not perform function call `A` in non-dataclass attribute defaults + | +23 | class B: +24 | hidden_mutable_default: list[int] = default_function() +25 | another_dataclass: A = A() + | ^^^ RUF011 +26 | not_optimal: ImmutableType = ImmutableType(20) +27 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES + | + +RUF011.py:24:34: RUF011 Do not perform function call `ImmutableType` in non-dataclass attribute defaults + | +24 | hidden_mutable_default: list[int] = default_function() +25 | another_dataclass: A = A() +26 | not_optimal: ImmutableType = ImmutableType(20) + | ^^^^^^^^^^^^^^^^^ RUF011 +27 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +28 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES + | + +RUF011.py:27:24: RUF011 Do not perform function call `field` in non-dataclass attribute defaults + | +27 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +28 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES +29 | field: list[int] = field(default_vactory=list) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF011 + | + + diff --git a/ruff.schema.json b/ruff.schema.json index 2d126f00f9dd8..b17fe6f58f6e2 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2160,6 +2160,9 @@ "RUF007", "RUF008", "RUF009", + "RUF01", + "RUF010", + "RUF011", "RUF1", "RUF10", "RUF100", From 941d47a13f41e4c791366540e857a30d8dc4431f Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Tue, 25 Apr 2023 21:40:25 -0700 Subject: [PATCH 2/7] Update crates/ruff/src/checkers/ast/mod.rs Co-authored-by: Micha Reiser --- crates/ruff/src/checkers/ast/mod.rs | 34 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 82ed1da8b30b2..cdc5fefca848b 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -848,22 +848,24 @@ where Rule::FunctionCallInClassDefaultArgument, ]) { let is_dataclass = ruff::rules::is_dataclass(self, decorator_list); - if is_dataclass && self.settings.rules.enabled(Rule::MutableDataclassDefault) { - ruff::rules::mutable_class_default(self, true, body); - } - - if is_dataclass - && self - .settings - .rules - .enabled(Rule::FunctionCallInDataclassDefaultArgument) - { - ruff::rules::function_call_in_class_defaults( - self, - body, - is_dataclass, - true, - ); + + if is_dataclass { + if self.settings.rules.enabled(Rule::MutableDataclassDefault) { + ruff::rules::mutable_class_default(self, true, body); + } + + if self + .settings + .rules + .enabled(Rule::FunctionCallInDataclassDefaultArgument) + { + ruff::rules::function_call_in_class_defaults( + self, + body, + is_dataclass, + true, + ); + } } if self.settings.rules.enabled(Rule::MutableClassDefault) { ruff::rules::mutable_class_default(self, false, body); From 87da6cb94b453bd466e3493abaa6571465b64cef Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Tue, 25 Apr 2023 21:40:31 -0700 Subject: [PATCH 3/7] Update crates/ruff/src/rules/ruff/mod.rs Co-authored-by: Micha Reiser --- crates/ruff/src/rules/ruff/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ruff/src/rules/ruff/mod.rs b/crates/ruff/src/rules/ruff/mod.rs index 8a971d6f3513c..b3812cbae21dc 100644 --- a/crates/ruff/src/rules/ruff/mod.rs +++ b/crates/ruff/src/rules/ruff/mod.rs @@ -164,6 +164,7 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } + #[test_case(Rule::MutableClassDefault, Path::new("RUF010.py"); "RUF010")] #[test_case(Rule::FunctionCallInClassDefaultArgument, Path::new("RUF011.py"); "RUF011")] fn mutable_class_defaults(rule_code: Rule, path: &Path) -> Result<()> { From 86dfc750b5cab3736b7ac26d57a8a3269287f653 Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Tue, 25 Apr 2023 21:42:28 -0700 Subject: [PATCH 4/7] fmt --- crates/ruff/src/checkers/ast/mod.rs | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index cdc5fefca848b..2395e6cf0efe4 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -848,24 +848,24 @@ where Rule::FunctionCallInClassDefaultArgument, ]) { let is_dataclass = ruff::rules::is_dataclass(self, decorator_list); - + if is_dataclass { - if self.settings.rules.enabled(Rule::MutableDataclassDefault) { - ruff::rules::mutable_class_default(self, true, body); - } - - if self - .settings - .rules - .enabled(Rule::FunctionCallInDataclassDefaultArgument) - { - ruff::rules::function_call_in_class_defaults( - self, - body, - is_dataclass, - true, - ); - } + if self.settings.rules.enabled(Rule::MutableDataclassDefault) { + ruff::rules::mutable_class_default(self, true, body); + } + + if self + .settings + .rules + .enabled(Rule::FunctionCallInDataclassDefaultArgument) + { + ruff::rules::function_call_in_class_defaults( + self, + body, + is_dataclass, + true, + ); + } } if self.settings.rules.enabled(Rule::MutableClassDefault) { ruff::rules::mutable_class_default(self, false, body); From 12aff2db5e331ea3b6ebbba33e16e961f48b32a4 Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Thu, 27 Apr 2023 09:11:08 -0700 Subject: [PATCH 5/7] bump ruleset size --- crates/ruff/src/registry/rule_set.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff/src/registry/rule_set.rs b/crates/ruff/src/registry/rule_set.rs index 8c358fa469837..4cb6df76b423a 100644 --- a/crates/ruff/src/registry/rule_set.rs +++ b/crates/ruff/src/registry/rule_set.rs @@ -7,10 +7,10 @@ use std::iter::FusedIterator; /// /// Uses a bitset where a bit of one signals that the Rule with that [u16] is in this set. #[derive(Clone, Default, CacheKey, PartialEq, Eq)] -pub struct RuleSet([u64; 9]); +pub struct RuleSet([u64; 10]); impl RuleSet { - const EMPTY: [u64; 9] = [0; 9]; + const EMPTY: [u64; 10] = [0; 10]; // 64 fits into a u16 without truncation #[allow(clippy::cast_possible_truncation)] From 03483ebc5f1fd22ec9c6f1958fdeffd8a3ae4b4b Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Fri, 28 Apr 2023 09:34:38 -0700 Subject: [PATCH 6/7] fixup --- .../resources/test/fixtures/ruff/RUF010.py | 1 - .../resources/test/fixtures/ruff/RUF011.py | 11 ++-- ..._rules__ruff__tests__RUF010_RUF010.py.snap | 44 +++++++-------- ..._rules__ruff__tests__RUF011_RUF011.py.snap | 54 ++++++++++--------- 4 files changed, 57 insertions(+), 53 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF010.py b/crates/ruff/resources/test/fixtures/ruff/RUF010.py index 2cf96991fd276..430129a45bac9 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF010.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF010.py @@ -1,5 +1,4 @@ import typing -from dataclasses import dataclass, field from typing import Sequence KNOWINGLY_MUTABLE_DEFAULT = [] diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF011.py b/crates/ruff/resources/test/fixtures/ruff/RUF011.py index ce604222baa30..d6454b174c66d 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF011.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF011.py @@ -1,6 +1,7 @@ -from dataclasses import dataclass +import datetime +import typing from typing import NamedTuple - +from dataclasses import field def default_function() -> list[int]: return [] @@ -12,6 +13,8 @@ class ImmutableType(NamedTuple): class A: hidden_mutable_default: list[int] = default_function() + class_variable: typing.ClassVar[list[int]] = default_function() + fine_date: datetime.date = datetime.date(2042, 1, 1) DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40) @@ -20,8 +23,8 @@ class A: class B: hidden_mutable_default: list[int] = default_function() - another_dataclass: A = A() + another_class: A = A() not_optimal: ImmutableType = ImmutableType(20) good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES - field: list[int] = field(default_vactory=list) + not_fine_field: list[int] = field(default_vactory=list) diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap index 44ea3287aeeea..f1db232d9fc2f 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF010_RUF010.py.snap @@ -1,42 +1,42 @@ --- source: crates/ruff/src/rules/ruff/mod.rs --- -RUF010.py:9:34: RUF010 Do not use mutable default values for class attributes +RUF010.py:8:34: RUF010 Do not use mutable default values for class attributes | - 9 | class A: -10 | mutable_default: list[int] = [] + 8 | class A: + 9 | mutable_default: list[int] = [] | ^^ RUF010 -11 | immutable_annotation: typing.Sequence[int] = [] -12 | without_annotation = [] +10 | immutable_annotation: typing.Sequence[int] = [] +11 | without_annotation = [] | -RUF010.py:11:26: RUF010 Do not use mutable default values for class attributes +RUF010.py:10:26: RUF010 Do not use mutable default values for class attributes | -11 | mutable_default: list[int] = [] -12 | immutable_annotation: typing.Sequence[int] = [] -13 | without_annotation = [] +10 | mutable_default: list[int] = [] +11 | immutable_annotation: typing.Sequence[int] = [] +12 | without_annotation = [] | ^^ RUF010 -14 | ignored_via_comment: list[int] = [] # noqa: RUF010 -15 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT +13 | ignored_via_comment: list[int] = [] # noqa: RUF010 +14 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT | -RUF010.py:17:34: RUF010 Do not use mutable default values for class attributes +RUF010.py:16:34: RUF010 Do not use mutable default values for class attributes | -17 | class B: -18 | mutable_default: list[int] = [] +16 | class B: +17 | mutable_default: list[int] = [] | ^^ RUF010 -19 | immutable_annotation: Sequence[int] = [] -20 | without_annotation = [] +18 | immutable_annotation: Sequence[int] = [] +19 | without_annotation = [] | -RUF010.py:19:26: RUF010 Do not use mutable default values for class attributes +RUF010.py:18:26: RUF010 Do not use mutable default values for class attributes | -19 | mutable_default: list[int] = [] -20 | immutable_annotation: Sequence[int] = [] -21 | without_annotation = [] +18 | mutable_default: list[int] = [] +19 | immutable_annotation: Sequence[int] = [] +20 | without_annotation = [] | ^^ RUF010 -22 | ignored_via_comment: list[int] = [] # noqa: RUF010 -23 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT +21 | ignored_via_comment: list[int] = [] # noqa: RUF010 +22 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT | diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap index 95673fe38df85..3d1fad7ed199e 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF011_RUF011.py.snap @@ -1,48 +1,50 @@ --- source: crates/ruff/src/rules/ruff/mod.rs --- -RUF011.py:14:41: RUF011 Do not perform function call `default_function` in non-dataclass attribute defaults +RUF011.py:15:41: RUF011 Do not perform function call `default_function` in non-dataclass attribute defaults | -14 | class A: -15 | hidden_mutable_default: list[int] = default_function() +15 | class A: +16 | hidden_mutable_default: list[int] = default_function() | ^^^^^^^^^^^^^^^^^^ RUF011 +17 | class_variable: typing.ClassVar[list[int]] = default_function() +18 | fine_date: datetime.date = datetime.date(2042, 1, 1) | -RUF011.py:22:41: RUF011 Do not perform function call `default_function` in non-dataclass attribute defaults +RUF011.py:25:41: RUF011 Do not perform function call `default_function` in non-dataclass attribute defaults | -22 | class B: -23 | hidden_mutable_default: list[int] = default_function() +25 | class B: +26 | hidden_mutable_default: list[int] = default_function() | ^^^^^^^^^^^^^^^^^^ RUF011 -24 | another_dataclass: A = A() -25 | not_optimal: ImmutableType = ImmutableType(20) +27 | another_class: A = A() +28 | not_optimal: ImmutableType = ImmutableType(20) | -RUF011.py:23:28: RUF011 Do not perform function call `A` in non-dataclass attribute defaults +RUF011.py:26:24: RUF011 Do not perform function call `A` in non-dataclass attribute defaults | -23 | class B: -24 | hidden_mutable_default: list[int] = default_function() -25 | another_dataclass: A = A() - | ^^^ RUF011 -26 | not_optimal: ImmutableType = ImmutableType(20) -27 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +26 | class B: +27 | hidden_mutable_default: list[int] = default_function() +28 | another_class: A = A() + | ^^^ RUF011 +29 | not_optimal: ImmutableType = ImmutableType(20) +30 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES | -RUF011.py:24:34: RUF011 Do not perform function call `ImmutableType` in non-dataclass attribute defaults +RUF011.py:27:34: RUF011 Do not perform function call `ImmutableType` in non-dataclass attribute defaults | -24 | hidden_mutable_default: list[int] = default_function() -25 | another_dataclass: A = A() -26 | not_optimal: ImmutableType = ImmutableType(20) +27 | hidden_mutable_default: list[int] = default_function() +28 | another_class: A = A() +29 | not_optimal: ImmutableType = ImmutableType(20) | ^^^^^^^^^^^^^^^^^ RUF011 -27 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES -28 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES +30 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +31 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES | -RUF011.py:27:24: RUF011 Do not perform function call `field` in non-dataclass attribute defaults +RUF011.py:30:33: RUF011 Do not perform function call `field` in non-dataclass attribute defaults | -27 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES -28 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES -29 | field: list[int] = field(default_vactory=list) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF011 +30 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +31 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES +32 | not_fine_field: list[int] = field(default_vactory=list) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF011 | From e1a2919462104075d8a38b0ee003d4c500742f83 Mon Sep 17 00:00:00 2001 From: Adam Pauls Date: Fri, 28 Apr 2023 09:45:58 -0700 Subject: [PATCH 7/7] that is one opinionated linter --- .../src/rules/ruff/rules/mutable_defaults_in_class_fields.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs b/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs index 8d02bf170eb6a..b119d7f92138e 100644 --- a/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs +++ b/crates/ruff/src/rules/ruff/rules/mutable_defaults_in_class_fields.rs @@ -221,8 +221,8 @@ pub fn function_call_in_class_defaults( continue; } if let ExprKind::Call { func, .. } = &expr.node { - if !is_immutable_func(&checker.ctx, func, &extend_immutable_calls) - && !(is_dataclass && is_allowed_dataclass_function(&checker.ctx, func)) + if !(is_immutable_func(&checker.ctx, func, &extend_immutable_calls) + || is_dataclass && is_allowed_dataclass_function(&checker.ctx, func)) { let diagnostic: Diagnostic = if emit_dataclass_error { Diagnostic::new(