@samitouri / QOS-React-2 / commits / 74d8a18637

[optim] All nested properties in refs are ref values

Forget currently removes memoization of callbacks that have `mutate` effects on `ref` inner properties. @gsathya pointed out that our existing compiler behavior is to (1) NOT extend mutable ranges for functions that mutate `ref.current` and (2) extend mutable ranges for functions that mutate `ref.current.inner`. ```js // input function Component() { const ref = useRef({ text: null }); const handleChange = useCallback((e) => { ref.current.text = e.target.value; }); return <input onChange={handleChange} />; } // output function Component() { const ref = useRef({ text: null }); // now unmemoized! const handleChange = (e) => { ref.current.text = e.target.value; };

Mofei Zhang committed Jan 29, 2024 at 17:04 UTC 74d8a18637587334a011d572afa52c3c60382611
6 files changed +199 -10
compiler/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts
+3 -1
@@ -389,7 +389,9 @@ addObject(BUILTIN_SHAPES, BuiltInUseRefId, [
389 ["current", { kind: "Object", shapeId: BuiltInRefValueId }],
390 ]);
391
392 -addObject(BUILTIN_SHAPES, BuiltInRefValueId, []);
392 +addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
393 + ["*", { kind: "Object", shapeId: BuiltInRefValueId }],
394 +]);
395
396 addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
397 [
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-current-field-write-not-added-to-dep.expect.md new
+67
@@ -0,0 +1,67 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useRef } from "react";
6 +
7 +function Component() {
8 + const ref = useRef({ text: { value: null } });
9 + const inputChanged = (e) => {
10 + ref.current.text.value = e.target.value;
11 + };
12 +
13 + return <input onChange={inputChanged} />;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { useRef, unstable_useMemoCache as useMemoCache } from "react";
27 +
28 +function Component() {
29 + const $ = useMemoCache(4);
30 + let t0;
31 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 + t0 = { text: { value: null } };
33 + $[0] = t0;
34 + } else {
35 + t0 = $[0];
36 + }
37 + const ref = useRef(t0);
38 + let t1;
39 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
40 + t1 = (e) => {
41 + ref.current.text.value = e.target.value;
42 + };
43 + $[1] = t1;
44 + } else {
45 + t1 = $[1];
46 + }
47 + const inputChanged = t1;
48 + let t2;
49 + if ($[2] !== inputChanged) {
50 + t2 = <input onChange={inputChanged} />;
51 + $[2] = inputChanged;
52 + $[3] = t2;
53 + } else {
54 + t2 = $[3];
55 + }
56 + return t2;
57 +}
58 +
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: Component,
61 + params: [{}],
62 +};
63 +
64 +```
65 +
66 +### Eval output
67 +(kind: ok) <input>
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ref-current-field-write-not-added-to-dep.js new
+15
@@ -0,0 +1,15 @@
1 +import { useRef } from "react";
2 +
3 +function Component() {
4 + const ref = useRef({ text: { value: null } });
5 + const inputChanged = (e) => {
6 + ref.current.text.value = e.target.value;
7 + };
8 +
9 + return <input onChange={inputChanged} />;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{}],
15 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useCallback-set-ref-nested-property-dont-preserve-memoization.expect.md
+16 -9
@@ -37,23 +37,30 @@ import {
37 } from "react";
38
39 function Component(props) {
40 - const $ = useMemoCache(2);
41 - const ref = useRef({ inner: null });
40 + const $ = useMemoCache(3);
41 + let t0;
42 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
43 + t0 = { inner: null };
44 + $[0] = t0;
45 + } else {
46 + t0 = $[0];
47 + }
48 + const ref = useRef(t0);
49
50 const onChange = (event) => {
51 ref.current.inner = event.target.value;
52 };
53
54 ref.current.inner = null;
48 - let t0;
49 - if ($[0] !== onChange) {
50 - t0 = <input onChange={onChange} />;
51 - $[0] = onChange;
52 - $[1] = t0;
55 + let t1;
56 + if ($[1] !== onChange) {
57 + t1 = <input onChange={onChange} />;
58 + $[1] = onChange;
59 + $[2] = t1;
60 } else {
54 - t0 = $[1];
61 + t1 = $[2];
62 }
56 - return t0;
63 + return t1;
64 }
65
66 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useCallback-set-ref-nested-property.expect.md new
+78
@@ -0,0 +1,78 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useCallback, useRef } from "react";
6 +
7 +// Identical to useCallback-set-ref-nested-property-preserve-memoization,
8 +// but with a different set of compiler flags
9 +function Component({}) {
10 + const ref = useRef({ inner: null });
11 +
12 + const onChange = useCallback((event) => {
13 + // The ref should still be mutable here even though function deps are frozen in
14 + // @enablePreserveExistingMemoizationGuarantees mode
15 + ref.current.inner = event.target.value;
16 + });
17 +
18 + return <input onChange={onChange} />;
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: Component,
23 + params: [{}],
24 +};
25 +
26 +```
27 +
28 +## Code
29 +
30 +```javascript
31 +import {
32 + useCallback,
33 + useRef,
34 + unstable_useMemoCache as useMemoCache,
35 +} from "react";
36 +
37 +// Identical to useCallback-set-ref-nested-property-preserve-memoization,
38 +// but with a different set of compiler flags
39 +function Component(t27) {
40 + const $ = useMemoCache(4);
41 + let t0;
42 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
43 + t0 = { inner: null };
44 + $[0] = t0;
45 + } else {
46 + t0 = $[0];
47 + }
48 + const ref = useRef(t0);
49 + let t1;
50 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
51 + t1 = (event) => {
52 + ref.current.inner = event.target.value;
53 + };
54 + $[1] = t1;
55 + } else {
56 + t1 = $[1];
57 + }
58 + const onChange = t1;
59 + let t2;
60 + if ($[2] !== onChange) {
61 + t2 = <input onChange={onChange} />;
62 + $[2] = onChange;
63 + $[3] = t2;
64 + } else {
65 + t2 = $[3];
66 + }
67 + return t2;
68 +}
69 +
70 +export const FIXTURE_ENTRYPOINT = {
71 + fn: Component,
72 + params: [{}],
73 +};
74 +
75 +```
76 +
77 +### Eval output
78 +(kind: ok) <input>
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useCallback-set-ref-nested-property.js new
+20
@@ -0,0 +1,20 @@
1 +import { useCallback, useRef } from "react";
2 +
3 +// Identical to useCallback-set-ref-nested-property-preserve-memoization,
4 +// but with a different set of compiler flags
5 +function Component({}) {
6 + const ref = useRef({ inner: null });
7 +
8 + const onChange = useCallback((event) => {
9 + // The ref should still be mutable here even though function deps are frozen in
10 + // @enablePreserveExistingMemoizationGuarantees mode
11 + ref.current.inner = event.target.value;
12 + });
13 +
14 + return <input onChange={onChange} />;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{}],
20 +};