Skip to content

Commit b3947b3

Browse files
committed
[ty] Support frozen dataclasses
17675
1 parent f783679 commit b3947b3

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

crates/ty_python_semantic/resources/mdtest/dataclasses.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,26 @@ To do
369369

370370
### `frozen`
371371

372-
To do
372+
If true (the default is False), assigning to fields will generate a diagnostic. This emulates
373+
read-only frozen instances. If __setattr__() or __delattr__() is defined in the class, we should
374+
emit a diagnostic.
375+
376+
```py
377+
from dataclasses import dataclass
378+
379+
@dataclass(frozen=True)
380+
class Frozen:
381+
x: int
382+
383+
# TODO: Emit a diagnostic here
384+
def __setattr__(self, name: str, value: object) -> None: ...
385+
386+
# TODO: Emit a diagnostic here
387+
def __delattr__(self, name: str) -> None: ...
388+
389+
frozen = Frozen(1)
390+
frozen.x = 2 # error: [invalid-assignment]
391+
```
373392

374393
### `match_args`
375394

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2924,6 +2924,23 @@ impl<'db> TypeInferenceBuilder<'db> {
29242924
| Type::TypeVar(..)
29252925
| Type::AlwaysTruthy
29262926
| Type::AlwaysFalsy => {
2927+
if let Type::NominalInstance(instance) = object_ty {
2928+
let dataclass_params = match instance.class() {
2929+
ClassType::NonGeneric(cls) => cls.dataclass_params(self.db()),
2930+
ClassType::Generic(_) => None,
2931+
};
2932+
let frozen = dataclass_params
2933+
.is_some_and(|params| params.contains(DataclassParams::FROZEN));
2934+
if frozen && emit_diagnostics {
2935+
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target)
2936+
{
2937+
builder.into_diagnostic(format_args!(
2938+
"Property `{attribute}` defined in `{ty}` is read-only",
2939+
ty = object_ty.display(self.db()),
2940+
));
2941+
}
2942+
}
2943+
}
29272944
match object_ty.class_member(db, attribute.into()) {
29282945
meta_attr @ SymbolAndQualifiers { .. } if meta_attr.is_class_var() => {
29292946
if emit_diagnostics {

0 commit comments

Comments
 (0)