main
ts 80 lines 2.54 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 {
9 HIRFunction,
10 IdentifierId,
11 ReactiveScope,
12 makeInstructionId,
13 } from '../HIR';
14 import DisjointSet from '../Utils/DisjointSet';
15
16 /**
17 * Ensures that method call instructions have scopes such that either:
18 * - Both the MethodCall and its property have the same scope
19 * - OR neither has a scope
20 */
21 export function alignMethodCallScopes(fn: HIRFunction): void {
22 const scopeMapping = new Map<IdentifierId, ReactiveScope | null>();
23 const mergedScopes = new DisjointSet<ReactiveScope>();
24
25 for (const [, block] of fn.body.blocks) {
26 for (const instr of block.instructions) {
27 const {lvalue, value} = instr;
28 if (value.kind === 'MethodCall') {
29 const lvalueScope = lvalue.identifier.scope;
30 const propertyScope = value.property.identifier.scope;
31 if (lvalueScope !== null) {
32 if (propertyScope !== null) {
33 // Both have a scope: merge the scopes
34 mergedScopes.union([lvalueScope, propertyScope]);
35 } else {
36 /*
37 * Else the call itself has a scope but not the property,
38 * record that this property should be in this scope
39 */
40 scopeMapping.set(value.property.identifier.id, lvalueScope);
41 }
42 } else if (propertyScope !== null) {
43 // else this property does not need a scope
44 scopeMapping.set(value.property.identifier.id, null);
45 }
46 } else if (
47 value.kind === 'FunctionExpression' ||
48 value.kind === 'ObjectMethod'
49 ) {
50 alignMethodCallScopes(value.loweredFunc.func);
51 }
52 }
53 }
54
55 mergedScopes.forEach((scope, root) => {
56 if (scope === root) {
57 return;
58 }
59 root.range.start = makeInstructionId(
60 Math.min(scope.range.start, root.range.start),
61 );
62 root.range.end = makeInstructionId(
63 Math.max(scope.range.end, root.range.end),
64 );
65 });
66
67 for (const [, block] of fn.body.blocks) {
68 for (const instr of block.instructions) {
69 const mappedScope = scopeMapping.get(instr.lvalue.identifier.id);
70 if (mappedScope !== undefined) {
71 instr.lvalue.identifier.scope = mappedScope;
72 } else if (instr.lvalue.identifier.scope !== null) {
73 const mergedScope = mergedScopes.find(instr.lvalue.identifier.scope);
74 if (mergedScope != null) {
75 instr.lvalue.identifier.scope = mergedScope;
76 }
77 }
78 }
79 }
80 }