@@ -145,6 +145,7 @@ impl Compiler<'_> {
145145
146146 // Compile inner with capture_effects on the match instruction
147147 let inner_capture = CaptureEffects {
148+ pre : outer_capture. pre ,
148149 post : capture_effects,
149150 } ;
150151 return self . compile_expr_inner ( inner, actual_exit, nav_override, inner_capture) ;
@@ -168,7 +169,9 @@ impl Compiler<'_> {
168169 } ) ) ;
169170
170171 // Compile inner WITH capture_effects on the match instruction
172+ // Note: pre effects don't propagate through Obj/EndObj scope wrapper
171173 let inner_capture = CaptureEffects {
174+ pre : vec ! [ ] ,
172175 post : capture_effects,
173176 } ;
174177 let inner_entry = self . with_scope ( scope_type_id. unwrap ( ) , |this| {
@@ -220,6 +223,7 @@ impl Compiler<'_> {
220223 } ) ) ;
221224
222225 let push_effects = CaptureEffects {
226+ pre : vec ! [ ] ,
223227 post : if self . quantifier_needs_node_for_push ( inner) {
224228 // Use Text if the capture has `:: string` annotation, else Node
225229 let opcode = if use_text_for_elements {
@@ -491,39 +495,34 @@ impl Compiler<'_> {
491495 use crate :: bytecode:: MAX_MATCH_PAYLOAD_SLOTS ;
492496
493497 if successors. len ( ) <= MAX_MATCH_PAYLOAD_SLOTS {
494- self . instructions . push ( Instruction :: Match ( MatchIR {
495- label,
496- nav : Nav :: Stay ,
497- node_type : None ,
498- node_field : None ,
499- pre_effects : vec ! [ ] ,
500- neg_fields : vec ! [ ] ,
501- post_effects : vec ! [ ] ,
502- successors,
503- } ) ) ;
504- } else {
505- // Split: first (MAX-1) successors + intermediate for rest.
506- // This preserves priority order: VM tries s0, s1, ..., then intermediate.
507- let split_at = MAX_MATCH_PAYLOAD_SLOTS - 1 ;
508- let ( first_batch, rest) = successors. split_at ( split_at) ;
509-
510- let intermediate = self . fresh_label ( ) ;
511- self . emit_epsilon ( intermediate, rest. to_vec ( ) ) ;
512-
513- let mut batch = first_batch. to_vec ( ) ;
514- batch. push ( intermediate) ;
515-
516- self . instructions . push ( Instruction :: Match ( MatchIR {
517- label,
518- nav : Nav :: Stay ,
519- node_type : None ,
520- node_field : None ,
521- pre_effects : vec ! [ ] ,
522- neg_fields : vec ! [ ] ,
523- post_effects : vec ! [ ] ,
524- successors : batch,
525- } ) ) ;
498+ self . push_epsilon ( label, successors) ;
499+ return ;
526500 }
501+
502+ // Split: first (MAX-1) successors + intermediate for rest.
503+ // This preserves priority order: VM tries s0, s1, ..., then intermediate.
504+ let split_at = MAX_MATCH_PAYLOAD_SLOTS - 1 ;
505+ let ( first_batch, rest) = successors. split_at ( split_at) ;
506+
507+ let intermediate = self . fresh_label ( ) ;
508+ self . emit_epsilon ( intermediate, rest. to_vec ( ) ) ;
509+
510+ let mut batch = first_batch. to_vec ( ) ;
511+ batch. push ( intermediate) ;
512+ self . push_epsilon ( label, batch) ;
513+ }
514+
515+ fn push_epsilon ( & mut self , label : Label , successors : Vec < Label > ) {
516+ self . instructions . push ( Instruction :: Match ( MatchIR {
517+ label,
518+ nav : Nav :: Stay ,
519+ node_type : None ,
520+ node_field : None ,
521+ pre_effects : vec ! [ ] ,
522+ neg_fields : vec ! [ ] ,
523+ post_effects : vec ! [ ] ,
524+ successors,
525+ } ) ) ;
527526 }
528527
529528 /// Emit a wildcard navigation step that accepts any node.
0 commit comments