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