From 91dcf24e6743a4ebe035fd729eb635e7863ba24c Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 25 May 2023 16:08:10 -0700 Subject: [PATCH] Add test case for invalid lambdas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ; } ``` 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 ; } ``` 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. --- .../invalid-freeze-mutable-lambda.expect.md | 62 +++++++++++++++++++ .../compiler/invalid-freeze-mutable-lambda.js | 9 +++ 2 files changed, 71 insertions(+) create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js diff --git a/compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md new file mode 100644 index 0000000000..41f4979204 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md @@ -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 ; +} + +``` + +## 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 = ; + $[4] = x.value; + $[5] = onChange; + $[6] = t2; + } else { + t2 = $[6]; + } + return t2; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js b/compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js new file mode 100644 index 0000000000..c4d62da44c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js @@ -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 ; +}