22
33from collections import defaultdict
44from contextlib import contextmanager
5- from typing import DefaultDict , Iterator , List , Optional , Tuple , Union , cast
5+ from typing import DefaultDict , Iterator , List , NamedTuple , Optional , Tuple , Union
66from typing_extensions import TypeAlias as _TypeAlias
77
88from mypy .erasetype import remove_instance_last_known_values
3030BindableExpression : _TypeAlias = Union [IndexExpr , MemberExpr , NameExpr ]
3131
3232
33+ class CurrentType (NamedTuple ):
34+ type : Type
35+ from_assignment : bool
36+
37+
3338class Frame :
3439 """A Frame represents a specific point in the execution of a program.
3540 It carries information about the current types of expressions at
@@ -44,7 +49,7 @@ class Frame:
4449
4550 def __init__ (self , id : int , conditional_frame : bool = False ) -> None :
4651 self .id = id
47- self .types : dict [Key , Type ] = {}
52+ self .types : dict [Key , CurrentType ] = {}
4853 self .unreachable = False
4954 self .conditional_frame = conditional_frame
5055 self .suppress_unreachable_warnings = False
@@ -132,18 +137,18 @@ def push_frame(self, conditional_frame: bool = False) -> Frame:
132137 self .options_on_return .append ([])
133138 return f
134139
135- def _put (self , key : Key , type : Type , index : int = - 1 ) -> None :
136- self .frames [index ].types [key ] = type
140+ def _put (self , key : Key , type : Type , from_assignment : bool , index : int = - 1 ) -> None :
141+ self .frames [index ].types [key ] = CurrentType ( type , from_assignment )
137142
138- def _get (self , key : Key , index : int = - 1 ) -> Type | None :
143+ def _get (self , key : Key , index : int = - 1 ) -> CurrentType | None :
139144 if index < 0 :
140145 index += len (self .frames )
141146 for i in range (index , - 1 , - 1 ):
142147 if key in self .frames [i ].types :
143148 return self .frames [i ].types [key ]
144149 return None
145150
146- def put (self , expr : Expression , typ : Type ) -> None :
151+ def put (self , expr : Expression , typ : Type , * , from_assignment : bool = True ) -> None :
147152 if not isinstance (expr , (IndexExpr , MemberExpr , NameExpr )):
148153 return
149154 if not literal (expr ):
@@ -153,7 +158,7 @@ def put(self, expr: Expression, typ: Type) -> None:
153158 if key not in self .declarations :
154159 self .declarations [key ] = get_declaration (expr )
155160 self ._add_dependencies (key )
156- self ._put (key , typ )
161+ self ._put (key , typ , from_assignment )
157162
158163 def unreachable (self ) -> None :
159164 self .frames [- 1 ].unreachable = True
@@ -164,7 +169,10 @@ def suppress_unreachable_warnings(self) -> None:
164169 def get (self , expr : Expression ) -> Type | None :
165170 key = literal_hash (expr )
166171 assert key is not None , "Internal error: binder tried to get non-literal"
167- return self ._get (key )
172+ found = self ._get (key )
173+ if found is None :
174+ return None
175+ return found .type
168176
169177 def is_unreachable (self ) -> bool :
170178 # TODO: Copy the value of unreachable into new frames to avoid
@@ -193,7 +201,7 @@ def update_from_options(self, frames: list[Frame]) -> bool:
193201 If a key is declared as AnyType, only update it if all the
194202 options are the same.
195203 """
196-
204+ all_reachable = all ( not f . unreachable for f in frames )
197205 frames = [f for f in frames if not f .unreachable ]
198206 changed = False
199207 keys = {key for f in frames for key in f .types }
@@ -207,17 +215,30 @@ def update_from_options(self, frames: list[Frame]) -> bool:
207215 # know anything about key in at least one possible frame.
208216 continue
209217
210- type = resulting_values [0 ]
211- assert type is not None
218+ if all_reachable and all (
219+ x is not None and not x .from_assignment for x in resulting_values
220+ ):
221+ # Do not synthesize a new type if we encountered a conditional block
222+ # (if, while or match-case) without assignments.
223+ # See check-isinstance.test::testNoneCheckDoesNotMakeTypeVarOptional
224+ # This is a safe assumption: the fact that we checked something with `is`
225+ # or `isinstance` does not change the type of the value.
226+ continue
227+
228+ current_type = resulting_values [0 ]
229+ assert current_type is not None
230+ type = current_type .type
212231 declaration_type = get_proper_type (self .declarations .get (key ))
213232 if isinstance (declaration_type , AnyType ):
214233 # At this point resulting values can't contain None, see continue above
215- if not all (is_same_type (type , cast (Type , t )) for t in resulting_values [1 :]):
234+ if not all (
235+ t is not None and is_same_type (type , t .type ) for t in resulting_values [1 :]
236+ ):
216237 type = AnyType (TypeOfAny .from_another_any , source_any = declaration_type )
217238 else :
218239 for other in resulting_values [1 :]:
219240 assert other is not None
220- type = join_simple (self .declarations [key ], type , other )
241+ type = join_simple (self .declarations [key ], type , other . type )
221242 # Try simplifying resulting type for unions involving variadic tuples.
222243 # Technically, everything is still valid without this step, but if we do
223244 # not do this, this may create long unions after exiting an if check like:
@@ -236,8 +257,8 @@ def update_from_options(self, frames: list[Frame]) -> bool:
236257 )
237258 if simplified == self .declarations [key ]:
238259 type = simplified
239- if current_value is None or not is_same_type (type , current_value ):
240- self ._put (key , type )
260+ if current_value is None or not is_same_type (type , current_value [ 0 ] ):
261+ self ._put (key , type , from_assignment = True )
241262 changed = True
242263
243264 self .frames [- 1 ].unreachable = not frames
@@ -374,7 +395,9 @@ def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Ty
374395 key = literal_hash (expr )
375396 assert key is not None
376397 enclosers = [get_declaration (expr )] + [
377- f .types [key ] for f in self .frames if key in f .types and is_subtype (type , f .types [key ])
398+ f .types [key ].type
399+ for f in self .frames
400+ if key in f .types and is_subtype (type , f .types [key ][0 ])
378401 ]
379402 return enclosers [- 1 ]
380403
0 commit comments