Skip to content

Commit 93b8cc6

Browse files
committed
[compiler] Improve ref validation error message
Improves the error message for ValidateNoRefAccessInRender, using the new diagnostic type as well as providing a longer but succinct summary of what refs are for and why they're unsafe to access in render.
1 parent 85bbe39 commit 93b8cc6

File tree

34 files changed

+270
-192
lines changed

34 files changed

+270
-192
lines changed

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

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

8-
import {CompilerError, ErrorSeverity} from '../CompilerError';
8+
import {
9+
CompilerDiagnostic,
10+
CompilerError,
11+
ErrorSeverity,
12+
} from '../CompilerError';
913
import {
1014
BlockId,
1115
HIRFunction,
@@ -385,28 +389,40 @@ function validateNoRefAccessInRenderImpl(
385389
const hookKind = getHookKindForType(fn.env, callee.identifier.type);
386390
let returnType: RefAccessType = {kind: 'None'};
387391
const fnType = env.get(callee.identifier.id);
392+
let didError = false;
388393
if (fnType?.kind === 'Structure' && fnType.fn !== null) {
389394
returnType = fnType.fn.returnType;
390395
if (fnType.fn.readRefEffect) {
391-
errors.push({
392-
severity: ErrorSeverity.InvalidReact,
393-
reason:
394-
'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)',
395-
loc: callee.loc,
396-
description:
397-
callee.identifier.name !== null &&
398-
callee.identifier.name.kind === 'named'
399-
? `Function \`${callee.identifier.name.value}\` accesses a ref`
400-
: null,
401-
suggestions: null,
402-
});
396+
didError = true;
397+
errors.pushDiagnostic(
398+
CompilerDiagnostic.create({
399+
severity: ErrorSeverity.InvalidReact,
400+
category: 'Cannot access refs during render',
401+
description: ERROR_DESCRIPTION,
402+
}).withDetail({
403+
kind: 'error',
404+
loc: callee.loc,
405+
message: `This function accesses a ref value`,
406+
}),
407+
);
403408
}
404409
}
405-
for (const operand of eachInstructionValueOperand(instr.value)) {
406-
if (hookKind != null) {
407-
validateNoDirectRefValueAccess(errors, operand, env);
408-
} else {
409-
validateNoRefAccess(errors, env, operand, operand.loc);
410+
if (!didError) {
411+
/*
412+
* If we already reported an error on this instruction, don't report
413+
* duplicate errors
414+
*/
415+
for (const operand of eachInstructionValueOperand(instr.value)) {
416+
if (hookKind != null) {
417+
validateNoDirectRefValueAccess(errors, operand, env);
418+
} else {
419+
validateNoRefPassedToFunction(
420+
errors,
421+
env,
422+
operand,
423+
operand.loc,
424+
);
425+
}
410426
}
411427
}
412428
env.set(instr.lvalue.identifier.id, returnType);
@@ -449,7 +465,7 @@ function validateNoRefAccessInRenderImpl(
449465
) {
450466
safeBlocks.delete(block.id);
451467
} else {
452-
validateNoRefAccess(errors, env, instr.value.object, instr.loc);
468+
validateNoRefUpdate(errors, env, instr.value.object, instr.loc);
453469
}
454470
for (const operand of eachInstructionValueOperand(instr.value)) {
455471
if (operand === instr.value.object) {
@@ -583,18 +599,17 @@ function destructure(
583599

584600
function guardCheck(errors: CompilerError, operand: Place, env: Env): void {
585601
if (env.get(operand.identifier.id)?.kind === 'Guard') {
586-
errors.push({
587-
severity: ErrorSeverity.InvalidReact,
588-
reason:
589-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
590-
loc: operand.loc,
591-
description:
592-
operand.identifier.name !== null &&
593-
operand.identifier.name.kind === 'named'
594-
? `Cannot access ref value \`${operand.identifier.name.value}\``
595-
: null,
596-
suggestions: null,
597-
});
602+
errors.pushDiagnostic(
603+
CompilerDiagnostic.create({
604+
severity: ErrorSeverity.InvalidReact,
605+
category: 'Cannot access refs during render',
606+
description: ERROR_DESCRIPTION,
607+
}).withDetail({
608+
kind: 'error',
609+
loc: operand.loc,
610+
message: `Cannot access ref value during render`,
611+
}),
612+
);
598613
}
599614
}
600615

@@ -608,22 +623,47 @@ function validateNoRefValueAccess(
608623
type?.kind === 'RefValue' ||
609624
(type?.kind === 'Structure' && type.fn?.readRefEffect)
610625
) {
611-
errors.push({
612-
severity: ErrorSeverity.InvalidReact,
613-
reason:
614-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
615-
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
616-
description:
617-
operand.identifier.name !== null &&
618-
operand.identifier.name.kind === 'named'
619-
? `Cannot access ref value \`${operand.identifier.name.value}\``
620-
: null,
621-
suggestions: null,
622-
});
626+
errors.pushDiagnostic(
627+
CompilerDiagnostic.create({
628+
severity: ErrorSeverity.InvalidReact,
629+
category: 'Cannot access refs during render',
630+
description: ERROR_DESCRIPTION,
631+
}).withDetail({
632+
kind: 'error',
633+
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
634+
message: `Cannot access ref value during render`,
635+
}),
636+
);
637+
}
638+
}
639+
640+
function validateNoRefPassedToFunction(
641+
errors: CompilerError,
642+
env: Env,
643+
operand: Place,
644+
loc: SourceLocation,
645+
): void {
646+
const type = destructure(env.get(operand.identifier.id));
647+
if (
648+
type?.kind === 'Ref' ||
649+
type?.kind === 'RefValue' ||
650+
(type?.kind === 'Structure' && type.fn?.readRefEffect)
651+
) {
652+
errors.pushDiagnostic(
653+
CompilerDiagnostic.create({
654+
severity: ErrorSeverity.InvalidReact,
655+
category: 'Cannot access refs during render',
656+
description: ERROR_DESCRIPTION,
657+
}).withDetail({
658+
kind: 'error',
659+
loc: (type.kind === 'RefValue' && type.loc) || loc,
660+
message: `Passing a ref to a function may read its value during render`,
661+
}),
662+
);
623663
}
624664
}
625665

626-
function validateNoRefAccess(
666+
function validateNoRefUpdate(
627667
errors: CompilerError,
628668
env: Env,
629669
operand: Place,
@@ -635,18 +675,17 @@ function validateNoRefAccess(
635675
type?.kind === 'RefValue' ||
636676
(type?.kind === 'Structure' && type.fn?.readRefEffect)
637677
) {
638-
errors.push({
639-
severity: ErrorSeverity.InvalidReact,
640-
reason:
641-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
642-
loc: (type.kind === 'RefValue' && type.loc) || loc,
643-
description:
644-
operand.identifier.name !== null &&
645-
operand.identifier.name.kind === 'named'
646-
? `Cannot access ref value \`${operand.identifier.name.value}\``
647-
: null,
648-
suggestions: null,
649-
});
678+
errors.pushDiagnostic(
679+
CompilerDiagnostic.create({
680+
severity: ErrorSeverity.InvalidReact,
681+
category: 'Cannot access refs during render',
682+
description: ERROR_DESCRIPTION,
683+
}).withDetail({
684+
kind: 'error',
685+
loc: (type.kind === 'RefValue' && type.loc) || loc,
686+
message: `Cannot update ref during render`,
687+
}),
688+
);
650689
}
651690
}
652691

@@ -657,17 +696,22 @@ function validateNoDirectRefValueAccess(
657696
): void {
658697
const type = destructure(env.get(operand.identifier.id));
659698
if (type?.kind === 'RefValue') {
660-
errors.push({
661-
severity: ErrorSeverity.InvalidReact,
662-
reason:
663-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
664-
loc: type.loc ?? operand.loc,
665-
description:
666-
operand.identifier.name !== null &&
667-
operand.identifier.name.kind === 'named'
668-
? `Cannot access ref value \`${operand.identifier.name.value}\``
669-
: null,
670-
suggestions: null,
671-
});
699+
errors.pushDiagnostic(
700+
CompilerDiagnostic.create({
701+
severity: ErrorSeverity.InvalidReact,
702+
category: 'Cannot access refs during render',
703+
description: ERROR_DESCRIPTION,
704+
}).withDetail({
705+
kind: 'error',
706+
loc: type.loc ?? operand.loc,
707+
message: `Cannot access ref value during render`,
708+
}),
709+
);
672710
}
673711
}
712+
713+
const ERROR_DESCRIPTION =
714+
'React refs are values that are not needed for rendering. Refs should only be accessed ' +
715+
'outside of render, such as in event handlers or effects. ' +
716+
'Accessing a ref value (the `current` property) during render can cause your component ' +
717+
'not to update as expected (https://react.dev/reference/react/useRef)';

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,48 +32,30 @@ export const FIXTURE_ENTRYPOINT = {
3232
## Error
3333

3434
```
35-
Found 4 errors:
35+
Found 2 errors:
3636
37-
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
37+
Error: Cannot access refs during render
3838
39-
error.capture-ref-for-mutation.ts:12:13
40-
10 | };
41-
11 | const moveLeft = {
42-
> 12 | handler: handleKey('left')(),
43-
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
44-
13 | };
45-
14 | const moveRight = {
46-
15 | handler: handleKey('right')(),
47-
48-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
39+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
4940
5041
error.capture-ref-for-mutation.ts:12:13
5142
10 | };
5243
11 | const moveLeft = {
5344
> 12 | handler: handleKey('left')(),
54-
| ^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
45+
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value
5546
13 | };
5647
14 | const moveRight = {
5748
15 | handler: handleKey('right')(),
5849
59-
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
60-
61-
error.capture-ref-for-mutation.ts:15:13
62-
13 | };
63-
14 | const moveRight = {
64-
> 15 | handler: handleKey('right')(),
65-
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
66-
16 | };
67-
17 | return [moveLeft, moveRight];
68-
18 | }
50+
Error: Cannot access refs during render
6951
70-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
52+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
7153
7254
error.capture-ref-for-mutation.ts:15:13
7355
13 | };
7456
14 | const moveRight = {
7557
> 15 | handler: handleKey('right')(),
76-
| ^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
58+
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value
7759
16 | };
7860
17 | return [moveLeft, moveRight];
7961
18 | }

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,28 @@ export const FIXTURE_ENTRYPOINT = {
2222
```
2323
Found 2 errors:
2424
25-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
25+
Error: Cannot access refs during render
26+
27+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2628
2729
error.hook-ref-value.ts:5:23
2830
3 | function Component(props) {
2931
4 | const ref = useRef();
3032
> 5 | useEffect(() => {}, [ref.current]);
31-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
33+
| ^^^^^^^^^^^ Cannot access ref value during render
3234
6 | }
3335
7 |
3436
8 | export const FIXTURE_ENTRYPOINT = {
3537
36-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
38+
Error: Cannot access refs during render
39+
40+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
3741
3842
error.hook-ref-value.ts:5:23
3943
3 | function Component(props) {
4044
4 | const ref = useRef();
4145
> 5 | useEffect(() => {}, [ref.current]);
42-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
46+
| ^^^^^^^^^^^ Cannot access ref value during render
4347
6 | }
4448
7 |
4549
8 | export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ function Component(props) {
1717
```
1818
Found 1 error:
1919
20-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
20+
Error: Cannot access refs during render
21+
22+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2123
2224
error.invalid-access-ref-during-render.ts:4:16
2325
2 | function Component(props) {
2426
3 | const ref = useRef(null);
2527
> 4 | const value = ref.current;
26-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
28+
| ^^^^^^^^^^^ Cannot access ref value during render
2729
5 | return value;
2830
6 | }
2931
7 |

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ function Component(props) {
2121
```
2222
Found 1 error:
2323
24-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
24+
Error: Cannot access refs during render
25+
26+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2527
2628
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33
2729
7 | return <Foo item={item} current={current} />;
2830
8 | };
2931
> 9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
30-
| ^^^^^^^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
3133
10 | }
3234
11 |
3335
```

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-current-inferred-ref-during-render.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ component Example() {
2020
```
2121
Found 1 error:
2222
23-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
23+
Error: Cannot access refs during render
24+
25+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2426
2527
4 | component Example() {
2628
5 | const fooRef = makeObject_Primitives();
2729
> 6 | fooRef.current = true;
28-
| ^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
30+
| ^^^^^^^^^^^^^^ Cannot update ref during render
2931
7 |
3032
8 | return <Stringify foo={fooRef} />;
3133
9 | }

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ function Component() {
1818
```
1919
Found 1 error:
2020
21-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
21+
Error: Cannot access refs during render
22+
23+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2224
2325
error.invalid-disallow-mutating-ref-in-render.ts:4:2
2426
2 | function Component() {
2527
3 | const ref = useRef(null);
2628
> 4 | ref.current = false;
27-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
29+
| ^^^^^^^^^^^ Cannot update ref during render
2830
5 |
2931
6 | return <button ref={ref} />;
3032
7 | }

0 commit comments

Comments
 (0)