Skip to content

fix(engine): run derived class field initializers after super() - #992

Draft
simonyang08 wants to merge 1 commit into
trynova:mainfrom
simonyang08:codex/nova-948-class-field-init-derived
Draft

fix(engine): run derived class field initializers after super()#992
simonyang08 wants to merge 1 commit into
trynova:mainfrom
simonyang08:codex/nova-948-class-field-init-derived

Conversation

@simonyang08

Copy link
Copy Markdown

Fixes #948

A user-written derived class constructor that declared instance fields used to throw ReferenceError: Uninitialized this binding because the field-initializer prelude was emitted at the start of the constructor body, before super() had bound this.

class A {}
class B extends A {
  b = 2
  constructor() { super() }
}
new B() // previously: ReferenceError: Uninitialized this binding

Change

For derived classes, the field-initializer prelude is now built as a separate executable stored on the function and invoked from EvaluateSuper after super() has bound this, mirroring how default constructors already behave. Base-class constructors keep the existing prelude-inside-body path because OrdinaryCallBindThis runs before the user body.

The new helper initialize_ecmascript_function_class_field_initializers mirrors initialize_instance_elements (used by default constructors) so future field-init environment changes should apply to both paths.

Regression test

tests/class-field-init-in-derived.js covers the original issue repro, single/multi-field cases, grand-child fields, a base-class no-regression check, and field visibility after super() inside the constructor.

Verification

  • cargo build --bin nova_cli --profile dev-fast — clean
  • cargo clippy --bin nova_cli --profile dev-fast — 0 warnings
  • cargo fmt --check — clean
  • Repro script: ReferenceError before the change → completes normally after
  • Workspace unit tests: all green (83 passed, 0 failed)
  • Full test262 sweep NOT run (submodule not initialized in this environment) — worth running in CI before merge

Scope note

This fix inherently threads new state through the bytecode compiler, VM, executable, and function data structures, so the diff spans 10 files (+233/-30) — larger than a typical focused patch, but it is a single semantic change. The GC mark/sweep handling for the new class_field_initializer_bytecode field is included.

Signed-off-by: simonyang08 ppt5928@gmail.com

…ova#948)

A user-written derived class constructor that declared instance fields used
to throw ReferenceError: Uninitialized this binding because the field
initializer prelude was emitted at the start of the constructor body,
before super() had bound this.

For derived classes, the prelude is now built as a separate executable
and stored on the function. It is invoked from step 11 of EvaluateSuper
after super() has bound this, mirroring the behaviour of default
constructors. Base-class constructors keep the existing
prelude-inside-body path because OrdinaryCallBindThis runs before the
user body and so this is already initialized.

Includes a regression script under tests/ that covers the original
issue, single/multi-field cases, grand-child fields, and a base-class
no-regression check.

Signed-off-by: simonyang08 <ppt5928@gmail.com>
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.

class field initializers are broken in subclasses

2 participants