@@ -199,6 +199,7 @@ pub fn add_local_with_attrs(
199199 register,
200200 is_const,
201201 is_to_be_closed,
202+ needs_close : false ,
202203 } ;
203204 c. scope_chain . borrow_mut ( ) . locals . push ( local) ;
204205
@@ -271,9 +272,11 @@ pub fn resolve_upvalue_from_chain(c: &mut Compiler, name: &str) -> Option<usize>
271272fn resolve_in_parent_scope ( scope : & Rc < RefCell < ScopeChain > > , name : & str ) -> Option < ( bool , u32 ) > {
272273 // First, search in this scope's locals
273274 {
274- let scope_ref = scope. borrow ( ) ;
275- if let Some ( local) = scope_ref. locals . iter ( ) . rev ( ) . find ( |l| l. name == name) {
275+ let mut scope_ref = scope. borrow_mut ( ) ;
276+ if let Some ( local) = scope_ref. locals . iter_mut ( ) . rev ( ) . find ( |l| l. name == name) {
276277 let register = local. register ;
278+ // Mark this local as captured by a closure - needs CLOSE on scope exit
279+ local. needs_close = true ;
277280 // Found as local - return (true, register)
278281 return Some ( ( true , register) ) ;
279282 }
@@ -568,8 +571,15 @@ pub fn try_expr_as_constant(c: &mut Compiler, expr: &emmylua_parser::LuaExpr) ->
568571
569572/// Begin a new loop (for break statement support)
570573pub fn begin_loop ( c : & mut Compiler ) {
574+ begin_loop_with_register ( c, c. freereg ) ;
575+ }
576+
577+ /// Begin a new loop with a specific first register for CLOSE
578+ pub fn begin_loop_with_register ( c : & mut Compiler , first_reg : u32 ) {
571579 c. loop_stack . push ( super :: LoopInfo {
572580 break_jumps : Vec :: new ( ) ,
581+ scope_depth : c. scope_depth ,
582+ first_local_register : first_reg,
573583 } ) ;
574584}
575585
@@ -589,6 +599,35 @@ pub fn emit_break(c: &mut Compiler) -> Result<(), String> {
589599 return Err ( "break statement outside loop" . to_string ( ) ) ;
590600 }
591601
602+ // Before breaking, check if we need to close any captured upvalues
603+ // This is needed when break jumps past the CLOSE instruction that would
604+ // normally be executed at the end of each iteration
605+ let loop_info = c. loop_stack . last ( ) . unwrap ( ) ;
606+ let loop_scope_depth = loop_info. scope_depth ;
607+ let first_reg = loop_info. first_local_register ;
608+
609+ // Find minimum register of captured locals in the loop scope
610+ let mut min_close_reg: Option < u32 > = None ;
611+ {
612+ let scope = c. scope_chain . borrow ( ) ;
613+ for local in scope. locals . iter ( ) . rev ( ) {
614+ if local. depth < loop_scope_depth {
615+ break ; // Only check loop scope and nested scopes
616+ }
617+ if local. needs_close && local. register >= first_reg {
618+ min_close_reg = Some ( match min_close_reg {
619+ None => local. register ,
620+ Some ( min_reg) => min_reg. min ( local. register ) ,
621+ } ) ;
622+ }
623+ }
624+ }
625+
626+ // Emit CLOSE if needed
627+ if let Some ( reg) = min_close_reg {
628+ emit ( c, Instruction :: encode_abc ( OpCode :: Close , reg, 0 , 0 ) ) ;
629+ }
630+
592631 let jump_pos = emit_jump ( c, OpCode :: Jmp ) ;
593632 c. loop_stack . last_mut ( ) . unwrap ( ) . break_jumps . push ( jump_pos) ;
594633 Ok ( ( ) )
0 commit comments