@@ -18684,7 +18684,7 @@ function printTerminal(terminal) {
1868418684 break;
1868518685 }
1868618686 case 'return': {
18687- value = `[${terminal.id}] Return${terminal.value != null ? ' ' + printPlace(terminal.value) : ''}`;
18687+ value = `[${terminal.id}] Return ${terminal.returnVariant} ${terminal.value != null ? ' ' + printPlace(terminal.value) : ''}`;
1868818688 if (terminal.effects != null) {
1868918689 value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`;
1869018690 }
@@ -20033,6 +20033,7 @@ function mapTerminalSuccessors(terminal, fn) {
2003320033 case 'return': {
2003420034 return {
2003520035 kind: 'return',
20036+ returnVariant: terminal.returnVariant,
2003620037 loc: terminal.loc,
2003720038 value: terminal.value,
2003820039 id: makeInstructionId(0),
@@ -22396,6 +22397,7 @@ function lower$1(func, env, bindings = null, capturedRefs = new Map()) {
2239622397 const fallthrough = builder.reserve('block');
2239722398 const terminal = {
2239822399 kind: 'return',
22400+ returnVariant: 'Implicit',
2239922401 loc: GeneratedSource,
2240022402 value: lowerExpressionToTemporary(builder, body),
2240122403 id: makeInstructionId(0),
@@ -22423,6 +22425,7 @@ function lower$1(func, env, bindings = null, capturedRefs = new Map()) {
2242322425 }
2242422426 builder.terminate({
2242522427 kind: 'return',
22428+ returnVariant: 'Void',
2242622429 loc: GeneratedSource,
2242722430 value: lowerValueToTemporary(builder, {
2242822431 kind: 'Primitive',
@@ -22490,6 +22493,7 @@ function lowerStatement(builder, stmtPath, label = null) {
2249022493 }
2249122494 const terminal = {
2249222495 kind: 'return',
22496+ returnVariant: 'Explicit',
2249322497 loc: (_c = stmt.node.loc) !== null && _c !== void 0 ? _c : GeneratedSource,
2249422498 value,
2249522499 id: makeInstructionId(0),
@@ -30730,6 +30734,7 @@ const EnvironmentConfigSchema = zod.z.object({
3073030734 hookPattern: zod.z.string().nullable().default(null),
3073130735 enableTreatRefLikeIdentifiersAsRefs: zod.z.boolean().default(false),
3073230736 lowerContextAccess: ExternalFunctionSchema.nullable().default(null),
30737+ validateNoVoidUseMemo: zod.z.boolean().default(false),
3073330738});
3073430739class Environment {
3073530740 constructor(scope, fnType, compilerMode, config, contextIdentifiers, parentFunction, logger, filename, code, programContext) {
@@ -43847,50 +43852,78 @@ function getManualMemoizationReplacement(fn, loc, kind) {
4384743852 };
4384843853 }
4384943854}
43850- function extractManualMemoizationArgs(instr, kind, sidemap) {
43855+ function extractManualMemoizationArgs(instr, kind, sidemap, errors ) {
4385143856 const [fnPlace, depsListPlace] = instr.value.args;
4385243857 if (fnPlace == null) {
43853- CompilerError.throwInvalidReact({
43854- reason: `Expected a callback function to be passed to ${kind}`,
43855- loc: instr.value.loc,
43858+ errors.pushDiagnostic(CompilerDiagnostic.create({
43859+ severity: ErrorSeverity.InvalidReact,
43860+ category: `Expected a callback function to be passed to ${kind}`,
43861+ description: `Expected a callback function to be passed to ${kind}`,
4385643862 suggestions: null,
43857- });
43863+ }).withDetail({
43864+ kind: 'error',
43865+ loc: instr.value.loc,
43866+ message: `Expected a callback function to be passed to ${kind}`,
43867+ }));
43868+ return { fnPlace: null, depsList: null };
4385843869 }
4385943870 if (fnPlace.kind === 'Spread' || (depsListPlace === null || depsListPlace === void 0 ? void 0 : depsListPlace.kind) === 'Spread') {
43860- CompilerError.throwInvalidReact({
43861- reason: `Unexpected spread argument to ${kind}`,
43862- loc: instr.value.loc,
43871+ errors.pushDiagnostic(CompilerDiagnostic.create({
43872+ severity: ErrorSeverity.InvalidReact,
43873+ category: `Unexpected spread argument to ${kind}`,
43874+ description: `Unexpected spread argument to ${kind}`,
4386343875 suggestions: null,
43864- });
43876+ }).withDetail({
43877+ kind: 'error',
43878+ loc: instr.value.loc,
43879+ message: `Unexpected spread argument to ${kind}`,
43880+ }));
43881+ return { fnPlace: null, depsList: null };
4386543882 }
4386643883 let depsList = null;
4386743884 if (depsListPlace != null) {
4386843885 const maybeDepsList = sidemap.maybeDepsLists.get(depsListPlace.identifier.id);
4386943886 if (maybeDepsList == null) {
43870- CompilerError.throwInvalidReact({
43871- reason: `Expected the dependency list for ${kind} to be an array literal`,
43887+ errors.pushDiagnostic(CompilerDiagnostic.create({
43888+ severity: ErrorSeverity.InvalidReact,
43889+ category: `Expected the dependency list for ${kind} to be an array literal`,
43890+ description: `Expected the dependency list for ${kind} to be an array literal`,
4387243891 suggestions: null,
43892+ }).withDetail({
43893+ kind: 'error',
4387343894 loc: depsListPlace.loc,
43874- });
43895+ message: `Expected the dependency list for ${kind} to be an array literal`,
43896+ }));
43897+ return { fnPlace, depsList: null };
4387543898 }
43876- depsList = maybeDepsList.map(dep => {
43899+ depsList = [];
43900+ for (const dep of maybeDepsList) {
4387743901 const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
4387843902 if (maybeDep == null) {
43879- CompilerError.throwInvalidReact({
43880- reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
43903+ errors.pushDiagnostic(CompilerDiagnostic.create({
43904+ severity: ErrorSeverity.InvalidReact,
43905+ category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
43906+ description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
4388143907 suggestions: null,
43908+ }).withDetail({
43909+ kind: 'error',
4388243910 loc: dep.loc,
43883- });
43911+ message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
43912+ }));
4388443913 }
43885- return maybeDep;
43886- });
43914+ else {
43915+ depsList.push(maybeDep);
43916+ }
43917+ }
4388743918 }
4388843919 return {
4388943920 fnPlace,
4389043921 depsList,
4389143922 };
4389243923}
4389343924function dropManualMemoization(func) {
43925+ var _a;
43926+ const errors = new CompilerError();
4389443927 const isValidationEnabled = func.env.config.validatePreserveExistingMemoizationGuarantees ||
4389543928 func.env.config.validateNoSetStateInRender ||
4389643929 func.env.config.enablePreserveExistingMemoizationGuarantees;
@@ -43915,15 +43948,44 @@ function dropManualMemoization(func) {
4391543948 : instr.value.property.identifier.id;
4391643949 const manualMemo = sidemap.manualMemos.get(id);
4391743950 if (manualMemo != null) {
43918- const { fnPlace, depsList } = extractManualMemoizationArgs(instr, manualMemo.kind, sidemap);
43951+ const { fnPlace, depsList } = extractManualMemoizationArgs(instr, manualMemo.kind, sidemap, errors);
43952+ if (fnPlace == null) {
43953+ continue;
43954+ }
43955+ if (func.env.config.validateNoVoidUseMemo &&
43956+ manualMemo.kind === 'useMemo') {
43957+ const funcToCheck = (_a = sidemap.functions.get(fnPlace.identifier.id)) === null || _a === void 0 ? void 0 : _a.value;
43958+ if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) {
43959+ if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) {
43960+ errors.pushDiagnostic(CompilerDiagnostic.create({
43961+ severity: ErrorSeverity.InvalidReact,
43962+ category: 'useMemo() callbacks must return a value',
43963+ description: `This ${manualMemo.loadInstr.value.kind === 'PropertyLoad'
43964+ ? 'React.useMemo'
43965+ : 'useMemo'} callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`,
43966+ suggestions: null,
43967+ }).withDetail({
43968+ kind: 'error',
43969+ loc: instr.value.loc,
43970+ message: 'useMemo() callbacks must return a value',
43971+ }));
43972+ }
43973+ }
43974+ }
4391943975 instr.value = getManualMemoizationReplacement(fnPlace, instr.value.loc, manualMemo.kind);
4392043976 if (isValidationEnabled) {
4392143977 if (!sidemap.functions.has(fnPlace.identifier.id)) {
43922- CompilerError.throwInvalidReact({
43923- reason: `Expected the first argument to be an inline function expression`,
43978+ errors.pushDiagnostic(CompilerDiagnostic.create({
43979+ severity: ErrorSeverity.InvalidReact,
43980+ category: `Expected the first argument to be an inline function expression`,
43981+ description: `Expected the first argument to be an inline function expression`,
4392443982 suggestions: [],
43983+ }).withDetail({
43984+ kind: 'error',
4392543985 loc: fnPlace.loc,
43926- });
43986+ message: `Expected the first argument to be an inline function expression`,
43987+ }));
43988+ continue;
4392743989 }
4392843990 const memoDecl = manualMemo.kind === 'useMemo'
4392943991 ? instr.lvalue
@@ -43970,6 +44032,7 @@ function dropManualMemoization(func) {
4397044032 markInstructionIds(func.body);
4397144033 }
4397244034 }
44035+ return errors.asResult();
4397344036}
4397444037function findOptionalPlaces(fn) {
4397544038 const optionals = new Set();
@@ -44013,6 +44076,17 @@ function findOptionalPlaces(fn) {
4401344076 }
4401444077 return optionals;
4401544078}
44079+ function hasNonVoidReturn(func) {
44080+ for (const [, block] of func.body.blocks) {
44081+ if (block.terminal.kind === 'return') {
44082+ if (block.terminal.returnVariant === 'Explicit' ||
44083+ block.terminal.returnVariant === 'Implicit') {
44084+ return true;
44085+ }
44086+ }
44087+ }
44088+ return false;
44089+ }
4401644090
4401744091class StableSidemap {
4401844092 constructor(env) {
@@ -49456,6 +49530,7 @@ function emitSelectorFn(env, keys) {
4945649530 terminal: {
4945749531 id: makeInstructionId(0),
4945849532 kind: 'return',
49533+ returnVariant: 'Explicit',
4945949534 loc: GeneratedSource,
4946049535 value: arrayInstr.lvalue,
4946149536 effects: null,
@@ -49887,6 +49962,7 @@ function emitOutlinedFn(env, jsx, oldProps, globals) {
4988749962 terminal: {
4988849963 id: makeInstructionId(0),
4988949964 kind: 'return',
49965+ returnVariant: 'Explicit',
4989049966 loc: GeneratedSource,
4989149967 value: instructions.at(-1).lvalue,
4989249968 effects: null,
@@ -50707,7 +50783,7 @@ function runWithEnvironment(func, env) {
5070750783 !env.config.enablePreserveExistingManualUseMemo &&
5070850784 !env.config.disableMemoizationForDebugging &&
5070950785 !env.config.enableChangeDetectionForDebugging) {
50710- dropManualMemoization(hir);
50786+ dropManualMemoization(hir).unwrap() ;
5071150787 log({ kind: 'hir', name: 'DropManualMemoization', value: hir });
5071250788 }
5071350789 inlineImmediatelyInvokedFunctionExpressions(hir);
@@ -52657,6 +52733,7 @@ const COMPILER_OPTIONS = {
5265752733 validateNoImpureFunctionsInRender: true,
5265852734 validateStaticComponents: true,
5265952735 validateNoFreezingKnownMutableFunctions: true,
52736+ validateNoVoidUseMemo: true,
5266052737 }),
5266152738};
5266252739const rule$1 = {
0 commit comments