Skip to content

codegen: derived-ctor this-TDZ on standalone constructor-symbol path (toward #5345)#5362

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-5345-class-test262
Jun 18, 2026
Merged

codegen: derived-ctor this-TDZ on standalone constructor-symbol path (toward #5345)#5362
proggeramlug merged 1 commit into
mainfrom
worktree-fix-5345-class-test262

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What

A derived class constructor that never calls super() leaves this uninitialized, so the implicit return this (spec GetThisBinding) must throw a ReferenceError. Perry's inline new path (lower_new) already enforces this, but the default construction path routes new C(...) through the shared <class>_constructor symbol (force_ctor_call), which lacked the check. As a result:

class A extends Array { constructor() {} }
new A();   // should throw ReferenceError — Perry constructed silently

This mirrors the inline path's guard in compile_method's standalone constructor-symbol emission, reusing the exact same predicate combination already battle-tested on the inline path:

  • ctor_body_calls_super — a direct super() suppresses the throw
  • ctor_body_closure_calls_super + ctor_body_uses_this — a closure-captured super() suppresses, unless the body also dereferences this directly
  • ctor_body_has_value_return — a value-bearing return takes the return-override path instead

The four predicates were pub(super) in lower_call::new_helpers; they're promoted to pub(crate) and re-exported so codegen/method.rs can share them (no logic duplication).

Results

Full test262 language/{statements,expressions}/class sweep (8426 cases, differential vs node --experimental-strip-types):

pass runtime-fail parity
before 5046 252 94.0%
after 5070 228 94.4%

+24 cases fixed, 0 regressions. Confirmed by diffing the failing-test sets before/after (not just counts), and by perry-codegen unit tests (16 passed).

Fixes:

  • subclass/builtin-objects/*/super-must-be-called (Array, ArrayBuffer, Boolean, DataView, Date, Error, Function, Map, NativeError/{Eval,Range,Reference,Syntax,Type,URI}Error, Number, Promise, RegExp, Set, String, WeakMap, WeakSet)
  • subclass/builtin-objects/Object/constructor-return-undefined-throws
  • subclass/class-definition-null-proto-this

A spot-check of valid super-calling shapes (conditional super, super in try, super after statements, no-own-ctor forwarding, multi-level subclass, return-override) constructs correctly and matches Node — no false-positive throws.

This is a contained slice of the larger #5345 class tail; it does not close the tracker.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced ECMAScript compliance for derived constructors. The compiler now enforces the "this-before-super" rule by detecting derived constructors that never call super() and emitting appropriate reference errors to prevent undefined behavior during runtime.

…symbol path

A derived class constructor that never calls `super()` leaves `this`
uninitialized, so the implicit `return this` (GetThisBinding) must throw a
ReferenceError per ECMAScript. The inline `new` path in `lower_new` already
detects the static no-super case and emits
`js_throw_reference_error_this_before_super`, but the DEFAULT construction
path routes `new C(...)` through the shared `<class>_constructor` symbol
(`force_ctor_call`) — which lacked the check. So
`class A extends Array { constructor() {} }; new A()` constructed silently
instead of throwing.

Mirror the inline path's guard in `compile_method`'s standalone
constructor-symbol emission, reusing the exact same predicate combination
(`ctor_body_calls_super` / `ctor_body_closure_calls_super` /
`ctor_body_uses_this` / `ctor_body_has_value_return`) so closure-captured
`super()` without a direct `this` use still suppresses the throw, and a
value-bearing `return` still takes the return-override path. The four
predicates were `pub(super)` in `lower_call::new_helpers`; promote them to
`pub(crate)` and re-export so `codegen/method.rs` can share them.

test262 language/{statements,expressions}/class: pass 5046 -> 5070 (+24),
0 regressions, parity 94.0% -> 94.4%. Fixes the
subclass/builtin-objects/*/super-must-be-called cluster (Array, ArrayBuffer,
Boolean, DataView, Date, Error, Function, Map, NativeError/*, Number,
Promise, RegExp, Set, String, WeakMap, WeakSet),
Object/constructor-return-undefined-throws, and
class-definition-null-proto-this. Toward #5345.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3345673f-0345-47ad-9a9f-8b438c6f7fb0

📥 Commits

Reviewing files that changed from the base of the PR and between f141748 and 88f3603.

📒 Files selected for processing (3)
  • crates/perry-codegen/src/codegen/method.rs
  • crates/perry-codegen/src/lower_call/mod.rs
  • crates/perry-codegen/src/lower_call/new_helpers.rs

📝 Walkthrough

Walkthrough

Four constructor-body predicate helpers (ctor_body_calls_super, ctor_body_closure_calls_super, ctor_body_uses_this, ctor_body_has_value_return) are widened from pub(super) to pub(crate) in new_helpers.rs and re-exported from lower_call/mod.rs. These predicates are then used in compile_method to detect derived constructors that never call super() and emit a js_throw_reference_error_this_before_super call, terminating the block as unreachable.

Changes

Derived Constructor TDZ Enforcement

Layer / File(s) Summary
Widen ctor-body predicate visibility and re-export
crates/perry-codegen/src/lower_call/new_helpers.rs, crates/perry-codegen/src/lower_call/mod.rs
ctor_body_calls_super, ctor_body_closure_calls_super, ctor_body_uses_this, and ctor_body_has_value_return are widened from pub(super) to pub(crate) in new_helpers.rs. A new pub(crate) use new_helpers::{...} block re-exports all four from lower_call/mod.rs.
Emit this-before-super throw in compile_method
crates/perry-codegen/src/codegen/method.rs
In the standalone constructor-symbol path of compile_method, a new ctor_no_super_throw predicate checks for derived constructors whose bodies never call super() (accounting for closure-super(), this-use, and value-return exceptions). When true, emits js_throw_reference_error_this_before_super and marks the block unreachable; otherwise falls through to the existing async/sync lowering.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • PerryTS/perry#5304: Both PRs operate on the same standalone constructor-symbol path in compile_method; #5304 controls when that symbol is emitted or inlined, while this PR adds the TDZ correctness check on that same path.

Poem

🐇 Hoppity-hop through the class hierarchy tree,
Where super() must come before this can be free!
No sneaking past TDZ with a derived ctor call—
A reference error awaits if you skip it at all.
The predicate checks, the block goes unreachable with glee,
ECMAScript compliance, enforced, just for thee! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: enforcing derived-constructor TDZ (temporal dead zone / this-before-super) behavior on the standalone constructor-symbol code path, directly matching the file changes in method.rs.
Description check ✅ Passed The description comprehensively covers the problem statement, solution approach, test results, and related issue reference. It includes what/why/how and concrete validation data, fully matching the template's Summary/Changes/Related issue/Test plan structure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-5345-class-test262

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

1 participant