You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
class methods referencing a const declared after the class: capture omitted (ReferenceError) or spurious TDZ at class definition — kills Next.js standalone boot (bundled semver) #6523
A class whose methods reference a lexical binding (const/let) declared after the class miscompiles. Legal JS — TDZ applies at call time, and the bindings are initialized before any method runs — but perry's class-capture machinery snapshots captures by value at the class's declaration site, so forward bindings break in two ways:
A. Simple function scope — capture omitted entirely:
(functionfactory(e){consts=Symbol("x");classComparator{staticgetANY(){returns;}constructor(v){a("dbg",v);this.max=n;}// a, n declared BELOW}e.exports=Comparator;consta=function(){};constn=16;newComparator("x");// perry: ReferenceError: a is not defined node: works})({exports: {}});
HIR shows ClassExprFresh { captured_args: [LocalGet(s)] } — a/n are missing from the captures.
B. Module-factory scope with TDZ boxes — spurious TDZ throw at class definition:
next/dist/compiled/semver module 842 (const s = Symbol("SemVer ANY"); class Comparator {...} with the debug fn + sibling requires declared below the class) lowers to PreallocateTdzBoxes([41..46]) + ClassExprFresh { captured_args: [LocalGet(41)..LocalGet(46), LocalGet(47)] }. The captured-arg LocalGet on a TDZ box emits a checked value read (js_box_get_bits → js_throw_reference_error_tdz), so defining the class throws ReferenceError: Cannot access undefined before initialization — at module init, before any user code runs.
Impact
Any bundled/minified package that declares module constants after a class that uses them. Concretely: next/dist/compiled/semver — a Next.js 16 standalone server dies at BOOT (before Ready, 100% deterministic) once module 608/842's init runs. This is the current gscmaster boot blocker (behind the statuses .message bug fixed in #6522).
lldb stack (debug-symbol build, b js_throw_reference_error_tdz):
Plain closures/arrows capturing the same forward-declared consts work correctly (box-based captures, read at call time). Only the class path (RegisterClassCaptures / ClassExprFresh decl-site snapshot; class_captures.rs "decl-site snapshot" for statics) has this semantics gap. The shared-mutable-capture path (#6497/#6470) already keeps whole box handles for mutated captured locals — but forward-declared immutable bindings either aren't collected (A) or are TDZ boxes read eagerly (B).
Direction
Class captures of not-yet-initialized lexical bindings need the closure treatment: capture the box, read through it at method-call time (mirror __perry_cap_* field reads / static snapshot reads via id[0]), never a checked value read at class-definition time. Case A additionally needs the capture collection to include bindings declared after the class (forward lexical names are already tracked for the TDZ machinery — forward_lexical_names, #6062).
Repros: standalone files above; the full trigger is perry compile of next/dist/compiled/semver/index.js + require driver (dies at init).
Activity
fix(classes): forward-declared lexical captures in class members (#6523…
fix(hir): extend PerryTS#6037/PerryTS#6052 class-capture refresh to c…
fix(hir): extend PerryTS#6037/PerryTS#6052 class-capture refresh to c…
fix(hir): extend PerryTS#6037/PerryTS#6052 class-capture refresh to c…
fix(hir): extend #6037/#6052 class-capture refresh to class expressio…
runtime: createRequire node:-prefix normalization + diagnostics_chann…
fix(codegen): globalize module-init lets nested in destructure Try sc…
fix(codegen): don't TDZ-check class capture forwards at a new site (#…