1414from mypy .types import (
1515 AnyType ,
1616 Instance ,
17+ NoneType ,
1718 PartialType ,
1819 ProperType ,
1920 TupleType ,
@@ -341,12 +342,34 @@ def assign_type(self, expr: Expression, type: Type, declared_type: Type | None)
341342 # times?
342343 return
343344
344- enclosing_type = get_proper_type (self .most_recent_enclosing_type (expr , type ))
345- if isinstance (enclosing_type , AnyType ):
346- # If x is Any and y is int, after x = y we do not infer that x is int,
347- # instead we keep it Any. This behavior is unsafe, but it exists since
348- # long time, so we will keep it until someone complains.
349- self .put (expr , enclosing_type )
345+ p_declared = get_proper_type (declared_type )
346+ p_type = get_proper_type (type )
347+ if isinstance (p_type , AnyType ):
348+ # Any type requires some special casing, for both historical reasons,
349+ # and to optimise user experience without sacrificing correctness too much.
350+ if isinstance (expr , RefExpr ) and isinstance (expr .node , Var ) and expr .node .is_inferred :
351+ # First case: a local/global variable without explicit annotation,
352+ # in this case we just assign Any (essentially following the SSA logic).
353+ self .put (expr , type )
354+ elif isinstance (p_declared , UnionType ) and any (
355+ isinstance (get_proper_type (item ), NoneType ) for item in p_declared .items
356+ ):
357+ # Second case: explicit optional type, in this case we optimize for a common
358+ # pattern when an untyped value used as a fallback replacing None.
359+ new_items = [
360+ type if isinstance (get_proper_type (item ), NoneType ) else item
361+ for item in p_declared .items
362+ ]
363+ self .put (expr , UnionType (new_items ))
364+ elif isinstance (p_declared , UnionType ) and any (
365+ isinstance (get_proper_type (item ), AnyType ) for item in p_declared .items
366+ ):
367+ # Third case: a union already containing Any (most likely from an un-imported
368+ # name), in this case we allow assigning Any as well.
369+ self .put (expr , type )
370+ else :
371+ # In all other cases we don't narrow to Any to minimize false negatives.
372+ self .put (expr , declared_type )
350373 else :
351374 self .put (expr , type )
352375
@@ -368,19 +391,6 @@ def invalidate_dependencies(self, expr: BindableExpression) -> None:
368391 for dep in self .dependencies .get (key , set ()):
369392 self ._cleanse_key (dep )
370393
371- def most_recent_enclosing_type (self , expr : BindableExpression , type : Type ) -> Type | None :
372- type = get_proper_type (type )
373- if isinstance (type , AnyType ):
374- return get_declaration (expr )
375- key = literal_hash (expr )
376- assert key is not None
377- enclosers = [get_declaration (expr )] + [
378- f .types [key ].type
379- for f in self .frames
380- if key in f .types and is_subtype (type , f .types [key ][0 ])
381- ]
382- return enclosers [- 1 ]
383-
384394 def allow_jump (self , index : int ) -> None :
385395 # self.frames and self.options_on_return have different lengths
386396 # so make sure the index is positive
0 commit comments