@@ -5069,31 +5069,56 @@ def fast_container_type(
50695069 module-level constant definitions.
50705070
50715071 Limitations:
5072+
50725073 - no active type context
5074+ - at least one item
50735075 - no star expressions
5074- - the joined type of all entries must be an Instance or Tuple type
5076+ - not after deferral
5077+ - either exactly one distinct type inside,
5078+ or the joined type of all entries is an Instance or Tuple type,
50755079 """
50765080 ctx = self .type_context [- 1 ]
5077- if ctx :
5081+ if ctx or not e .items :
5082+ return None
5083+ if self .chk .current_node_deferred :
5084+ # Guarantees that all items will be Any, we'll reject it anyway.
50785085 return None
50795086 rt = self .resolved_type .get (e , None )
50805087 if rt is not None :
50815088 return rt if isinstance (rt , Instance ) else None
50825089 values : list [Type ] = []
5090+ # Preserve join order while avoiding O(n) lookups at every iteration
5091+ values_set : set [Type ] = set ()
50835092 for item in e .items :
50845093 if isinstance (item , StarExpr ):
50855094 # fallback to slow path
50865095 self .resolved_type [e ] = NoneType ()
50875096 return None
5088- values .append (self .accept (item ))
5089- vt = join .join_type_list (values )
5090- if not allow_fast_container_literal (vt ):
5097+
5098+ typ = self .accept (item )
5099+ if typ not in values_set :
5100+ values .append (typ )
5101+ values_set .add (typ )
5102+
5103+ vt = self ._first_or_join_fast_item (values )
5104+ if vt is None :
50915105 self .resolved_type [e ] = NoneType ()
50925106 return None
50935107 ct = self .chk .named_generic_type (container_fullname , [vt ])
50945108 self .resolved_type [e ] = ct
50955109 return ct
50965110
5111+ def _first_or_join_fast_item (self , items : list [Type ]) -> Type | None :
5112+ if len (items ) == 1 and not self .chk .current_node_deferred :
5113+ return items [0 ]
5114+ typ = join .join_type_list (items )
5115+ if not allow_fast_container_literal (typ ):
5116+ # TODO: This is overly strict, many other types can be joined safely here.
5117+ # However, our join implementation isn't bug-free, and some joins may produce
5118+ # undesired `Any`s or even more surprising results.
5119+ return None
5120+ return typ
5121+
50975122 def check_lst_expr (self , e : ListExpr | SetExpr | TupleExpr , fullname : str , tag : str ) -> Type :
50985123 # fast path
50995124 t = self .fast_container_type (e , fullname )
@@ -5254,18 +5279,30 @@ def fast_dict_type(self, e: DictExpr) -> Type | None:
52545279 module-level constant definitions.
52555280
52565281 Limitations:
5282+
52575283 - no active type context
5284+ - at least one item
52585285 - only supported star expressions are other dict instances
5259- - the joined types of all keys and values must be Instance or Tuple types
5286+ - either exactly one distinct type (keys and values separately) inside,
5287+ or the joined type of all entries is an Instance or Tuple type
52605288 """
52615289 ctx = self .type_context [- 1 ]
5262- if ctx :
5290+ if ctx or not e . items :
52635291 return None
5292+
5293+ if self .chk .current_node_deferred :
5294+ # Guarantees that all items will be Any, we'll reject it anyway.
5295+ return None
5296+
52645297 rt = self .resolved_type .get (e , None )
52655298 if rt is not None :
52665299 return rt if isinstance (rt , Instance ) else None
5300+
52675301 keys : list [Type ] = []
52685302 values : list [Type ] = []
5303+ # Preserve join order while avoiding O(n) lookups at every iteration
5304+ keys_set : set [Type ] = set ()
5305+ values_set : set [Type ] = set ()
52695306 stargs : tuple [Type , Type ] | None = None
52705307 for key , value in e .items :
52715308 if key is None :
@@ -5280,13 +5317,25 @@ def fast_dict_type(self, e: DictExpr) -> Type | None:
52805317 self .resolved_type [e ] = NoneType ()
52815318 return None
52825319 else :
5283- keys .append (self .accept (key ))
5284- values .append (self .accept (value ))
5285- kt = join .join_type_list (keys )
5286- vt = join .join_type_list (values )
5287- if not (allow_fast_container_literal (kt ) and allow_fast_container_literal (vt )):
5320+ key_t = self .accept (key )
5321+ if key_t not in keys_set :
5322+ keys .append (key_t )
5323+ keys_set .add (key_t )
5324+ value_t = self .accept (value )
5325+ if value_t not in values_set :
5326+ values .append (value_t )
5327+ values_set .add (value_t )
5328+
5329+ kt = self ._first_or_join_fast_item (keys )
5330+ if kt is None :
52885331 self .resolved_type [e ] = NoneType ()
52895332 return None
5333+
5334+ vt = self ._first_or_join_fast_item (values )
5335+ if vt is None :
5336+ self .resolved_type [e ] = NoneType ()
5337+ return None
5338+
52905339 if stargs and (stargs [0 ] != kt or stargs [1 ] != vt ):
52915340 self .resolved_type [e ] = NoneType ()
52925341 return None
@@ -5994,7 +6043,7 @@ def accept(
59946043 # We cannot use cache inside lambdas, because they skip immediate type
59956044 # context, and use enclosing one, see infer_lambda_type_using_context().
59966045 # TODO: consider using cache for more expression kinds.
5997- elif isinstance (node , (CallExpr , ListExpr , TupleExpr )) and not (
6046+ elif isinstance (node , (CallExpr , ListExpr , TupleExpr , OpExpr )) and not (
59986047 self .in_lambda_expr or self .chk .current_node_deferred
59996048 ):
60006049 if (node , type_context ) in self .expr_cache :
0 commit comments