3030 StrExpr ,
3131 TempNode ,
3232 TupleExpr ,
33+ TypeAlias ,
3334 TypedDictExpr ,
3435 TypeInfo ,
3536)
5051 TypedDictType ,
5152 TypeOfAny ,
5253 TypeVarLikeType ,
54+ get_proper_type ,
5355)
5456
5557TPDICT_CLASS_ERROR : Final = (
@@ -137,23 +139,18 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N
137139 typeddict_bases_set .add ("TypedDict" )
138140 else :
139141 self .fail ('Duplicate base class "TypedDict"' , defn )
140- elif isinstance (expr , RefExpr ) and self .is_typeddict (expr ):
141- assert expr .fullname
142- if expr .fullname not in typeddict_bases_set :
143- typeddict_bases_set .add (expr .fullname )
142+ elif (
143+ isinstance (expr , RefExpr )
144+ and self .is_typeddict (expr )
145+ or isinstance (expr , IndexExpr )
146+ and self .is_typeddict (expr .base )
147+ ):
148+ info = self ._parse_typeddict_base (expr , defn )
149+ if info .fullname not in typeddict_bases_set :
150+ typeddict_bases_set .add (info .fullname )
144151 typeddict_bases .append (expr )
145152 else :
146- assert isinstance (expr .node , TypeInfo )
147- self .fail (f'Duplicate base class "{ expr .node .name } "' , defn )
148- elif isinstance (expr , IndexExpr ) and self .is_typeddict (expr .base ):
149- assert isinstance (expr .base , RefExpr )
150- assert expr .base .fullname
151- if expr .base .fullname not in typeddict_bases_set :
152- typeddict_bases_set .add (expr .base .fullname )
153- typeddict_bases .append (expr )
154- else :
155- assert isinstance (expr .base .node , TypeInfo )
156- self .fail (f'Duplicate base class "{ expr .base .node .name } "' , defn )
153+ self .fail (f'Duplicate base class "{ info .name } "' , defn )
157154 else :
158155 self .fail ("All bases of a new TypedDict must be TypedDict types" , defn )
159156
@@ -190,22 +187,13 @@ def add_keys_and_types_from_base(
190187 readonly_keys : set [str ],
191188 ctx : Context ,
192189 ) -> None :
190+ info = self ._parse_typeddict_base (base , ctx )
193191 base_args : list [Type ] = []
194- if isinstance (base , RefExpr ):
195- assert isinstance (base .node , TypeInfo )
196- info = base .node
197- elif isinstance (base , IndexExpr ):
198- assert isinstance (base .base , RefExpr )
199- assert isinstance (base .base .node , TypeInfo )
200- info = base .base .node
192+ if isinstance (base , IndexExpr ):
201193 args = self .analyze_base_args (base , ctx )
202194 if args is None :
203195 return
204196 base_args = args
205- else :
206- assert isinstance (base , CallExpr )
207- assert isinstance (base .analyzed , TypedDictExpr )
208- info = base .analyzed .info
209197
210198 assert info .typeddict_type is not None
211199 base_typed_dict = info .typeddict_type
@@ -231,6 +219,26 @@ def add_keys_and_types_from_base(
231219 required_keys .update (base_typed_dict .required_keys )
232220 readonly_keys .update (base_typed_dict .readonly_keys )
233221
222+ def _parse_typeddict_base (self , base : Expression , ctx : Context ) -> TypeInfo :
223+ if isinstance (base , RefExpr ):
224+ if isinstance (base .node , TypeInfo ):
225+ return base .node
226+ elif isinstance (base .node , TypeAlias ):
227+ # Only old TypeAlias / plain assignment, PEP695 `type` stmt
228+ # cannot be used as a base class
229+ target = get_proper_type (base .node .target )
230+ assert isinstance (target , TypedDictType )
231+ return target .fallback .type
232+ else :
233+ assert False
234+ elif isinstance (base , IndexExpr ):
235+ assert isinstance (base .base , RefExpr )
236+ return self ._parse_typeddict_base (base .base , ctx )
237+ else :
238+ assert isinstance (base , CallExpr )
239+ assert isinstance (base .analyzed , TypedDictExpr )
240+ return base .analyzed .info
241+
234242 def analyze_base_args (self , base : IndexExpr , ctx : Context ) -> list [Type ] | None :
235243 """Analyze arguments of base type expressions as types.
236244
@@ -527,7 +535,7 @@ def parse_typeddict_args(
527535 return "" , [], [], True , [], False
528536 dictexpr = args [1 ]
529537 tvar_defs = self .api .get_and_bind_all_tvars ([t for k , t in dictexpr .items ])
530- res = self .parse_typeddict_fields_with_types (dictexpr .items , call )
538+ res = self .parse_typeddict_fields_with_types (dictexpr .items )
531539 if res is None :
532540 # One of the types is not ready, defer.
533541 return None
@@ -536,7 +544,7 @@ def parse_typeddict_args(
536544 return args [0 ].value , items , types , total , tvar_defs , ok
537545
538546 def parse_typeddict_fields_with_types (
539- self , dict_items : list [tuple [Expression | None , Expression ]], context : Context
547+ self , dict_items : list [tuple [Expression | None , Expression ]]
540548 ) -> tuple [list [str ], list [Type ], bool ] | None :
541549 """Parse typed dict items passed as pairs (name expression, type expression).
542550
@@ -609,10 +617,11 @@ def build_typeddict_typeinfo(
609617 # Helpers
610618
611619 def is_typeddict (self , expr : Expression ) -> bool :
612- return (
613- isinstance (expr , RefExpr )
614- and isinstance (expr .node , TypeInfo )
620+ return isinstance (expr , RefExpr ) and (
621+ isinstance (expr .node , TypeInfo )
615622 and expr .node .typeddict_type is not None
623+ or isinstance (expr .node , TypeAlias )
624+ and isinstance (get_proper_type (expr .node .target ), TypedDictType )
616625 )
617626
618627 def fail (self , msg : str , ctx : Context , * , code : ErrorCode | None = None ) -> None :
0 commit comments