@samitouri / QOS-React / commits / 91dcf24e67

Add test case for invalid lambdas

This is the example we discussed in our design sync. ```javascript function Component(props) { const [x, setX] = useState({ value: "" }); const onChange = (e) => { // INVALID! should use copy-on-write and pass the new value x.value = e.target.value; setX(x); }; return <input value={x.value} onChange={onChange} />; } ``` Here `onChange` is a mutable lambda, and it should be invalid to pass a mutable lambda where a frozen value is expected. This is because unlike other value types, you cannot freeze a lambda — the only choice is to not call it at all. Note that there is a harder case to catch: ```js function Component(props) { const [x, setX] = useState({ value: "" }); const onChange = (e) => { // INVALID! should use copy-on-write and pass the new value x.value = e.target.value; setX(x); }; const x = constructAValueThatMaybeAliasesItsInput(onChange); return <input value={x.value} onChange={x.maybeGetTheLambdaBack()} />; } ``` This case demonstrates how mutable lambdas can be captured and then accessed later — the analysis to catch this case is more sophisticated bc it involves inferring that `x` aliases a mutable lambda. But we also can't be sure that `x` does alias the lambda, so disallowing this code could prevent a lot of valid code from compiling. My hypothesis is that we should start with at least validating the example at the top, while allowing the second case for now.

Joe Savona committed May 25, 2023 at 16:08 UTC 91dcf24e6743a4ebe035fd729eb635e7863ba24c
2 files changed +71
compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md new
+62
@@ -0,0 +1,62 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + const [x, setX] = useState({ value: "" });
7 + const onChange = (e) => {
8 + // INVALID! should use copy-on-write and pass the new value
9 + x.value = e.target.value;
10 + setX(x);
11 + };
12 + return <input value={x.value} onChange={onChange} />;
13 +}
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +import { unstable_useMemoCache as useMemoCache } from "react";
21 +function Component(props) {
22 + const $ = useMemoCache(7);
23 + let t0;
24 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25 + t0 = { value: "" };
26 + $[0] = t0;
27 + } else {
28 + t0 = $[0];
29 + }
30 + const [x, setX] = useState(t0);
31 + const c_1 = $[1] !== x;
32 + const c_2 = $[2] !== setX;
33 + let t1;
34 + if (c_1 || c_2) {
35 + t1 = (e) => {
36 + // INVALID! should use copy-on-write and pass the new value
37 + x.value = e.target.value;
38 + setX(x);
39 + };
40 + $[1] = x;
41 + $[2] = setX;
42 + $[3] = t1;
43 + } else {
44 + t1 = $[3];
45 + }
46 + const onChange = t1;
47 + const c_4 = $[4] !== x.value;
48 + const c_5 = $[5] !== onChange;
49 + let t2;
50 + if (c_4 || c_5) {
51 + t2 = <input value={x.value} onChange={onChange} />;
52 + $[4] = x.value;
53 + $[5] = onChange;
54 + $[6] = t2;
55 + } else {
56 + t2 = $[6];
57 + }
58 + return t2;
59 +}
60 +
61 +```
62 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js new
+9
@@ -0,0 +1,9 @@
1 +function Component(props) {
2 + const [x, setX] = useState({ value: "" });
3 + const onChange = (e) => {
4 + // INVALID! should use copy-on-write and pass the new value
5 + x.value = e.target.value;
6 + setX(x);
7 + };
8 + return <input value={x.value} onChange={onChange} />;
9 +}