@@ -18684,7 +18684,7 @@ function printTerminal(terminal) {
18684
18684
break;
18685
18685
}
18686
18686
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) : ''}`;
18688
18688
if (terminal.effects != null) {
18689
18689
value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`;
18690
18690
}
@@ -20033,6 +20033,7 @@ function mapTerminalSuccessors(terminal, fn) {
20033
20033
case 'return': {
20034
20034
return {
20035
20035
kind: 'return',
20036
+ returnVariant: terminal.returnVariant,
20036
20037
loc: terminal.loc,
20037
20038
value: terminal.value,
20038
20039
id: makeInstructionId(0),
@@ -22396,6 +22397,7 @@ function lower$1(func, env, bindings = null, capturedRefs = new Map()) {
22396
22397
const fallthrough = builder.reserve('block');
22397
22398
const terminal = {
22398
22399
kind: 'return',
22400
+ returnVariant: 'Implicit',
22399
22401
loc: GeneratedSource,
22400
22402
value: lowerExpressionToTemporary(builder, body),
22401
22403
id: makeInstructionId(0),
@@ -22423,6 +22425,7 @@ function lower$1(func, env, bindings = null, capturedRefs = new Map()) {
22423
22425
}
22424
22426
builder.terminate({
22425
22427
kind: 'return',
22428
+ returnVariant: 'Void',
22426
22429
loc: GeneratedSource,
22427
22430
value: lowerValueToTemporary(builder, {
22428
22431
kind: 'Primitive',
@@ -22490,6 +22493,7 @@ function lowerStatement(builder, stmtPath, label = null) {
22490
22493
}
22491
22494
const terminal = {
22492
22495
kind: 'return',
22496
+ returnVariant: 'Explicit',
22493
22497
loc: (_c = stmt.node.loc) !== null && _c !== void 0 ? _c : GeneratedSource,
22494
22498
value,
22495
22499
id: makeInstructionId(0),
@@ -30730,6 +30734,7 @@ const EnvironmentConfigSchema = zod.z.object({
30730
30734
hookPattern: zod.z.string().nullable().default(null),
30731
30735
enableTreatRefLikeIdentifiersAsRefs: zod.z.boolean().default(false),
30732
30736
lowerContextAccess: ExternalFunctionSchema.nullable().default(null),
30737
+ validateNoVoidUseMemo: zod.z.boolean().default(false),
30733
30738
});
30734
30739
class Environment {
30735
30740
constructor(scope, fnType, compilerMode, config, contextIdentifiers, parentFunction, logger, filename, code, programContext) {
@@ -43847,50 +43852,78 @@ function getManualMemoizationReplacement(fn, loc, kind) {
43847
43852
};
43848
43853
}
43849
43854
}
43850
- function extractManualMemoizationArgs(instr, kind, sidemap) {
43855
+ function extractManualMemoizationArgs(instr, kind, sidemap, errors ) {
43851
43856
const [fnPlace, depsListPlace] = instr.value.args;
43852
43857
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}`,
43856
43862
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 };
43858
43869
}
43859
43870
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}`,
43863
43875
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 };
43865
43882
}
43866
43883
let depsList = null;
43867
43884
if (depsListPlace != null) {
43868
43885
const maybeDepsList = sidemap.maybeDepsLists.get(depsListPlace.identifier.id);
43869
43886
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`,
43872
43891
suggestions: null,
43892
+ }).withDetail({
43893
+ kind: 'error',
43873
43894
loc: depsListPlace.loc,
43874
- });
43895
+ message: `Expected the dependency list for ${kind} to be an array literal`,
43896
+ }));
43897
+ return { fnPlace, depsList: null };
43875
43898
}
43876
- depsList = maybeDepsList.map(dep => {
43899
+ depsList = [];
43900
+ for (const dep of maybeDepsList) {
43877
43901
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
43878
43902
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\`)`,
43881
43907
suggestions: null,
43908
+ }).withDetail({
43909
+ kind: 'error',
43882
43910
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
+ }));
43884
43913
}
43885
- return maybeDep;
43886
- });
43914
+ else {
43915
+ depsList.push(maybeDep);
43916
+ }
43917
+ }
43887
43918
}
43888
43919
return {
43889
43920
fnPlace,
43890
43921
depsList,
43891
43922
};
43892
43923
}
43893
43924
function dropManualMemoization(func) {
43925
+ var _a;
43926
+ const errors = new CompilerError();
43894
43927
const isValidationEnabled = func.env.config.validatePreserveExistingMemoizationGuarantees ||
43895
43928
func.env.config.validateNoSetStateInRender ||
43896
43929
func.env.config.enablePreserveExistingMemoizationGuarantees;
@@ -43915,15 +43948,44 @@ function dropManualMemoization(func) {
43915
43948
: instr.value.property.identifier.id;
43916
43949
const manualMemo = sidemap.manualMemos.get(id);
43917
43950
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
+ }
43919
43975
instr.value = getManualMemoizationReplacement(fnPlace, instr.value.loc, manualMemo.kind);
43920
43976
if (isValidationEnabled) {
43921
43977
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`,
43924
43982
suggestions: [],
43983
+ }).withDetail({
43984
+ kind: 'error',
43925
43985
loc: fnPlace.loc,
43926
- });
43986
+ message: `Expected the first argument to be an inline function expression`,
43987
+ }));
43988
+ continue;
43927
43989
}
43928
43990
const memoDecl = manualMemo.kind === 'useMemo'
43929
43991
? instr.lvalue
@@ -43970,6 +44032,7 @@ function dropManualMemoization(func) {
43970
44032
markInstructionIds(func.body);
43971
44033
}
43972
44034
}
44035
+ return errors.asResult();
43973
44036
}
43974
44037
function findOptionalPlaces(fn) {
43975
44038
const optionals = new Set();
@@ -44013,6 +44076,17 @@ function findOptionalPlaces(fn) {
44013
44076
}
44014
44077
return optionals;
44015
44078
}
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
+ }
44016
44090
44017
44091
class StableSidemap {
44018
44092
constructor(env) {
@@ -49456,6 +49530,7 @@ function emitSelectorFn(env, keys) {
49456
49530
terminal: {
49457
49531
id: makeInstructionId(0),
49458
49532
kind: 'return',
49533
+ returnVariant: 'Explicit',
49459
49534
loc: GeneratedSource,
49460
49535
value: arrayInstr.lvalue,
49461
49536
effects: null,
@@ -49887,6 +49962,7 @@ function emitOutlinedFn(env, jsx, oldProps, globals) {
49887
49962
terminal: {
49888
49963
id: makeInstructionId(0),
49889
49964
kind: 'return',
49965
+ returnVariant: 'Explicit',
49890
49966
loc: GeneratedSource,
49891
49967
value: instructions.at(-1).lvalue,
49892
49968
effects: null,
@@ -50707,7 +50783,7 @@ function runWithEnvironment(func, env) {
50707
50783
!env.config.enablePreserveExistingManualUseMemo &&
50708
50784
!env.config.disableMemoizationForDebugging &&
50709
50785
!env.config.enableChangeDetectionForDebugging) {
50710
- dropManualMemoization(hir);
50786
+ dropManualMemoization(hir).unwrap() ;
50711
50787
log({ kind: 'hir', name: 'DropManualMemoization', value: hir });
50712
50788
}
50713
50789
inlineImmediatelyInvokedFunctionExpressions(hir);
@@ -52657,6 +52733,7 @@ const COMPILER_OPTIONS = {
52657
52733
validateNoImpureFunctionsInRender: true,
52658
52734
validateStaticComponents: true,
52659
52735
validateNoFreezingKnownMutableFunctions: true,
52736
+ validateNoVoidUseMemo: true,
52660
52737
}),
52661
52738
};
52662
52739
const rule$1 = {
0 commit comments