mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Reposition ref-in-render errors to the read location of .current
Summary:
Since we want to make ref-in-render errors enabled by default, we should position those errors at the location of the read. Not only will this be a better experience, but it also aligns the behavior of Forget and Flow.
This PR also cleans up the resulting error messages to not emit implementation details about place values.
ghstack-source-id: 1d11317068
Pull Request resolved: https://github.com/facebook/react/pull/30723
This commit is contained in:
+59
-14
@@ -15,7 +15,6 @@ import {
|
||||
isRefValueType,
|
||||
isUseRefType,
|
||||
} from '../HIR';
|
||||
import {printPlace} from '../HIR/PrintHIR';
|
||||
import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
@@ -53,6 +52,7 @@ function validateNoRefAccessInRenderImpl(
|
||||
refAccessingFunctions: Set<IdentifierId>,
|
||||
): Result<void, CompilerError> {
|
||||
const errors = new CompilerError();
|
||||
const lookupLocations: Map<IdentifierId, SourceLocation> = new Map();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
switch (instr.value.kind) {
|
||||
@@ -64,10 +64,12 @@ function validateNoRefAccessInRenderImpl(
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason:
|
||||
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
|
||||
loc: operand.loc,
|
||||
description: `Cannot access ref value at ${printPlace(
|
||||
operand,
|
||||
)}`,
|
||||
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
|
||||
description:
|
||||
operand.identifier.name !== null &&
|
||||
operand.identifier.name.kind === 'named'
|
||||
? `Cannot access ref value \`${operand.identifier.name.value}\``
|
||||
: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -75,12 +77,24 @@ function validateNoRefAccessInRenderImpl(
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
if (
|
||||
isRefValueType(instr.lvalue.identifier) &&
|
||||
instr.value.property === 'current'
|
||||
) {
|
||||
lookupLocations.set(instr.lvalue.identifier.id, instr.loc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'LoadLocal': {
|
||||
if (refAccessingFunctions.has(instr.value.place.identifier.id)) {
|
||||
refAccessingFunctions.add(instr.lvalue.identifier.id);
|
||||
}
|
||||
if (isRefValueType(instr.lvalue.identifier)) {
|
||||
const loc = lookupLocations.get(instr.value.place.identifier.id);
|
||||
if (loc !== undefined) {
|
||||
lookupLocations.set(instr.lvalue.identifier.id, loc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'StoreLocal': {
|
||||
@@ -88,6 +102,13 @@ function validateNoRefAccessInRenderImpl(
|
||||
refAccessingFunctions.add(instr.value.lvalue.place.identifier.id);
|
||||
refAccessingFunctions.add(instr.lvalue.identifier.id);
|
||||
}
|
||||
if (isRefValueType(instr.value.lvalue.place.identifier)) {
|
||||
const loc = lookupLocations.get(instr.value.value.identifier.id);
|
||||
if (loc !== undefined) {
|
||||
lookupLocations.set(instr.value.lvalue.place.identifier.id, loc);
|
||||
lookupLocations.set(instr.lvalue.identifier.id, loc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ObjectMethod':
|
||||
@@ -140,7 +161,11 @@ function validateNoRefAccessInRenderImpl(
|
||||
reason:
|
||||
'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)',
|
||||
loc: callee.loc,
|
||||
description: `Function ${printPlace(callee)} accesses a ref`,
|
||||
description:
|
||||
callee.identifier.name !== null &&
|
||||
callee.identifier.name.kind === 'named'
|
||||
? `Function \`${callee.identifier.name.value}\` accesses a ref`
|
||||
: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -149,7 +174,7 @@ function validateNoRefAccessInRenderImpl(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
operand,
|
||||
operand.loc,
|
||||
lookupLocations.get(operand.identifier.id) ?? operand.loc,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -162,7 +187,7 @@ function validateNoRefAccessInRenderImpl(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
operand,
|
||||
operand.loc,
|
||||
lookupLocations.get(operand.identifier.id) ?? operand.loc,
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -175,13 +200,18 @@ function validateNoRefAccessInRenderImpl(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
instr.value.object,
|
||||
instr.loc,
|
||||
lookupLocations.get(instr.value.object.identifier.id) ?? instr.loc,
|
||||
);
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
if (operand === instr.value.object) {
|
||||
continue;
|
||||
}
|
||||
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
|
||||
validateNoRefValueAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
lookupLocations,
|
||||
operand,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -190,14 +220,24 @@ function validateNoRefAccessInRenderImpl(
|
||||
break;
|
||||
default: {
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
|
||||
validateNoRefValueAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
lookupLocations,
|
||||
operand,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const operand of eachTerminalOperand(block.terminal)) {
|
||||
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
|
||||
validateNoRefValueAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
lookupLocations,
|
||||
operand,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +251,7 @@ function validateNoRefAccessInRenderImpl(
|
||||
function validateNoRefValueAccess(
|
||||
errors: CompilerError,
|
||||
refAccessingFunctions: Set<IdentifierId>,
|
||||
lookupLocations: Map<IdentifierId, SourceLocation>,
|
||||
operand: Place,
|
||||
): void {
|
||||
if (
|
||||
@@ -221,8 +262,12 @@ function validateNoRefValueAccess(
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason:
|
||||
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
|
||||
loc: operand.loc,
|
||||
description: `Cannot access ref value at ${printPlace(operand)}`,
|
||||
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
|
||||
description:
|
||||
operand.identifier.name !== null &&
|
||||
operand.identifier.name.kind === 'named'
|
||||
? `Cannot access ref value \`${operand.identifier.name.value}\``
|
||||
: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
+4
-3
@@ -15,10 +15,11 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
4 | const value = ref.current;
|
||||
> 5 | return value;
|
||||
| ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $22:TObject<BuiltInRefValue> (5:5)
|
||||
> 4 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
5 | return value;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ function Component() {
|
||||
7 | };
|
||||
8 | const changeRef = setRef;
|
||||
> 9 | changeRef();
|
||||
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef). Function mutate? $39[11:13]:TObject<BuiltInFunction> accesses a ref (9:9)
|
||||
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
|
||||
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
|
||||
10 |
|
||||
|
||||
+4
-3
@@ -14,10 +14,11 @@ function Component({ref}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | // @validateRefAccessDuringRender @compilationMode(infer)
|
||||
2 | function Component({ref}) {
|
||||
3 | const value = ref.current;
|
||||
> 4 | return <div>{value}</div>;
|
||||
| ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $17:TObject<BuiltInRefValue> (4:4)
|
||||
> 3 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
|
||||
4 | return <div>{value}</div>;
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
+4
-3
@@ -14,10 +14,11 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | // @validateRefAccessDuringRender @compilationMode(infer)
|
||||
2 | function Component(props) {
|
||||
3 | const value = props.ref.current;
|
||||
> 4 | return <div>{value}</div>;
|
||||
| ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $15:TObject<BuiltInRefValue> (4:4)
|
||||
> 3 | const value = props.ref.current;
|
||||
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
|
||||
4 | return <div>{value}</div>;
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ function Component(props) {
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
> 4 | return <Foo ref={ref.current} />;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $19:TObject<BuiltInRefValue> (4:4)
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ function Component(props) {
|
||||
> 4 | ref.current = props.value;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $24:TObject<BuiltInRefValue> (5:5)
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
|
||||
5 | return ref.current;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
+2
-2
@@ -18,9 +18,9 @@ function Component(props) {
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef({inner: null});
|
||||
> 4 | ref.current.inner = props.value;
|
||||
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $30:TObject<BuiltInRefValue> (5:5)
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
|
||||
5 | return ref.current.inner;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
8 | };
|
||||
9 |
|
||||
> 10 | return s;
|
||||
| ^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $25:TObject<BuiltInFunction> (10:10)
|
||||
| ^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (10:10)
|
||||
11 | }
|
||||
12 |
|
||||
13 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
12 |
|
||||
13 | // The ref is modified later, extending its range and preventing memoization of onChange
|
||||
> 14 | ref.current.inner = null;
|
||||
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (14:14)
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (14:14)
|
||||
15 |
|
||||
16 | return <input onChange={onChange} />;
|
||||
17 | }
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
15 | ref.current.inner = null;
|
||||
16 | };
|
||||
> 17 | reset();
|
||||
| ^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef). Function mutate? $77[20:22]:TObject<BuiltInFunction> accesses a ref (17:17)
|
||||
| ^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (17:17)
|
||||
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (17:17)
|
||||
18 |
|
||||
|
||||
Reference in New Issue
Block a user