From fdbfac8f8216839520d093513908918de591468f Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Tue, 18 Apr 2023 16:10:00 -0700 Subject: [PATCH] Repro for bug with memo vars at wrong block scope Minimal(ish) repro of a bug we saw internally, where an output of a nested reactive scope is defined at the wrong block scope, and so later references to that value are invalid. The simplified structure is: ``` scope0 inputs=[] outputs=[] { scope1 inputs=[] outputs=[t0] { t0 = ... } } t0 ``` Note that `t0` correctly appears as an output of the inner scope1, but not as an output of the outer scope0. We need to propagate outputs upward as necessary to ensure they are available at the right block scope: in this case, that would add `t0` as an output of scope0. An earlier version of PropagateScopeDependencies did this but it looks like it got lost along the way (not a big deal) --- ...-promoted-to-outer-scope-dynamic.expect.md | 127 ++++++++++++++++++ ...lue-not-promoted-to-outer-scope-dynamic.js | 14 ++ ...t-promoted-to-outer-scope-static.expect.md | 102 ++++++++++++++ ...alue-not-promoted-to-outer-scope-static.js | 11 ++ 4 files changed, 254 insertions(+) create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.js create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.js diff --git a/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md new file mode 100644 index 0000000000..03af913e61 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -0,0 +1,127 @@ + +## Input + +```javascript +function Component(props) { + const item = useFragment(FRAGMENT, props.item); + useFreeze(item); + + const count = new MaybeMutable(item); + return ( + + + {Text} + {{maybeMutate(count)}} + + + ); +} + +``` + +## Code + +```javascript +import * as React from "react"; +function Component(props) { + const $ = React.unstable_useMemoCache(21); + const item = useFragment(FRAGMENT, props.item); + useFreeze(item); + const c_0 = $[0] !== item; + let t1; + let t2; + let t3; + let t4; + let t5; + let t6; + let t7; + if (c_0) { + const count = new MaybeMutable(item); + + t6 = View; + t7 = "\n "; + t3 = View; + t4 = "\n "; + let t0; + if ($[8] === Symbol.for("react.memo_cache_sentinel")) { + t0 = Text; + $[8] = t0; + } else { + t0 = $[8]; + } + t5 = "\n "; + t1 = "span"; + t2 = maybeMutate(count); + $[0] = item; + $[1] = t1; + $[2] = t2; + $[3] = t3; + $[4] = t4; + $[5] = t5; + $[6] = t6; + $[7] = t7; + } else { + t1 = $[1]; + t2 = $[2]; + t3 = $[3]; + t4 = $[4]; + t5 = $[5]; + t6 = $[6]; + t7 = $[7]; + } + const c_9 = $[9] !== t1; + const c_10 = $[10] !== t2; + let t8; + if (c_9 || c_10) { + t8 = {t2}; + $[9] = t1; + $[10] = t2; + $[11] = t8; + } else { + t8 = $[11]; + } + const c_12 = $[12] !== t3; + const c_13 = $[13] !== t4; + const c_14 = $[14] !== t5; + const c_15 = $[15] !== t8; + let t9; + if (c_12 || c_13 || c_14 || c_15) { + t9 = ( + + {t4} + {t0} + {t5} + {t8} + + ); + $[12] = t3; + $[13] = t4; + $[14] = t5; + $[15] = t8; + $[16] = t9; + } else { + t9 = $[16]; + } + const c_17 = $[17] !== t6; + const c_18 = $[18] !== t7; + const c_19 = $[19] !== t9; + let t10; + if (c_17 || c_18 || c_19) { + t10 = ( + + {t7} + {t9} + + ); + $[17] = t6; + $[18] = t7; + $[19] = t9; + $[20] = t10; + } else { + t10 = $[20]; + } + return t10; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.js b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.js new file mode 100644 index 0000000000..dc36d75464 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-dynamic.js @@ -0,0 +1,14 @@ +function Component(props) { + const item = useFragment(FRAGMENT, props.item); + useFreeze(item); + + const count = new MaybeMutable(item); + return ( + + + {Text} + {{maybeMutate(count)}} + + + ); +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.expect.md new file mode 100644 index 0000000000..193962c826 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.expect.md @@ -0,0 +1,102 @@ + +## Input + +```javascript +function Component(props) { + const count = new MaybeMutable(); + return ( + + + {Text} + {{maybeMutate(count)}} + + + ); +} + +``` + +## Code + +```javascript +import * as React from "react"; +function Component(props) { + const $ = React.unstable_useMemoCache(11); + let t1; + let t2; + let t3; + let t4; + let t5; + let t6; + let t7; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const count = new MaybeMutable(); + + t6 = View; + t7 = "\n "; + t3 = View; + t4 = "\n "; + let t0; + if ($[7] === Symbol.for("react.memo_cache_sentinel")) { + t0 = Text; + $[7] = t0; + } else { + t0 = $[7]; + } + t5 = "\n "; + t1 = "span"; + t2 = maybeMutate(count); + $[0] = t1; + $[1] = t2; + $[2] = t3; + $[3] = t4; + $[4] = t5; + $[5] = t6; + $[6] = t7; + } else { + t1 = $[0]; + t2 = $[1]; + t3 = $[2]; + t4 = $[3]; + t5 = $[4]; + t6 = $[5]; + t7 = $[6]; + } + let t8; + if ($[8] === Symbol.for("react.memo_cache_sentinel")) { + t8 = {t2}; + $[8] = t8; + } else { + t8 = $[8]; + } + let t9; + if ($[9] === Symbol.for("react.memo_cache_sentinel")) { + t9 = ( + + {t4} + {t0} + {t5} + {t8} + + ); + $[9] = t9; + } else { + t9 = $[9]; + } + let t10; + if ($[10] === Symbol.for("react.memo_cache_sentinel")) { + t10 = ( + + {t7} + {t9} + + ); + $[10] = t10; + } else { + t10 = $[10]; + } + return t10; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.js b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.js new file mode 100644 index 0000000000..5c92f8bcf5 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/_bug.inner-memo-value-not-promoted-to-outer-scope-static.js @@ -0,0 +1,11 @@ +function Component(props) { + const count = new MaybeMutable(); + return ( + + + {Text} + {{maybeMutate(count)}} + + + ); +}