main
ts 100 lines 3.09 KB
Raw
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 {CompilerError} from '..';
9 import {
10 GeneratedSource,
11 HIRFunction,
12 Identifier,
13 ReactiveScope,
14 makeInstructionId,
15 } from '../HIR';
16 import {eachInstructionValueOperand} from '../HIR/visitors';
17 import DisjointSet from '../Utils/DisjointSet';
18
19 /**
20 * Align scopes of object method values to that of their enclosing object expressions.
21 * To produce a well-formed JS program in Codegen, object methods and object expressions
22 * must be in the same ReactiveBlock as object method definitions must be inlined.
23 */
24
25 function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
26 const objectMethodDecls: Set<Identifier> = new Set();
27 const mergeScopesBuilder = new DisjointSet<ReactiveScope>();
28
29 for (const [_, block] of fn.body.blocks) {
30 for (const {lvalue, value} of block.instructions) {
31 if (value.kind === 'ObjectMethod') {
32 objectMethodDecls.add(lvalue.identifier);
33 } else if (value.kind === 'ObjectExpression') {
34 for (const operand of eachInstructionValueOperand(value)) {
35 if (objectMethodDecls.has(operand.identifier)) {
36 const operandScope = operand.identifier.scope;
37 const lvalueScope = lvalue.identifier.scope;
38
39 CompilerError.invariant(
40 operandScope != null && lvalueScope != null,
41 {
42 reason:
43 'Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.',
44 loc: GeneratedSource,
45 },
46 );
47 mergeScopesBuilder.union([operandScope, lvalueScope]);
48 }
49 }
50 }
51 }
52 }
53 return mergeScopesBuilder;
54 }
55
56 export function alignObjectMethodScopes(fn: HIRFunction): void {
57 // Handle inner functions: we assume that Scopes are disjoint across functions
58 for (const [_, block] of fn.body.blocks) {
59 for (const {value} of block.instructions) {
60 if (
61 value.kind === 'ObjectMethod' ||
62 value.kind === 'FunctionExpression'
63 ) {
64 alignObjectMethodScopes(value.loweredFunc.func);
65 }
66 }
67 }
68
69 const scopeGroupsMap = findScopesToMerge(fn).canonicalize();
70 /**
71 * Step 1: Merge affected scopes to their canonical root.
72 */
73 for (const [scope, root] of scopeGroupsMap) {
74 if (scope !== root) {
75 root.range.start = makeInstructionId(
76 Math.min(scope.range.start, root.range.start),
77 );
78 root.range.end = makeInstructionId(
79 Math.max(scope.range.end, root.range.end),
80 );
81 }
82 }
83
84 /**
85 * Step 2: Repoint identifiers whose scopes were merged.
86 */
87 for (const [_, block] of fn.body.blocks) {
88 for (const {
89 lvalue: {identifier},
90 } of block.instructions) {
91 if (identifier.scope != null) {
92 const root = scopeGroupsMap.get(identifier.scope);
93 if (root != null) {
94 identifier.scope = root;
95 }
96 // otherwise, this identifier's scope was not affected by this pass
97 }
98 }
99 }
100 }