| 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 | IdentifierId, |
| 10 | ReactiveFunction, |
| 11 | ReactiveInstruction, |
| 12 | ReactiveScopeBlock, |
| 13 | isStableType, |
| 14 | } from '../HIR'; |
| 15 | import {eachPatternOperand} from '../HIR/visitors'; |
| 16 | import {collectReactiveIdentifiers} from './CollectReactiveIdentifiers'; |
| 17 | import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; |
| 18 | |
| 19 | /* |
| 20 | * PropagateScopeDependencies infers dependencies without considering whether dependencies |
| 21 | * are actually reactive or not (ie, whether their value can change over time). |
| 22 | * |
| 23 | * This pass prunes dependencies that are guaranteed to be non-reactive. |
| 24 | */ |
| 25 | export function pruneNonReactiveDependencies(fn: ReactiveFunction): void { |
| 26 | const reactiveIdentifiers = collectReactiveIdentifiers(fn); |
| 27 | visitReactiveFunction(fn, new Visitor(), reactiveIdentifiers); |
| 28 | } |
| 29 | |
| 30 | type ReactiveIdentifiers = Set<IdentifierId>; |
| 31 | |
| 32 | class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> { |
| 33 | override visitInstruction( |
| 34 | instruction: ReactiveInstruction, |
| 35 | state: ReactiveIdentifiers, |
| 36 | ): void { |
| 37 | this.traverseInstruction(instruction, state); |
| 38 | |
| 39 | const {lvalue, value} = instruction; |
| 40 | switch (value.kind) { |
| 41 | case 'LoadLocal': { |
| 42 | if (lvalue !== null && state.has(value.place.identifier.id)) { |
| 43 | state.add(lvalue.identifier.id); |
| 44 | } |
| 45 | break; |
| 46 | } |
| 47 | case 'StoreLocal': { |
| 48 | if (state.has(value.value.identifier.id)) { |
| 49 | state.add(value.lvalue.place.identifier.id); |
| 50 | if (lvalue !== null) { |
| 51 | state.add(lvalue.identifier.id); |
| 52 | } |
| 53 | } |
| 54 | break; |
| 55 | } |
| 56 | case 'Destructure': { |
| 57 | if (state.has(value.value.identifier.id)) { |
| 58 | for (const lvalue of eachPatternOperand(value.lvalue.pattern)) { |
| 59 | if (isStableType(lvalue.identifier)) { |
| 60 | continue; |
| 61 | } |
| 62 | state.add(lvalue.identifier.id); |
| 63 | } |
| 64 | if (lvalue !== null) { |
| 65 | state.add(lvalue.identifier.id); |
| 66 | } |
| 67 | } |
| 68 | break; |
| 69 | } |
| 70 | case 'PropertyLoad': { |
| 71 | if ( |
| 72 | lvalue !== null && |
| 73 | state.has(value.object.identifier.id) && |
| 74 | !isStableType(lvalue.identifier) |
| 75 | ) { |
| 76 | state.add(lvalue.identifier.id); |
| 77 | } |
| 78 | break; |
| 79 | } |
| 80 | case 'ComputedLoad': { |
| 81 | if ( |
| 82 | lvalue !== null && |
| 83 | (state.has(value.object.identifier.id) || |
| 84 | state.has(value.property.identifier.id)) |
| 85 | ) { |
| 86 | state.add(lvalue.identifier.id); |
| 87 | } |
| 88 | break; |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | override visitScope( |
| 94 | scopeBlock: ReactiveScopeBlock, |
| 95 | state: ReactiveIdentifiers, |
| 96 | ): void { |
| 97 | this.traverseScope(scopeBlock, state); |
| 98 | for (const dep of scopeBlock.scope.dependencies) { |
| 99 | const isReactive = state.has(dep.identifier.id); |
| 100 | if (!isReactive) { |
| 101 | scopeBlock.scope.dependencies.delete(dep); |
| 102 | } |
| 103 | } |
| 104 | if (scopeBlock.scope.dependencies.size !== 0) { |
| 105 | /** |
| 106 | * If any of a scope's dependencies are reactive, then all of its |
| 107 | * outputs will re-evaluate whenever those dependencies change. |
| 108 | * Mark all of the outputs as reactive to reflect the fact that |
| 109 | * they may change in practice based on a reactive input. |
| 110 | */ |
| 111 | for (const [, declaration] of scopeBlock.scope.declarations) { |
| 112 | state.add(declaration.identifier.id); |
| 113 | } |
| 114 | for (const reassignment of scopeBlock.scope.reassignments) { |
| 115 | state.add(reassignment.id); |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | } |