| 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 | |
| 14 | use react_compiler_hir::{BlockId, HirFunction, Terminal}; |
| 15 | |
| 16 | /// Flattens reactive scopes that are inside loops by converting `Scope` terminals |
| 17 | /// to `PrunedScope` terminals. |
| 18 | pub 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 | } |