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.
This commit is contained in:
Joe Savona
2023-05-25 16:08:10 -07:00
parent edbb6e2bdb
commit 91dcf24e67
2 changed files with 71 additions and 0 deletions
@@ -0,0 +1,62 @@
## Input
```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} />;
}
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(7);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { value: "" };
$[0] = t0;
} else {
t0 = $[0];
}
const [x, setX] = useState(t0);
const c_1 = $[1] !== x;
const c_2 = $[2] !== setX;
let t1;
if (c_1 || c_2) {
t1 = (e) => {
// INVALID! should use copy-on-write and pass the new value
x.value = e.target.value;
setX(x);
};
$[1] = x;
$[2] = setX;
$[3] = t1;
} else {
t1 = $[3];
}
const onChange = t1;
const c_4 = $[4] !== x.value;
const c_5 = $[5] !== onChange;
let t2;
if (c_4 || c_5) {
t2 = <input value={x.value} onChange={onChange} />;
$[4] = x.value;
$[5] = onChange;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
```
@@ -0,0 +1,9 @@
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} />;
}