@samitouri / QOS-React-1 / commits / 6b22f31f1a

[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) {