From 84da8994bd55f4a908fe0ca8446d1bb4d09adee2 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Tue, 17 Oct 2023 09:48:23 +0100 Subject: [PATCH] Test cases for reactive (control) dependencies Adds test cases for all the cases of control dependencies that I can think of. We don't currently handle control dependencies correctly in any of these cases. There's also another test case which demonstrates why reactive dependency inference needs to be fixpoint, even for non-control dependencies. --- ...control-dependency-do-while-test.expect.md | 64 +++++++++++++++ ...active-control-dependency-do-while-test.js | 22 ++++++ ...tive-control-dependency-for-init.expect.md | 61 +++++++++++++++ ...ug.reactive-control-dependency-for-init.js | 21 +++++ ...tive-control-dependency-for-test.expect.md | 58 ++++++++++++++ ...ug.reactive-control-dependency-for-test.js | 20 +++++ ...ve-control-dependency-for-update.expect.md | 58 ++++++++++++++ ....reactive-control-dependency-for-update.js | 20 +++++ ...trol-dependency-forin-collection.expect.md | 60 ++++++++++++++ ...ive-control-dependency-forin-collection.js | 21 +++++ ...trol-dependency-forof-collection.expect.md | 58 ++++++++++++++ ...ive-control-dependency-forof-collection.js | 20 +++++ ...g.reactive-control-dependency-if.expect.md | 53 +++++++++++++ .../bug.reactive-control-dependency-if.js | 17 ++++ ...pendency-reactive-after-fixpoint.expect.md | 78 +++++++++++++++++++ ...trol-dependency-reactive-after-fixpoint.js | 31 ++++++++ ...trol-dependency-switch-case-test.expect.md | 69 ++++++++++++++++ ...ive-control-dependency-switch-case-test.js | 25 ++++++ ...trol-dependency-switch-condition.expect.md | 68 ++++++++++++++++ ...ive-control-dependency-switch-condition.js | 24 ++++++ ...ve-control-dependency-while-test.expect.md | 63 +++++++++++++++ ....reactive-control-dependency-while-test.js | 22 ++++++ ...bug.reactive-dependency-fixpoint.expect.md | 56 +++++++++++++ .../bug.reactive-dependency-fixpoint.js | 20 +++++ .../packages/sprout/src/SproutTodoFilter.ts | 2 + 25 files changed, 1011 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md new file mode 100644 index 0000000000..0ae7395500 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +function Component(props) { + let x; + let i = 0; + do { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } while (i < props.test); + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + + let i = 0; + do { + let x = undefined; + if (i > 10) { + x = 10; + } else { + x = 1; + } + + i++; + } while (i < props.test); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js new file mode 100644 index 0000000000..8c1382e4a1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js @@ -0,0 +1,22 @@ +function Component(props) { + let x; + let i = 0; + do { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } while (i < props.test); + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md new file mode 100644 index 0000000000..922fb3e3bb --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (let i = props.init; i < 10; i++) { + if (i === 0) { + x = 0; + break; + } else { + x = 1; + break; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose initial value `props.init` is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ init: 0 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (const i = props.init; i < 10; ) { + if (i === 0) { + x = 0; + break; + } else { + x = 1; + break; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ init: 0 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js new file mode 100644 index 0000000000..11c0bb101b --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js @@ -0,0 +1,21 @@ +function Component(props) { + let x; + for (let i = props.init; i < 10; i++) { + if (i === 0) { + x = 0; + break; + } else { + x = 1; + break; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose initial value `props.init` is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ init: 0 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md new file mode 100644 index 0000000000..b754aff7dc --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (let i = 0; i < props.test; i++) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is capped by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (let i = 0; i < props.test; i++) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js new file mode 100644 index 0000000000..1f16801221 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js @@ -0,0 +1,20 @@ +function Component(props) { + let x; + for (let i = 0; i < props.test; i++) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is capped by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md new file mode 100644 index 0000000000..5ae53a1e6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (let i = 0; i < 10; i += props.update) { + if (i > 0 && i % 2 === 0) { + x = 2; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose possible values are + // affected by `props.update` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ update: 2 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (let i = 0; i < 10; i = i + props.update, i) { + if (i > 0 && i % 2 === 0) { + x = 2; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ update: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js new file mode 100644 index 0000000000..bc6e7e0033 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js @@ -0,0 +1,20 @@ +function Component(props) { + let x; + for (let i = 0; i < 10; i += props.update) { + if (i > 0 && i % 2 === 0) { + x = 2; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose possible values are + // affected by `props.update` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ update: 2 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md new file mode 100644 index 0000000000..d2c917a468 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (const key in props.values) { + const i = parseInt(key, 10); + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: { "12": true } }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (const key in props.values) { + const i = parseInt(key, 10); + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: { "12": true } }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js new file mode 100644 index 0000000000..870c5085fc --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js @@ -0,0 +1,21 @@ +function Component(props) { + let x; + for (const key in props.values) { + const i = parseInt(key, 10); + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: { "12": true } }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md new file mode 100644 index 0000000000..fb0b473b67 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (const i of props.values) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: [12] }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (const i of props.values) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: [12] }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js new file mode 100644 index 0000000000..18a5e720d3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js @@ -0,0 +1,20 @@ +function Component(props) { + let x; + for (const i of props.values) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: [12] }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md new file mode 100644 index 0000000000..c7fb89fe9e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +function Component(props) { + let x; + if (props.cond) { + x = 1; + } else { + x = 2; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = undefined; + if (props.cond) { + x = 1; + } else { + x = 2; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js new file mode 100644 index 0000000000..436328d817 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js @@ -0,0 +1,17 @@ +function Component(props) { + let x; + if (props.cond) { + x = 1; + } else { + x = 2; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md new file mode 100644 index 0000000000..700da688d0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md @@ -0,0 +1,78 @@ + +## Input + +```javascript +function Component(props) { + let x = 0; + + let value = null; + loop: for (let i = 0; i < 10; i++) { + switch (value) { + case true: { + x = 1; + break loop; + } + case false: { + x = 2; + break loop; + } + } + + value = props.value; + } + + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `value` used as the switch test + // condition. That variable is initially null on the first iteration + // of the loop, but is later set to `props.value` which is reactive. + // Therefore x should be treated as reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = 0; + + let value = null; + for (let i = 0; i < 10; i++) { + switch (value) { + case true: { + x = 1; + break; + } + case false: { + x = 2; + break; + } + } + + value = props.value; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js new file mode 100644 index 0000000000..f6c90d6906 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js @@ -0,0 +1,31 @@ +function Component(props) { + let x = 0; + + let value = null; + loop: for (let i = 0; i < 10; i++) { + switch (value) { + case true: { + x = 1; + break loop; + } + case false: { + x = 2; + break loop; + } + } + + value = props.value; + } + + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `value` used as the switch test + // condition. That variable is initially null on the first iteration + // of the loop, but is later set to `props.value` which is reactive. + // Therefore x should be treated as reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md new file mode 100644 index 0000000000..306369b10e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md @@ -0,0 +1,69 @@ + +## Input + +```javascript +function Component(props) { + let x; + switch (props.cond) { + case true: { + x = 1; + break; + } + case false: { + x = 2; + break; + } + default: { + x = 3; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = undefined; + bb1: switch (props.cond) { + case true: { + x = 1; + break bb1; + } + case false: { + x = 2; + break bb1; + } + default: { + x = 3; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js new file mode 100644 index 0000000000..cde6e913e7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js @@ -0,0 +1,25 @@ +function Component(props) { + let x; + switch (props.cond) { + case true: { + x = 1; + break; + } + case false: { + x = 2; + break; + } + default: { + x = 3; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md new file mode 100644 index 0000000000..e17147c356 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md @@ -0,0 +1,68 @@ + +## Input + +```javascript +const GLOBAL = 42; + +function Component({ value }) { + let x; + switch (GLOBAL) { + case value: { + x = 1; + break; + } + default: { + x = 2; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.value` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: GLOBAL }], + // TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL} +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +const GLOBAL = 42; + +function Component(t14) { + const $ = useMemoCache(1); + const { value } = t14; + let x = undefined; + bb1: switch (GLOBAL) { + case value: { + x = 1; + break bb1; + } + default: { + x = 2; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: GLOBAL }], + // TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL} +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js new file mode 100644 index 0000000000..b11940cbbe --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js @@ -0,0 +1,24 @@ +const GLOBAL = 42; + +function Component({ value }) { + let x; + switch (GLOBAL) { + case value: { + x = 1; + break; + } + default: { + x = 2; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.value` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: GLOBAL }], + // TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL} +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md new file mode 100644 index 0000000000..f9d853ddeb --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md @@ -0,0 +1,63 @@ + +## Input + +```javascript +function Component(props) { + let x; + let i = 0; + while (i < props.test) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + let i = 0; + while (i < props.test) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + + i++; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js new file mode 100644 index 0000000000..26061e12a4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js @@ -0,0 +1,22 @@ +function Component(props) { + let x; + let i = 0; + while (i < props.test) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md new file mode 100644 index 0000000000..0be5812f5e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +function Component(props) { + let x = 0; + let y = 0; + + while (x === 0) { + x = y; + y = props.value; + } + + // x and y initially start out with non-reactive values, + // but after an iteration of the loop y becomes reactive, + // and this reactive value then flows into x on the next + // loop iteration, making x reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = 0; + let y = 0; + while (x === 0) { + x = y; + y = props.value; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js new file mode 100644 index 0000000000..0c00f61e55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js @@ -0,0 +1,20 @@ +function Component(props) { + let x = 0; + let y = 0; + + while (x === 0) { + x = y; + y = props.value; + } + + // x and y initially start out with non-reactive values, + // but after an iteration of the loop y becomes reactive, + // and this reactive value then flows into x on the next + // loop iteration, making x reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], +}; diff --git a/compiler/packages/sprout/src/SproutTodoFilter.ts b/compiler/packages/sprout/src/SproutTodoFilter.ts index 94b04a9680..88f7a408b2 100644 --- a/compiler/packages/sprout/src/SproutTodoFilter.ts +++ b/compiler/packages/sprout/src/SproutTodoFilter.ts @@ -416,6 +416,8 @@ const skipFilter = new Set([ "readonly-object-method-calls", "readonly-object-method-calls-mutable-lambda", + "bug.reactive-control-dependency-do-while-test", + // TODO: 🌲 "forest-basic",