[compiler] Aggregate all errors reported from DropManualMemoization (#34002)
Noticed this from my previous PR that this pass was throwing on the first error. This PR is a small refactor to aggregate every violation and report them all at once. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34002). * #34022 * __->__ #34002
lauren committed
Jul 28, 2025 at 13:25 UTC
6b22f31f1ac88cce1b38c67a5c97c7ab0e832823
3 files changed
+80
-29
compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
+76
-29
@@ -289,26 +289,43 @@ function extractManualMemoizationArgs(
289
instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
290
kind: 'useCallback' | 'useMemo',
291
sidemap: IdentifierSidemap,
292
+ errors: CompilerError,
293
): {
293
- fnPlace: Place;
294
+ fnPlace: Place | null;
295
depsList: Array<ManualMemoDependency> | null;
296
} {
297
const [fnPlace, depsListPlace] = instr.value.args as Array<
298
Place | SpreadPattern | undefined
299
>;
300
if (fnPlace == null) {
300
- CompilerError.throwInvalidReact({
301
- reason: `Expected a callback function to be passed to ${kind}`,
302
- loc: instr.value.loc,
303
- suggestions: null,
304
- });
301
+ errors.pushDiagnostic(
302
+ CompilerDiagnostic.create({
303
+ severity: ErrorSeverity.InvalidReact,
304
+ category: `Expected a callback function to be passed to ${kind}`,
305
+ description: `Expected a callback function to be passed to ${kind}`,
306
+ suggestions: null,
307
+ }).withDetail({
308
+ kind: 'error',
309
+ loc: instr.value.loc,
310
+ message: `Expected a callback function to be passed to ${kind}`,
311
+ }),
312
+ );
313
+ return {fnPlace: null, depsList: null};
314
}
315
if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') {
307
- CompilerError.throwInvalidReact({
308
- reason: `Unexpected spread argument to ${kind}`,
309
- loc: instr.value.loc,
310
- suggestions: null,
311
- });
316
+ errors.pushDiagnostic(
317
+ CompilerDiagnostic.create({
318
+ severity: ErrorSeverity.InvalidReact,
319
+ category: `Unexpected spread argument to ${kind}`,
320
+ description: `Unexpected spread argument to ${kind}`,
321
+ suggestions: null,
322
+ }).withDetail({
323
+ kind: 'error',
324
+ loc: instr.value.loc,
325
+ message: `Unexpected spread argument to ${kind}`,
326
+ }),
327
+ );
328
+ return {fnPlace: null, depsList: null};
329
}
330
let depsList: Array<ManualMemoDependency> | null = null;
331
if (depsListPlace != null) {
@@ -316,23 +333,40 @@ function extractManualMemoizationArgs(
333
depsListPlace.identifier.id,
334
);
335
if (maybeDepsList == null) {
319
- CompilerError.throwInvalidReact({
320
- reason: `Expected the dependency list for ${kind} to be an array literal`,
321
- suggestions: null,
322
- loc: depsListPlace.loc,
323
- });
336
+ errors.pushDiagnostic(
337
+ CompilerDiagnostic.create({
338
+ severity: ErrorSeverity.InvalidReact,
339
+ category: `Expected the dependency list for ${kind} to be an array literal`,
340
+ description: `Expected the dependency list for ${kind} to be an array literal`,
341
+ suggestions: null,
342
+ }).withDetail({
343
+ kind: 'error',
344
+ loc: depsListPlace.loc,
345
+ message: `Expected the dependency list for ${kind} to be an array literal`,
346
+ }),
347
+ );
348
+ return {fnPlace, depsList: null};
349
}
325
- depsList = maybeDepsList.map(dep => {
350
+ depsList = [];
351
+ for (const dep of maybeDepsList) {
352
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
353
if (maybeDep == null) {
328
- CompilerError.throwInvalidReact({
329
- reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
330
- suggestions: null,
331
- loc: dep.loc,
332
- });
354
+ errors.pushDiagnostic(
355
+ CompilerDiagnostic.create({
356
+ severity: ErrorSeverity.InvalidReact,
357
+ category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
358
+ description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
359
+ suggestions: null,
360
+ }).withDetail({
361
+ kind: 'error',
362
+ loc: dep.loc,
363
+ message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
364
+ }),
365
+ );
366
+ } else {
367
+ depsList.push(maybeDep);
368
}
334
- return maybeDep;
335
- });
369
+ }
370
}
371
return {
372
fnPlace,
@@ -401,8 +435,13 @@ export function dropManualMemoization(
435
instr as TInstruction<CallExpression> | TInstruction<MethodCall>,
436
manualMemo.kind,
437
sidemap,
438
+ errors,
439
);
440
441
+ if (fnPlace == null) {
442
+ continue;
443
+ }
444
+
445
/**
446
* Bailout on void return useMemos. This is an anti-pattern where code might be using
447
* useMemo like useEffect: running arbirtary side-effects synced to changes in specific
@@ -457,11 +496,19 @@ export function dropManualMemoization(
496
* is rare and likely sketchy.
497
*/
498
if (!sidemap.functions.has(fnPlace.identifier.id)) {
460
- CompilerError.throwInvalidReact({
461
- reason: `Expected the first argument to be an inline function expression`,
462
- suggestions: [],
463
- loc: fnPlace.loc,
464
- });
499
+ errors.pushDiagnostic(
500
+ CompilerDiagnostic.create({
501
+ severity: ErrorSeverity.InvalidReact,
502
+ category: `Expected the first argument to be an inline function expression`,
503
+ description: `Expected the first argument to be an inline function expression`,
504
+ suggestions: [],
505
+ }).withDetail({
506
+ kind: 'error',
507
+ loc: fnPlace.loc,
508
+ message: `Expected the first argument to be an inline function expression`,
509
+ }),
510
+ );
511
+ continue;
512
}
513
const memoDecl: Place =
514
manualMemo.kind === 'useMemo'
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md
+2
@@ -32,6 +32,8 @@ Found 1 error:
32
33
Error: Expected the dependency list for useMemo to be an array literal
34
35
+Expected the dependency list for useMemo to be an array literal
36
+
37
error.useMemo-non-literal-depslist.ts:10:4
38
8 | return text.toUpperCase();
39
9 | },
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md
+2
@@ -24,6 +24,8 @@ Found 1 error:
24
25
Error: Expected the first argument to be an inline function expression
26
27
+Expected the first argument to be an inline function expression
28
+
29
error.validate-useMemo-named-function.ts:9:20
30
7 | // for now.
31
8 | function Component(props) {