[ty] Synthesize __delitem__ for TypedDict to allow deleting non-required keys (#22122)

## Summary

TypedDict now synthesizes a proper `__delitem__` method that...

- ...allows deletion of `NotRequired` keys and keys in `total=False`
TypedDicts.
- ...rejects deletion of required keys (synthesizes `__delitem__(k:
Never)`).
This commit is contained in:
Charlie Marsh
2025-12-23 22:39:54 -05:00
committed by GitHub
parent acdda78189
commit 969c8a547e
2 changed files with 85 additions and 7 deletions

View File

@@ -2818,6 +2818,62 @@ impl<'db> ClassLiteral<'db> {
CallableTypeKind::FunctionLike,
)))
}
(CodeGeneratorKind::TypedDict, "__delitem__") => {
let fields = self.fields(db, specialization, field_policy);
// Only non-required fields can be deleted. Required fields cannot be deleted
// because that would violate the TypedDict's structural type.
let mut deletable_fields = fields
.iter()
.filter(|(_, field)| !field.is_required())
.peekable();
if deletable_fields.peek().is_none() {
// If there are no deletable fields (all fields are required), synthesize a
// `__delitem__` that takes a `key` of type `Never` to signal that no keys
// can be deleted.
return Some(Type::Callable(CallableType::new(
db,
CallableSignature::single(Signature::new(
Parameters::new(
db,
[
Parameter::positional_only(Some(Name::new_static("self")))
.with_annotated_type(instance_ty),
Parameter::positional_only(Some(Name::new_static("key")))
.with_annotated_type(Type::Never),
],
),
Some(Type::none(db)),
)),
CallableTypeKind::FunctionLike,
)));
}
// Otherwise, add overloads for all deletable fields.
let overloads = deletable_fields.map(|(name, _field)| {
let key_type = Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
Signature::new(
Parameters::new(
db,
[
Parameter::positional_only(Some(Name::new_static("self")))
.with_annotated_type(instance_ty),
Parameter::positional_only(Some(Name::new_static("key")))
.with_annotated_type(key_type),
],
),
Some(Type::none(db)),
)
});
Some(Type::Callable(CallableType::new(
db,
CallableSignature::from_overloads(overloads),
CallableTypeKind::FunctionLike,
)))
}
(CodeGeneratorKind::TypedDict, "get") => {
let overloads = self
.fields(db, specialization, field_policy)