From aae9b01f91347e3417a20da861762ea0402d7cfb Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 15 Jun 2023 09:32:30 -0700 Subject: [PATCH] Test cases for inferring mutability of function expressions (as values) This PR contains just the test cases from #1717, showing some tricky cases around function expression mutability inference. --- ...-captures-value-later-frozen-jsx.expect.md | 27 +++++++++ ...ression-captures-value-later-frozen-jsx.js | 12 ++++ ...-maybe-mutates-hook-return-value.expect.md | 27 +++++++++ ...ression-maybe-mutates-hook-return-value.js | 12 ++++ ...sion-captures-value-later-frozen.expect.md | 58 +++++++++++++++++++ ...-expression-captures-value-later-frozen.js | 14 +++++ ...ression-mutates-immutable-value.expect.md} | 0 ...ion-expression-mutates-immutable-value.js} | 0 8 files changed, 150 insertions(+) create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.expect.md create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.js create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.expect.md create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.js create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.expect.md create mode 100644 compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.js rename compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/{invalid-freeze-mutable-lambda.expect.md => todo-invalid-function-expression-mutates-immutable-value.expect.md} (100%) rename compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/{invalid-freeze-mutable-lambda.js => todo-invalid-function-expression-mutates-immutable-value.js} (100%) diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.expect.md b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.expect.md new file mode 100644 index 0000000000..f4507d3e5a --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.expect.md @@ -0,0 +1,27 @@ + +## Input + +```javascript +function Component(props) { + let x = {}; + // onChange should be inferred as immutable, because the value + // it captures (`x`) is frozen by the time the function is referenced + const onChange = (e) => { + maybeMutate(x, e.target.value); + }; + if (props.cond) { +
{x}
; + } + return ; +} + +``` + + +## Error + +``` +[ReactForget] InvalidInput: Cannot use a mutable function where an immutable value is expected (11:11) +``` + + \ No newline at end of file diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.js new file mode 100644 index 0000000000..7e694fa1bd --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-captures-value-later-frozen-jsx.js @@ -0,0 +1,12 @@ +function Component(props) { + let x = {}; + // onChange should be inferred as immutable, because the value + // it captures (`x`) is frozen by the time the function is referenced + const onChange = (e) => { + maybeMutate(x, e.target.value); + }; + if (props.cond) { +
{x}
; + } + return ; +} diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.expect.md b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.expect.md new file mode 100644 index 0000000000..9427787184 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.expect.md @@ -0,0 +1,27 @@ + +## Input + +```javascript +function Component(props) { + const id = useSelectedEntitytId(); + // this example should infer `id` as mutable, and then infer `onLoad` as mutable, + // and be rejected because onLoad cannot be passed as a frozen value in the JSX. + // however, we likely have to allow this example to work, because hook return + // values are generally immutable in practice and are also widely referenced in + // callbacks. + const onLoad = () => { + log(id); + }; + return ; +} + +``` + + +## Error + +``` +[ReactForget] InvalidInput: Cannot use a mutable function where an immutable value is expected (11:11) +``` + + \ No newline at end of file diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.js new file mode 100644 index 0000000000..fcf38652b1 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-function-expression-maybe-mutates-hook-return-value.js @@ -0,0 +1,12 @@ +function Component(props) { + const id = useSelectedEntitytId(); + // this example should infer `id` as mutable, and then infer `onLoad` as mutable, + // and be rejected because onLoad cannot be passed as a frozen value in the JSX. + // however, we likely have to allow this example to work, because hook return + // values are generally immutable in practice and are also widely referenced in + // callbacks. + const onLoad = () => { + log(id); + }; + return ; +} diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.expect.md b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.expect.md new file mode 100644 index 0000000000..3e276ce3ee --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x = {}; + // onChange should be inferred as immutable, because the value + // it captures (`x`) is frozen by the time the function is referenced + const onChange = (e) => { + maybeMutate(x, e.target.value); + }; + if (props.cond) { +
{x}
; + } + // ideally this call would be outside the memoization block for `x` + onChange(); + return ; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(4); + const c_0 = $[0] !== props.cond; + let x; + if (c_0) { + x = {}; + + const onChange = (e) => { + maybeMutate(x, e.target.value); + }; + if (props.cond) { + } + + onChange(); + $[0] = props.cond; + $[1] = x; + } else { + x = $[1]; + } + const c_2 = $[2] !== x; + let t0; + if (c_2) { + t0 = ; + $[2] = x; + $[3] = t0; + } else { + t0 = $[3]; + } + return t0; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.js new file mode 100644 index 0000000000..a5e39b4254 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-function-expression-captures-value-later-frozen.js @@ -0,0 +1,14 @@ +function Component(props) { + let x = {}; + // onChange should be inferred as immutable, because the value + // it captures (`x`) is frozen by the time the function is referenced + const onChange = (e) => { + maybeMutate(x, e.target.value); + }; + if (props.cond) { +
{x}
; + } + // ideally this call would be outside the memoization block for `x` + onChange(); + return ; +} diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-invalid-function-expression-mutates-immutable-value.expect.md similarity index 100% rename from compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.expect.md rename to compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-invalid-function-expression-mutates-immutable-value.expect.md diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-invalid-function-expression-mutates-immutable-value.js similarity index 100% rename from compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/invalid-freeze-mutable-lambda.js rename to compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-invalid-function-expression-mutates-immutable-value.js