Skip to content

Commit a1a6803

Browse files
committed
Update
[ghstack-poisoned]
2 parents 4817909 + 0b0f92b commit a1a6803

File tree

65 files changed

+1465
-623
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1465
-623
lines changed

ReactVersions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const stablePackages = {
5252
// These packages do not exist in the @canary or @latest channel, only
5353
// @experimental. We don't use semver, just the commit sha, so this is just a
5454
// list of package names instead of a map.
55-
const experimentalPackages = [];
55+
const experimentalPackages = ['react-markup'];
5656

5757
module.exports = {
5858
ReactVersion,

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ const EnvironmentConfigSchema = z.object({
223223
validateHooksUsage: z.boolean().default(true),
224224

225225
// Validate that ref values (`ref.current`) are not accessed during render.
226-
validateRefAccessDuringRender: z.boolean().default(false),
226+
validateRefAccessDuringRender: z.boolean().default(true),
227227

228228
/*
229229
* Validates that setState is not unconditionally called during render, as it can lead to

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,10 @@ export function isUseStateType(id: Identifier): boolean {
15911591
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
15921592
}
15931593

1594+
export function isRefOrRefValue(id: Identifier): boolean {
1595+
return isUseRefType(id) || isRefValueType(id);
1596+
}
1597+
15941598
export function isSetStateType(id: Identifier): boolean {
15951599
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState';
15961600
}

compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import {
1414
LoweredFunction,
1515
Place,
1616
ReactiveScopeDependency,
17-
isRefValueType,
18-
isUseRefType,
17+
isRefOrRefValue,
1918
makeInstructionId,
2019
} from '../HIR';
2120
import {deadCodeElimination} from '../Optimization';
@@ -139,7 +138,7 @@ function infer(
139138
name = dep.identifier.name;
140139
}
141140

142-
if (isUseRefType(dep.identifier) || isRefValueType(dep.identifier)) {
141+
if (isRefOrRefValue(dep.identifier)) {
143142
/*
144143
* TODO: this is a hack to ensure we treat functions which reference refs
145144
* as having a capture and therefore being considered mutable. this ensures

compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Identifier,
1212
InstructionId,
1313
InstructionKind,
14+
isRefOrRefValue,
1415
makeInstructionId,
1516
Place,
1617
} from '../HIR/HIR';
@@ -66,7 +67,9 @@ import {assertExhaustive} from '../Utils/utils';
6667
*/
6768

6869
function infer(place: Place, instrId: InstructionId): void {
69-
place.identifier.mutableRange.end = makeInstructionId(instrId + 1);
70+
if (!isRefOrRefValue(place.identifier)) {
71+
place.identifier.mutableRange.end = makeInstructionId(instrId + 1);
72+
}
7073
}
7174

7275
function inferPlace(
@@ -171,7 +174,10 @@ export function inferMutableLifetimes(
171174
const declaration = contextVariableDeclarationInstructions.get(
172175
instr.value.lvalue.place.identifier,
173176
);
174-
if (declaration != null) {
177+
if (
178+
declaration != null &&
179+
!isRefOrRefValue(instr.value.lvalue.place.identifier)
180+
) {
175181
const range = instr.value.lvalue.place.identifier.mutableRange;
176182
if (range.start === 0) {
177183
range.start = declaration;

compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {HIRFunction, Identifier, InstructionId} from '../HIR/HIR';
8+
import {
9+
HIRFunction,
10+
Identifier,
11+
InstructionId,
12+
isRefOrRefValue,
13+
} from '../HIR/HIR';
914
import DisjointSet from '../Utils/DisjointSet';
1015

1116
export function inferMutableRangesForAlias(
@@ -19,7 +24,8 @@ export function inferMutableRangesForAlias(
1924
* mutated.
2025
*/
2126
const mutatingIdentifiers = [...aliasSet].filter(
22-
id => id.mutableRange.end - id.mutableRange.start > 1,
27+
id =>
28+
id.mutableRange.end - id.mutableRange.start > 1 && !isRefOrRefValue(id),
2329
);
2430

2531
if (mutatingIdentifiers.length > 0) {
@@ -36,7 +42,10 @@ export function inferMutableRangesForAlias(
3642
* last mutation.
3743
*/
3844
for (const alias of aliasSet) {
39-
if (alias.mutableRange.end < lastMutatingInstructionId) {
45+
if (
46+
alias.mutableRange.end < lastMutatingInstructionId &&
47+
!isRefOrRefValue(alias)
48+
) {
4049
alias.mutableRange.end = lastMutatingInstructionId as InstructionId;
4150
}
4251
}

compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ import {
3030
isArrayType,
3131
isMutableEffect,
3232
isObjectType,
33-
isRefValueType,
34-
isUseRefType,
33+
isRefOrRefValue,
3534
} from '../HIR/HIR';
3635
import {FunctionSignature} from '../HIR/ObjectShape';
3736
import {
@@ -523,10 +522,7 @@ class InferenceState {
523522
break;
524523
}
525524
case Effect.Mutate: {
526-
if (
527-
isRefValueType(place.identifier) ||
528-
isUseRefType(place.identifier)
529-
) {
525+
if (isRefOrRefValue(place.identifier)) {
530526
// no-op: refs are validate via ValidateNoRefAccessInRender
531527
} else if (valueKind.kind === ValueKind.Context) {
532528
functionEffect = {
@@ -567,10 +563,7 @@ class InferenceState {
567563
break;
568564
}
569565
case Effect.Store: {
570-
if (
571-
isRefValueType(place.identifier) ||
572-
isUseRefType(place.identifier)
573-
) {
566+
if (isRefOrRefValue(place.identifier)) {
574567
// no-op: refs are validate via ValidateNoRefAccessInRender
575568
} else if (valueKind.kind === ValueKind.Context) {
576569
functionEffect = {

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {
1111
IdentifierId,
1212
Place,
1313
SourceLocation,
14+
isRefOrRefValue,
1415
isRefValueType,
1516
isUseRefType,
1617
} from '../HIR';
17-
import {printPlace} from '../HIR/PrintHIR';
1818
import {
1919
eachInstructionValueOperand,
2020
eachTerminalOperand,
@@ -52,41 +52,50 @@ function validateNoRefAccessInRenderImpl(
5252
refAccessingFunctions: Set<IdentifierId>,
5353
): Result<void, CompilerError> {
5454
const errors = new CompilerError();
55+
const lookupLocations: Map<IdentifierId, SourceLocation> = new Map();
5556
for (const [, block] of fn.body.blocks) {
5657
for (const instr of block.instructions) {
5758
switch (instr.value.kind) {
5859
case 'JsxExpression':
5960
case 'JsxFragment': {
6061
for (const operand of eachInstructionValueOperand(instr.value)) {
61-
if (isRefValueType(operand.identifier)) {
62-
errors.push({
63-
severity: ErrorSeverity.InvalidReact,
64-
reason:
65-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
66-
loc: operand.loc,
67-
description: `Cannot access ref value at ${printPlace(
68-
operand,
69-
)}`,
70-
suggestions: null,
71-
});
72-
}
62+
validateNoDirectRefValueAccess(errors, operand, lookupLocations);
7363
}
7464
break;
7565
}
7666
case 'PropertyLoad': {
67+
if (
68+
isRefValueType(instr.lvalue.identifier) &&
69+
instr.value.property === 'current'
70+
) {
71+
lookupLocations.set(instr.lvalue.identifier.id, instr.loc);
72+
}
7773
break;
7874
}
7975
case 'LoadLocal': {
8076
if (refAccessingFunctions.has(instr.value.place.identifier.id)) {
8177
refAccessingFunctions.add(instr.lvalue.identifier.id);
8278
}
79+
if (isRefValueType(instr.lvalue.identifier)) {
80+
const loc = lookupLocations.get(instr.value.place.identifier.id);
81+
if (loc !== undefined) {
82+
lookupLocations.set(instr.lvalue.identifier.id, loc);
83+
}
84+
}
8385
break;
8486
}
8587
case 'StoreLocal': {
8688
if (refAccessingFunctions.has(instr.value.value.identifier.id)) {
8789
refAccessingFunctions.add(instr.value.lvalue.place.identifier.id);
8890
refAccessingFunctions.add(instr.lvalue.identifier.id);
8991
}
92+
if (isRefValueType(instr.value.lvalue.place.identifier)) {
93+
const loc = lookupLocations.get(instr.value.value.identifier.id);
94+
if (loc !== undefined) {
95+
lookupLocations.set(instr.value.lvalue.place.identifier.id, loc);
96+
lookupLocations.set(instr.lvalue.identifier.id, loc);
97+
}
98+
}
9099
break;
91100
}
92101
case 'ObjectMethod':
@@ -139,7 +148,11 @@ function validateNoRefAccessInRenderImpl(
139148
reason:
140149
'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)',
141150
loc: callee.loc,
142-
description: `Function ${printPlace(callee)} accesses a ref`,
151+
description:
152+
callee.identifier.name !== null &&
153+
callee.identifier.name.kind === 'named'
154+
? `Function \`${callee.identifier.name.value}\` accesses a ref`
155+
: null,
143156
suggestions: null,
144157
});
145158
}
@@ -148,7 +161,7 @@ function validateNoRefAccessInRenderImpl(
148161
errors,
149162
refAccessingFunctions,
150163
operand,
151-
operand.loc,
164+
lookupLocations.get(operand.identifier.id) ?? operand.loc,
152165
);
153166
}
154167
}
@@ -161,7 +174,7 @@ function validateNoRefAccessInRenderImpl(
161174
errors,
162175
refAccessingFunctions,
163176
operand,
164-
operand.loc,
177+
lookupLocations.get(operand.identifier.id) ?? operand.loc,
165178
);
166179
}
167180
break;
@@ -174,26 +187,49 @@ function validateNoRefAccessInRenderImpl(
174187
errors,
175188
refAccessingFunctions,
176189
instr.value.object,
177-
instr.loc,
190+
lookupLocations.get(instr.value.object.identifier.id) ?? instr.loc,
178191
);
179192
for (const operand of eachInstructionValueOperand(instr.value)) {
180193
if (operand === instr.value.object) {
181194
continue;
182195
}
183-
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
196+
validateNoRefValueAccess(
197+
errors,
198+
refAccessingFunctions,
199+
lookupLocations,
200+
operand,
201+
);
184202
}
185203
break;
186204
}
205+
case 'StartMemoize':
206+
case 'FinishMemoize':
207+
break;
187208
default: {
188209
for (const operand of eachInstructionValueOperand(instr.value)) {
189-
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
210+
validateNoRefValueAccess(
211+
errors,
212+
refAccessingFunctions,
213+
lookupLocations,
214+
operand,
215+
);
190216
}
191217
break;
192218
}
193219
}
194220
}
195221
for (const operand of eachTerminalOperand(block.terminal)) {
196-
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
222+
if (block.terminal.kind !== 'return') {
223+
validateNoRefValueAccess(
224+
errors,
225+
refAccessingFunctions,
226+
lookupLocations,
227+
operand,
228+
);
229+
} else {
230+
// Allow functions containing refs to be returned, but not direct ref values
231+
validateNoDirectRefValueAccess(errors, operand, lookupLocations);
232+
}
197233
}
198234
}
199235

@@ -207,6 +243,7 @@ function validateNoRefAccessInRenderImpl(
207243
function validateNoRefValueAccess(
208244
errors: CompilerError,
209245
refAccessingFunctions: Set<IdentifierId>,
246+
lookupLocations: Map<IdentifierId, SourceLocation>,
210247
operand: Place,
211248
): void {
212249
if (
@@ -217,8 +254,12 @@ function validateNoRefValueAccess(
217254
severity: ErrorSeverity.InvalidReact,
218255
reason:
219256
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
220-
loc: operand.loc,
221-
description: `Cannot access ref value at ${printPlace(operand)}`,
257+
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
258+
description:
259+
operand.identifier.name !== null &&
260+
operand.identifier.name.kind === 'named'
261+
? `Cannot access ref value \`${operand.identifier.name.value}\``
262+
: null,
222263
suggestions: null,
223264
});
224265
}
@@ -231,8 +272,7 @@ function validateNoRefAccess(
231272
loc: SourceLocation,
232273
): void {
233274
if (
234-
isRefValueType(operand.identifier) ||
235-
isUseRefType(operand.identifier) ||
275+
isRefOrRefValue(operand.identifier) ||
236276
refAccessingFunctions.has(operand.identifier.id)
237277
) {
238278
errors.push({
@@ -249,3 +289,24 @@ function validateNoRefAccess(
249289
});
250290
}
251291
}
292+
293+
function validateNoDirectRefValueAccess(
294+
errors: CompilerError,
295+
operand: Place,
296+
lookupLocations: Map<IdentifierId, SourceLocation>,
297+
): void {
298+
if (isRefValueType(operand.identifier)) {
299+
errors.push({
300+
severity: ErrorSeverity.InvalidReact,
301+
reason:
302+
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
303+
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
304+
description:
305+
operand.identifier.name !== null &&
306+
operand.identifier.name.kind === 'named'
307+
? `Cannot access ref value \`${operand.identifier.name.value}\``
308+
: null,
309+
suggestions: null,
310+
});
311+
}
312+
}

0 commit comments

Comments
 (0)