diff --git a/crates/ty_python_semantic/resources/mdtest/bidirectional.md b/crates/ty_python_semantic/resources/mdtest/bidirectional.md index ee233ebeb3..17d31794ab 100644 --- a/crates/ty_python_semantic/resources/mdtest/bidirectional.md +++ b/crates/ty_python_semantic/resources/mdtest/bidirectional.md @@ -55,10 +55,13 @@ class TD(TypedDict): d1 = {"x": 1} d2: TD = {"x": 1} d3: dict[str, int] = {"x": 1} +d4: TD = dict(x=1) +d5: TD = dict(x="1") # error: [invalid-argument-type] reveal_type(d1) # revealed: dict[Unknown | str, Unknown | int] reveal_type(d2) # revealed: TD reveal_type(d3) # revealed: dict[str, int] +reveal_type(d4) # revealed: TD def _() -> TD: return {"x": 1} diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index fda0a75756..52b4e3847e 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -8448,6 +8448,43 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { arguments, } = call_expression; + // Fast-path dict(...) in TypedDict context: infer keyword values against fields, + // then validate and return the TypedDict type. + if let Some(tcx) = tcx.annotation + && let Some(typed_dict) = tcx + .filter_union(self.db(), Type::is_typed_dict) + .as_typed_dict() + && callable_type + .as_class_literal() + .is_some_and(|class_literal| class_literal.is_known(self.db(), KnownClass::Dict)) + && arguments.args.is_empty() + && arguments + .keywords + .iter() + .all(|keyword| keyword.arg.is_some()) + { + let items = typed_dict.items(self.db()); + for keyword in &arguments.keywords { + if let Some(arg_name) = &keyword.arg { + let value_tcx = items + .get(arg_name.id.as_str()) + .map(|field| TypeContext::new(Some(field.declared_ty))) + .unwrap_or_default(); + self.infer_expression(&keyword.value, value_tcx); + } + } + + validate_typed_dict_constructor( + &self.context, + typed_dict, + arguments, + func.as_ref().into(), + |expr| self.expression_type(expr), + ); + + return Type::TypedDict(typed_dict); + } + // We don't call `Type::try_call`, because we want to perform type inference on the // arguments after matching them to parameters, but before checking that the argument types // are assignable to any parameter annotations.