@@ -43,20 +43,37 @@ import {retainWhere} from '../Utils/utils';
4343const DEBUG = false ;
4444
4545/**
46- * Validates that existing manual memoization had exhaustive dependencies.
47- * Memoization with missing or extra reactive dependencies is invalid React
48- * and compilation can change behavior, causing a value to be computed more
49- * or less times.
46+ * Validates that existing manual memoization is exhaustive and does not
47+ * have extraneous dependencies. The goal of the validation is to ensure
48+ * that auto-memoization will not substantially change the behavior of
49+ * the program:
50+ * - If the manual dependencies were non-exhaustive (missing important deps)
51+ * then auto-memoization will include those dependencies, and cause the
52+ * value to update *more* frequently.
53+ * - If the manual dependencies had extraneous deps, then auto memoization
54+ * will remove them and cause the value to update *less* frequently.
5055 *
51- * TODOs:
52- * - Handle cases of mixed optional and non-optional versions of the same path,
53- * eg referecing both x.y.z and x.y?.z in the same memo block. we should collapse
54- * this into a single canonical dep that we look for in the manual deps. see the
55- * existing exhaustive deps rule for implementation.
56- * - Handle cases where the user deps were not simple identifiers + property chains.
57- * We try to detect this in ValidateUseMemo but we miss some cases. The problem
58- * is that invalid forms can be value blocks or function calls that don't get
59- * removed by DCE, leaving a structure like:
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.
70+ *
71+ * ## TODO: Invalid, Complex Deps
72+ *
73+ * Handle cases where the user deps were not simple identifiers + property chains.
74+ * We try to detect this in ValidateUseMemo but we miss some cases. The problem
75+ * is that invalid forms can be value blocks or function calls that don't get
76+ * removed by DCE, leaving a structure like:
6077 *
6178 * StartMemoize
6279 * t0 = <value to memoize>
@@ -209,31 +226,9 @@ export function validateExhaustiveDependencies(
209226 reason : 'Unexpected function dependency' ,
210227 loc : value . loc ,
211228 } ) ;
212- /**
213- * Dependencies technically only need to include reactive values. However,
214- * reactivity inference for general values is subtle since it involves all
215- * of our complex control and data flow analysis. To keep results more
216- * stable and predictable to developers, we intentionally stay closer to
217- * the rules of the classic exhaustive-deps rule. Values should be included
218- * as dependencies if either of the following is true:
219- * - They're reactive
220- * - They're non-reactive and not a known-stable value type.
221- *
222- * Thus `const ref: Ref = cond ? ref1 : ref2` has to be a dependency
223- * (assuming `cond` is reactive) since it's reactive despite being a ref.
224- *
225- * Similarly, `const x = [1,2,3]` has to be a dependency since even
226- * though it's non reactive, it's not a known stable type.
227- *
228- * TODO: consider reimplementing a simpler form of reactivity inference.
229- * Ideally we'd consider `const ref: Ref = cond ? ref1 : ref2` as a required
230- * dependency even if our data/control flow tells us that `cond` is non-reactive.
231- * It's simpler for developers to reason about based on a more structural/AST
232- * driven approach.
233- */
234- const isRequiredDependency =
235- reactive . has ( inferredDependency . identifier . id ) ||
236- ! isStableType ( inferredDependency . identifier ) ;
229+ const isRequiredDependency = reactive . has (
230+ inferredDependency . identifier . id ,
231+ ) ;
237232 let hasMatchingManualDependency = false ;
238233 for ( const manualDependency of manualDependencies ) {
239234 if (
@@ -265,18 +260,13 @@ export function validateExhaustiveDependencies(
265260 extra . push ( dep ) ;
266261 }
267262
268- /*
269- * For compatiblity with the existing exhaustive-deps rule, we allow
270- * known-stable values as dependencies even if the value is not reactive.
271- * This allows code that takes a dep on a non-reactive setState function
272- * to pass, for example.
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.
273267 */
274268 retainWhere ( extra , dep => {
275- const isNonReactiveStableValue =
276- dep . root . kind === 'NamedLocal' &&
277- ! dep . root . value . reactive &&
278- isStableType ( dep . root . value . identifier ) ;
279- return ! isNonReactiveStableValue ;
269+ return dep . root . kind === 'Global' || dep . root . value . reactive ;
280270 } ) ;
281271
282272 if ( missing . length !== 0 || extra . length !== 0 ) {
0 commit comments