@samitouri / QOS-React-2 / commits / 6cf5bd9013

[compiler] Allow refs to be lazily initialized during render

Summary: The official guidance for useRef notes an exception to the rule that refs cannot be accessed during render: to avoid recreating the ref's contents, you can test that the ref is uninitialized and then initialize it using an if statement: ``` if (ref.current == null) { ref.current = SomeExpensiveOperation() } ``` The compiler didn't recognize this exception, however, leading to code that obeyed all the official guidance for refs being rejected by the compiler. This PR fixes that, by extending the ref validation machinery with an awareness of guard operations that allow lazy initialization. We now understand `== null` and similar operations, when applied to a ref and consumed by an if terminal, as marking the consequent of the if as a block in which the ref can be safely written to. In order to do so we need to create a notion of ref ids, which link different usages of the same ref via both the ref and the ref value. ghstack-source-id: d2729274f351e1eb0268f28f629fa4c2568ebc4d Pull Request resolved: https://github.com/facebook/react/pull/31188

Mike Vitousek committed Oct 11, 2024 at 16:14 UTC 6cf5bd90135823d249fb5270896f238d04ec296c
19 files changed +628 -26
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts
+162 -26
@@ -7,8 +7,8 @@
7
8 import {CompilerError, ErrorSeverity} from '../CompilerError';
9 import {
10 + BlockId,
11 HIRFunction,
11 - Identifier,
12 IdentifierId,
13 Place,
14 SourceLocation,
@@ -17,6 +17,7 @@ import {
17 isUseRefType,
18 } from '../HIR';
19 import {
20 + eachInstructionOperand,
21 eachInstructionValueOperand,
22 eachPatternOperand,
23 eachTerminalOperand,
@@ -44,11 +45,32 @@ import {Err, Ok, Result} from '../Utils/Result';
45 * or based on property name alone (`foo.current` might be a ref).
46 */
47
47 -type RefAccessType = {kind: 'None'} | RefAccessRefType;
48 +const opaqueRefId = Symbol();
49 +type RefId = number & {[opaqueRefId]: 'RefId'};
50 +
51 +function makeRefId(id: number): RefId {
52 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
53 + reason: 'Expected identifier id to be a non-negative integer',
54 + description: null,
55 + loc: null,
56 + suggestions: null,
57 + });
58 + return id as RefId;
59 +}
60 +let _refId = 0;
61 +function nextRefId(): RefId {
62 + return makeRefId(_refId++);
63 +}
64 +
65 +type RefAccessType =
66 + | {kind: 'None'}
67 + | {kind: 'Nullable'}
68 + | {kind: 'Guard'; refId: RefId}
69 + | RefAccessRefType;
70
71 type RefAccessRefType =
50 - | {kind: 'Ref'}
51 - | {kind: 'RefValue'; loc?: SourceLocation}
72 + | {kind: 'Ref'; refId: RefId}
73 + | {kind: 'RefValue'; loc?: SourceLocation; refId?: RefId}
74 | {kind: 'Structure'; value: null | RefAccessRefType; fn: null | RefFnType};
75
76 type RefFnType = {readRefEffect: boolean; returnType: RefAccessType};
@@ -82,11 +104,11 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
104 validateNoRefAccessInRenderImpl(fn, env).unwrap();
105 }
106
85 -function refTypeOfType(identifier: Identifier): RefAccessType {
86 - if (isRefValueType(identifier)) {
107 +function refTypeOfType(place: Place): RefAccessType {
108 + if (isRefValueType(place.identifier)) {
109 return {kind: 'RefValue'};
88 - } else if (isUseRefType(identifier)) {
89 - return {kind: 'Ref'};
110 + } else if (isUseRefType(place.identifier)) {
111 + return {kind: 'Ref', refId: nextRefId()};
112 } else {
113 return {kind: 'None'};
114 }
@@ -101,6 +123,14 @@ function tyEqual(a: RefAccessType, b: RefAccessType): boolean {
123 return true;
124 case 'Ref':
125 return true;
126 + case 'Nullable':
127 + return true;
128 + case 'Guard':
129 + CompilerError.invariant(b.kind === 'Guard', {
130 + reason: 'Expected ref value',
131 + loc: null,
132 + });
133 + return a.refId === b.refId;
134 case 'RefValue':
135 CompilerError.invariant(b.kind === 'RefValue', {
136 reason: 'Expected ref value',
@@ -133,11 +163,17 @@ function joinRefAccessTypes(...types: Array<RefAccessType>): RefAccessType {
163 b: RefAccessRefType,
164 ): RefAccessRefType {
165 if (a.kind === 'RefValue') {
136 - return a;
166 + if (b.kind === 'RefValue' && a.refId === b.refId) {
167 + return a;
168 + }
169 + return {kind: 'RefValue'};
170 } else if (b.kind === 'RefValue') {
171 return b;
172 } else if (a.kind === 'Ref' || b.kind === 'Ref') {
140 - return {kind: 'Ref'};
173 + if (a.kind === 'Ref' && b.kind === 'Ref' && a.refId === b.refId) {
174 + return a;
175 + }
176 + return {kind: 'Ref', refId: nextRefId()};
177 } else {
178 CompilerError.invariant(
179 a.kind === 'Structure' && b.kind === 'Structure',
@@ -178,6 +214,16 @@ function joinRefAccessTypes(...types: Array<RefAccessType>): RefAccessType {
214 return b;
215 } else if (b.kind === 'None') {
216 return a;
217 + } else if (a.kind === 'Guard' || b.kind === 'Guard') {
218 + if (a.kind === 'Guard' && b.kind === 'Guard' && a.refId === b.refId) {
219 + return a;
220 + }
221 + return {kind: 'None'};
222 + } else if (a.kind === 'Nullable' || b.kind === 'Nullable') {
223 + if (a.kind === 'Nullable' && b.kind === 'Nullable') {
224 + return a;
225 + }
226 + return {kind: 'None'};
227 } else {
228 return joinRefAccessRefTypes(a, b);
229 }
@@ -198,13 +244,14 @@ function validateNoRefAccessInRenderImpl(
244 } else {
245 place = param.place;
246 }
201 - const type = refTypeOfType(place.identifier);
247 + const type = refTypeOfType(place);
248 env.set(place.identifier.id, type);
249 }
250
251 for (let i = 0; (i == 0 || env.hasChanged()) && i < 10; i++) {
252 env.resetChanged();
253 returnValues = [];
254 + const safeBlocks = new Map<BlockId, RefId>();
255 const errors = new CompilerError();
256 for (const [, block] of fn.body.blocks) {
257 for (const phi of block.phis) {
@@ -238,11 +285,15 @@ function validateNoRefAccessInRenderImpl(
285 if (objType?.kind === 'Structure') {
286 lookupType = objType.value;
287 } else if (objType?.kind === 'Ref') {
241 - lookupType = {kind: 'RefValue', loc: instr.loc};
288 + lookupType = {
289 + kind: 'RefValue',
290 + loc: instr.loc,
291 + refId: objType.refId,
292 + };
293 }
294 env.set(
295 instr.lvalue.identifier.id,
245 - lookupType ?? refTypeOfType(instr.lvalue.identifier),
296 + lookupType ?? refTypeOfType(instr.lvalue),
297 );
298 break;
299 }
@@ -251,7 +302,7 @@ function validateNoRefAccessInRenderImpl(
302 env.set(
303 instr.lvalue.identifier.id,
304 env.get(instr.value.place.identifier.id) ??
254 - refTypeOfType(instr.lvalue.identifier),
305 + refTypeOfType(instr.lvalue),
306 );
307 break;
308 }
@@ -260,12 +311,12 @@ function validateNoRefAccessInRenderImpl(
311 env.set(
312 instr.value.lvalue.place.identifier.id,
313 env.get(instr.value.value.identifier.id) ??
263 - refTypeOfType(instr.value.lvalue.place.identifier),
314 + refTypeOfType(instr.value.lvalue.place),
315 );
316 env.set(
317 instr.lvalue.identifier.id,
318 env.get(instr.value.value.identifier.id) ??
268 - refTypeOfType(instr.lvalue.identifier),
319 + refTypeOfType(instr.lvalue),
320 );
321 break;
322 }
@@ -277,13 +328,10 @@ function validateNoRefAccessInRenderImpl(
328 }
329 env.set(
330 instr.lvalue.identifier.id,
280 - lookupType ?? refTypeOfType(instr.lvalue.identifier),
331 + lookupType ?? refTypeOfType(instr.lvalue),
332 );
333 for (const lval of eachPatternOperand(instr.value.lvalue.pattern)) {
283 - env.set(
284 - lval.identifier.id,
285 - lookupType ?? refTypeOfType(lval.identifier),
286 - );
334 + env.set(lval.identifier.id, lookupType ?? refTypeOfType(lval));
335 }
336 break;
337 }
@@ -354,7 +402,11 @@ function validateNoRefAccessInRenderImpl(
402 types.push(env.get(operand.identifier.id) ?? {kind: 'None'});
403 }
404 const value = joinRefAccessTypes(...types);
357 - if (value.kind === 'None') {
405 + if (
406 + value.kind === 'None' ||
407 + value.kind === 'Guard' ||
408 + value.kind === 'Nullable'
409 + ) {
410 env.set(instr.lvalue.identifier.id, {kind: 'None'});
411 } else {
412 env.set(instr.lvalue.identifier.id, {
@@ -369,7 +421,18 @@ function validateNoRefAccessInRenderImpl(
421 case 'PropertyStore':
422 case 'ComputedDelete':
423 case 'ComputedStore': {
372 - validateNoRefAccess(errors, env, instr.value.object, instr.loc);
424 + const safe = safeBlocks.get(block.id);
425 + const target = env.get(instr.value.object.identifier.id);
426 + if (
427 + instr.value.kind === 'PropertyStore' &&
428 + safe != null &&
429 + target?.kind === 'Ref' &&
430 + target.refId === safe
431 + ) {
432 + safeBlocks.delete(block.id);
433 + } else {
434 + validateNoRefAccess(errors, env, instr.value.object, instr.loc);
435 + }
436 for (const operand of eachInstructionValueOperand(instr.value)) {
437 if (operand === instr.value.object) {
438 continue;
@@ -381,6 +444,38 @@ function validateNoRefAccessInRenderImpl(
444 case 'StartMemoize':
445 case 'FinishMemoize':
446 break;
447 + case 'Primitive': {
448 + if (instr.value.value == null) {
449 + env.set(instr.lvalue.identifier.id, {kind: 'Nullable'});
450 + }
451 + break;
452 + }
453 + case 'BinaryExpression': {
454 + const left = env.get(instr.value.left.identifier.id);
455 + const right = env.get(instr.value.right.identifier.id);
456 + let nullish: boolean = false;
457 + let refId: RefId | null = null;
458 + if (left?.kind === 'RefValue' && left.refId != null) {
459 + refId = left.refId;
460 + } else if (right?.kind === 'RefValue' && right.refId != null) {
461 + refId = right.refId;
462 + }
463 +
464 + if (left?.kind === 'Nullable') {
465 + nullish = true;
466 + } else if (right?.kind === 'Nullable') {
467 + nullish = true;
468 + }
469 +
470 + if (refId !== null && nullish) {
471 + env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId});
472 + } else {
473 + for (const operand of eachInstructionValueOperand(instr.value)) {
474 + validateNoRefValueAccess(errors, env, operand);
475 + }
476 + }
477 + break;
478 + }
479 default: {
480 for (const operand of eachInstructionValueOperand(instr.value)) {
481 validateNoRefValueAccess(errors, env, operand);
@@ -388,16 +483,28 @@ function validateNoRefAccessInRenderImpl(
483 break;
484 }
485 }
391 - if (isUseRefType(instr.lvalue.identifier)) {
486 +
487 + // Guard values are derived from ref.current, so they can only be used in if statement targets
488 + for (const operand of eachInstructionOperand(instr)) {
489 + guardCheck(errors, operand, env);
490 + }
491 +
492 + if (
493 + isUseRefType(instr.lvalue.identifier) &&
494 + env.get(instr.lvalue.identifier.id)?.kind !== 'Ref'
495 + ) {
496 env.set(
497 instr.lvalue.identifier.id,
498 joinRefAccessTypes(
499 env.get(instr.lvalue.identifier.id) ?? {kind: 'None'},
396 - {kind: 'Ref'},
500 + {kind: 'Ref', refId: nextRefId()},
501 ),
502 );
503 }
400 - if (isRefValueType(instr.lvalue.identifier)) {
504 + if (
505 + isRefValueType(instr.lvalue.identifier) &&
506 + env.get(instr.lvalue.identifier.id)?.kind !== 'RefValue'
507 + ) {
508 env.set(
509 instr.lvalue.identifier.id,
510 joinRefAccessTypes(
@@ -407,12 +514,24 @@ function validateNoRefAccessInRenderImpl(
514 );
515 }
516 }
517 +
518 + if (block.terminal.kind === 'if') {
519 + const test = env.get(block.terminal.test.identifier.id);
520 + if (test?.kind === 'Guard') {
521 + safeBlocks.set(block.terminal.consequent, test.refId);
522 + }
523 + }
524 +
525 for (const operand of eachTerminalOperand(block.terminal)) {
526 if (block.terminal.kind !== 'return') {
527 validateNoRefValueAccess(errors, env, operand);
528 + if (block.terminal.kind !== 'if') {
529 + guardCheck(errors, operand, env);
530 + }
531 } else {
532 // Allow functions containing refs to be returned, but not direct ref values
533 validateNoDirectRefValueAccess(errors, operand, env);
534 + guardCheck(errors, operand, env);
535 returnValues.push(env.get(operand.identifier.id));
536 }
537 }
@@ -444,6 +563,23 @@ function destructure(
563 return type;
564 }
565
566 +function guardCheck(errors: CompilerError, operand: Place, env: Env): void {
567 + if (env.get(operand.identifier.id)?.kind === 'Guard') {
568 + errors.push({
569 + severity: ErrorSeverity.InvalidReact,
570 + reason:
571 + 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
572 + loc: operand.loc,
573 + description:
574 + operand.identifier.name !== null &&
575 + operand.identifier.name.kind === 'named'
576 + ? `Cannot access ref value \`${operand.identifier.name.value}\``
577 + : null,
578 + suggestions: null,
579 + });
580 + }
581 +}
582 +
583 function validateNoRefValueAccess(
584 errors: CompilerError,
585 env: Env,
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.expect.md new
+42
@@ -0,0 +1,42 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + if (r.current == null) {
11 + r.current = 1;
12 + }
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: C,
17 + params: [{}],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { useRef } from "react";
26 +
27 +function C() {
28 + const r = useRef(null);
29 + if (r.current == null) {
30 + r.current = 1;
31 + }
32 +}
33 +
34 +export const FIXTURE_ENTRYPOINT = {
35 + fn: C,
36 + params: [{}],
37 +};
38 +
39 +```
40 +
41 +### Eval output
42 +(kind: ok)
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.js new
+14
@@ -0,0 +1,14 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + if (r.current == null) {
7 + r.current = 1;
8 + }
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: C,
13 + params: [{}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-arbitrary.expect.md new
+39
@@ -0,0 +1,39 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +const DEFAULT_VALUE = 1;
9 +
10 +component C() {
11 + const r = useRef(DEFAULT_VALUE);
12 + if (r.current == DEFAULT_VALUE) {
13 + r.current = 1;
14 + }
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: C,
19 + params: [{}],
20 +};
21 +
22 +```
23 +
24 +
25 +## Error
26 +
27 +```
28 + 6 | component C() {
29 + 7 | const r = useRef(DEFAULT_VALUE);
30 +> 8 | if (r.current == DEFAULT_VALUE) {
31 + | ^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
32 +
33 +InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
34 + 9 | r.current = 1;
35 + 10 | }
36 + 11 | }
37 +```
38 +
39 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-arbitrary.js new
+16
@@ -0,0 +1,16 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +const DEFAULT_VALUE = 1;
5 +
6 +component C() {
7 + const r = useRef(DEFAULT_VALUE);
8 + if (r.current == DEFAULT_VALUE) {
9 + r.current = 1;
10 + }
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: C,
15 + params: [{}],
16 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call-2.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + if (r.current == null) {
11 + f(r);
12 + }
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: C,
17 + params: [{}],
18 +};
19 +
20 +```
21 +
22 +
23 +## Error
24 +
25 +```
26 + 5 | const r = useRef(null);
27 + 6 | if (r.current == null) {
28 +> 7 | f(r);
29 + | ^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (7:7)
30 + 8 | }
31 + 9 | }
32 + 10 |
33 +```
34 +
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call-2.js new
+14
@@ -0,0 +1,14 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + if (r.current == null) {
7 + f(r);
8 + }
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: C,
13 + params: [{}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + if (r.current == null) {
11 + f(r.current);
12 + }
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: C,
17 + params: [{}],
18 +};
19 +
20 +```
21 +
22 +
23 +## Error
24 +
25 +```
26 + 5 | const r = useRef(null);
27 + 6 | if (r.current == null) {
28 +> 7 | f(r.current);
29 + | ^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (7:7)
30 + 8 | }
31 + 9 | }
32 + 10 |
33 +```
34 +
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call.js new
+14
@@ -0,0 +1,14 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + if (r.current == null) {
7 + f(r.current);
8 + }
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: C,
13 + params: [{}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-linear.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + if (r.current == null) {
11 + r.current = 42;
12 + r.current = 42;
13 + }
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: C,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 6 | if (r.current == null) {
28 + 7 | r.current = 42;
29 +> 8 | r.current = 42;
30 + | ^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
31 + 9 | }
32 + 10 | }
33 + 11 |
34 +```
35 +
36 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-linear.js new
+15
@@ -0,0 +1,15 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + if (r.current == null) {
7 + r.current = 42;
8 + r.current = 42;
9 + }
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: C,
14 + params: [{}],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-nonif.expect.md new
+38
@@ -0,0 +1,38 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + const guard = r.current == null;
11 + if (guard) {
12 + r.current = 1;
13 + }
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: C,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 4 | component C() {
28 + 5 | const r = useRef(null);
29 +> 6 | const guard = r.current == null;
30 + | ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (6:6)
31 +
32 +InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `guard` (7:7)
33 + 7 | if (guard) {
34 + 8 | r.current = 1;
35 + 9 | }
36 +```
37 +
38 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-nonif.js new
+15
@@ -0,0 +1,15 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + const guard = r.current == null;
7 + if (guard) {
8 + r.current = 1;
9 + }
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: C,
14 + params: [{}],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-other.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + const r2 = useRef(null);
11 + if (r.current == null) {
12 + r2.current = 1;
13 + }
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: C,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 6 | const r2 = useRef(null);
28 + 7 | if (r.current == null) {
29 +> 8 | r2.current = 1;
30 + | ^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
31 + 9 | }
32 + 10 | }
33 + 11 |
34 +```
35 +
36 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-other.js new
+15
@@ -0,0 +1,15 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + const r2 = useRef(null);
7 + if (r.current == null) {
8 + r2.current = 1;
9 + }
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: C,
14 + params: [{}],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access-2.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + if (r.current == null) {
11 + r.current = 1;
12 + }
13 + f(r.current);
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: C,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 7 | r.current = 1;
28 + 8 | }
29 +> 9 | f(r.current);
30 + | ^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
31 + 10 | }
32 + 11 |
33 + 12 | export const FIXTURE_ENTRYPOINT = {
34 +```
35 +
36 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access-2.js new
+15
@@ -0,0 +1,15 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + if (r.current == null) {
7 + r.current = 1;
8 + }
9 + f(r.current);
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: C,
14 + params: [{}],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +//@flow
6 +import {useRef} from 'react';
7 +
8 +component C() {
9 + const r = useRef(null);
10 + if (r.current == null) {
11 + r.current = 1;
12 + }
13 + r.current = 1;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: C,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 7 | r.current = 1;
28 + 8 | }
29 +> 9 | r.current = 1;
30 + | ^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
31 + 10 | }
32 + 11 |
33 + 12 | export const FIXTURE_ENTRYPOINT = {
34 +```
35 +
36 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access.js new
+15
@@ -0,0 +1,15 @@
1 +//@flow
2 +import {useRef} from 'react';
3 +
4 +component C() {
5 + const r = useRef(null);
6 + if (r.current == null) {
7 + r.current = 1;
8 + }
9 + r.current = 1;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: C,
14 + params: [{}],
15 +};