@@ -30871,6 +30871,7 @@ const EnvironmentConfigSchema = zod.z.object({
30871
30871
validateRefAccessDuringRender: zod.z.boolean().default(true),
30872
30872
validateNoSetStateInRender: zod.z.boolean().default(true),
30873
30873
validateNoSetStateInEffects: zod.z.boolean().default(false),
30874
+ validateNoDerivedComputationsInEffects: zod.z.boolean().default(false),
30874
30875
validateNoJSXInTryStatements: zod.z.boolean().default(false),
30875
30876
validateStaticComponents: zod.z.boolean().default(false),
30876
30877
validateMemoizedEffectDependencies: zod.z.boolean().default(false),
@@ -49629,46 +49630,6 @@ function outlineFunctions(fn, fbtOperands) {
49629
49630
}
49630
49631
}
49631
49632
49632
- function propagatePhiTypes(fn) {
49633
- const propagated = new Set();
49634
- for (const [, block] of fn.body.blocks) {
49635
- for (const phi of block.phis) {
49636
- if (phi.place.identifier.type.kind !== 'Type' ||
49637
- phi.place.identifier.name !== null) {
49638
- continue;
49639
- }
49640
- let type = null;
49641
- for (const [, operand] of phi.operands) {
49642
- if (type === null) {
49643
- type = operand.identifier.type;
49644
- }
49645
- else if (!typeEquals(type, operand.identifier.type)) {
49646
- type = null;
49647
- break;
49648
- }
49649
- }
49650
- if (type !== null) {
49651
- phi.place.identifier.type = type;
49652
- propagated.add(phi.place.identifier.id);
49653
- }
49654
- }
49655
- for (const instr of block.instructions) {
49656
- const { value } = instr;
49657
- switch (value.kind) {
49658
- case 'StoreLocal': {
49659
- const lvalue = value.lvalue.place;
49660
- if (propagated.has(value.value.identifier.id) &&
49661
- lvalue.identifier.type.kind === 'Type' &&
49662
- lvalue.identifier.name === null) {
49663
- lvalue.identifier.type = value.value.identifier.type;
49664
- propagated.add(lvalue.identifier.id);
49665
- }
49666
- }
49667
- }
49668
- }
49669
- }
49670
- }
49671
-
49672
49633
function lowerContextAccess(fn, loweredContextCalleeConfig) {
49673
49634
const contextAccess = new Map();
49674
49635
const contextKeys = new Map();
@@ -51050,6 +51011,167 @@ function validateNoFreezingKnownMutableFunctions(fn) {
51050
51011
return errors.asResult();
51051
51012
}
51052
51013
51014
+ function validateNoDerivedComputationsInEffects(fn) {
51015
+ const candidateDependencies = new Map();
51016
+ const functions = new Map();
51017
+ const locals = new Map();
51018
+ const errors = new CompilerError();
51019
+ for (const block of fn.body.blocks.values()) {
51020
+ for (const instr of block.instructions) {
51021
+ const { lvalue, value } = instr;
51022
+ if (value.kind === 'LoadLocal') {
51023
+ locals.set(lvalue.identifier.id, value.place.identifier.id);
51024
+ }
51025
+ else if (value.kind === 'ArrayExpression') {
51026
+ candidateDependencies.set(lvalue.identifier.id, value);
51027
+ }
51028
+ else if (value.kind === 'FunctionExpression') {
51029
+ functions.set(lvalue.identifier.id, value);
51030
+ }
51031
+ else if (value.kind === 'CallExpression' ||
51032
+ value.kind === 'MethodCall') {
51033
+ const callee = value.kind === 'CallExpression' ? value.callee : value.property;
51034
+ if (isUseEffectHookType(callee.identifier) &&
51035
+ value.args.length === 2 &&
51036
+ value.args[0].kind === 'Identifier' &&
51037
+ value.args[1].kind === 'Identifier') {
51038
+ const effectFunction = functions.get(value.args[0].identifier.id);
51039
+ const deps = candidateDependencies.get(value.args[1].identifier.id);
51040
+ if (effectFunction != null &&
51041
+ deps != null &&
51042
+ deps.elements.length !== 0 &&
51043
+ deps.elements.every(element => element.kind === 'Identifier')) {
51044
+ const dependencies = deps.elements.map(dep => {
51045
+ var _a;
51046
+ CompilerError.invariant(dep.kind === 'Identifier', {
51047
+ reason: `Dependency is checked as a place above`,
51048
+ loc: value.loc,
51049
+ });
51050
+ return (_a = locals.get(dep.identifier.id)) !== null && _a !== void 0 ? _a : dep.identifier.id;
51051
+ });
51052
+ validateEffect(effectFunction.loweredFunc.func, dependencies, errors);
51053
+ }
51054
+ }
51055
+ }
51056
+ }
51057
+ }
51058
+ if (errors.hasErrors()) {
51059
+ throw errors;
51060
+ }
51061
+ }
51062
+ function validateEffect(effectFunction, effectDeps, errors) {
51063
+ for (const operand of effectFunction.context) {
51064
+ if (isSetStateType(operand.identifier)) {
51065
+ continue;
51066
+ }
51067
+ else if (effectDeps.find(dep => dep === operand.identifier.id) != null) {
51068
+ continue;
51069
+ }
51070
+ else {
51071
+ return;
51072
+ }
51073
+ }
51074
+ for (const dep of effectDeps) {
51075
+ if (effectFunction.context.find(operand => operand.identifier.id === dep) ==
51076
+ null) {
51077
+ return;
51078
+ }
51079
+ }
51080
+ const seenBlocks = new Set();
51081
+ const values = new Map();
51082
+ for (const dep of effectDeps) {
51083
+ values.set(dep, [dep]);
51084
+ }
51085
+ const setStateLocations = [];
51086
+ for (const block of effectFunction.body.blocks.values()) {
51087
+ for (const pred of block.preds) {
51088
+ if (!seenBlocks.has(pred)) {
51089
+ return;
51090
+ }
51091
+ }
51092
+ for (const phi of block.phis) {
51093
+ const aggregateDeps = new Set();
51094
+ for (const operand of phi.operands.values()) {
51095
+ const deps = values.get(operand.identifier.id);
51096
+ if (deps != null) {
51097
+ for (const dep of deps) {
51098
+ aggregateDeps.add(dep);
51099
+ }
51100
+ }
51101
+ }
51102
+ if (aggregateDeps.size !== 0) {
51103
+ values.set(phi.place.identifier.id, Array.from(aggregateDeps));
51104
+ }
51105
+ }
51106
+ for (const instr of block.instructions) {
51107
+ switch (instr.value.kind) {
51108
+ case 'Primitive':
51109
+ case 'JSXText':
51110
+ case 'LoadGlobal': {
51111
+ break;
51112
+ }
51113
+ case 'LoadLocal': {
51114
+ const deps = values.get(instr.value.place.identifier.id);
51115
+ if (deps != null) {
51116
+ values.set(instr.lvalue.identifier.id, deps);
51117
+ }
51118
+ break;
51119
+ }
51120
+ case 'ComputedLoad':
51121
+ case 'PropertyLoad':
51122
+ case 'BinaryExpression':
51123
+ case 'TemplateLiteral':
51124
+ case 'CallExpression':
51125
+ case 'MethodCall': {
51126
+ const aggregateDeps = new Set();
51127
+ for (const operand of eachInstructionValueOperand(instr.value)) {
51128
+ const deps = values.get(operand.identifier.id);
51129
+ if (deps != null) {
51130
+ for (const dep of deps) {
51131
+ aggregateDeps.add(dep);
51132
+ }
51133
+ }
51134
+ }
51135
+ if (aggregateDeps.size !== 0) {
51136
+ values.set(instr.lvalue.identifier.id, Array.from(aggregateDeps));
51137
+ }
51138
+ if (instr.value.kind === 'CallExpression' &&
51139
+ isSetStateType(instr.value.callee.identifier) &&
51140
+ instr.value.args.length === 1 &&
51141
+ instr.value.args[0].kind === 'Identifier') {
51142
+ const deps = values.get(instr.value.args[0].identifier.id);
51143
+ if (deps != null && new Set(deps).size === effectDeps.length) {
51144
+ setStateLocations.push(instr.value.callee.loc);
51145
+ }
51146
+ else {
51147
+ return;
51148
+ }
51149
+ }
51150
+ break;
51151
+ }
51152
+ default: {
51153
+ return;
51154
+ }
51155
+ }
51156
+ }
51157
+ for (const operand of eachTerminalOperand(block.terminal)) {
51158
+ if (values.has(operand.identifier.id)) {
51159
+ return;
51160
+ }
51161
+ }
51162
+ seenBlocks.add(block.id);
51163
+ }
51164
+ for (const loc of setStateLocations) {
51165
+ errors.push({
51166
+ reason: 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)',
51167
+ description: null,
51168
+ severity: ErrorSeverity.InvalidReact,
51169
+ loc,
51170
+ suggestions: null,
51171
+ });
51172
+ }
51173
+ }
51174
+
51053
51175
function run(func, config, fnType, mode, programContext, logger, filename, code) {
51054
51176
var _a, _b;
51055
51177
const contextIdentifiers = findContextIdentifiers(func);
@@ -51172,6 +51294,9 @@ function runWithEnvironment(func, env) {
51172
51294
if (env.config.validateNoSetStateInRender) {
51173
51295
validateNoSetStateInRender(hir).unwrap();
51174
51296
}
51297
+ if (env.config.validateNoDerivedComputationsInEffects) {
51298
+ validateNoDerivedComputationsInEffects(hir);
51299
+ }
51175
51300
if (env.config.validateNoSetStateInEffects) {
51176
51301
env.logErrors(validateNoSetStateInEffects(hir));
51177
51302
}
@@ -51194,12 +51319,6 @@ function runWithEnvironment(func, env) {
51194
51319
name: 'RewriteInstructionKindsBasedOnReassignment',
51195
51320
value: hir,
51196
51321
});
51197
- propagatePhiTypes(hir);
51198
- log({
51199
- kind: 'hir',
51200
- name: 'PropagatePhiTypes',
51201
- value: hir,
51202
- });
51203
51322
if (env.isInferredMemoEnabled) {
51204
51323
if (env.config.validateStaticComponents) {
51205
51324
env.logErrors(validateStaticComponents(hir));
0 commit comments