6
*/
7
8
import {CompilerError, ErrorSeverity} from '../CompilerError';
9
-import {HIRFunction, IdentifierId, Place, isSetStateType} from '../HIR';
9
+import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
10
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
11
import {eachInstructionValueOperand} from '../HIR/visitors';
12
import {Err, Ok, Result} from '../Utils/Result';
49
unconditionalSetStateFunctions: Set<IdentifierId>,
50
): Result<void, CompilerError> {
51
const unconditionalBlocks = computeUnconditionalBlocks(fn);
52
-
52
+ let activeManualMemoId: number | null = null;
53
const errors = new CompilerError();
54
for (const [, block] of fn.body.blocks) {
55
- if (unconditionalBlocks.has(block.id)) {
56
- for (const instr of block.instructions) {
57
- switch (instr.value.kind) {
58
- case 'LoadLocal': {
59
- if (
60
- unconditionalSetStateFunctions.has(
61
- instr.value.place.identifier.id,
62
- )
63
- ) {
64
- unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
65
- }
66
- break;
55
+ for (const instr of block.instructions) {
56
+ switch (instr.value.kind) {
57
+ case 'LoadLocal': {
58
+ if (
59
+ unconditionalSetStateFunctions.has(instr.value.place.identifier.id)
60
+ ) {
61
+ unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
62
}
68
- case 'StoreLocal': {
69
- if (
70
- unconditionalSetStateFunctions.has(
71
- instr.value.value.identifier.id,
72
- )
73
- ) {
74
- unconditionalSetStateFunctions.add(
75
- instr.value.lvalue.place.identifier.id,
76
- );
77
- unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
78
- }
79
- break;
80
- }
81
- case 'ObjectMethod':
82
- case 'FunctionExpression': {
83
- if (
84
- // faster-path to check if the function expression references a setState
85
- [...eachInstructionValueOperand(instr.value)].some(
86
- operand =>
87
- isSetStateType(operand.identifier) ||
88
- unconditionalSetStateFunctions.has(operand.identifier.id),
89
- ) &&
90
- // if yes, does it unconditonally call it?
91
- validateNoSetStateInRenderImpl(
92
- instr.value.loweredFunc.func,
93
- unconditionalSetStateFunctions,
94
- ).isErr()
95
- ) {
96
- // This function expression unconditionally calls a setState
97
- unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
98
- }
99
- break;
63
+ break;
64
+ }
65
+ case 'StoreLocal': {
66
+ if (
67
+ unconditionalSetStateFunctions.has(instr.value.value.identifier.id)
68
+ ) {
69
+ unconditionalSetStateFunctions.add(
70
+ instr.value.lvalue.place.identifier.id,
71
+ );
72
+ unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
73
}
101
- case 'CallExpression': {
102
- validateNonSetState(
103
- errors,
74
+ break;
75
+ }
76
+ case 'ObjectMethod':
77
+ case 'FunctionExpression': {
78
+ if (
79
+ // faster-path to check if the function expression references a setState
80
+ [...eachInstructionValueOperand(instr.value)].some(
81
+ operand =>
82
+ isSetStateType(operand.identifier) ||
83
+ unconditionalSetStateFunctions.has(operand.identifier.id),
84
+ ) &&
85
+ // if yes, does it unconditonally call it?
86
+ validateNoSetStateInRenderImpl(
87
+ instr.value.loweredFunc.func,
88
unconditionalSetStateFunctions,
105
- instr.value.callee,
106
- );
107
- break;
89
+ ).isErr()
90
+ ) {
91
+ // This function expression unconditionally calls a setState
92
+ unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
93
}
94
+ break;
95
+ }
96
+ case 'StartMemoize': {
97
+ CompilerError.invariant(activeManualMemoId === null, {
98
+ reason: 'Unexpected nested StartMemoize instructions',
99
+ loc: instr.value.loc,
100
+ });
101
+ activeManualMemoId = instr.value.manualMemoId;
102
+ break;
103
+ }
104
+ case 'FinishMemoize': {
105
+ CompilerError.invariant(
106
+ activeManualMemoId === instr.value.manualMemoId,
107
+ {
108
+ reason:
109
+ 'Expected FinishMemoize to align with previous StartMemoize instruction',
110
+ loc: instr.value.loc,
111
+ },
112
+ );
113
+ activeManualMemoId = null;
114
+ break;
115
+ }
116
+ case 'CallExpression': {
117
+ const callee = instr.value.callee;
118
+ if (
119
+ isSetStateType(callee.identifier) ||
120
+ unconditionalSetStateFunctions.has(callee.identifier.id)
121
+ ) {
122
+ if (activeManualMemoId !== null) {
123
+ errors.push({
124
+ reason:
125
+ 'Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)',
126
+ description: null,
127
+ severity: ErrorSeverity.InvalidReact,
128
+ loc: callee.loc,
129
+ suggestions: null,
130
+ });
131
+ } else if (unconditionalBlocks.has(block.id)) {
132
+ errors.push({
133
+ reason:
134
+ 'This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)',
135
+ description: null,
136
+ severity: ErrorSeverity.InvalidReact,
137
+ loc: callee.loc,
138
+ suggestions: null,
139
+ });
140
+ }
141
+ }
142
+ break;
143
}
144
}
145
}
151
return Ok(undefined);
152
}
153
}
120
-
121
-function validateNonSetState(
122
- errors: CompilerError,
123
- unconditionalSetStateFunctions: Set<IdentifierId>,
124
- operand: Place,
125
-): void {
126
- if (
127
- isSetStateType(operand.identifier) ||
128
- unconditionalSetStateFunctions.has(operand.identifier.id)
129
- ) {
130
- errors.push({
131
- reason:
132
- 'This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)',
133
- description: null,
134
- severity: ErrorSeverity.InvalidReact,
135
- loc: typeof operand.loc !== 'symbol' ? operand.loc : null,
136
- suggestions: null,
137
- });
138
- }
139
-}