| 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 | //! PruneUnusedScopes — converts scopes without outputs into regular blocks. |
| 7 | //! |
| 8 | //! Corresponds to `src/ReactiveScopes/PruneUnusedScopes.ts`. |
| 9 | |
| 10 | use react_compiler_hir::{ |
| 11 | PrunedReactiveScopeBlock, ReactiveFunction, ReactiveScopeBlock, ReactiveStatement, |
| 12 | ReactiveTerminal, ReactiveTerminalStatement, environment::Environment, |
| 13 | }; |
| 14 | |
| 15 | use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; |
| 16 | |
| 17 | struct State { |
| 18 | has_return_statement: bool, |
| 19 | } |
| 20 | |
| 21 | /// Converts scopes without outputs into pruned-scopes (regular blocks). |
| 22 | /// TS: `pruneUnusedScopes` |
| 23 | pub fn prune_unused_scopes( |
| 24 | func: &mut ReactiveFunction, |
| 25 | env: &Environment, |
| 26 | ) -> Result<(), react_compiler_diagnostics::CompilerError> { |
| 27 | let mut transform = Transform { env }; |
| 28 | let mut state = State { |
| 29 | has_return_statement: false, |
| 30 | }; |
| 31 | transform_reactive_function(func, &mut transform, &mut state) |
| 32 | } |
| 33 | |
| 34 | struct Transform<'a> { |
| 35 | env: &'a Environment, |
| 36 | } |
| 37 | |
| 38 | impl<'a> ReactiveFunctionTransform for Transform<'a> { |
| 39 | type State = State; |
| 40 | |
| 41 | fn env(&self) -> &Environment { |
| 42 | self.env |
| 43 | } |
| 44 | |
| 45 | fn visit_terminal( |
| 46 | &mut self, |
| 47 | stmt: &mut ReactiveTerminalStatement, |
| 48 | state: &mut State, |
| 49 | ) -> Result<(), react_compiler_diagnostics::CompilerError> { |
| 50 | self.traverse_terminal(stmt, state)?; |
| 51 | if matches!(stmt.terminal, ReactiveTerminal::Return { .. }) { |
| 52 | state.has_return_statement = true; |
| 53 | } |
| 54 | Ok(()) |
| 55 | } |
| 56 | |
| 57 | fn transform_scope( |
| 58 | &mut self, |
| 59 | scope: &mut ReactiveScopeBlock, |
| 60 | _state: &mut State, |
| 61 | ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { |
| 62 | let mut scope_state = State { |
| 63 | has_return_statement: false, |
| 64 | }; |
| 65 | self.visit_scope(scope, &mut scope_state)?; |
| 66 | |
| 67 | let scope_id = scope.scope; |
| 68 | let scope_data = &self.env.scopes[scope_id.0 as usize]; |
| 69 | |
| 70 | if !scope_state.has_return_statement |
| 71 | && scope_data.reassignments.is_empty() |
| 72 | && (scope_data.declarations.is_empty() || !has_own_declaration(scope_data, scope_id)) |
| 73 | { |
| 74 | // Replace with pruned scope |
| 75 | Ok(Transformed::Replace(ReactiveStatement::PrunedScope( |
| 76 | PrunedReactiveScopeBlock { |
| 77 | scope: scope.scope, |
| 78 | instructions: std::mem::take(&mut scope.instructions), |
| 79 | }, |
| 80 | ))) |
| 81 | } else { |
| 82 | Ok(Transformed::Keep) |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /// Does the scope block declare any values of its own? |
| 88 | /// Returns false if all declarations are propagated from nested scopes. |
| 89 | /// TS: `hasOwnDeclaration` |
| 90 | fn has_own_declaration( |
| 91 | scope_data: &react_compiler_hir::ReactiveScope, |
| 92 | scope_id: react_compiler_hir::ScopeId, |
| 93 | ) -> bool { |
| 94 | for (_, decl) in &scope_data.declarations { |
| 95 | if decl.scope == scope_id { |
| 96 | return true; |
| 97 | } |
| 98 | } |
| 99 | false |
| 100 | } |