@@ -23,7 +23,7 @@ use super::special_form::SpecialFormType;
2323use super :: tuple:: TupleSpec ;
2424use super :: {
2525 DynamicType , IntersectionBuilder , IntersectionType , KnownInstanceType , Type , TypeAliasType ,
26- UnionBuilder , UnionType , todo_type,
26+ TypedDictType , UnionBuilder , UnionType , todo_type,
2727} ;
2828
2929/// The kind of subscriptable type that had an out-of-bounds index.
@@ -120,6 +120,11 @@ pub(crate) enum SubscriptErrorKind<'db> {
120120 kind : CallErrorKind ,
121121 bindings : Box < Bindings < ' db > > ,
122122 } ,
123+ /// A `TypedDict` was subscripted with an invalid key.
124+ InvalidTypedDictKey {
125+ typed_dict : TypedDictType < ' db > ,
126+ slice_ty : Type < ' db > ,
127+ } ,
123128 /// The type does not support subscripting via the expected dunder.
124129 NotSubscriptable {
125130 value_ty : Type < ' db > ,
@@ -280,6 +285,21 @@ impl<'db> SubscriptErrorKind<'db> {
280285 }
281286 }
282287 } ,
288+ Self :: InvalidTypedDictKey {
289+ typed_dict,
290+ slice_ty,
291+ } => {
292+ let typed_dict_ty = Type :: TypedDict ( * typed_dict) ;
293+ report_invalid_key_on_typed_dict (
294+ context,
295+ value_node. into ( ) ,
296+ slice_node. into ( ) ,
297+ typed_dict_ty,
298+ None ,
299+ * slice_ty,
300+ typed_dict. items ( db) ,
301+ ) ;
302+ }
283303 Self :: NotSubscriptable { value_ty, method } => {
284304 report_not_subscriptable ( context, subscript, * value_ty, method. as_str ( ) ) ;
285305 }
@@ -411,6 +431,45 @@ where
411431 ) )
412432}
413433
434+ // `TypedDict` subscripts need custom handling because invalid keys should still
435+ // recover with `Unknown` while emitting `invalid-key`, which is not naturally
436+ // representable via synthesized `__getitem__` overloads alone.
437+ fn typed_dict_subscript < ' db > (
438+ db : & ' db dyn Db ,
439+ typed_dict : TypedDictType < ' db > ,
440+ slice_ty : Type < ' db > ,
441+ ) -> Result < Type < ' db > , SubscriptError < ' db > > {
442+ if slice_ty. is_dynamic ( ) {
443+ return Ok ( Type :: unknown ( ) ) ;
444+ }
445+
446+ let Some ( key) = slice_ty
447+ . as_string_literal ( )
448+ . map ( |literal| literal. value ( db) )
449+ else {
450+ return Err ( SubscriptError :: new (
451+ Type :: unknown ( ) ,
452+ SubscriptErrorKind :: InvalidTypedDictKey {
453+ typed_dict,
454+ slice_ty,
455+ } ,
456+ ) ) ;
457+ } ;
458+
459+ typed_dict. items ( db) . get ( key) . map_or_else (
460+ || {
461+ Err ( SubscriptError :: new (
462+ Type :: unknown ( ) ,
463+ SubscriptErrorKind :: InvalidTypedDictKey {
464+ typed_dict,
465+ slice_ty,
466+ } ,
467+ ) )
468+ } ,
469+ |field| Ok ( field. declared_ty ) ,
470+ )
471+ }
472+
414473impl < ' db > Type < ' db > {
415474 pub ( super ) fn subscript (
416475 self ,
@@ -451,6 +510,11 @@ impl<'db> Type<'db> {
451510 } ) )
452511 }
453512
513+ // Ex) Given `person["name"]`, return `str`
514+ ( Type :: TypedDict ( typed_dict) , _) if expr_context != ast:: ExprContext :: Store => {
515+ Some ( typed_dict_subscript ( db, typed_dict, slice_ty) )
516+ }
517+
454518 // Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
455519 ( Type :: NominalInstance ( nominal) , Type :: LiteralValue ( literal) ) if literal. is_int ( ) => {
456520 let i64_int = literal. as_int ( ) . unwrap ( ) ;
0 commit comments