@samitouri / QOS-React-2 / commits / c50d593e63

[compiler] Type `ref` prop as a ref

Adds a shape type for component props, which has one defined property: "ref". This means that if the ref property exists, we can type usage of `props.ref` (or via destructuring) the same as the result of `useRef()` and infer downstream usage similarly. ghstack-source-id: 76cd07c5dfeea2a4aafe141912663b097308fd73 Pull Request resolved: https://github.com/facebook/react/pull/29834

Joe Savona committed Jun 10, 2024 at 09:52 UTC c50d593e6390ad200f355ad147dfbc61deaaf8b5
8 files changed +107 -1
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+6
@@ -188,6 +188,7 @@ export type ObjectShape = {
188 * the inferred types for [] and {}.
189 */
190 export type ShapeRegistry = Map<string, ObjectShape>;
191 +export const BuiltInPropsId = "BuiltInProps";
192 export const BuiltInArrayId = "BuiltInArray";
193 export const BuiltInFunctionId = "BuiltInFunction";
194 export const BuiltInJsxId = "BuiltInJsx";
@@ -207,6 +208,11 @@ export const BuiltInDispatchId = "BuiltInDispatch";
208 // ShapeRegistry with default definitions for built-ins.
209 export const BUILTIN_SHAPES: ShapeRegistry = new Map();
210
211 +// If the `ref` prop exists, it has the ref type
212 +addObject(BUILTIN_SHAPES, BuiltInPropsId, [
213 + ["ref", { kind: "Object", shapeId: BuiltInUseRefId }],
214 +]);
215 +
216 /* Built-in array shape */
217 addObject(BUILTIN_SHAPES, BuiltInArrayId, [
218 [
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+8 -1
@@ -23,6 +23,7 @@ import {
23 BuiltInFunctionId,
24 BuiltInJsxId,
25 BuiltInObjectId,
26 + BuiltInPropsId,
27 BuiltInUseRefId,
28 } from "../HIR/ObjectShape";
29 import { eachInstructionLValue, eachInstructionOperand } from "../HIR/visitors";
@@ -101,7 +102,13 @@ function* generate(
102 func: HIRFunction
103 ): Generator<TypeEquation, void, undefined> {
104 if (func.env.fnType === "Component") {
104 - const [_, ref] = func.params;
105 + const [props, ref] = func.params;
106 + if (props && props.kind === "Identifier") {
107 + yield equation(props.identifier.type, {
108 + kind: "Object",
109 + shapeId: BuiltInPropsId,
110 + });
111 + }
112 if (ref && ref.kind === "Identifier") {
113 yield equation(ref.identifier.type, {
114 kind: "Object",
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md new
+25
@@ -0,0 +1,25 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender @compilationMode(infer)
6 +function Component({ ref }) {
7 + const value = ref.current;
8 + return <div>{value}</div>;
9 +}
10 +
11 +```
12 +
13 +
14 +## Error
15 +
16 +```
17 + 2 | function Component({ ref }) {
18 + 3 | const value = ref.current;
19 +> 4 | return <div>{value}</div>;
20 + | ^^^^^ 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)
21 + 5 | }
22 + 6 |
23 +```
24 +
25 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js new
+5
@@ -0,0 +1,5 @@
1 +// @validateRefAccessDuringRender @compilationMode(infer)
2 +function Component({ ref }) {
3 + const value = ref.current;
4 + return <div>{value}</div>;
5 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md new
+25
@@ -0,0 +1,25 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender @compilationMode(infer)
6 +function Component(props) {
7 + const value = props.ref.current;
8 + return <div>{value}</div>;
9 +}
10 +
11 +```
12 +
13 +
14 +## Error
15 +
16 +```
17 + 2 | function Component(props) {
18 + 3 | const value = props.ref.current;
19 +> 4 | return <div>{value}</div>;
20 + | ^^^^^ 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)
21 + 5 | }
22 + 6 |
23 +```
24 +
25 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js new
+5
@@ -0,0 +1,5 @@
1 +// @validateRefAccessDuringRender @compilationMode(infer)
2 +function Component(props) {
3 + const value = props.ref.current;
4 + return <div>{value}</div>;
5 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md new
+27
@@ -0,0 +1,27 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender @compilationMode(infer)
6 +function Component(props) {
7 + const ref = props.ref;
8 + ref.current = true;
9 + return <div>{value}</div>;
10 +}
11 +
12 +```
13 +
14 +
15 +## Error
16 +
17 +```
18 + 2 | function Component(props) {
19 + 3 | const ref = props.ref;
20 +> 4 | ref.current = true;
21 + | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
22 + 5 | return <div>{value}</div>;
23 + 6 | }
24 + 7 |
25 +```
26 +
27 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js new
+6
@@ -0,0 +1,6 @@
1 +// @validateRefAccessDuringRender @compilationMode(infer)
2 +function Component(props) {
3 + const ref = props.ref;
4 + ref.current = true;
5 + return <div>{value}</div>;
6 +}