Skip to content

Commit e171a27

Browse files
committed
[ty] synthesize __setattr__ and __delattr__ for frozen dataclasses
1 parent dca594f commit e171a27

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,39 @@ from dataclasses import dataclass
425425
class MyFrozenClass: ...
426426

427427
frozen = MyFrozenClass()
428-
frozen.x = 2 # error: [unresolved-attribute]
428+
frozen.x = 2 # error: [invalid-assignment]
429+
```
430+
431+
diagnostic is also emitted if a frozen dataclass is inherited, and an attempt is made to mutate an
432+
attribute in the child class:
433+
434+
```py
435+
from dataclasses import dataclass
436+
437+
@dataclass(frozen=True)
438+
class MyFrozenClass:
439+
x: int = 1
440+
441+
class MyFrozenChildClass(MyFrozenClass): ...
442+
443+
frozen = MyFrozenChildClass()
444+
frozen.x = 2 # error: [invalid-assignment]
445+
```
446+
447+
The same diagnostic is emitted if a frozen dataclass is inherited, and an attempt is made to delete
448+
an attribute:
449+
450+
```py
451+
from dataclasses import dataclass
452+
453+
@dataclass(frozen=True)
454+
class MyFrozenClass:
455+
x: int = 1
456+
457+
class MyFrozenChildClass(MyFrozenClass): ...
458+
459+
frozen = MyFrozenChildClass()
460+
del frozen.x # TODO this should emit an [invalid-assignment]
429461
```
430462

431463
### `match_args`

crates/ty_python_semantic/src/types/class.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,25 @@ impl<'db> ClassLiteral<'db> {
15891589
.place
15901590
.ignore_possibly_unbound()
15911591
}
1592+
(CodeGeneratorKind::DataclassLike, "__setattr__") => {
1593+
if has_dataclass_param(DataclassParams::FROZEN) {
1594+
let signature = Signature::new(
1595+
Parameters::new([
1596+
Parameter::positional_or_keyword(Name::new_static("self"))
1597+
.with_annotated_type(Type::instance(
1598+
db,
1599+
self.apply_optional_specialization(db, specialization),
1600+
)),
1601+
Parameter::positional_or_keyword(Name::new_static("name")),
1602+
Parameter::positional_or_keyword(Name::new_static("value")),
1603+
]),
1604+
Some(Type::Never),
1605+
);
1606+
1607+
return Some(CallableType::function_like(db, signature));
1608+
}
1609+
None
1610+
}
15921611
_ => None,
15931612
}
15941613
}

0 commit comments

Comments
 (0)