Skip to content

Commit 2949f76

Browse files
committed
support dictionary literal TypedDict constructors
1 parent b753851 commit 2949f76

File tree

2 files changed

+37
-10
lines changed

2 files changed

+37
-10
lines changed

crates/ty_python_semantic/resources/mdtest/typed_dict.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ Person(name="Alice", age=30, extra=True) # type: ignore
315315
The positional dictionary constructor pattern (used by libraries like strawberry) should work
316316
correctly:
317317

318+
`class.py`:
319+
318320
```py
319321
from typing import TypedDict
320322

@@ -335,6 +337,26 @@ user3 = User({"name": None, "age": 25})
335337
user4 = User({"name": "Charlie", "age": 30, "extra": True})
336338
```
337339

340+
`functional.py`:
341+
342+
```py
343+
from typing import TypedDict
344+
345+
User = TypedDict("User", {"name": str, "age": int})
346+
347+
# Valid usage - all required fields provided
348+
user1 = User({"name": "Alice", "age": 30})
349+
350+
# error: [missing-typed-dict-key] "Missing required key 'age' in TypedDict `User` constructor"
351+
user2 = User({"name": "Bob"})
352+
353+
# error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `User`: value of type `None`"
354+
user3 = User({"name": None, "age": 25})
355+
356+
# error: [invalid-key] "Invalid key access on TypedDict `User`: Unknown key "extra""
357+
user4 = User({"name": "Charlie", "age": 30, "extra": True})
358+
```
359+
338360
## Optional fields with `total=False`
339361

340362
By default, all fields in a `TypedDict` are required (`total=True`). You can make all fields

crates/ty_python_semantic/src/types.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4865,16 +4865,21 @@ impl<'db> Type<'db> {
48654865

48664866
Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db),
48674867

4868-
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single(
4869-
self,
4870-
Signature::new(
4871-
// TODO: List more specific parameter types here for better code completion.
4872-
Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs"))
4873-
.with_annotated_type(Type::any())]),
4874-
Some(Type::TypedDict(TypedDictType::Synthesized(typed_dict))),
4875-
),
4876-
)
4877-
.into(),
4868+
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => {
4869+
CallableBinding::from_overloads(
4870+
self,
4871+
[Signature::new(
4872+
// TODO: List more specific parameter types here for better code completion.
4873+
Parameters::new([
4874+
Parameter::variadic(Name::new_static("args")),
4875+
Parameter::keyword_variadic(Name::new_static("kwargs"))
4876+
.with_annotated_type(Type::any()),
4877+
]),
4878+
Some(Type::TypedDict(TypedDictType::Synthesized(typed_dict))),
4879+
)],
4880+
)
4881+
.into()
4882+
}
48784883

48794884
Type::KnownInstance(known_instance) => {
48804885
known_instance.instance_fallback(db).bindings(db)

0 commit comments

Comments
 (0)