24
eachPatternOperand,
25
} from "../HIR/visitors";
26
import { assertExhaustive } from "../Utils/utils";
27
-import { ReactiveScopeDependencyTree } from "./DeriveMinimalDependencies";
27
+import {
28
+ ReactiveScopeDependencyTree,
29
+ ReactiveScopePropertyDependency,
30
+} from "./DeriveMinimalDependencies";
31
32
/**
33
* Infers the dependencies of each scope to include variables whose values
71
// - a ReactiveScope (A) containing a PropertyLoad may differ from the
72
// ReactiveScope (B) that uses the produced temporary.
73
// - codegen will inline these PropertyLoads back into scope (B)
71
- #properties: Map<Identifier, ReactiveScopeDependency> = new Map();
74
+ #properties: Map<Identifier, ReactiveScopePropertyDependency> = new Map();
75
#temporaries: Map<Identifier, Place> = new Map();
76
#inConditionalWithinScope: boolean = false;
77
// Reactive dependencies used unconditionally in the current conditional.
189
this.#temporaries.set(lvalue.identifier, value);
190
}
191
189
- declareProperty(lvalue: Place, object: Place, property: string): void {
192
+ #getProperty(
193
+ object: Place,
194
+ property: string,
195
+ isConditional: boolean
196
+ ): ReactiveScopePropertyDependency {
197
const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
191
- const objectDependency = this.#properties.get(resolvedObject.identifier);
192
- let nextDependency: ReactiveScopeDependency;
193
- if (objectDependency === undefined) {
194
- nextDependency = {
198
+ const resolvedDependency = this.#properties.get(resolvedObject.identifier);
199
+ let objectDependency: ReactiveScopePropertyDependency;
200
+ // (1) Create the base property dependency as either a LoadLocal (from a temporary)
201
+ // or a deep copy of an existing property dependency.
202
+ if (resolvedDependency === undefined) {
203
+ objectDependency = {
204
identifier: resolvedObject.identifier,
196
- path: [property],
205
+ path: [],
206
+ optionalPath: [],
207
};
208
} else {
199
- nextDependency = {
200
- identifier: objectDependency.identifier,
201
- path: [...objectDependency.path, property],
209
+ objectDependency = {
210
+ identifier: resolvedDependency.identifier,
211
+ path: [...resolvedDependency.path],
212
+ optionalPath: [...resolvedDependency.optionalPath],
213
};
214
}
215
+
216
+ // (2) Determine whether property is an optional access
217
+ if (objectDependency.optionalPath.length > 0) {
218
+ // If the base property dependency represents a optional member expression,
219
+ // property is on the optionalPath (regardless of whether this PropertyLoad
220
+ // itself was conditional)
221
+ // e.g. for `a.b?.c.d`, `d` should be added to optionalPath
222
+ objectDependency.optionalPath.push(property);
223
+ } else if (isConditional) {
224
+ objectDependency.optionalPath.push(property);
225
+ } else {
226
+ objectDependency.path.push(property);
227
+ }
228
+
229
+ return objectDependency;
230
+ }
231
+
232
+ declareProperty(
233
+ lvalue: Place,
234
+ object: Place,
235
+ property: string,
236
+ isConditional: boolean
237
+ ): void {
238
+ const nextDependency = this.#getProperty(object, property, isConditional);
239
this.#properties.set(lvalue.identifier, nextDependency);
240
}
241
269
// if this operand is a temporary created for a property load, try to resolve it to
270
// the expanded Place. Fall back to using the operand as-is.
271
237
- let dependency: ReactiveScopeDependency = {
272
+ let dependency: ReactiveScopePropertyDependency = {
273
identifier: resolved.identifier,
274
path: [],
275
+ optionalPath: [],
276
};
277
if (resolved.identifier.name === null) {
278
const propertyDependency = this.#properties.get(resolved.identifier);
283
this.visitDependency(dependency);
284
}
285
250
- visitProperty(object: Place, property: string): void {
251
- const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
252
- const objectDependency = this.#properties.get(resolvedObject.identifier);
253
- let nextDependency: ReactiveScopeDependency;
254
- if (objectDependency === undefined) {
255
- nextDependency = {
256
- identifier: resolvedObject.identifier,
257
- path: [property],
258
- };
259
- } else {
260
- nextDependency = {
261
- identifier: objectDependency.identifier,
262
- path: [...objectDependency.path, property],
263
- };
264
- }
286
+ visitProperty(object: Place, property: string, isConditional: boolean): void {
287
+ const nextDependency = this.#getProperty(object, property, isConditional);
288
this.visitDependency(nextDependency);
289
}
290
268
- visitDependency(maybeDependency: ReactiveScopeDependency): void {
291
+ visitDependency(maybeDependency: ReactiveScopePropertyDependency): void {
292
// Any value used after its originally defining scope has concluded must be added as an
293
// output of its defining scope. Regardless of whether its a const or not,
294
// some later code needs access to the value. If the current
516
}
517
} else if (value.kind === "PropertyLoad") {
518
if (lvalue !== null) {
496
- context.declareProperty(lvalue, value.object, value.property);
519
+ context.declareProperty(
520
+ lvalue,
521
+ value.object,
522
+ value.property,
523
+ value.optional
524
+ );
525
} else {
498
- context.visitProperty(value.object, value.property);
526
+ context.visitProperty(value.object, value.property, value.optional);
527
}
528
} else if (value.kind === "StoreLocal") {
529
context.visitOperand(value.value);