diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeConsecutiveScopes.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeConsecutiveScopes.ts index ed110b9fea..a655beb1e2 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeConsecutiveScopes.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeConsecutiveScopes.ts @@ -108,6 +108,9 @@ class Transform extends ReactiveFunctionTransform { // The updated set of instructions for the block. Stays null until // we make changes (ie merge scopes) let nextInstructions: ReactiveBlock | null = null; + // The maximum index within the original instructions that we have reached. + // Used to avoid emitting duplicate instructions + let maxIndex: number = 0; // Called when we find some instruction that cannot be merged into a // preceding scope, or we otherwise need to reset and not consider @@ -121,8 +124,14 @@ class Transform extends ReactiveFunctionTransform { if (currentScope !== null) { nextInstructions.push(...block.slice(currentScope.to, index)); } - if (index < block.length) { + // We can sometimes call resetCurrentScope twice for the same index, + // such as when an instruction resets and then a subsequent scope also resets. + // This is the only case in which we push instructions w/o gating on + // `currentScope != null`, so we avoid duplicates by checking the max index + // already emitted. + if (index < block.length && index > maxIndex) { nextInstructions.push(block[index]!); + maxIndex = index; } } currentScope = null; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-duplicate-instruction-from-merge-consecutive-scopes.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-duplicate-instruction-from-merge-consecutive-scopes.expect.md new file mode 100644 index 0000000000..9551cc8072 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-duplicate-instruction-from-merge-consecutive-scopes.expect.md @@ -0,0 +1,69 @@ + +## Input + +```javascript +// @enableMergeConsecutiveScopes +function Component(id) { + const bar = (() => {})(); + + return ( + <> + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [null], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; // @enableMergeConsecutiveScopes +function Component(id) { + const $ = useMemoCache(4); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (() => {})(); + $[0] = t0; + } else { + t0 = $[0]; + } + const bar = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = id ? true : false; + const c_2 = $[2] !== t2; + let t3; + if (c_2) { + t3 = ( + <> + {t1} + + + ); + $[2] = t2; + $[3] = t3; + } else { + t3 = $[3]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [null], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-duplicate-instruction-from-merge-consecutive-scopes.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-duplicate-instruction-from-merge-consecutive-scopes.js new file mode 100644 index 0000000000..3a2d707c7b --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-duplicate-instruction-from-merge-consecutive-scopes.js @@ -0,0 +1,16 @@ +// @enableMergeConsecutiveScopes +function Component(id) { + const bar = (() => {})(); + + return ( + <> + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [null], +};