22
Identifier,
23
IdentifierId,
24
InstructionKind,
25
+ isPrimitiveType,
26
isStableType,
27
isSubPath,
28
isSubPathIgnoringOptionals,
54
* - If the manual dependencies had extraneous deps, then auto memoization
55
* will remove them and cause the value to update *less* frequently.
56
*
56
- * We consider a value V as missing if ALL of the following conditions are met:
57
- * - V is reactive
58
- * - There is no manual dependency path P such that whenever V would change,
59
- * P would also change. If V is `x.y.z`, this means there must be some
60
- * path P that is either `x.y.z`, `x.y`, or `x`. Note that we assume no
61
- * interior mutability, such that a shorter path "covers" changes to longer
62
- * more precise paths.
63
- *
64
- * We consider a value V extraneous if either of the folowing are true:
65
- * - V is a reactive local that is unreferenced
66
- * - V is a global that is unreferenced
67
- *
68
- * In other words, we allow extraneous non-reactive values since we know they cannot
69
- * impact how often the memoization would run.
57
+ * The implementation compares the manual dependencies against the values
58
+ * actually used within the memoization function
59
+ * - For each value V referenced in the memo function, either:
60
+ * - If the value is non-reactive *and* a known stable type, then the
61
+ * value may optionally be specified as an exact dependency.
62
+ * - Otherwise, report an error unless there is a manual dependency that will
63
+ * invalidate whenever V invalidates. If `x.y.z` is referenced, there must
64
+ * be a manual dependency for `x.y.z`, `x.y`, or `x`. Note that we assume
65
+ * no interior mutability, ie we assume that any changes to inner paths must
66
+ * always cause the other path to change as well.
67
+ * - Any dependencies that do not correspond to a value referenced in the memo
68
+ * function are considered extraneous and throw an error
69
*
70
* ## TODO: Invalid, Complex Deps
71
*
225
reason: 'Unexpected function dependency',
226
loc: value.loc,
227
});
229
- const isRequiredDependency = reactive.has(
230
- inferredDependency.identifier.id,
231
- );
228
let hasMatchingManualDependency = false;
229
for (const manualDependency of manualDependencies) {
230
if (
239
) {
240
hasMatchingManualDependency = true;
241
matched.add(manualDependency);
246
- if (!isRequiredDependency) {
247
- extra.push(manualDependency);
248
- }
242
}
243
}
251
- if (isRequiredDependency && !hasMatchingManualDependency) {
252
- missing.push(inferredDependency);
244
+ const isOptionalDependency =
245
+ !reactive.has(inferredDependency.identifier.id) &&
246
+ (isStableType(inferredDependency.identifier) ||
247
+ isPrimitiveType(inferredDependency.identifier));
248
+ if (hasMatchingManualDependency || isOptionalDependency) {
249
+ continue;
250
}
251
+ missing.push(inferredDependency);
252
}
253
254
for (const dep of startMemo.deps ?? []) {
255
if (matched.has(dep)) {
256
continue;
257
}
258
+ if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
259
+ CompilerError.simpleInvariant(
260
+ !dep.root.value.reactive &&
261
+ isPrimitiveType(dep.root.value.identifier),
262
+ {
263
+ reason: 'Expected constant-folded dependency to be non-reactive',
264
+ loc: dep.root.value.loc,
265
+ },
266
+ );
267
+ /*
268
+ * Constant primitives can get constant-folded, which means we won't
269
+ * see a LoadLocal for the value within the memo function.
270
+ */
271
+ continue;
272
+ }
273
extra.push(dep);
274
}
275
263
- /**
264
- * Per docblock, we only consider dependencies as extraneous if
265
- * they are unused globals or reactive locals. Notably, this allows
266
- * non-reactive locals.
267
- */
268
- retainWhere(extra, dep => {
269
- return dep.root.kind === 'Global' || dep.root.value.reactive;
270
- });
271
-
276
if (missing.length !== 0 || extra.length !== 0) {
277
let suggestions: Array<CompilerSuggestion> | null = null;
278
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {