@samitouri / QOS-React-2 / commits / 474c38c573

[rhir][tests] Added tests for primitives as dependencies

--- Our current compiler has specific logic for determining what can be a reactive value / reactive dependency. Currently, all of the following affect whether an identifier is a reactive: - **alias analysis** (applicable to objects) - **data + control flow** (whether any other reactive identifiers is used in determining it) - **reactive scopes** (we generalize and say anything produced by a block with reactive dependencies must be non-stable and reactive) - this is not true in the case of const primitives, but an overestimate is safe - whether the **scope that declares this identifier** is ~~currently active~~ the same scope in which it is used (fixed by #1275) (since a scope cannot be dependent on itself) These conditions are complex. We end up inferring most identifiers as `mutable` and `object` types, which have different stability and aliasing properties from primitives. As a result, we're missing some cases in our existing test coverage. Test case output is fixed by #1274 and #1275 --- (This can be separated from the stack below, which implements conditional dependencies. Happy to merge that first and open this as a new stack if that produces a significantly better Git PR history.)

Mofei Zhang committed Feb 28, 2023 at 16:35 UTC 474c38c573e4a56ff3f8eb31bb22c9c65895118e
4 files changed +215
compiler/forget/src/__tests__/fixtures/hir/allocating-primitive-as-dep.expect.md new
+99
@@ -0,0 +1,99 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// bar(props.b) is an allocating expression that produces a primitive, which means
6 +// that Forget should memoize it.
7 +// Correctness:
8 +// - y depends on either bar(props.b) or bar(props.b) + 1
9 +function AllocatingPrimitiveAsDep(props) {
10 + let y = foo(bar(props).b + 1);
11 + return y;
12 +}
13 +
14 +function PrimitiveAsDepNested(props) {
15 + let x = {};
16 + mutate(x);
17 + let y = foo(bar(props.b) + 1);
18 + mutate(x, props.a);
19 + return [x, y];
20 +}
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +// bar(props.b) is an allocating expression that produces a primitive, which means
28 +// that Forget should memoize it.
29 +// Correctness:
30 +// - y depends on either bar(props.b) or bar(props.b) + 1
31 +function AllocatingPrimitiveAsDep(props) {
32 + const $ = React.unstable_useMemoCache(4);
33 + const c_0 = $[0] !== props;
34 + let t0;
35 + if (c_0) {
36 + t0 = bar(props);
37 + $[0] = props;
38 + $[1] = t0;
39 + } else {
40 + t0 = $[1];
41 + }
42 + const t1 = t0.b + 1;
43 + const c_2 = $[2] !== t1;
44 + let y;
45 + if (c_2) {
46 + y = foo(t1);
47 + $[2] = t1;
48 + $[3] = y;
49 + } else {
50 + y = $[3];
51 + }
52 + return y;
53 +}
54 +
55 +function PrimitiveAsDepNested(props) {
56 + const $ = React.unstable_useMemoCache(8);
57 + const c_0 = $[0] !== props.b;
58 + const c_1 = $[1] !== props.a;
59 + let x;
60 + if (c_0 || c_1) {
61 + x = {};
62 + mutate(x);
63 + const c_3 = $[3] !== props.b;
64 + let t0;
65 + if (c_3) {
66 + t0 = bar(props.b);
67 + $[3] = props.b;
68 + $[4] = t0;
69 + } else {
70 + t0 = $[4];
71 + }
72 + let y;
73 + if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
74 + y = foo(t0 + 1);
75 + $[5] = y;
76 + } else {
77 + y = $[5];
78 + }
79 + mutate(x, props.a);
80 + $[0] = props.b;
81 + $[1] = props.a;
82 + $[2] = x;
83 + } else {
84 + x = $[2];
85 + }
86 + const c_6 = $[6] !== x;
87 + let t1;
88 + if (c_6) {
89 + t1 = [x, y];
90 + $[6] = x;
91 + $[7] = t1;
92 + } else {
93 + t1 = $[7];
94 + }
95 + return t1;
96 +}
97 +
98 +```
99 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/allocating-primitive-as-dep.js new
+16
@@ -0,0 +1,16 @@
1 +// bar(props.b) is an allocating expression that produces a primitive, which means
2 +// that Forget should memoize it.
3 +// Correctness:
4 +// - y depends on either bar(props.b) or bar(props.b) + 1
5 +function AllocatingPrimitiveAsDep(props) {
6 + let y = foo(bar(props).b + 1);
7 + return y;
8 +}
9 +
10 +function PrimitiveAsDepNested(props) {
11 + let x = {};
12 + mutate(x);
13 + let y = foo(bar(props.b) + 1);
14 + mutate(x, props.a);
15 + return [x, y];
16 +}
compiler/forget/src/__tests__/fixtures/hir/primitive-as-dep.expect.md new
+83
@@ -0,0 +1,83 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// props.b + 1 is an non-allocating expression, which means Forget can
6 +// emit it trivially and repeatedly (e.g. no need to memoize props.b + 1
7 +// separately from props.b)
8 +// Correctness:
9 +// y depends on either props.b or props.b + 1
10 +function PrimitiveAsDep(props) {
11 + let y = foo(props.b + 1);
12 + return y;
13 +}
14 +
15 +function PrimitiveAsDepNested(props) {
16 + let x = {};
17 + mutate(x);
18 + let y = foo(props.b + 1);
19 + mutate(x, props.a);
20 + return [x, y];
21 +}
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +// props.b + 1 is an non-allocating expression, which means Forget can
29 +// emit it trivially and repeatedly (e.g. no need to memoize props.b + 1
30 +// separately from props.b)
31 +// Correctness:
32 +// y depends on either props.b or props.b + 1
33 +function PrimitiveAsDep(props) {
34 + const $ = React.unstable_useMemoCache(2);
35 + const t0 = props.b + 1;
36 + const c_0 = $[0] !== t0;
37 + let y;
38 + if (c_0) {
39 + y = foo(t0);
40 + $[0] = t0;
41 + $[1] = y;
42 + } else {
43 + y = $[1];
44 + }
45 + return y;
46 +}
47 +
48 +function PrimitiveAsDepNested(props) {
49 + const $ = React.unstable_useMemoCache(6);
50 + const c_0 = $[0] !== props.b;
51 + const c_1 = $[1] !== props.a;
52 + let x;
53 + if (c_0 || c_1) {
54 + x = {};
55 + mutate(x);
56 + let y;
57 + if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
58 + y = foo(props.b + 1);
59 + $[3] = y;
60 + } else {
61 + y = $[3];
62 + }
63 + mutate(x, props.a);
64 + $[0] = props.b;
65 + $[1] = props.a;
66 + $[2] = x;
67 + } else {
68 + x = $[2];
69 + }
70 + const c_4 = $[4] !== x;
71 + let t0;
72 + if (c_4) {
73 + t0 = [x, y];
74 + $[4] = x;
75 + $[5] = t0;
76 + } else {
77 + t0 = $[5];
78 + }
79 + return t0;
80 +}
81 +
82 +```
83 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/primitive-as-dep.js new
+17
@@ -0,0 +1,17 @@
1 +// props.b + 1 is an non-allocating expression, which means Forget can
2 +// emit it trivially and repeatedly (e.g. no need to memoize props.b + 1
3 +// separately from props.b)
4 +// Correctness:
5 +// y depends on either props.b or props.b + 1
6 +function PrimitiveAsDep(props) {
7 + let y = foo(props.b + 1);
8 + return y;
9 +}
10 +
11 +function PrimitiveAsDepNested(props) {
12 + let x = {};
13 + mutate(x);
14 + let y = foo(props.b + 1);
15 + mutate(x, props.a);
16 + return [x, y];
17 +}