support dictionary literal `TypedDict` constructors

This commit is contained in:
Ibraheem Ahmed 2025-10-07 16:44:48 -04:00
parent b753851379
commit 2949f76b47
2 changed files with 37 additions and 10 deletions

View File

@ -315,6 +315,8 @@ Person(name="Alice", age=30, extra=True) # type: ignore
The positional dictionary constructor pattern (used by libraries like strawberry) should work The positional dictionary constructor pattern (used by libraries like strawberry) should work
correctly: correctly:
`class.py`:
```py ```py
from typing import TypedDict from typing import TypedDict
@ -335,6 +337,26 @@ user3 = User({"name": None, "age": 25})
user4 = User({"name": "Charlie", "age": 30, "extra": True}) user4 = User({"name": "Charlie", "age": 30, "extra": True})
``` ```
`functional.py`:
```py
from typing import TypedDict
User = TypedDict("User", {"name": str, "age": int})
# Valid usage - all required fields provided
user1 = User({"name": "Alice", "age": 30})
# error: [missing-typed-dict-key] "Missing required key 'age' in TypedDict `User` constructor"
user2 = User({"name": "Bob"})
# error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `User`: value of type `None`"
user3 = User({"name": None, "age": 25})
# error: [invalid-key] "Invalid key access on TypedDict `User`: Unknown key "extra""
user4 = User({"name": "Charlie", "age": 30, "extra": True})
```
## Optional fields with `total=False` ## Optional fields with `total=False`
By default, all fields in a `TypedDict` are required (`total=True`). You can make all fields By default, all fields in a `TypedDict` are required (`total=True`). You can make all fields

View File

@ -4865,16 +4865,21 @@ impl<'db> Type<'db> {
Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db), Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db),
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single( Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => {
self, CallableBinding::from_overloads(
Signature::new( self,
// TODO: List more specific parameter types here for better code completion. [Signature::new(
Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs")) // TODO: List more specific parameter types here for better code completion.
.with_annotated_type(Type::any())]), Parameters::new([
Some(Type::TypedDict(TypedDictType::Synthesized(typed_dict))), Parameter::variadic(Name::new_static("args")),
), Parameter::keyword_variadic(Name::new_static("kwargs"))
) .with_annotated_type(Type::any()),
.into(), ]),
Some(Type::TypedDict(TypedDictType::Synthesized(typed_dict))),
)],
)
.into()
}
Type::KnownInstance(known_instance) => { Type::KnownInstance(known_instance) => {
known_instance.instance_fallback(db).bindings(db) known_instance.instance_fallback(db).bindings(db)