Skip to content

fix(hir): synthesized capture ctor on a derived class must call super() (#4972) - #4980

Merged
proggeramlug merged 1 commit into
mainfrom
fix/derived-class-capture-super-4972
Jun 11, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
fix/derived-class-capture-super-4972

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Fixes #4972.

Problem

test-http-client-readable failed with:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

As the issue predicted, this is general class codegen, not http-specific. The trigger is the combination derived class + captured outer binding + no user-written constructor:

const Duplex = require('stream').Duplex;
class FakeAgent extends http.Agent {
  createConnection() { return new Duplex(); }  // captures `Duplex`
}
new FakeAgent();                               // threw pre-fix

Pure-TS minimal repro (also threw; Node prints 1 42):

function make() {
  const v = 42;
  class P { p: number; constructor() { this.p = 1; } }
  class C extends P { m() { return v; } }
  return new C();
}

Root cause

synthesize_class_captures (#212/#740) materializes a capture-stashing constructor (__perry_cap_<id> params + this PropertySets) when class members reference enclosing-scope locals. That synthesized ctor contained no super() call. Codegen's static derived-ctor TDZ check in lower_call/new.rs (own ctor + heritage + no super() ⇒ unconditional js_throw_reference_error_this_before_super) then fired for a class the user never wrote a constructor for — the captured binding alone forced the ctor into existence and flipped the class from the (working) no-own-ctor new path onto the own-ctor path.

Fix

When the fresh (non-user) ctor is synthesized for a class with any heritage (extends / extends_name / native_extends / extends_expr), seed its body with super() — mirroring the spec default ctor constructor(...args) { super(...args) }. The existing insertion logic already places capture assignments after the first SuperCall, so this-after-super ordering is preserved. User-written ctors are untouched; the static TDZ check still fires for genuinely missing super().

Effects:

Validation

Code-only PR — no version bump / changelog per contributor flow.

…() (#4972)

A derived class with NO user-written constructor whose members capture an
enclosing-scope local threw "Must call super constructor in derived class
before accessing 'this'" at construction time (test-http-client-readable).

synthesize_class_captures (#212/#740) materializes a capture-stashing
constructor (__perry_cap_<id> params + this-PropertySets) when class
members reference outer locals. That synthesized ctor contained no
super() call, so codegen's static derived-ctor TDZ check in
lower_call/new.rs (own ctor + heritage + no super() => unconditional
ReferenceError) fired for a class the user never wrote a ctor for:

    const Duplex = require('stream').Duplex;
    class FakeAgent extends http.Agent {
      createConnection() { return new Duplex(); }  // captures Duplex
    }
    new FakeAgent();                               // threw pre-fix

Fix: when the fresh (non-user) ctor is synthesized for a class with any
heritage (extends / extends_name / native_extends / extends_expr), seed
its body with super() - mirroring the spec default ctor
constructor(...args) { super(...args) }. The existing insertion logic
already places the capture assignments after the first SuperCall, so
TDZ ordering is preserved. This both suppresses the bogus static throw
and routes known user-class parents through the inline-parent-ctor arm
so the parent body actually runs (pure-TS shape: function-nested
class C extends P { m() { return capturedLocal; } } previously threw;
now constructs and runs P's ctor, matching Node).

SuperCall codegen is a safe no-op for unknown/native member bases
("Agent"), so the corpus shape stays effectively parentless, same as
the no-capture case (#4908).

test-http-client-readable now passes (exit 0) staged against the
node-core shim common.
@proggeramlug
proggeramlug merged commit dd43469 into main Jun 11, 2026
12 of 13 checks passed
@proggeramlug
proggeramlug deleted the fix/derived-class-capture-super-4972 branch June 11, 2026 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codegen: derived class over native base — 'Must call super constructor before accessing this' (test-http-client-readable)

1 participant