@@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, HasNodeIndex};
33use rustc_hash:: FxHashMap ;
44
55use super :: { ArgExpr , TypeInferenceBuilder } ;
6- use crate :: types:: typed_dict:: validate_typed_dict_constructor;
6+ use crate :: types:: typed_dict:: { supports_typed_dict_unpack , validate_typed_dict_constructor} ;
77use crate :: types:: { KnownClass , Type , TypeContext } ;
88
99impl < ' db > TypeInferenceBuilder < ' db , ' _ > {
@@ -13,44 +13,62 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
1313 arguments : & ast:: Arguments ,
1414 call_expression_tcx : TypeContext < ' db > ,
1515 ) -> Option < Type < ' db > > {
16- if !arguments. args . is_empty ( )
17- || arguments
18- . keywords
19- . iter ( )
20- . any ( |keyword| keyword. arg . is_none ( ) )
21- {
16+ if !arguments. args . is_empty ( ) {
2217 return None ;
2318 }
2419
2520 // Fast-path dict(...) in TypedDict context: infer keyword values against fields,
26- // then validate and return the TypedDict type.
21+ // then validate and return the TypedDict type. This also covers `dict(**src)` when `src`
22+ // is `TypedDict`-shaped.
2723 if let Some ( tcx) = call_expression_tcx. annotation
2824 && let Some ( typed_dict) = tcx
2925 . filter_union ( self . db ( ) , Type :: is_typed_dict)
3026 . as_typed_dict ( )
3127 {
32- let items = typed_dict. items ( self . db ( ) ) ;
28+ let mut speculative_builder = self . speculate ( ) ;
29+ let items = typed_dict. items ( speculative_builder. db ( ) ) ;
30+ let mut unpacked_keyword_types = Vec :: with_capacity ( arguments. keywords . len ( ) ) ;
3331 for keyword in & arguments. keywords {
34- if let Some ( arg_name) = & keyword. arg {
35- let value_tcx = items
36- . get ( arg_name. id . as_str ( ) )
37- . map ( |field| TypeContext :: new ( Some ( field. declared_ty ) ) )
38- . unwrap_or_default ( ) ;
39- self . infer_expression ( & keyword. value , value_tcx) ;
40- }
32+ let value_tcx = keyword
33+ . arg
34+ . as_ref ( )
35+ . and_then ( |arg_name| items. get ( arg_name. id . as_str ( ) ) )
36+ . map ( |field| TypeContext :: new ( Some ( field. declared_ty ) ) )
37+ . unwrap_or_default ( ) ;
38+ let value_ty = speculative_builder. infer_expression ( & keyword. value , value_tcx) ;
39+ unpacked_keyword_types. push ( keyword. arg . is_none ( ) . then_some ( value_ty) ) ;
40+ }
41+
42+ let supports_typed_dict_context = unpacked_keyword_types
43+ . iter ( )
44+ . copied ( )
45+ . flatten ( )
46+ . all ( |keyword_ty| supports_typed_dict_unpack ( speculative_builder. db ( ) , keyword_ty) ) ;
47+
48+ if !supports_typed_dict_context {
49+ return None ;
4150 }
4251
4352 validate_typed_dict_constructor (
44- & self . context ,
53+ & speculative_builder . context ,
4554 typed_dict,
4655 arguments,
4756 func. into ( ) ,
48- |expr, _| self . expression_type ( expr) ,
57+ |expr, _| speculative_builder . expression_type ( expr) ,
4958 ) ;
59+ self . extend ( speculative_builder) ;
5060
5161 return Some ( Type :: TypedDict ( typed_dict) ) ;
5262 }
5363
64+ if arguments
65+ . keywords
66+ . iter ( )
67+ . any ( |keyword| keyword. arg . is_none ( ) )
68+ {
69+ return None ;
70+ }
71+
5472 // Lower `dict(a=..., b=...)` to synthetic `(Literal["a"], value)` pairs so we can
5573 // reuse dict-literal inference. We key the synthetic name off the value node because
5674 // `infer_collection_literal` operates on expressions rather than keywords.
0 commit comments