Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,33 @@ pub(super) fn compile_method(
}
}

if method.is_async {
// ECMAScript TDZ-on-`this`: a DERIVED constructor whose body never calls
// `super()` leaves `this` uninitialized, so the implicit `return this`
// throws ReferenceError. The inline `new` path enforces this in
// `lower_new`; mirror it here for the standalone constructor-symbol path
// — the DEFAULT when `force_ctor_call` routes `new C(...)` through the
// shared `<class>_constructor` symbol instead of inlining. Without this,
// `class A extends Array { constructor() {} }; new A()` constructs
// silently instead of throwing. The predicate combination matches the
// inline path verbatim (closure-`super()` without a direct `this` use
// suppresses; a value-bearing `return` takes the return-override path).
// Refs class/subclass/builtin-objects/*/super-must-be-called.
let ctor_no_super_throw = is_constructor_method
&& (class.extends.is_some()
|| class.extends_name.is_some()
|| class.native_extends.is_some()
|| class.extends_expr.is_some())
&& class.constructor.as_ref().is_some_and(|ctor| {
!crate::lower_call::ctor_body_calls_super(&ctor.body)
&& !(crate::lower_call::ctor_body_closure_calls_super(&ctor.body)
&& !crate::lower_call::ctor_body_uses_this(&ctor.body))
&& !crate::lower_call::ctor_body_has_value_return(&ctor.body)
});
if ctor_no_super_throw {
ctx.block()
.call(DOUBLE, "js_throw_reference_error_this_before_super", &[]);
ctx.block().unreachable();
} else if method.is_async {
stmt::lower_async_rejecting_stmts(&mut ctx, &method.body).with_context(|| {
format!(
"lowering async body of method '{}::{}'",
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-codegen/src/lower_call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ pub(crate) use native::lower_native_method_call;
// etc. keep resolving after the split.
pub(crate) use field_init::{apply_field_initializers_recursive, FieldInitMode};
pub(crate) use new::{bind_inline_constructor_params, lower_new, restore_inline_constructor_scope};
// The derived-ctor no-super static-throw predicates (shared with the
// standalone-ctor-symbol path in `codegen/method.rs`, which is the default
// `new` lowering when `force_ctor_call` redirects construction to the shared
// symbol instead of inlining). Refs class/subclass/builtin-objects/*/
// super-must-be-called.
pub(crate) use new_helpers::{
ctor_body_calls_super, ctor_body_closure_calls_super, ctor_body_has_value_return,
ctor_body_uses_this,
};
// `extract_options_fields` is consumed by `expr.rs` as
// `crate::lower_call::extract_options_fields` — keep that path stable.
pub(crate) use options::extract_options_fields;
Expand Down
8 changes: 4 additions & 4 deletions crates/perry-codegen/src/lower_call/new_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const NO_STMT_PRED: &dyn Fn(&perry_hir::Stmt) -> bool = &|_| false;
/// uninitialized — ECMAScript then throws ReferenceError at the implicit
/// `return this`. We detect the static no-super case at compile time so
/// `new Sub()` throws instead of returning a half-built object.
pub(super) fn ctor_body_calls_super(body: &[perry_hir::Stmt]) -> bool {
pub(crate) fn ctor_body_calls_super(body: &[perry_hir::Stmt]) -> bool {
ctor_body_any(body, &expr_calls_super, NO_STMT_PRED)
}

Expand All @@ -123,7 +123,7 @@ fn expr_calls_super(expr: &Expr) -> bool {
/// for-of is still iterating), so the static no-super throw must not fire —
/// unless the body also dereferences `this` directly (see the call site).
/// Refs class/subclass/derived-class-return-override-{for-of,finally-super}-arrow.
pub(super) fn ctor_body_closure_calls_super(body: &[perry_hir::Stmt]) -> bool {
pub(crate) fn ctor_body_closure_calls_super(body: &[perry_hir::Stmt]) -> bool {
ctor_body_any(body, &expr_calls_super_incl_closures, NO_STMT_PRED)
}

Expand All @@ -148,7 +148,7 @@ fn expr_calls_super_incl_closures(expr: &Expr) -> bool {
/// a no-direct-super derived ctor throws ReferenceError per spec before any
/// closure could run `super()`, so the static entry throw stays correct
/// (test262 class/elements/privatefieldset-evaluation-order-1).
pub(super) fn ctor_body_uses_this(body: &[perry_hir::Stmt]) -> bool {
pub(crate) fn ctor_body_uses_this(body: &[perry_hir::Stmt]) -> bool {
ctor_body_any(body, &expr_uses_this_direct, NO_STMT_PRED)
}

Expand Down Expand Up @@ -177,7 +177,7 @@ fn expr_uses_this_direct(expr: &Expr) -> bool {
/// returned value at runtime. Refs
/// class/subclass/class-definition-null-proto-contains-return-override and
/// class/subclass/builtin-objects/Object/constructor-return-undefined-throws.
pub(super) fn ctor_body_has_value_return(body: &[perry_hir::Stmt]) -> bool {
pub(crate) fn ctor_body_has_value_return(body: &[perry_hir::Stmt]) -> bool {
ctor_body_any(
body,
&|_| false,
Expand Down
Loading