diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index 1d96506729..085759654c 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -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 correctly: +`class.py`: + ```py from typing import TypedDict @@ -335,6 +337,26 @@ user3 = User({"name": None, "age": 25}) 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` By default, all fields in a `TypedDict` are required (`total=True`). You can make all fields diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 7bae91b8c8..dc71f0313f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -4865,16 +4865,21 @@ impl<'db> Type<'db> { Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db), - Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single( - self, - Signature::new( - // TODO: List more specific parameter types here for better code completion. - Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs")) - .with_annotated_type(Type::any())]), - Some(Type::TypedDict(TypedDictType::Synthesized(typed_dict))), - ), - ) - .into(), + Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => { + CallableBinding::from_overloads( + self, + [Signature::new( + // TODO: List more specific parameter types here for better code completion. + Parameters::new([ + Parameter::variadic(Name::new_static("args")), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(Type::any()), + ]), + Some(Type::TypedDict(TypedDictType::Synthesized(typed_dict))), + )], + ) + .into() + } Type::KnownInstance(known_instance) => { known_instance.instance_fallback(db).bindings(db)