Skip to content

Commit 0fe7255

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

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

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 an exception.
373+
This emulates read-only frozen instances.
374+
If __setattr__() or __delattr__() is defined in the class, we should 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2924,6 +2924,24 @@ 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 = instance
2929+
.class()
2930+
.class_literal(self.db())
2931+
.0
2932+
.dataclass_params(self.db());
2933+
let frozen = dataclass_params
2934+
.map_or(false, |params| params.contains(DataclassParams::FROZEN));
2935+
if frozen && emit_diagnostics {
2936+
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target)
2937+
{
2938+
builder.into_diagnostic(format_args!(
2939+
"Property `{attribute}` defined in `{ty}` is read-only",
2940+
ty = object_ty.display(self.db()),
2941+
));
2942+
}
2943+
}
2944+
}
29272945
match object_ty.class_member(db, attribute.into()) {
29282946
meta_attr @ SymbolAndQualifiers { .. } if meta_attr.is_class_var() => {
29292947
if emit_diagnostics {

0 commit comments

Comments
 (0)