@@ -13,7 +13,7 @@ use crate::{
1313 IndexMap , IndexSet , ToPythonName ,
1414 error:: { CodegenError , CodegenErrorType , InternalError , PatternUnreachableReason } ,
1515 ir:: { self , BlockIdx } ,
16- symboltable:: { self , CompilerScope , SymbolFlags , SymbolScope , SymbolTable } ,
16+ symboltable:: { self , CompilerScope , Symbol , SymbolFlags , SymbolScope , SymbolTable } ,
1717 unparse:: UnparseExpr ,
1818} ;
1919use alloc:: borrow:: Cow ;
@@ -1012,12 +1012,14 @@ impl Compiler {
10121012 // Use varnames from symbol table (already collected in definition order)
10131013 let varname_cache: IndexSet < String > = ste. varnames . iter ( ) . cloned ( ) . collect ( ) ;
10141014
1015- // Build cellvars using dictbytype (CELL scope, sorted)
1015+ // Build cellvars using dictbytype (CELL scope or COMP_CELL flag , sorted)
10161016 let mut cellvar_cache = IndexSet :: default ( ) ;
10171017 let mut cell_names: Vec < _ > = ste
10181018 . symbols
10191019 . iter ( )
1020- . filter ( |( _, s) | s. scope == SymbolScope :: Cell )
1020+ . filter ( |( _, s) | {
1021+ s. scope == SymbolScope :: Cell || s. flags . contains ( SymbolFlags :: COMP_CELL )
1022+ } )
10211023 . map ( |( name, _) | name. clone ( ) )
10221024 . collect ( ) ;
10231025 cell_names. sort ( ) ;
@@ -7656,13 +7658,16 @@ impl Compiler {
76567658 // We must have at least one generator:
76577659 assert ! ( !generators. is_empty( ) ) ;
76587660
7659- if is_inlined && !has_an_async_gen && !element_contains_await {
7661+ if is_inlined {
76607662 // PEP 709: Inlined comprehension - compile inline without new scope
7661- // Async comprehensions are not inlined due to complex exception handling
76627663 let was_in_inlined_comp = self . current_code_info ( ) . in_inlined_comp ;
76637664 self . current_code_info ( ) . in_inlined_comp = true ;
7664- let result =
7665- self . compile_inlined_comprehension ( init_collection, generators, compile_element) ;
7665+ let result = self . compile_inlined_comprehension (
7666+ init_collection,
7667+ generators,
7668+ compile_element,
7669+ has_an_async_gen,
7670+ ) ;
76667671 self . current_code_info ( ) . in_inlined_comp = was_in_inlined_comp;
76677672 return result;
76687673 }
@@ -7816,90 +7821,136 @@ impl Compiler {
78167821 Ok ( ( ) )
78177822 }
78187823
7819- /// Collect variable names from an assignment target expression
7820- fn collect_target_names ( & self , target : & ast:: Expr , names : & mut Vec < String > ) {
7821- match target {
7822- ast:: Expr :: Name ( name) => {
7823- let name_str = name. id . to_string ( ) ;
7824- if !names. contains ( & name_str) {
7825- names. push ( name_str) ;
7826- }
7827- }
7828- ast:: Expr :: Tuple ( tuple) => {
7829- for elt in & tuple. elts {
7830- self . collect_target_names ( elt, names) ;
7831- }
7832- }
7833- ast:: Expr :: List ( list) => {
7834- for elt in & list. elts {
7835- self . collect_target_names ( elt, names) ;
7836- }
7837- }
7838- ast:: Expr :: Starred ( starred) => {
7839- self . collect_target_names ( & starred. value , names) ;
7840- }
7841- _ => {
7842- // Other targets (attribute, subscript) don't bind local names
7843- }
7844- }
7845- }
7846-
78477824 /// Compile an inlined comprehension (PEP 709)
78487825 /// This generates bytecode inline without creating a new code object
78497826 fn compile_inlined_comprehension (
78507827 & mut self ,
78517828 init_collection : Option < AnyInstruction > ,
78527829 generators : & [ ast:: Comprehension ] ,
78537830 compile_element : & dyn Fn ( & mut Self ) -> CompileResult < ( ) > ,
7831+ has_async : bool ,
78547832 ) -> CompileResult < ( ) > {
7855- // PEP 709: Consume the comprehension's sub_table (but we won't use it as a separate scope) .
7833+ // PEP 709: Consume the comprehension's sub_table.
78567834 // The symbols are already merged into parent scope by analyze_symbol_table.
7857- // Splice the comprehension's sub_tables into the parent so nested scopes
7858- // (e.g. inner comprehensions, lambdas) can still find their sub_tables .
7835+ // Splice the comprehension's children into the parent so nested scopes
7836+ // (e.g. inner comprehensions, lambdas) can be found by the compiler .
78597837 let current_table = self
78607838 . symbol_table_stack
78617839 . last_mut ( )
78627840 . expect ( "no current symbol table" ) ;
78637841 let comp_table = current_table. sub_tables [ current_table. next_sub_table ] . clone ( ) ;
78647842 current_table. next_sub_table += 1 ;
7865- // Insert the comprehension's sub_tables right after the consumed entry
78667843 if !comp_table. sub_tables . is_empty ( ) {
78677844 let insert_pos = current_table. next_sub_table ;
78687845 for ( i, st) in comp_table. sub_tables . iter ( ) . enumerate ( ) {
78697846 current_table. sub_tables . insert ( insert_pos + i, st. clone ( ) ) ;
78707847 }
78717848 }
78727849
7873- // Collect local variables that need to be saved/restored
7874- // These are variables bound in the comprehension (iteration vars from targets)
7875- let mut pushed_locals: Vec < String > = Vec :: new ( ) ;
7876- for generator in generators {
7877- self . collect_target_names ( & generator. target , & mut pushed_locals) ;
7850+ // Step 1: Compile the outermost iterator BEFORE tweaking scopes
7851+ self . compile_expression ( & generators[ 0 ] . iter ) ?;
7852+ if has_async && generators[ 0 ] . is_async {
7853+ emit ! ( self , Instruction :: GetAIter ) ;
7854+ } else {
7855+ emit ! ( self , Instruction :: GetIter ) ;
78787856 }
78797857
7880- // Step 1: Compile the outermost iterator
7881- // Async comprehensions are never inlined, so only sync iteration here
7882- self . compile_expression ( & generators[ 0 ] . iter ) ?;
7883- emit ! ( self , Instruction :: GetIter ) ;
7858+ // Collect local variables that need to be saved/restored.
7859+ // All DEF_LOCAL && !DEF_NONLOCAL names from the comp table, plus class block names.
7860+ let in_class_block = {
7861+ let ct = self . current_symbol_table ( ) ;
7862+ ct. typ == CompilerScope :: Class && !self . current_code_info ( ) . in_inlined_comp
7863+ } ;
7864+ let mut pushed_locals: Vec < String > = Vec :: new ( ) ;
7865+ for ( name, sym) in & comp_table. symbols {
7866+ if sym. flags . contains ( SymbolFlags :: PARAMETER ) {
7867+ continue ; // skip .0
7868+ }
7869+ // Walrus operator targets (ASSIGNED_IN_COMPREHENSION without ITER)
7870+ // are not local to the comprehension; they leak to the outer scope.
7871+ let is_walrus = sym. flags . contains ( SymbolFlags :: ASSIGNED_IN_COMPREHENSION )
7872+ && !sym. flags . contains ( SymbolFlags :: ITER ) ;
7873+ let is_local = sym
7874+ . flags
7875+ . intersects ( SymbolFlags :: ASSIGNED | SymbolFlags :: ITER )
7876+ && !sym. flags . contains ( SymbolFlags :: NONLOCAL )
7877+ && !is_walrus;
7878+ if is_local || in_class_block {
7879+ pushed_locals. push ( name. clone ( ) ) ;
7880+ }
7881+ }
7882+
7883+ // TweakInlinedComprehensionScopes: temporarily override parent symbols
7884+ // with comp scopes where they differ.
7885+ let mut temp_symbols: IndexMap < String , Symbol > = IndexMap :: default ( ) ;
7886+ for ( name, comp_sym) in & comp_table. symbols {
7887+ if comp_sym. flags . contains ( SymbolFlags :: PARAMETER ) {
7888+ continue ; // skip .0
7889+ }
7890+ let comp_scope = comp_sym. scope ;
7891+
7892+ let current_table = self . symbol_table_stack . last ( ) . expect ( "no symbol table" ) ;
7893+ if let Some ( outer_sym) = current_table. symbols . get ( name) {
7894+ let outer_scope = outer_sym. scope ;
7895+ if ( comp_scope != outer_scope
7896+ && comp_scope != SymbolScope :: Free
7897+ && !( comp_scope == SymbolScope :: Cell && outer_scope == SymbolScope :: Free ) )
7898+ || in_class_block
7899+ {
7900+ temp_symbols. insert ( name. clone ( ) , outer_sym. clone ( ) ) ;
7901+ let current_table =
7902+ self . symbol_table_stack . last_mut ( ) . expect ( "no symbol table" ) ;
7903+ current_table. symbols . insert ( name. clone ( ) , comp_sym. clone ( ) ) ;
7904+ }
7905+ }
7906+ }
78847907
7885- // Step 2: Save local variables that will be shadowed by the comprehension
7908+ // Step 2: Save local variables that will be shadowed by the comprehension.
7909+ // For each variable, we push the fast local value via LoadFastAndClear.
7910+ // For CELL variables, we additionally push the cell content via MakeCell
7911+ // (which saves the old cell value and clears the cell for the comprehension).
7912+ // Track cell indices to restore them later.
7913+ let mut cell_indices: Vec < Option < u32 > > = Vec :: new ( ) ;
7914+ let mut total_stack_items: usize = 0 ;
78867915 for name in & pushed_locals {
78877916 let var_num = self . varname ( name) ?;
78887917 emit ! ( self , Instruction :: LoadFastAndClear { var_num } ) ;
7918+ total_stack_items += 1 ;
7919+ // If the comp symbol is CELL, emit MAKE_CELL to save cell value
7920+ if let Some ( comp_sym) = comp_table. symbols . get ( name) {
7921+ if comp_sym. scope == SymbolScope :: Cell {
7922+ let i = if self
7923+ . current_symbol_table ( )
7924+ . symbols
7925+ . get ( name)
7926+ . is_some_and ( |s| s. scope == SymbolScope :: Free )
7927+ {
7928+ self . get_free_var_index ( name) ?
7929+ } else {
7930+ self . get_cell_var_index ( name) ?
7931+ } ;
7932+ emit ! ( self , Instruction :: MakeCell { i } ) ;
7933+ cell_indices. push ( Some ( i) ) ;
7934+ total_stack_items += 1 ;
7935+ } else {
7936+ cell_indices. push ( None ) ;
7937+ }
7938+ } else {
7939+ cell_indices. push ( None ) ;
7940+ }
78897941 }
78907942
7891- // Step 3: SWAP iterator to TOS (above saved locals)
7892- if !pushed_locals . is_empty ( ) {
7943+ // Step 3: SWAP iterator to TOS (above saved locals + cell values )
7944+ if total_stack_items > 0 {
78937945 emit ! (
78947946 self ,
78957947 Instruction :: Swap {
7896- i: u32 :: try_from( pushed_locals . len ( ) + 1 ) . unwrap( )
7948+ i: u32 :: try_from( total_stack_items + 1 ) . unwrap( )
78977949 }
78987950 ) ;
78997951 }
79007952
79017953 // Step 4: Create the collection (list/set/dict)
7902- // For generator expressions, init_collection is None
79037954 if let Some ( init_collection) = init_collection {
79047955 self . _emit ( init_collection, OpArg :: new ( 0 ) , BlockIdx :: NULL ) ;
79057956 // SWAP to get iterator on top
@@ -7921,24 +7972,49 @@ impl Compiler {
79217972 }
79227973
79237974 // Step 5: Compile the comprehension loop(s)
7924- let mut loop_labels = vec ! [ ] ;
7975+ let mut loop_labels: Vec < ( BlockIdx , BlockIdx , BlockIdx , bool , BlockIdx ) > = vec ! [ ] ;
79257976 for ( i, generator) in generators. iter ( ) . enumerate ( ) {
79267977 let loop_block = self . new_block ( ) ;
79277978 let if_cleanup_block = self . new_block ( ) ;
79287979 let after_block = self . new_block ( ) ;
79297980
79307981 if i > 0 {
7931- // For nested loops, compile the iterator expression
79327982 self . compile_expression ( & generator. iter ) ?;
7933- emit ! ( self , Instruction :: GetIter ) ;
7983+ if generator. is_async {
7984+ emit ! ( self , Instruction :: GetAIter ) ;
7985+ } else {
7986+ emit ! ( self , Instruction :: GetIter ) ;
7987+ }
79347988 }
79357989
79367990 self . switch_to_block ( loop_block) ;
79377991
7938- emit ! ( self , Instruction :: ForIter { delta: after_block } ) ;
7939- self . compile_store ( & generator. target ) ?;
7992+ let mut end_async_for_target = BlockIdx :: NULL ;
7993+ if generator. is_async {
7994+ emit ! ( self , PseudoInstruction :: SetupFinally { delta: after_block } ) ;
7995+ emit ! ( self , Instruction :: GetANext ) ;
7996+ self . push_fblock (
7997+ FBlockType :: AsyncComprehensionGenerator ,
7998+ loop_block,
7999+ after_block,
8000+ ) ?;
8001+ self . emit_load_const ( ConstantData :: None ) ;
8002+ end_async_for_target = self . compile_yield_from_sequence ( true ) ?;
8003+ emit ! ( self , PseudoInstruction :: PopBlock ) ;
8004+ self . pop_fblock ( FBlockType :: AsyncComprehensionGenerator ) ;
8005+ self . compile_store ( & generator. target ) ?;
8006+ } else {
8007+ emit ! ( self , Instruction :: ForIter { delta: after_block } ) ;
8008+ self . compile_store ( & generator. target ) ?;
8009+ }
79408010
7941- loop_labels. push ( ( loop_block, if_cleanup_block, after_block) ) ;
8011+ loop_labels. push ( (
8012+ loop_block,
8013+ if_cleanup_block,
8014+ after_block,
8015+ generator. is_async ,
8016+ end_async_for_target,
8017+ ) ) ;
79428018
79438019 // Evaluate the if conditions
79448020 for if_condition in & generator. ifs {
@@ -7949,20 +8025,26 @@ impl Compiler {
79498025 // Step 6: Compile the element expression and append to collection
79508026 compile_element ( self ) ?;
79518027
7952- // Step 7: Close all loops (sync only - async comprehensions are never inlined)
7953- for ( loop_block, if_cleanup_block, after_block) in loop_labels. iter ( ) . rev ( ) . copied ( ) {
8028+ // Step 7: Close all loops
8029+ for & ( loop_block, if_cleanup_block, after_block, is_async, end_async_for_target) in
8030+ loop_labels. iter ( ) . rev ( )
8031+ {
79548032 emit ! ( self , PseudoInstruction :: Jump { delta: loop_block } ) ;
79558033
79568034 self . switch_to_block ( if_cleanup_block) ;
79578035 emit ! ( self , PseudoInstruction :: Jump { delta: loop_block } ) ;
79588036
79598037 self . switch_to_block ( after_block) ;
7960- emit ! ( self , Instruction :: EndFor ) ;
7961- emit ! ( self , Instruction :: PopIter ) ;
8038+ if is_async {
8039+ self . emit_end_async_for ( end_async_for_target) ;
8040+ } else {
8041+ emit ! ( self , Instruction :: EndFor ) ;
8042+ emit ! ( self , Instruction :: PopIter ) ;
8043+ }
79628044 }
79638045
7964- // Step 8: Clean up - restore saved locals
7965- if !pushed_locals . is_empty ( ) {
8046+ // Step 8: Clean up - restore saved locals (and cell values)
8047+ if total_stack_items > 0 {
79668048 emit ! ( self , PseudoInstruction :: PopBlock ) ;
79678049 self . pop_fblock ( FBlockType :: TryExcept ) ;
79688050
@@ -7971,19 +8053,21 @@ impl Compiler {
79718053
79728054 // Exception cleanup path
79738055 self . switch_to_block ( cleanup_block) ;
7974- // Stack: [saved_locals..., collection, exception]
7975- // Swap to get collection out from under exception
8056+ // Stack: [saved_values..., collection, exception]
79768057 emit ! ( self , Instruction :: Swap { i: 2 } ) ;
79778058 emit ! ( self , Instruction :: PopTop ) ; // Pop incomplete collection
79788059
7979- // Restore locals
8060+ // Restore locals and cell values
79808061 emit ! (
79818062 self ,
79828063 Instruction :: Swap {
7983- i: u32 :: try_from( pushed_locals . len ( ) + 1 ) . unwrap( )
8064+ i: u32 :: try_from( total_stack_items + 1 ) . unwrap( )
79848065 }
79858066 ) ;
7986- for name in pushed_locals. iter ( ) . rev ( ) {
8067+ for ( name, cell_idx) in pushed_locals. iter ( ) . rev ( ) . zip ( cell_indices. iter ( ) . rev ( ) ) {
8068+ if let Some ( i) = cell_idx {
8069+ emit ! ( self , Instruction :: RestoreCell { i: * i } ) ;
8070+ }
79878071 let var_num = self . varname ( name) ?;
79888072 emit ! ( self , Instruction :: StoreFast { var_num } ) ;
79898073 }
@@ -7994,22 +8078,31 @@ impl Compiler {
79948078 self . switch_to_block ( end_block) ;
79958079 }
79968080
7997- // SWAP result to TOS (above saved locals )
7998- if !pushed_locals . is_empty ( ) {
8081+ // SWAP result to TOS (above saved values )
8082+ if total_stack_items > 0 {
79998083 emit ! (
80008084 self ,
80018085 Instruction :: Swap {
8002- i: u32 :: try_from( pushed_locals . len ( ) + 1 ) . unwrap( )
8086+ i: u32 :: try_from( total_stack_items + 1 ) . unwrap( )
80038087 }
80048088 ) ;
80058089 }
80068090
8007- // Restore saved locals
8008- for name in pushed_locals. iter ( ) . rev ( ) {
8091+ // Restore saved locals and cell values
8092+ for ( name, cell_idx) in pushed_locals. iter ( ) . rev ( ) . zip ( cell_indices. iter ( ) . rev ( ) ) {
8093+ if let Some ( i) = cell_idx {
8094+ emit ! ( self , Instruction :: RestoreCell { i: * i } ) ;
8095+ }
80098096 let var_num = self . varname ( name) ?;
80108097 emit ! ( self , Instruction :: StoreFast { var_num } ) ;
80118098 }
80128099
8100+ // RevertInlinedComprehensionScopes: restore original symbols
8101+ let current_table = self . symbol_table_stack . last_mut ( ) . expect ( "no symbol table" ) ;
8102+ for ( name, original_sym) in temp_symbols {
8103+ current_table. symbols . insert ( name, original_sym) ;
8104+ }
8105+
80138106 Ok ( ( ) )
80148107 }
80158108
0 commit comments