@@ -32250,7 +32250,7 @@ const EnvironmentConfigSchema = v4.z.object({
3225032250 enableResetCacheOnSourceFileChanges: v4.z.nullable(v4.z.boolean()).default(null),
3225132251 enablePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
3225232252 validatePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
32253- validateExhaustiveMemoizationDependencies: v4.z.boolean().default(false ),
32253+ validateExhaustiveMemoizationDependencies: v4.z.boolean().default(true ),
3225432254 enablePreserveExistingManualUseMemo: v4.z.boolean().default(false),
3225532255 enableForest: v4.z.boolean().default(false),
3225632256 enableUseTypeAnnotations: v4.z.boolean().default(false),
@@ -34534,6 +34534,19 @@ function evaluateInstruction(constants, instr) {
3453434534 constantPropagationImpl(value.loweredFunc.func, constants);
3453534535 return null;
3453634536 }
34537+ case 'StartMemoize': {
34538+ if (value.deps != null) {
34539+ for (const dep of value.deps) {
34540+ if (dep.root.kind === 'NamedLocal') {
34541+ const placeValue = read(constants, dep.root.value);
34542+ if (placeValue != null && placeValue.kind === 'Primitive') {
34543+ dep.root.constant = true;
34544+ }
34545+ }
34546+ }
34547+ }
34548+ return null;
34549+ }
3453734550 default: {
3453834551 return null;
3453934552 }
@@ -44651,6 +44664,7 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
4465144664 identifierName: value.binding.name,
4465244665 },
4465344666 path: [],
44667+ loc: value.loc,
4465444668 };
4465544669 }
4465644670 case 'PropertyLoad': {
@@ -44659,6 +44673,7 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
4465944673 return {
4466044674 root: object.root,
4466144675 path: [...object.path, { property: value.property, optional }],
44676+ loc: value.loc,
4466244677 };
4466344678 }
4466444679 break;
@@ -44675,8 +44690,10 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
4467544690 root: {
4467644691 kind: 'NamedLocal',
4467744692 value: Object.assign({}, value.place),
44693+ constant: false,
4467844694 },
4467944695 path: [],
44696+ loc: value.place.loc,
4468044697 };
4468144698 }
4468244699 break;
@@ -50238,6 +50255,7 @@ function validateInferredDep(dep, temporaries, declsWithinMemoBlock, validDepsIn
5023850255 normalizedDep = {
5023950256 root: maybeNormalizedRoot.root,
5024050257 path: [...maybeNormalizedRoot.path, ...dep.path],
50258+ loc: maybeNormalizedRoot.loc,
5024150259 };
5024250260 }
5024350261 else {
@@ -50263,8 +50281,10 @@ function validateInferredDep(dep, temporaries, declsWithinMemoBlock, validDepsIn
5026350281 effect: Effect.Read,
5026450282 reactive: false,
5026550283 },
50284+ constant: false,
5026650285 },
5026750286 path: [...dep.path],
50287+ loc: GeneratedSource,
5026850288 };
5026950289 }
5027050290 for (const decl of declsWithinMemoBlock) {
@@ -50350,8 +50370,10 @@ class Visitor extends ReactiveFunctionVisitor {
5035050370 root: {
5035150371 kind: 'NamedLocal',
5035250372 value: storeTarget,
50373+ constant: false,
5035350374 },
5035450375 path: [],
50376+ loc: storeTarget.loc,
5035550377 });
5035650378 }
5035750379 }
@@ -50378,8 +50400,10 @@ class Visitor extends ReactiveFunctionVisitor {
5037850400 root: {
5037950401 kind: 'NamedLocal',
5038050402 value: Object.assign({}, lvalue),
50403+ constant: false,
5038150404 },
5038250405 path: [],
50406+ loc: lvalue.loc,
5038350407 });
5038450408 }
5038550409 }
@@ -53499,7 +53523,7 @@ function validateExhaustiveDependencies(fn) {
5349953523 locals.clear();
5350053524 }
5350153525 function onFinishMemoize(value, dependencies, locals) {
53502- var _b, _c, _d;
53526+ var _b, _c, _d, _e, _f, _g, _h, _j ;
5350353527 CompilerError.simpleInvariant(startMemo != null && startMemo.manualMemoId === value.manualMemoId, {
5350453528 reason: 'Found FinishMemoize without corresponding StartMemoize',
5350553529 loc: value.loc,
@@ -53578,7 +53602,6 @@ function validateExhaustiveDependencies(fn) {
5357853602 reason: 'Unexpected function dependency',
5357953603 loc: value.loc,
5358053604 });
53581- const isRequiredDependency = reactive.has(inferredDependency.identifier.id);
5358253605 let hasMatchingManualDependency = false;
5358353606 for (const manualDependency of manualDependencies) {
5358453607 if (manualDependency.root.kind === 'NamedLocal' &&
@@ -53588,72 +53611,114 @@ function validateExhaustiveDependencies(fn) {
5358853611 isSubPathIgnoringOptionals(manualDependency.path, inferredDependency.path))) {
5358953612 hasMatchingManualDependency = true;
5359053613 matched.add(manualDependency);
53591- if (!isRequiredDependency) {
53592- extra.push(manualDependency);
53593- }
5359453614 }
5359553615 }
53596- if (isRequiredDependency && !hasMatchingManualDependency) {
53597- missing.push(inferredDependency);
53616+ if (hasMatchingManualDependency ||
53617+ isOptionalDependency(inferredDependency, reactive)) {
53618+ continue;
5359853619 }
53620+ missing.push(inferredDependency);
5359953621 }
5360053622 for (const dep of (_c = startMemo.deps) !== null && _c !== void 0 ? _c : []) {
5360153623 if (matched.has(dep)) {
5360253624 continue;
5360353625 }
53626+ if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
53627+ CompilerError.simpleInvariant(!dep.root.value.reactive &&
53628+ isPrimitiveType(dep.root.value.identifier), {
53629+ reason: 'Expected constant-folded dependency to be non-reactive',
53630+ loc: dep.root.value.loc,
53631+ });
53632+ continue;
53633+ }
5360453634 extra.push(dep);
5360553635 }
53606- retainWhere(extra, dep => {
53607- return dep.root.kind === 'Global' || dep.root.value.reactive;
53608- });
5360953636 if (missing.length !== 0 || extra.length !== 0) {
53610- let suggestions = null;
53637+ let suggestion = null;
5361153638 if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
53612- suggestions = [
53613- {
53614- description: 'Update dependencies',
53615- range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
53616- op: CompilerSuggestionOperation.Replace,
53617- text: `[${inferred.map(printInferredDependency).join(', ')}]`,
53618- },
53619- ];
53639+ suggestion = {
53640+ description: 'Update dependencies',
53641+ range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
53642+ op: CompilerSuggestionOperation.Replace,
53643+ text: `[${inferred
53644+ .filter(dep => dep.kind === 'Local' && !isOptionalDependency(dep, reactive))
53645+ .map(printInferredDependency)
53646+ .join(', ')}]`,
53647+ };
5362053648 }
53621- if (missing.length !== 0) {
53622- const diagnostic = CompilerDiagnostic.create({
53623- category: ErrorCategory.MemoDependencies,
53624- reason: 'Found missing memoization dependencies',
53625- description: 'Missing dependencies can cause a value not to update when those inputs change, ' +
53626- 'resulting in stale UI',
53627- suggestions,
53649+ const diagnostic = CompilerDiagnostic.create({
53650+ category: ErrorCategory.MemoDependencies,
53651+ reason: 'Found missing/extra memoization dependencies',
53652+ description: [
53653+ missing.length !== 0
53654+ ? 'Missing dependencies can cause a value to update less often than it should, ' +
53655+ 'resulting in stale UI'
53656+ : null,
53657+ extra.length !== 0
53658+ ? 'Extra dependencies can cause a value to update more often than it should, ' +
53659+ 'resulting in performance problems such as excessive renders or effects firing too often'
53660+ : null,
53661+ ]
53662+ .filter(Boolean)
53663+ .join('. '),
53664+ suggestions: suggestion != null ? [suggestion] : null,
53665+ });
53666+ for (const dep of missing) {
53667+ let reactiveStableValueHint = '';
53668+ if (isStableType(dep.identifier)) {
53669+ reactiveStableValueHint =
53670+ '. Refs, setState functions, and other "stable" values generally do not need to be added ' +
53671+ 'as dependencies, but this variable may change over time to point to different values';
53672+ }
53673+ diagnostic.withDetails({
53674+ kind: 'error',
53675+ message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
53676+ loc: dep.loc,
5362853677 });
53629- for (const dep of missing) {
53630- let reactiveStableValueHint = '';
53631- if (isStableType(dep.identifier)) {
53632- reactiveStableValueHint =
53633- '. Refs, setState functions, and other "stable" values generally do not need to be added as dependencies, but this variable may change over time to point to different values';
53634- }
53678+ }
53679+ for (const dep of extra) {
53680+ if (dep.root.kind === 'Global') {
5363553681 diagnostic.withDetails({
5363653682 kind: 'error',
53637- message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
53638- loc: dep.loc,
53683+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
53684+ 'Values declared outside of a component/hook should not be listed as ' +
53685+ 'dependencies as the component will not re-render if they change',
53686+ loc: (_e = (_d = dep.loc) !== null && _d !== void 0 ? _d : startMemo.depsLoc) !== null && _e !== void 0 ? _e : value.loc,
5363953687 });
53688+ error.pushDiagnostic(diagnostic);
53689+ }
53690+ else {
53691+ const root = dep.root.value;
53692+ const matchingInferred = inferred.find((inferredDep) => {
53693+ return (inferredDep.kind === 'Local' &&
53694+ inferredDep.identifier.id === root.identifier.id &&
53695+ isSubPathIgnoringOptionals(inferredDep.path, dep.path));
53696+ });
53697+ if (matchingInferred != null &&
53698+ !isOptionalDependency(matchingInferred, reactive)) {
53699+ diagnostic.withDetails({
53700+ kind: 'error',
53701+ message: `Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
53702+ `use \`${printInferredDependency(matchingInferred)}\` instead`,
53703+ loc: (_g = (_f = dep.loc) !== null && _f !== void 0 ? _f : startMemo.depsLoc) !== null && _g !== void 0 ? _g : value.loc,
53704+ });
53705+ }
53706+ else {
53707+ diagnostic.withDetails({
53708+ kind: 'error',
53709+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
53710+ loc: (_j = (_h = dep.loc) !== null && _h !== void 0 ? _h : startMemo.depsLoc) !== null && _j !== void 0 ? _j : value.loc,
53711+ });
53712+ }
5364053713 }
53641- error.pushDiagnostic(diagnostic);
5364253714 }
53643- else if (extra.length !== 0) {
53644- const diagnostic = CompilerDiagnostic.create({
53645- category: ErrorCategory.MemoDependencies,
53646- reason: 'Found unnecessary memoization dependencies',
53647- description: 'Unnecessary dependencies can cause a value to update more often than necessary, ' +
53648- 'causing performance regressions and effects to fire more often than expected',
53649- });
53715+ if (suggestion != null) {
5365053716 diagnostic.withDetails({
53651- kind: 'error',
53652- message: `Unnecessary dependencies ${extra.map(dep => `\`${printManualMemoDependency(dep)}\``).join(', ')}`,
53653- loc: (_d = startMemo.depsLoc) !== null && _d !== void 0 ? _d : value.loc,
53717+ kind: 'hint',
53718+ message: `Inferred dependencies: \`${suggestion.text}\``,
5365453719 });
53655- error.pushDiagnostic(diagnostic);
5365653720 }
53721+ error.pushDiagnostic(diagnostic);
5365753722 }
5365853723 dependencies.clear();
5365953724 locals.clear();
@@ -54044,6 +54109,11 @@ function findOptionalPlaces(fn) {
5404454109 }
5404554110 return optionals;
5404654111}
54112+ function isOptionalDependency(inferredDependency, reactive) {
54113+ return (!reactive.has(inferredDependency.identifier.id) &&
54114+ (isStableType(inferredDependency.identifier) ||
54115+ isPrimitiveType(inferredDependency.identifier)));
54116+ }
5404754117
5404854118function run(func, config, fnType, mode, programContext, logger, filename, code) {
5404954119 var _a, _b;
0 commit comments