From a8e50a7f40b2883b904d9ba347ef01c466179a75 Mon Sep 17 00:00:00 2001 From: Guilherme Vasconcelos <49197151+Guilherme-Vasconcelos@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:18:03 -0300 Subject: [PATCH] [RUF008] Make it clearer that a mutable default in a dataclass is only valid if it is typed as a ClassVar (#10395) ## Summary The previous documentation sounded as if typing a mutable default as a `ClassVar` were optional. However, it is not, as not doing so causes a `ValueError`. The snippet below was tested in Python's interactive shell: ``` >>> from dataclasses import dataclass >>> @dataclass ... class A: ... mutable_default: list[int] = [] ... Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.11/dataclasses.py", line 1230, in dataclass return wrap(cls) ^^^^^^^^^ File "/usr/lib/python3.11/dataclasses.py", line 1220, in wrap return _process_class(cls, init, repr, eq, order, unsafe_hash, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/dataclasses.py", line 958, in _process_class cls_fields.append(_get_field(cls, name, type, kw_only)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/dataclasses.py", line 815, in _get_field raise ValueError(f'mutable default {type(f.default)} for field ' ValueError: mutable default for field mutable_default is not allowed: use default_factory >>> ``` This behavior is also documented in Python's docs, see [here](https://docs.python.org/3/library/dataclasses.html#mutable-default-values): > [...] the [dataclass()](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass) decorator will raise a [ValueError](https://docs.python.org/3/library/exceptions.html#ValueError) if it detects an unhashable default parameter. The assumption is that if a value is unhashable, it is mutable. This is a partial solution, but it does protect against many common errors. And [here](https://docs.python.org/3/library/dataclasses.html#class-variables) it is documented why it works if it is typed as a `ClassVar`: > One of the few places where [dataclass()](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass) actually inspects the type of a field is to determine if a field is a class variable as defined in [PEP 526](https://peps.python.org/pep-0526/). It does this by checking if the type of the field is typing.ClassVar. If a field is a ClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. Such ClassVar pseudo-fields are not returned by the module-level [fields()](https://docs.python.org/3/library/dataclasses.html#dataclasses.fields) function. In this PR I have changed the documentation to make it a little bit clearer that not using `ClassVar` makes the code invalid. --- .../src/rules/ruff/rules/mutable_dataclass_default.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs index f12d8cf87a128..9eebf896b523f 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs @@ -19,8 +19,8 @@ use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass}; /// Instead of sharing mutable defaults, use the `field(default_factory=...)` /// pattern. /// -/// If the default value is intended to be mutable, it should be annotated with -/// `typing.ClassVar`. +/// If the default value is intended to be mutable, it must be annotated with +/// `typing.ClassVar`; otherwise, a `ValueError` will be raised. /// /// ## Examples /// ```python @@ -29,6 +29,8 @@ use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass}; /// /// @dataclass /// class A: +/// # A list without a `default_factory` or `ClassVar` annotation +/// # will raise a `ValueError`. /// mutable_default: list[int] = [] /// ``` /// @@ -44,7 +46,7 @@ use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass}; /// /// Or: /// ```python -/// from dataclasses import dataclass, field +/// from dataclasses import dataclass /// from typing import ClassVar /// ///