Skip to content

Commit 3b1c145

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

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses.md

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

370370
### `frozen`
371371

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

374392
### `match_args`
375393

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)