Skip to main content

react_compiler_reactive_scopes/
visitors.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6//! Visitor and transform traits for ReactiveFunction.
7//!
8//! Corresponds to `src/ReactiveScopes/visitors.ts` in the TypeScript compiler.
9
10use react_compiler_diagnostics::CompilerError;
11use react_compiler_hir::{
12    EvaluationOrder, FunctionId, InstructionValue, ParamPattern, Place,
13    PrunedReactiveScopeBlock, ReactiveBlock, ReactiveFunction, ReactiveInstruction,
14    ReactiveStatement, ReactiveTerminal, ReactiveTerminalStatement, ReactiveValue,
15    ReactiveScopeBlock,
16    environment::Environment,
17};
18
19// =============================================================================
20// ReactiveFunctionVisitor trait
21// =============================================================================
22
23/// Visitor trait for walking a ReactiveFunction tree.
24///
25/// Override individual `visit_*` methods to customize behavior; call the
26/// corresponding `traverse_*` to continue the default recursion.
27///
28/// TS: `class ReactiveFunctionVisitor<TState>`
29pub trait ReactiveFunctionVisitor {
30    type State;
31
32    /// Provide Environment access. The default traversal uses this to include
33    /// FunctionExpression/ObjectMethod context places as operands (matching the
34    /// TS `eachInstructionValueOperand` behavior).
35    fn env(&self) -> &Environment;
36
37    fn visit_id(&self, _id: EvaluationOrder, _state: &mut Self::State) {}
38
39    fn visit_place(&self, _id: EvaluationOrder, _place: &Place, _state: &mut Self::State) {}
40
41    fn visit_lvalue(&self, _id: EvaluationOrder, _lvalue: &Place, _state: &mut Self::State) {}
42
43    fn visit_param(&self, _place: &Place, _state: &mut Self::State) {}
44
45    /// Walk an inner HIR function, visiting params, instructions (with lvalues,
46    /// value-lvalues, operands, and nested functions), and terminal operands.
47    /// TS: `visitHirFunction`
48    fn visit_hir_function(&self, func_id: FunctionId, state: &mut Self::State) {
49        let inner_func = &self.env().functions[func_id.0 as usize];
50        for param in &inner_func.params {
51            let place = match param {
52                ParamPattern::Place(p) => p,
53                ParamPattern::Spread(s) => &s.place,
54            };
55            self.visit_param(place, state);
56        }
57        let block_ids: Vec<_> = inner_func.body.blocks.keys().copied().collect();
58        for block_id in block_ids {
59            let inner_func = &self.env().functions[func_id.0 as usize];
60            let block = &inner_func.body.blocks[&block_id];
61            let instr_ids: Vec<_> = block.instructions.clone();
62            let terminal_operands: Vec<Place> =
63                react_compiler_hir::visitors::each_terminal_operand(&block.terminal);
64            let terminal_id = block.terminal.evaluation_order();
65
66            for instr_id in &instr_ids {
67                let inner_func = &self.env().functions[func_id.0 as usize];
68                let instr = &inner_func.instructions[instr_id.0 as usize];
69                // Build a temporary ReactiveInstruction for the visitor
70                let reactive_instr = ReactiveInstruction {
71                    id: instr.id,
72                    lvalue: Some(instr.lvalue.clone()),
73                    value: ReactiveValue::Instruction(instr.value.clone()),
74                    effects: None,
75                    loc: instr.loc,
76                };
77                self.visit_instruction(&reactive_instr, state);
78                // Recurse into nested functions
79                match &instr.value {
80                    InstructionValue::FunctionExpression { lowered_func, .. }
81                    | InstructionValue::ObjectMethod { lowered_func, .. } => {
82                        self.visit_hir_function(lowered_func.func, state);
83                    }
84                    _ => {}
85                }
86            }
87            for operand in &terminal_operands {
88                self.visit_place(terminal_id, operand, state);
89            }
90        }
91    }
92
93    fn visit_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Self::State) {
94        self.traverse_value(id, value, state);
95    }
96
97    fn traverse_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Self::State) {
98        match value {
99            ReactiveValue::OptionalExpression { value: inner, .. } => {
100                self.visit_value(id, inner, state);
101            }
102            ReactiveValue::LogicalExpression { left, right, .. } => {
103                self.visit_value(id, left, state);
104                self.visit_value(id, right, state);
105            }
106            ReactiveValue::ConditionalExpression {
107                test,
108                consequent,
109                alternate,
110                ..
111            } => {
112                self.visit_value(id, test, state);
113                self.visit_value(id, consequent, state);
114                self.visit_value(id, alternate, state);
115            }
116            ReactiveValue::SequenceExpression {
117                instructions,
118                id: seq_id,
119                value: inner,
120                ..
121            } => {
122                for instr in instructions {
123                    self.visit_instruction(instr, state);
124                }
125                self.visit_value(*seq_id, inner, state);
126            }
127            ReactiveValue::Instruction(instr_value) => {
128                let operands = react_compiler_hir::visitors::each_instruction_value_operand(instr_value, self.env());
129                for place in &operands {
130                    self.visit_place(id, place, state);
131                }
132            }
133        }
134    }
135
136    fn visit_instruction(&self, instruction: &ReactiveInstruction, state: &mut Self::State) {
137        self.traverse_instruction(instruction, state);
138    }
139
140    fn traverse_instruction(&self, instruction: &ReactiveInstruction, state: &mut Self::State) {
141        self.visit_id(instruction.id, state);
142        // Visit instruction-level lvalue
143        if let Some(lvalue) = &instruction.lvalue {
144            self.visit_lvalue(instruction.id, lvalue, state);
145        }
146        // Visit value-level lvalues (TS: eachInstructionValueLValue)
147        if let ReactiveValue::Instruction(iv) = &instruction.value {
148            for place in react_compiler_hir::visitors::each_instruction_value_lvalue(iv) {
149                self.visit_lvalue(instruction.id, &place, state);
150            }
151        }
152        self.visit_value(instruction.id, &instruction.value, state);
153    }
154
155    fn visit_terminal(
156        &self,
157        stmt: &ReactiveTerminalStatement,
158        state: &mut Self::State,
159    ) {
160        self.traverse_terminal(stmt, state);
161    }
162
163    fn traverse_terminal(
164        &self,
165        stmt: &ReactiveTerminalStatement,
166        state: &mut Self::State,
167    ) {
168        let terminal = &stmt.terminal;
169        let id = terminal_id(terminal);
170        self.visit_id(id, state);
171        match terminal {
172            ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {}
173            ReactiveTerminal::Return { value, id, .. } => {
174                self.visit_place(*id, value, state);
175            }
176            ReactiveTerminal::Throw { value, id, .. } => {
177                self.visit_place(*id, value, state);
178            }
179            ReactiveTerminal::For {
180                init,
181                test,
182                update,
183                loop_block,
184                id,
185                ..
186            } => {
187                self.visit_value(*id, init, state);
188                self.visit_value(*id, test, state);
189                self.visit_block(loop_block, state);
190                if let Some(update) = update {
191                    self.visit_value(*id, update, state);
192                }
193            }
194            ReactiveTerminal::ForOf {
195                init,
196                test,
197                loop_block,
198                id,
199                ..
200            } => {
201                self.visit_value(*id, init, state);
202                self.visit_value(*id, test, state);
203                self.visit_block(loop_block, state);
204            }
205            ReactiveTerminal::ForIn {
206                init,
207                loop_block,
208                id,
209                ..
210            } => {
211                self.visit_value(*id, init, state);
212                self.visit_block(loop_block, state);
213            }
214            ReactiveTerminal::DoWhile {
215                loop_block,
216                test,
217                id,
218                ..
219            } => {
220                self.visit_block(loop_block, state);
221                self.visit_value(*id, test, state);
222            }
223            ReactiveTerminal::While {
224                test,
225                loop_block,
226                id,
227                ..
228            } => {
229                self.visit_value(*id, test, state);
230                self.visit_block(loop_block, state);
231            }
232            ReactiveTerminal::If {
233                test,
234                consequent,
235                alternate,
236                id,
237                ..
238            } => {
239                self.visit_place(*id, test, state);
240                self.visit_block(consequent, state);
241                if let Some(alt) = alternate {
242                    self.visit_block(alt, state);
243                }
244            }
245            ReactiveTerminal::Switch {
246                test, cases, id, ..
247            } => {
248                self.visit_place(*id, test, state);
249                for case in cases {
250                    if let Some(t) = &case.test {
251                        self.visit_place(*id, t, state);
252                    }
253                    if let Some(block) = &case.block {
254                        self.visit_block(block, state);
255                    }
256                }
257            }
258            ReactiveTerminal::Label { block, .. } => {
259                self.visit_block(block, state);
260            }
261            ReactiveTerminal::Try {
262                block,
263                handler_binding,
264                handler,
265                id,
266                ..
267            } => {
268                self.visit_block(block, state);
269                if let Some(binding) = handler_binding {
270                    self.visit_place(*id, binding, state);
271                }
272                self.visit_block(handler, state);
273            }
274        }
275    }
276
277    fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) {
278        self.traverse_scope(scope, state);
279    }
280
281    fn traverse_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) {
282        self.visit_block(&scope.instructions, state);
283    }
284
285    fn visit_pruned_scope(
286        &self,
287        scope: &PrunedReactiveScopeBlock,
288        state: &mut Self::State,
289    ) {
290        self.traverse_pruned_scope(scope, state);
291    }
292
293    fn traverse_pruned_scope(
294        &self,
295        scope: &PrunedReactiveScopeBlock,
296        state: &mut Self::State,
297    ) {
298        self.visit_block(&scope.instructions, state);
299    }
300
301    fn visit_block(&self, block: &ReactiveBlock, state: &mut Self::State) {
302        self.traverse_block(block, state);
303    }
304
305    fn traverse_block(&self, block: &ReactiveBlock, state: &mut Self::State) {
306        for stmt in block {
307            match stmt {
308                ReactiveStatement::Instruction(instr) => {
309                    self.visit_instruction(instr, state);
310                }
311                ReactiveStatement::Scope(scope) => {
312                    self.visit_scope(scope, state);
313                }
314                ReactiveStatement::PrunedScope(scope) => {
315                    self.visit_pruned_scope(scope, state);
316                }
317                ReactiveStatement::Terminal(terminal) => {
318                    self.visit_terminal(terminal, state);
319                }
320            }
321        }
322    }
323}
324
325/// Entry point for visiting a reactive function.
326/// TS: `visitReactiveFunction`
327pub fn visit_reactive_function<V: ReactiveFunctionVisitor>(
328    func: &ReactiveFunction,
329    visitor: &V,
330    state: &mut V::State,
331) {
332    visitor.visit_block(&func.body, state);
333}
334
335// =============================================================================
336// Transformed / TransformedValue enums
337// =============================================================================
338
339/// Result of transforming a ReactiveStatement.
340/// TS: `Transformed<T>`
341pub enum Transformed<T> {
342    Keep,
343    Remove,
344    Replace(T),
345    ReplaceMany(Vec<T>),
346}
347
348/// Result of transforming a ReactiveValue.
349/// TS: `TransformedValue`
350#[allow(dead_code)]
351pub enum TransformedValue {
352    Keep,
353    Replace(ReactiveValue),
354}
355
356// =============================================================================
357// ReactiveFunctionTransform trait
358// =============================================================================
359
360/// Transform trait for modifying a ReactiveFunction tree in-place.
361///
362/// Extends the visitor pattern with `transform_*` methods that can modify
363/// or remove statements. The `traverse_block` implementation handles applying
364/// transform results to the block.
365///
366/// TS: `class ReactiveFunctionTransform<TState>`
367pub trait ReactiveFunctionTransform {
368    type State;
369
370    /// Provide Environment access. The default traversal uses this to include
371    /// FunctionExpression/ObjectMethod context places as operands (matching the
372    /// TS `eachInstructionValueOperand` behavior).
373    fn env(&self) -> &Environment;
374
375    fn visit_id(&mut self, _id: EvaluationOrder, _state: &mut Self::State) -> Result<(), CompilerError> { Ok(()) }
376
377    fn visit_place(&mut self, _id: EvaluationOrder, _place: &Place, _state: &mut Self::State) -> Result<(), CompilerError> { Ok(()) }
378
379    fn visit_lvalue(&mut self, _id: EvaluationOrder, _lvalue: &Place, _state: &mut Self::State) -> Result<(), CompilerError> { Ok(()) }
380
381    fn visit_value(
382        &mut self,
383        id: EvaluationOrder,
384        value: &mut ReactiveValue,
385        state: &mut Self::State,
386    ) -> Result<(), CompilerError> {
387        self.traverse_value(id, value, state)
388    }
389
390    fn traverse_value(
391        &mut self,
392        id: EvaluationOrder,
393        value: &mut ReactiveValue,
394        state: &mut Self::State,
395    ) -> Result<(), CompilerError> {
396        match value {
397            ReactiveValue::OptionalExpression { value: inner, .. } => {
398                let next = self.transform_value(id, inner, state)?;
399                if let TransformedValue::Replace(new_value) = next {
400                    **inner = new_value;
401                }
402            }
403            ReactiveValue::LogicalExpression { left, right, .. } => {
404                let next_left = self.transform_value(id, left, state)?;
405                if let TransformedValue::Replace(new_value) = next_left {
406                    **left = new_value;
407                }
408                let next_right = self.transform_value(id, right, state)?;
409                if let TransformedValue::Replace(new_value) = next_right {
410                    **right = new_value;
411                }
412            }
413            ReactiveValue::ConditionalExpression {
414                test,
415                consequent,
416                alternate,
417                ..
418            } => {
419                let next_test = self.transform_value(id, test, state)?;
420                if let TransformedValue::Replace(new_value) = next_test {
421                    **test = new_value;
422                }
423                let next_cons = self.transform_value(id, consequent, state)?;
424                if let TransformedValue::Replace(new_value) = next_cons {
425                    **consequent = new_value;
426                }
427                let next_alt = self.transform_value(id, alternate, state)?;
428                if let TransformedValue::Replace(new_value) = next_alt {
429                    **alternate = new_value;
430                }
431            }
432            ReactiveValue::SequenceExpression {
433                instructions,
434                id: seq_id,
435                value: inner,
436                ..
437            } => {
438                let seq_id = *seq_id;
439                for instr in instructions.iter_mut() {
440                    self.visit_instruction(instr, state)?;
441                }
442                let next = self.transform_value(seq_id, inner, state)?;
443                if let TransformedValue::Replace(new_value) = next {
444                    **inner = new_value;
445                }
446            }
447            ReactiveValue::Instruction(instr_value) => {
448                // Collect operands before visiting to avoid borrow conflict
449                // (self.env() borrows self immutably, self.visit_place() needs &mut self).
450                let operands = react_compiler_hir::visitors::each_instruction_value_operand(instr_value, self.env());
451                for place in &operands {
452                    self.visit_place(id, place, state)?;
453                }
454            }
455        }
456        Ok(())
457    }
458
459    fn visit_instruction(
460        &mut self,
461        instruction: &mut ReactiveInstruction,
462        state: &mut Self::State,
463    ) -> Result<(), CompilerError> {
464        self.traverse_instruction(instruction, state)
465    }
466
467    fn transform_value(
468        &mut self,
469        id: EvaluationOrder,
470        value: &mut ReactiveValue,
471        state: &mut Self::State,
472    ) -> Result<TransformedValue, CompilerError> {
473        self.visit_value(id, value, state)?;
474        Ok(TransformedValue::Keep)
475    }
476
477    fn traverse_instruction(
478        &mut self,
479        instruction: &mut ReactiveInstruction,
480        state: &mut Self::State,
481    ) -> Result<(), CompilerError> {
482        self.visit_id(instruction.id, state)?;
483        // Visit instruction-level lvalue
484        if let Some(lvalue) = &instruction.lvalue {
485            self.visit_lvalue(instruction.id, lvalue, state)?;
486        }
487        // Visit value-level lvalues (TS: eachInstructionValueLValue)
488        if let ReactiveValue::Instruction(iv) = &instruction.value {
489            for place in react_compiler_hir::visitors::each_instruction_value_lvalue(iv) {
490                self.visit_lvalue(instruction.id, &place, state)?;
491            }
492        }
493        let next_value = self.transform_value(instruction.id, &mut instruction.value, state)?;
494        if let TransformedValue::Replace(new_value) = next_value {
495            instruction.value = new_value;
496        }
497        Ok(())
498    }
499
500    fn visit_terminal(
501        &mut self,
502        stmt: &mut ReactiveTerminalStatement,
503        state: &mut Self::State,
504    ) -> Result<(), CompilerError> {
505        self.traverse_terminal(stmt, state)
506    }
507
508    fn traverse_terminal(
509        &mut self,
510        stmt: &mut ReactiveTerminalStatement,
511        state: &mut Self::State,
512    ) -> Result<(), CompilerError> {
513        let terminal = &mut stmt.terminal;
514        let id = terminal_id(terminal);
515        self.visit_id(id, state)?;
516        match terminal {
517            ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {}
518            ReactiveTerminal::Return { value, id, .. } => {
519                self.visit_place(*id, value, state)?;
520            }
521            ReactiveTerminal::Throw { value, id, .. } => {
522                self.visit_place(*id, value, state)?;
523            }
524            ReactiveTerminal::For {
525                init,
526                test,
527                update,
528                loop_block,
529                id,
530                ..
531            } => {
532                let id = *id;
533                let next_init = self.transform_value(id, init, state)?;
534                if let TransformedValue::Replace(new_value) = next_init {
535                    *init = new_value;
536                }
537                let next_test = self.transform_value(id, test, state)?;
538                if let TransformedValue::Replace(new_value) = next_test {
539                    *test = new_value;
540                }
541                if let Some(update) = update {
542                    let next_update = self.transform_value(id, update, state)?;
543                    if let TransformedValue::Replace(new_value) = next_update {
544                        *update = new_value;
545                    }
546                }
547                self.visit_block(loop_block, state)?;
548            }
549            ReactiveTerminal::ForOf {
550                init,
551                test,
552                loop_block,
553                id,
554                ..
555            } => {
556                let id = *id;
557                let next_init = self.transform_value(id, init, state)?;
558                if let TransformedValue::Replace(new_value) = next_init {
559                    *init = new_value;
560                }
561                let next_test = self.transform_value(id, test, state)?;
562                if let TransformedValue::Replace(new_value) = next_test {
563                    *test = new_value;
564                }
565                self.visit_block(loop_block, state)?;
566            }
567            ReactiveTerminal::ForIn {
568                init,
569                loop_block,
570                id,
571                ..
572            } => {
573                let id = *id;
574                let next_init = self.transform_value(id, init, state)?;
575                if let TransformedValue::Replace(new_value) = next_init {
576                    *init = new_value;
577                }
578                self.visit_block(loop_block, state)?;
579            }
580            ReactiveTerminal::DoWhile {
581                loop_block,
582                test,
583                id,
584                ..
585            } => {
586                let id = *id;
587                self.visit_block(loop_block, state)?;
588                let next_test = self.transform_value(id, test, state)?;
589                if let TransformedValue::Replace(new_value) = next_test {
590                    *test = new_value;
591                }
592            }
593            ReactiveTerminal::While {
594                test,
595                loop_block,
596                id,
597                ..
598            } => {
599                let id = *id;
600                let next_test = self.transform_value(id, test, state)?;
601                if let TransformedValue::Replace(new_value) = next_test {
602                    *test = new_value;
603                }
604                self.visit_block(loop_block, state)?;
605            }
606            ReactiveTerminal::If {
607                test,
608                consequent,
609                alternate,
610                id,
611                ..
612            } => {
613                self.visit_place(*id, test, state)?;
614                self.visit_block(consequent, state)?;
615                if let Some(alt) = alternate {
616                    self.visit_block(alt, state)?;
617                }
618            }
619            ReactiveTerminal::Switch {
620                test, cases, id, ..
621            } => {
622                let id = *id;
623                self.visit_place(id, test, state)?;
624                for case in cases.iter_mut() {
625                    if let Some(t) = &case.test {
626                        self.visit_place(id, t, state)?;
627                    }
628                    if let Some(block) = &mut case.block {
629                        self.visit_block(block, state)?;
630                    }
631                }
632            }
633            ReactiveTerminal::Label { block, .. } => {
634                self.visit_block(block, state)?;
635            }
636            ReactiveTerminal::Try {
637                block,
638                handler_binding,
639                handler,
640                id,
641                ..
642            } => {
643                let id = *id;
644                self.visit_block(block, state)?;
645                if let Some(binding) = handler_binding {
646                    self.visit_place(id, binding, state)?;
647                }
648                self.visit_block(handler, state)?;
649            }
650        }
651        Ok(())
652    }
653
654    fn visit_scope(&mut self, scope: &mut ReactiveScopeBlock, state: &mut Self::State) -> Result<(), CompilerError> {
655        self.traverse_scope(scope, state)
656    }
657
658    fn traverse_scope(&mut self, scope: &mut ReactiveScopeBlock, state: &mut Self::State) -> Result<(), CompilerError> {
659        self.visit_block(&mut scope.instructions, state)
660    }
661
662    fn visit_pruned_scope(
663        &mut self,
664        scope: &mut PrunedReactiveScopeBlock,
665        state: &mut Self::State,
666    ) -> Result<(), CompilerError> {
667        self.traverse_pruned_scope(scope, state)
668    }
669
670    fn traverse_pruned_scope(
671        &mut self,
672        scope: &mut PrunedReactiveScopeBlock,
673        state: &mut Self::State,
674    ) -> Result<(), CompilerError> {
675        self.visit_block(&mut scope.instructions, state)
676    }
677
678    fn visit_block(&mut self, block: &mut ReactiveBlock, state: &mut Self::State) -> Result<(), CompilerError> {
679        self.traverse_block(block, state)
680    }
681
682    fn transform_instruction(
683        &mut self,
684        instruction: &mut ReactiveInstruction,
685        state: &mut Self::State,
686    ) -> Result<Transformed<ReactiveStatement>, CompilerError> {
687        self.visit_instruction(instruction, state)?;
688        Ok(Transformed::Keep)
689    }
690
691    fn transform_terminal(
692        &mut self,
693        stmt: &mut ReactiveTerminalStatement,
694        state: &mut Self::State,
695    ) -> Result<Transformed<ReactiveStatement>, CompilerError> {
696        self.visit_terminal(stmt, state)?;
697        Ok(Transformed::Keep)
698    }
699
700    fn transform_scope(
701        &mut self,
702        scope: &mut ReactiveScopeBlock,
703        state: &mut Self::State,
704    ) -> Result<Transformed<ReactiveStatement>, CompilerError> {
705        self.visit_scope(scope, state)?;
706        Ok(Transformed::Keep)
707    }
708
709    fn transform_pruned_scope(
710        &mut self,
711        scope: &mut PrunedReactiveScopeBlock,
712        state: &mut Self::State,
713    ) -> Result<Transformed<ReactiveStatement>, CompilerError> {
714        self.visit_pruned_scope(scope, state)?;
715        Ok(Transformed::Keep)
716    }
717
718    fn traverse_block(&mut self, block: &mut ReactiveBlock, state: &mut Self::State) -> Result<(), CompilerError> {
719        let mut next_block: Option<Vec<ReactiveStatement>> = None;
720        let len = block.len();
721        for i in 0..len {
722            // Take the statement out temporarily
723            let mut stmt = std::mem::replace(
724                &mut block[i],
725                // Placeholder — will be overwritten or discarded
726                ReactiveStatement::Instruction(ReactiveInstruction {
727                    id: EvaluationOrder(0),
728                    lvalue: None,
729                    value: ReactiveValue::Instruction(
730                        react_compiler_hir::InstructionValue::Debugger { loc: None },
731                    ),
732                    effects: None,
733                    loc: None,
734                }),
735            );
736            let transformed = match &mut stmt {
737                ReactiveStatement::Instruction(instr) => {
738                    self.transform_instruction(instr, state)?
739                }
740                ReactiveStatement::Scope(scope) => {
741                    self.transform_scope(scope, state)?
742                }
743                ReactiveStatement::PrunedScope(scope) => {
744                    self.transform_pruned_scope(scope, state)?
745                }
746                ReactiveStatement::Terminal(terminal) => {
747                    self.transform_terminal(terminal, state)?
748                }
749            };
750            match transformed {
751                Transformed::Keep => {
752                    if let Some(ref mut nb) = next_block {
753                        nb.push(stmt);
754                    } else {
755                        // Put it back
756                        block[i] = stmt;
757                    }
758                }
759                Transformed::Remove => {
760                    if next_block.is_none() {
761                        next_block = Some(block[..i].to_vec());
762                    }
763                }
764                Transformed::Replace(replacement) => {
765                    if next_block.is_none() {
766                        next_block = Some(block[..i].to_vec());
767                    }
768                    next_block.as_mut().unwrap().push(replacement);
769                }
770                Transformed::ReplaceMany(replacements) => {
771                    if next_block.is_none() {
772                        next_block = Some(block[..i].to_vec());
773                    }
774                    next_block.as_mut().unwrap().extend(replacements);
775                }
776            }
777        }
778        if let Some(nb) = next_block {
779            *block = nb;
780        }
781        Ok(())
782    }
783}
784
785/// Entry point for transforming a reactive function.
786/// TS: `visitReactiveFunction` (used with transforms too)
787pub fn transform_reactive_function<T: ReactiveFunctionTransform>(
788    func: &mut ReactiveFunction,
789    transform: &mut T,
790    state: &mut T::State,
791) -> Result<(), CompilerError> {
792    transform.visit_block(&mut func.body, state)
793}
794
795// =============================================================================
796// Helper: extract terminal ID
797// =============================================================================
798
799fn terminal_id(terminal: &ReactiveTerminal) -> EvaluationOrder {
800    match terminal {
801        ReactiveTerminal::Break { id, .. }
802        | ReactiveTerminal::Continue { id, .. }
803        | ReactiveTerminal::Return { id, .. }
804        | ReactiveTerminal::Throw { id, .. }
805        | ReactiveTerminal::Switch { id, .. }
806        | ReactiveTerminal::DoWhile { id, .. }
807        | ReactiveTerminal::While { id, .. }
808        | ReactiveTerminal::For { id, .. }
809        | ReactiveTerminal::ForOf { id, .. }
810        | ReactiveTerminal::ForIn { id, .. }
811        | ReactiveTerminal::If { id, .. }
812        | ReactiveTerminal::Label { id, .. }
813        | ReactiveTerminal::Try { id, .. } => *id,
814    }
815}
816