12
IdentifierId,
13
InstructionId,
14
Place,
15
+ PrunedReactiveScopeBlock,
16
ReactiveFunction,
17
ReactiveScopeBlock,
18
ReactiveValue,
19
+ ScopeId,
20
promoteTemporary,
21
promoteTemporaryJsxTag,
22
} from "../HIR/HIR";
23
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
24
23
-type VisitorState = {
24
- tags: JsxExpressionTags;
25
-};
26
-class Visitor extends ReactiveFunctionVisitor<VisitorState> {
27
- override visitScope(block: ReactiveScopeBlock, state: VisitorState): void {
28
- this.traverseScope(block, state);
29
- for (const dep of block.scope.dependencies) {
25
+class Visitor extends ReactiveFunctionVisitor<State> {
26
+ override visitScope(scopeBlock: ReactiveScopeBlock, state: State): void {
27
+ this.traverseScope(scopeBlock, state);
28
+ for (const dep of scopeBlock.scope.dependencies) {
29
const { identifier } = dep;
30
if (identifier.name == null) {
31
promoteIdentifier(identifier, state);
38
* Many of our current test fixtures do not return a value, so
39
* it is better for now to promote (and memoize) every output.
40
*/
42
- for (const [, declaration] of block.scope.declarations) {
41
+ for (const [, declaration] of scopeBlock.scope.declarations) {
42
if (declaration.identifier.name == null) {
43
promoteIdentifier(declaration.identifier, state);
44
}
45
}
46
}
47
49
- override visitParam(place: Place, state: VisitorState): void {
48
+ override visitPrunedScope(
49
+ scopeBlock: PrunedReactiveScopeBlock,
50
+ state: State
51
+ ): void {
52
+ this.traversePrunedScope(scopeBlock, state);
53
+ for (const [, declaration] of scopeBlock.scope.declarations) {
54
+ if (
55
+ declaration.identifier.name == null &&
56
+ state.pruned.get(declaration.identifier.id)?.usedOutsideScope === true
57
+ ) {
58
+ promoteIdentifier(declaration.identifier, state);
59
+ }
60
+ }
61
+ }
62
+
63
+ override visitParam(place: Place, state: State): void {
64
if (place.identifier.name === null) {
65
promoteIdentifier(place.identifier, state);
66
}
69
override visitValue(
70
id: InstructionId,
71
value: ReactiveValue,
58
- state: VisitorState
72
+ state: State
73
): void {
74
this.traverseValue(id, value, state);
75
if (value.kind === "FunctionExpression" || value.kind === "ObjectMethod") {
81
_id: InstructionId,
82
_dependencies: Array<Place>,
83
fn: ReactiveFunction,
70
- state: VisitorState
84
+ state: State
85
): void {
86
for (const operand of fn.params) {
87
const place = operand.kind === "Identifier" ? operand : operand.place;
94
}
95
96
type JsxExpressionTags = Set<IdentifierId>;
83
-class CollectJsxTagsVisitor extends ReactiveFunctionVisitor<JsxExpressionTags> {
97
+type State = {
98
+ tags: JsxExpressionTags;
99
+ pruned: Map<
100
+ IdentifierId,
101
+ { activeScopes: Array<ScopeId>; usedOutsideScope: boolean }
102
+ >; // true if referenced within another scope, false if only accessed outside of scopes
103
+};
104
+
105
+class CollectPromotableTemporaries extends ReactiveFunctionVisitor<State> {
106
+ activeScopes: Array<ScopeId> = [];
107
+
108
+ override visitPlace(_id: InstructionId, place: Place, state: State): void {
109
+ if (
110
+ this.activeScopes.length !== 0 &&
111
+ state.pruned.has(place.identifier.id)
112
+ ) {
113
+ const prunedPlace = state.pruned.get(place.identifier.id)!;
114
+ if (prunedPlace.activeScopes.indexOf(this.activeScopes.at(-1)!) === -1) {
115
+ prunedPlace.usedOutsideScope = true;
116
+ }
117
+ }
118
+ }
119
+
120
override visitValue(
121
id: InstructionId,
122
value: ReactiveValue,
87
- state: JsxExpressionTags
123
+ state: State
124
): void {
125
this.traverseValue(id, value, state);
126
if (value.kind === "JsxExpression" && value.tag.kind === "Identifier") {
91
- state.add(value.tag.identifier.id);
127
+ state.tags.add(value.tag.identifier.id);
128
}
129
}
130
+
131
+ override visitPrunedScope(
132
+ scopeBlock: PrunedReactiveScopeBlock,
133
+ state: State
134
+ ): void {
135
+ for (const [id] of scopeBlock.scope.declarations) {
136
+ state.pruned.set(id, {
137
+ activeScopes: [...this.activeScopes],
138
+ usedOutsideScope: false,
139
+ });
140
+ }
141
+ }
142
+
143
+ override visitScope(scopeBlock: ReactiveScopeBlock, state: State): void {
144
+ this.activeScopes.push(scopeBlock.scope.id);
145
+ this.traverseScope(scopeBlock, state);
146
+ this.activeScopes.pop();
147
+ }
148
}
149
150
export function promoteUsedTemporaries(fn: ReactiveFunction): void {
97
- const tags: JsxExpressionTags = new Set();
98
- visitReactiveFunction(fn, new CollectJsxTagsVisitor(), tags);
99
- const state: VisitorState = {
100
- tags,
151
+ const state: State = {
152
+ tags: new Set(),
153
+ pruned: new Map(),
154
};
155
+ visitReactiveFunction(fn, new CollectPromotableTemporaries(), state);
156
for (const operand of fn.params) {
157
const place = operand.kind === "Identifier" ? operand : operand.place;
158
if (place.identifier.name === null) {
162
visitReactiveFunction(fn, new Visitor(), state);
163
}
164
111
-function promoteIdentifier(identifier: Identifier, state: VisitorState): void {
165
+function promoteIdentifier(identifier: Identifier, state: State): void {
166
CompilerError.invariant(identifier.name === null, {
167
reason:
168
"promoteTemporary: Expected to be called only for temporary variables",