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}