diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 486773d5eb9..b85746965d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -2420,7 +2420,12 @@ function codegenPlace(cx: Context, place: Place): t.Expression | t.JSXText { loc: place.loc, }); const identifier = convertIdentifier(place.identifier); - identifier.loc = place.loc as any; + /* + * Guard against GeneratedSource (a Symbol) leaking into Babel AST nodes. + * Babel requires Node.loc to be SourceLocation | null, so synthesized nodes + * without real source positions must use null, not the internal sentinel. + */ + identifier.loc = place.loc !== GeneratedSource ? (place.loc as t.SourceLocation) : null; return identifier; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-generated-source-symbol-in-babel-loc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-generated-source-symbol-in-babel-loc.expect.md new file mode 100644 index 00000000000..d2a3d3a946c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-generated-source-symbol-in-babel-loc.expect.md @@ -0,0 +1,180 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useMemo} from 'react'; + +function visualFor(state, getLabels) { + return {label: getLabels(state), tint: 'red', glyph: () => null}; +} + +// Repro: synthesized temporaries for destructured bindings from cross-scope +// hoisting would have their Babel AST identifier node's .loc set to the +// internal GeneratedSource Symbol sentinel instead of null, causing +// v8.serialize / jest-worker IPC failures. +export function Example({state, getLabels, colors, onTap}) { + const session = useMemo(() => ({state}), [state]); + if (session.state === 'off') return null; + + const handleTap = () => onTap?.(session.state); + const {label, tint, glyph} = visualFor(session.state, getLabels); + + return ( + + ); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import { useMemo } from "react"; + +function visualFor(state, getLabels) { + const $ = _c(5); + let t0; + if ($[0] !== getLabels || $[1] !== state) { + t0 = getLabels(state); + $[0] = getLabels; + $[1] = state; + $[2] = t0; + } else { + t0 = $[2]; + } + let t1; + if ($[3] !== t0) { + t1 = { label: t0, tint: "red", glyph: _temp }; + $[3] = t0; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +// Repro: synthesized temporaries for destructured bindings from cross-scope +// hoisting would have their Babel AST identifier node's .loc set to the +// internal GeneratedSource Symbol sentinel instead of null, causing +// v8.serialize / jest-worker IPC failures. +function _temp() { + return null; +} +export function Example(t0) { + const $ = _c(27); + const { state, getLabels, colors, onTap } = t0; + let t1; + if ($[0] !== state) { + t1 = { state }; + $[0] = state; + $[1] = t1; + } else { + t1 = $[1]; + } + const session = t1; + if (session.state === "off") { + return null; + } + let t2; + if ($[2] !== onTap || $[3] !== session.state) { + t2 = () => onTap?.(session.state); + $[2] = onTap; + $[3] = session.state; + $[4] = t2; + } else { + t2 = $[4]; + } + const handleTap = t2; + let label; + let t3; + let t4; + let t5; + let t6; + if ( + $[5] !== colors.fg || + $[6] !== getLabels || + $[7] !== handleTap || + $[8] !== session.state + ) { + const { label: t7, tint, glyph } = visualFor(session.state, getLabels); + label = t7; + t4 = label; + t5 = handleTap; + t6 = { background: tint }; + t3 = session.state === "listening" ? ... : glyph(colors.fg); + $[5] = colors.fg; + $[6] = getLabels; + $[7] = handleTap; + $[8] = session.state; + $[9] = label; + $[10] = t3; + $[11] = t4; + $[12] = t5; + $[13] = t6; + } else { + label = $[9]; + t3 = $[10]; + t4 = $[11]; + t5 = $[12]; + t6 = $[13]; + } + let t7; + if ($[14] !== colors.fg) { + t7 = { color: colors.fg }; + $[14] = colors.fg; + $[15] = t7; + } else { + t7 = $[15]; + } + let t8; + if ($[16] !== label || $[17] !== t7) { + t8 = {label}; + $[16] = label; + $[17] = t7; + $[18] = t8; + } else { + t8 = $[18]; + } + let t9; + if ($[19] !== t3 || $[20] !== t8) { + t9 = ( + + {t3} + {t8} + + ); + $[19] = t3; + $[20] = t8; + $[21] = t9; + } else { + t9 = $[21]; + } + let t10; + if ($[22] !== t4 || $[23] !== t5 || $[24] !== t6 || $[25] !== t9) { + t10 = ( + + ); + $[22] = t4; + $[23] = t5; + $[24] = t6; + $[25] = t9; + $[26] = t10; + } else { + t10 = $[26]; + } + return t10; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-generated-source-symbol-in-babel-loc.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-generated-source-symbol-in-babel-loc.js new file mode 100644 index 00000000000..4b979d8bb76 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-generated-source-symbol-in-babel-loc.js @@ -0,0 +1,27 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useMemo} from 'react'; + +function visualFor(state, getLabels) { + return {label: getLabels(state), tint: 'red', glyph: () => null}; +} + +// Repro: synthesized temporaries for destructured bindings from cross-scope +// hoisting would have their Babel AST identifier node's .loc set to the +// internal GeneratedSource Symbol sentinel instead of null, causing +// v8.serialize / jest-worker IPC failures. +export function Example({state, getLabels, colors, onTap}) { + const session = useMemo(() => ({state}), [state]); + if (session.state === 'off') return null; + + const handleTap = () => onTap?.(session.state); + const {label, tint, glyph} = visualFor(session.state, getLabels); + + return ( + + ); +}