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
+}