Skip to main content

react_compiler_inference/
flatten_reactive_loops_hir.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//! Prunes any reactive scopes that are within a loop (for, while, etc). We don't yet
7//! support memoization within loops because this would require an extra layer of reconciliation
8//! (plus a way to identify values across runs, similar to how we use `key` in JSX for lists).
9//! Eventually we may integrate more deeply into the runtime so that we can do a single level
10//! of reconciliation, but for now we've found it's sufficient to memoize *around* the loop.
11//!
12//! Analogous to TS `ReactiveScopes/FlattenReactiveLoopsHIR.ts`.
13
14use react_compiler_hir::{BlockId, HirFunction, Terminal};
15
16/// Flattens reactive scopes that are inside loops by converting `Scope` terminals
17/// to `PrunedScope` terminals.
18pub fn flatten_reactive_loops_hir(func: &mut HirFunction) {
19    let mut active_loops: Vec<BlockId> = Vec::new();
20
21    // Collect block ids in iteration order so we can iterate while mutating terminals
22    let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect();
23
24    for block_id in block_ids {
25        // Remove this block from active loops (matching TS retainWhere)
26        active_loops.retain(|id| *id != block_id);
27
28        let block = &func.body.blocks[&block_id];
29        let terminal = &block.terminal;
30
31        match terminal {
32            Terminal::DoWhile { fallthrough, .. }
33            | Terminal::For { fallthrough, .. }
34            | Terminal::ForIn { fallthrough, .. }
35            | Terminal::ForOf { fallthrough, .. }
36            | Terminal::While { fallthrough, .. } => {
37                active_loops.push(*fallthrough);
38            }
39            Terminal::Scope {
40                block,
41                fallthrough,
42                scope,
43                id,
44                loc,
45            } => {
46                if !active_loops.is_empty() {
47                    let new_terminal = Terminal::PrunedScope {
48                        block: *block,
49                        fallthrough: *fallthrough,
50                        scope: *scope,
51                        id: *id,
52                        loc: *loc,
53                    };
54                    // We need to drop the borrow and reborrow mutably
55                    let block_mut = func.body.blocks.get_mut(&block_id).unwrap();
56                    block_mut.terminal = new_terminal;
57                }
58            }
59            // All other terminal kinds: no action needed
60            _ => {}
61        }
62    }
63}