| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | */ |
| 7 | |
| 8 | import { |
| 9 | ReactiveFunction, |
| 10 | ReactiveScopeBlock, |
| 11 | ReactiveStatement, |
| 12 | ReactiveTerminalStatement, |
| 13 | } from '../HIR/HIR'; |
| 14 | import { |
| 15 | ReactiveFunctionTransform, |
| 16 | Transformed, |
| 17 | visitReactiveFunction, |
| 18 | } from './visitors'; |
| 19 | |
| 20 | // Converts scopes without outputs into regular blocks. |
| 21 | export function pruneUnusedScopes(fn: ReactiveFunction): void { |
| 22 | visitReactiveFunction(fn, new Transform(), { |
| 23 | hasReturnStatement: false, |
| 24 | } as State); |
| 25 | } |
| 26 | |
| 27 | type State = { |
| 28 | hasReturnStatement: boolean; |
| 29 | }; |
| 30 | |
| 31 | class Transform extends ReactiveFunctionTransform<State> { |
| 32 | override visitTerminal(stmt: ReactiveTerminalStatement, state: State): void { |
| 33 | this.traverseTerminal(stmt, state); |
| 34 | if (stmt.terminal.kind === 'return') { |
| 35 | state.hasReturnStatement = true; |
| 36 | } |
| 37 | } |
| 38 | override transformScope( |
| 39 | scopeBlock: ReactiveScopeBlock, |
| 40 | _state: State, |
| 41 | ): Transformed<ReactiveStatement> { |
| 42 | const scopeState: State = {hasReturnStatement: false}; |
| 43 | this.visitScope(scopeBlock, scopeState); |
| 44 | if ( |
| 45 | !scopeState.hasReturnStatement && |
| 46 | scopeBlock.scope.reassignments.size === 0 && |
| 47 | (scopeBlock.scope.declarations.size === 0 || |
| 48 | /* |
| 49 | * Can prune scopes where all declarations bubbled up from inner |
| 50 | * scopes |
| 51 | */ |
| 52 | !hasOwnDeclaration(scopeBlock)) |
| 53 | ) { |
| 54 | return { |
| 55 | kind: 'replace', |
| 56 | value: { |
| 57 | kind: 'pruned-scope', |
| 58 | scope: scopeBlock.scope, |
| 59 | instructions: scopeBlock.instructions, |
| 60 | }, |
| 61 | }; |
| 62 | } else { |
| 63 | return {kind: 'keep'}; |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | /* |
| 69 | * Does the scope block declare any values of its own? This can return |
| 70 | * false if all the block's declarations are propagated from nested scopes. |
| 71 | */ |
| 72 | function hasOwnDeclaration(block: ReactiveScopeBlock): boolean { |
| 73 | for (const declaration of block.scope.declarations.values()) { |
| 74 | if (declaration.scope.id === block.scope.id) { |
| 75 | return true; |
| 76 | } |
| 77 | } |
| 78 | return false; |
| 79 | } |