Skip to content
Merged
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
extends_name: ic.parent_name.clone(),
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: ic
.field_names
.iter()
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/class_accessors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ mod tests {
extends_name: extends_name.map(str::to_string),
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/this_as_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ mod tests {
extends_name: extends_name.map(str::to_string),
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down
26 changes: 24 additions & 2 deletions crates/perry-codegen/src/expr/this_super_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,23 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
}
return Ok(double_literal(0.0));
};
let parent_class = match ctx.classes.get(&parent_name).copied() {
// #5437 (Next.js p-queue `PQueue`): when HIR captured a dynamic
// `extends_expr` for this class, the parent is a LEXICAL runtime
// value (an in-scope local / require result) — NOT the same-named
// module-global class that `ctx.classes.get(parent_name)` would
// wrongly return (minified turbopack chunks reuse single-letter
// class names across webpack factories). Force the `None` arm's
// dynamic-parent dispatch so `super()` invokes the real lexical
// parent value, mirroring the synthesized-ctor dynamic-parent path
// in `codegen/method.rs`. Without this, `PQueue extends t` resolved
// `t` to superstruct's `StructError` base and `super()` inlined its
// destructuring ctor on the undefined options arg → HTTP 500.
let static_parent_lookup = if current_class.extends_expr.is_some() {
None
} else {
ctx.classes.get(&parent_name).copied()
};
let parent_class = match static_parent_lookup {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Some(c) => c,
None => {
// #321 / #66 (#1787 follow-up): `class Sub extends <runtimeValueFn>`
Expand Down Expand Up @@ -286,7 +302,13 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
) || (is_stream_family_name
&& !has_extends_expr)
|| is_other_builtin_constructor_name(parent_name.as_str()))
&& !(is_stream_family_name && has_extends_expr);
&& !(is_stream_family_name && has_extends_expr)
// #5437: a parent NAME shadowed by an in-scope lexical
// local is NOT the built-in — route it through the
// dynamic `extends_expr` value so `super()` runs the
// local's constructor (`const Error = class {…}; class X
// extends Error {}`), not the built-in Error initializer.
&& !current_class.heritage_lexically_shadowed;
if !is_builtin_parent_name {
if let Some(extends_expr) = current_class.extends_expr.as_deref() {
// Lower the super-call args first so they get fresh slots
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/type_analysis_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ fn hir_inferred_types_reuse_codegen_contextual_class_facts() {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: vec![perry_hir::Function {
Expand Down Expand Up @@ -221,6 +222,7 @@ fn hir_inferred_types_reuse_codegen_contextual_class_facts() {
extends_name: Some("Base".to_string()),
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: vec![perry_hir::ClassField {
name: "label".to_string(),
key_expr: None,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/class_keys_gc_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ fn module_with_declared_field_class() -> Module {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: vec![field("x"), field("y"), field("z")],
constructor: None,
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/constructor_recursion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ fn module_with_recursive_constructor_return() -> Module {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: Some(ctor),
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/native_proof_buffer_views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ fn class(id: u32, name: &str, fields: Vec<ClassField>) -> Class {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields,
constructor: None,
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/native_proof_regressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ fn class(id: u32, name: &str, fields: Vec<ClassField>) -> Class {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields,
constructor: None,
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/static_symbol_hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ fn class_with_static(id: u32, value: f64) -> Class {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/typed_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ fn class(id: u32, name: &str, fields: Vec<ClassField>) -> Class {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields,
constructor: None,
methods: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/tests/typed_shape_descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ fn class(id: u32, name: &str, fields: Vec<ClassField>) -> Class {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields,
constructor: None,
methods: Vec::new(),
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-hir/src/analysis/value_types_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ fn seeds_contextual_class_and_enum_facts_from_module() {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down Expand Up @@ -677,6 +678,7 @@ fn infers_named_class_and_interface_property_facts() {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: vec![class_field("label", Type::String)],
constructor: None,
methods: vec![function_decl(10, "score", Type::Number)],
Expand All @@ -700,6 +702,7 @@ fn infers_named_class_and_interface_property_facts() {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down Expand Up @@ -1636,6 +1639,7 @@ fn resolves_this_and_super_in_class_context() {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: vec![class_field("label", Type::String)],
constructor: None,
methods: vec![function_decl(10, "score", Type::Number)],
Expand All @@ -1659,6 +1663,7 @@ fn resolves_this_and_super_in_class_context() {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down
21 changes: 21 additions & 0 deletions crates/perry-hir/src/eval_classifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,8 +615,25 @@ pub fn check_site(
mod tests {
use super::*;
use crate::ir::{clear_current_module_source, set_current_module_source};
use std::sync::Mutex;
use swc_common::{BytePos, Span};

/// Serializes the tests that drain the process-global deferred-eval-site
/// sink. `take_deferred_eval_sites()` is destructive (it drains the WHOLE
/// sink), so two such tests running concurrently under the parallel
/// `cargo test` harness steal each other's recorded sites — which flaked
/// `unimplemented_defers_by_default_and_records_site` ("exactly one recorded
/// site"). Each sink-touching test holds this lock across its push→take.
static EVAL_SITE_TEST_LOCK: Mutex<()> = Mutex::new(());

/// Acquire [`EVAL_SITE_TEST_LOCK`], tolerating poisoning from an unrelated
/// panicking test — we only need the mutual exclusion, not protected data.
fn lock_eval_sink() -> std::sync::MutexGuard<'static, ()> {
EVAL_SITE_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner())
}

fn str_lit(s: &str) -> ast::Expr {
ast::Expr::Lit(ast::Lit::Str(ast::Str {
span: Span::new(BytePos(0), BytePos(0)),
Expand Down Expand Up @@ -790,6 +807,7 @@ mod tests {
/// throw-on-reach value AND is recorded for the end-of-compile notice.
#[test]
fn default_mode_defers_runtime_unknown_and_records_site() {
let _sink_guard = lock_eval_sink();
set_eval_strict_mode(false);
// Use a unique path so this test's recorded site is identifiable even
// if other tests push to the process-global sink concurrently.
Expand All @@ -813,6 +831,7 @@ mod tests {
/// Strict-eval mode: a runtime-unknown site is a hard compile-time error.
#[test]
fn strict_mode_refuses_runtime_unknown() {
let _sink_guard = lock_eval_sink();
// PERRY_ALLOW_EVAL would force non-strict; only assert when unset.
if eval_override_enabled() {
return;
Expand Down Expand Up @@ -867,6 +886,7 @@ mod tests {
/// the `"unimplemented API"` kind.
#[test]
fn unimplemented_defers_by_default_and_records_site() {
let _sink_guard = lock_eval_sink();
// PERRY_ALLOW_UNIMPLEMENTED forces defer regardless — fine for this
// (defer) assertion either way, so no skip needed.
set_unimplemented_strict_mode(false);
Expand Down Expand Up @@ -897,6 +917,7 @@ mod tests {
/// caller raises the hard `#463` error) and records no notice site.
#[test]
fn unimplemented_refuses_in_strict_mode() {
let _sink_guard = lock_eval_sink();
// PERRY_ALLOW_UNIMPLEMENTED would force defer; only assert when unset.
if unimplemented_override_enabled() {
return;
Expand Down
8 changes: 8 additions & 0 deletions crates/perry-hir/src/ir/decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ pub struct Class {
/// `extends` and `extends_name` are both `None` for these classes (the
/// parent class_id is only known at runtime).
pub extends_expr: Option<Box<Expr>>,
/// #5437: the parent name resolves to an in-scope LEXICAL LOCAL that shadows
/// a same-named native/built-in/module-global parent (`const Error = class
/// {…}; class X extends Error {}`). When set, `super()` codegen must use the
/// dynamic `extends_expr` value (the local) and MUST NOT take any built-in
/// special-case path keyed on the parent NAME (Error/Request/Response/Event/
/// CustomEvent/stream family) — that would run the built-in initializer
/// instead of the lexical local's constructor.
pub heritage_lexically_shadowed: bool,
/// Instance fields
pub fields: Vec<ClassField>,
/// Constructor (if any)
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/lower/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@ impl LoweringContext {
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields,
constructor: Some(constructor),
methods: Vec::new(),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-hir/src/lower/module_decl/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub(crate) fn lower_namespace_as_class(
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down Expand Up @@ -395,6 +396,7 @@ pub(crate) fn lower_namespace_as_class(
extends_name: None,
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
Expand Down
Loading
Loading