33from collections import defaultdict
44from collections .abc import Iterator
55from contextlib import contextmanager
6- from typing import NamedTuple , TypeAlias as _TypeAlias
6+ from typing import Literal , NamedTuple , TypeAlias as _TypeAlias
77
88from mypy .erasetype import remove_instance_last_known_values
99from mypy .literals import Key , extract_var_from_literal_hash , literal , literal_hash , subkeys
@@ -83,6 +83,61 @@ def __repr__(self) -> str:
8383Assigns = defaultdict [Expression , list [tuple [Type , Type | None ]]]
8484
8585
86+ class FrameContext :
87+ """Context manager pushing a Frame to ConditionalTypeBinder.
88+
89+ See frame_context() below for documentation on parameters. We use this class
90+ instead of @contextmanager as a mypyc-specific performance optimization.
91+ """
92+
93+ def __init__ (
94+ self ,
95+ binder : ConditionalTypeBinder ,
96+ can_skip : bool ,
97+ fall_through : int ,
98+ break_frame : int ,
99+ continue_frame : int ,
100+ conditional_frame : bool ,
101+ try_frame : bool ,
102+ discard : bool ,
103+ ) -> None :
104+ self .binder = binder
105+ self .can_skip = can_skip
106+ self .fall_through = fall_through
107+ self .break_frame = break_frame
108+ self .continue_frame = continue_frame
109+ self .conditional_frame = conditional_frame
110+ self .try_frame = try_frame
111+ self .discard = discard
112+
113+ def __enter__ (self ) -> Frame :
114+ assert len (self .binder .frames ) > 1
115+
116+ if self .break_frame :
117+ self .binder .break_frames .append (len (self .binder .frames ) - self .break_frame )
118+ if self .continue_frame :
119+ self .binder .continue_frames .append (len (self .binder .frames ) - self .continue_frame )
120+ if self .try_frame :
121+ self .binder .try_frames .add (len (self .binder .frames ) - 1 )
122+
123+ new_frame = self .binder .push_frame (self .conditional_frame )
124+ if self .try_frame :
125+ # An exception may occur immediately
126+ self .binder .allow_jump (- 1 )
127+ return new_frame
128+
129+ def __exit__ (self , exc_type : object , exc_val : object , exc_tb : object ) -> Literal [False ]:
130+ self .binder .pop_frame (self .can_skip , self .fall_through , discard = self .discard )
131+
132+ if self .break_frame :
133+ self .binder .break_frames .pop ()
134+ if self .continue_frame :
135+ self .binder .continue_frames .pop ()
136+ if self .try_frame :
137+ self .binder .try_frames .remove (len (self .binder .frames ) - 1 )
138+ return False
139+
140+
86141class ConditionalTypeBinder :
87142 """Keep track of conditional types of variables.
88143
@@ -338,10 +393,10 @@ def update_from_options(self, frames: list[Frame]) -> bool:
338393
339394 return changed
340395
341- def pop_frame (self , can_skip : bool , fall_through : int ) -> Frame :
396+ def pop_frame (self , can_skip : bool , fall_through : int , * , discard : bool = False ) -> Frame :
342397 """Pop a frame and return it.
343398
344- See frame_context() for documentation of fall_through.
399+ See frame_context() for documentation of fall_through and discard .
345400 """
346401
347402 if fall_through > 0 :
@@ -350,6 +405,10 @@ def pop_frame(self, can_skip: bool, fall_through: int) -> Frame:
350405 result = self .frames .pop ()
351406 options = self .options_on_return .pop ()
352407
408+ if discard :
409+ self .last_pop_changed = False
410+ return result
411+
353412 if can_skip :
354413 options .insert (0 , self .frames [- 1 ])
355414
@@ -484,7 +543,6 @@ def handle_continue(self) -> None:
484543 self .allow_jump (self .continue_frames [- 1 ])
485544 self .unreachable ()
486545
487- @contextmanager
488546 def frame_context (
489547 self ,
490548 * ,
@@ -494,53 +552,45 @@ def frame_context(
494552 continue_frame : int = 0 ,
495553 conditional_frame : bool = False ,
496554 try_frame : bool = False ,
497- ) -> Iterator [Frame ]:
555+ discard : bool = False ,
556+ ) -> FrameContext :
498557 """Return a context manager that pushes/pops frames on enter/exit.
499558
500559 If can_skip is True, control flow is allowed to bypass the
501560 newly-created frame.
502561
503562 If fall_through > 0, then it will allow control flow that
504563 falls off the end of the frame to escape to its ancestor
505- `fall_through` levels higher. Otherwise control flow ends
564+ `fall_through` levels higher. Otherwise, control flow ends
506565 at the end of the frame.
507566
508567 If break_frame > 0, then 'break' statements within this frame
509568 will jump out to the frame break_frame levels higher than the
510- frame created by this call to frame_context. Similarly for
569+ frame created by this call to frame_context. Similarly, for
511570 continue_frame and 'continue' statements.
512571
513572 If try_frame is true, then execution is allowed to jump at any
514573 point within the newly created frame (or its descendants) to
515574 its parent (i.e., to the frame that was on top before this
516575 call to frame_context).
517576
577+ If discard is True, then this is a temporary throw-away frame
578+ (used e.g. for isolation) and its effect will be discarded on pop.
579+
518580 After the context manager exits, self.last_pop_changed indicates
519581 whether any types changed in the newly-topmost frame as a result
520582 of popping this frame.
521583 """
522- assert len (self .frames ) > 1
523-
524- if break_frame :
525- self .break_frames .append (len (self .frames ) - break_frame )
526- if continue_frame :
527- self .continue_frames .append (len (self .frames ) - continue_frame )
528- if try_frame :
529- self .try_frames .add (len (self .frames ) - 1 )
530-
531- new_frame = self .push_frame (conditional_frame )
532- if try_frame :
533- # An exception may occur immediately
534- self .allow_jump (- 1 )
535- yield new_frame
536- self .pop_frame (can_skip , fall_through )
537-
538- if break_frame :
539- self .break_frames .pop ()
540- if continue_frame :
541- self .continue_frames .pop ()
542- if try_frame :
543- self .try_frames .remove (len (self .frames ) - 1 )
584+ return FrameContext (
585+ self ,
586+ can_skip = can_skip ,
587+ fall_through = fall_through ,
588+ break_frame = break_frame ,
589+ continue_frame = continue_frame ,
590+ conditional_frame = conditional_frame ,
591+ try_frame = try_frame ,
592+ discard = discard ,
593+ )
544594
545595 @contextmanager
546596 def top_frame_context (self ) -> Iterator [Frame ]:
0 commit comments