Summary
ce3eed1 (#6054, the #5951 shared-mutable-capture one-element-array-box desugar) regressed the compiled Next.js standalone server from 5/8 byte-identical routes to 0/8: every route (pages AND api) returns a 21-byte 500, server log shows TypeError: Cannot convert undefined or null to object per request.
Bisected on the #5437 matrix app: a868c7516 + the #6055 boot fix = 5/8; ce3eed163 + same = 0/8. Confirmed via the pass's own PERRY_NO_5951=1 escape hatch on current main: with the pass disabled the app is back to 5/8.
Root cause
The declaring-Let array-wrap skips a let with no initializer (if let Some(e) = init.take()), but every use of the flagged id is still rewritten to id[0]. SWC emits exactly this shape for hoisted computed-property temps:
let prop;
class NodeNextRequest extends BaseNextRequest {
static #_ = prop = _NEXT_REQUEST_META = ...; // mutates `prop`
}
(next/dist/server/base-http/node.js — the wrapper classes for every request/response.) prop is flagged shared-mutable, its uses become prop[0], but no array is ever allocated — the static initializer's prop[0] = ... is an IndexSet on undefined and throws the observed TypeError when Next lazy-requires the module during the first request. Every route uses these wrappers → uniform 500.
Found along the way (real unsoundness, hardened in the fix PR though neither caused this app's breakage): the pass applied one module-global LocalId set to every body in the module, but LocalIds are not unique across function scopes (member params/lets and nested closures restart their id spaces) — a numeric rewrite is only sound for ids declared exactly once in the rewritten region.
Fix
PR incoming: wrap a None init as [undefined] (preserves let x; semantics through the box), plus per-body scoping + a declared-exactly-once ambiguity guard, plus PERRY_5951_SKIP_MODS / PERRY_5951_TRACE compile-time diagnostics.
References
#6054 / ce3eed1 (the regressing merge), #5951 (the semantics the pass fixes), #6055 (boot fix needed to test this window), #5437 (the Next.js matrix).
Summary
ce3eed1 (#6054, the #5951 shared-mutable-capture one-element-array-box desugar) regressed the compiled Next.js standalone server from 5/8 byte-identical routes to 0/8: every route (pages AND api) returns a 21-byte 500, server log shows
TypeError: Cannot convert undefined or null to objectper request.Bisected on the #5437 matrix app:
a868c7516+ the #6055 boot fix = 5/8;ce3eed163+ same = 0/8. Confirmed via the pass's ownPERRY_NO_5951=1escape hatch on current main: with the pass disabled the app is back to 5/8.Root cause
The declaring-
Letarray-wrap skips aletwith no initializer (if let Some(e) = init.take()), but every use of the flagged id is still rewritten toid[0]. SWC emits exactly this shape for hoisted computed-property temps:(
next/dist/server/base-http/node.js— the wrapper classes for every request/response.)propis flagged shared-mutable, its uses becomeprop[0], but no array is ever allocated — the static initializer'sprop[0] = ...is an IndexSet onundefinedand throws the observed TypeError when Next lazy-requires the module during the first request. Every route uses these wrappers → uniform 500.Found along the way (real unsoundness, hardened in the fix PR though neither caused this app's breakage): the pass applied one module-global
LocalIdset to every body in the module, but LocalIds are not unique across function scopes (member params/lets and nested closures restart their id spaces) — a numeric rewrite is only sound for ids declared exactly once in the rewritten region.Fix
PR incoming: wrap a
Noneinit as[undefined](preserveslet x;semantics through the box), plus per-body scoping + a declared-exactly-once ambiguity guard, plusPERRY_5951_SKIP_MODS/PERRY_5951_TRACEcompile-time diagnostics.References
#6054 / ce3eed1 (the regressing merge), #5951 (the semantics the pass fixes), #6055 (boot fix needed to test this window), #5437 (the Next.js matrix).