diff --git a/crates/perry-hir/src/lower/expr_member.rs b/crates/perry-hir/src/lower/expr_member.rs index 7deefd80af..ffcff664bb 100644 --- a/crates/perry-hir/src/lower/expr_member.rs +++ b/crates/perry-hir/src/lower/expr_member.rs @@ -1899,6 +1899,34 @@ fn lower_member_inner(ctx: &mut LoweringContext, member: &ast::MemberExpr) -> Re matches!(member, "fromCharCode" | "fromCodePoint" | "raw") }) .unwrap_or(false); + // #4521: `Promise.resolve` / `reject` / `all` / `race` / + // `allSettled` / `any` / `withResolvers` / `try` read as + // VALUES need the reified Promise constructor receiver so + // they resolve to the real native function objects (correct + // `.name` / `.length`, callable via reference / `.call`). + // They are installed with metadata via + // `install_constructor_static` (global_this.rs); the + // reroute-undo otherwise collapses them to + // `GlobalGet(0).` (undefined). Direct calls + // (`Promise.all([...])`) take the codegen fast path via the + // `!member_is_call_callee` gate. + let outer_is_reified_promise_static_value = !member_is_call_callee + && property == "Promise" + && outer_static_member + .map(|member| { + matches!( + member, + "resolve" + | "reject" + | "all" + | "race" + | "allSettled" + | "any" + | "withResolvers" + | "try" + ) + }) + .unwrap_or(false); if !outer_is_prototype_or_proto && !receiver_is_namespace_value && !outer_is_websocket_static @@ -1906,6 +1934,7 @@ fn lower_member_inner(ctx: &mut LoweringContext, member: &ast::MemberExpr) -> Re && !outer_is_reified_builtin_static_value && !outer_is_reified_date_static_value && !outer_is_reified_string_static_value + && !outer_is_reified_promise_static_value && !receiver_is_detached_console_read { object_expr = Expr::GlobalGet(0); diff --git a/crates/perry-runtime/src/object/global_this.rs b/crates/perry-runtime/src/object/global_this.rs index 17435ba67b..94c85ab3c2 100644 --- a/crates/perry-runtime/src/object/global_this.rs +++ b/crates/perry-runtime/src/object/global_this.rs @@ -3245,6 +3245,79 @@ extern "C" fn string_from_code_point_static( crate::value::js_nanbox_string(s as i64) } +// #4521: reified `Promise` statics so `Promise.all` / `Promise.resolve` / etc. +// are first-class function values (correct `.name` / `.length`, usable via +// reference, `.call`, `.apply`, spread). Direct calls (`Promise.all([...])`) +// still take the codegen fast path in `lower_call/console_promise.rs`; these +// thunks back value reads and rebound/`.call` usage by delegating to the same +// runtime entry points the direct-call path emits. Spec-internal observable +// semantics (per-iteration `this.resolve`, real resolve-element closures with +// `[[AlreadyCalled]]`, `NewPromiseCapability(this)`) are a follow-up — these +// thunks intentionally use the native Promise machinery regardless of `this`. +extern "C" fn promise_resolve_static( + _closure: *const crate::closure::ClosureHeader, + value: f64, +) -> f64 { + let p = crate::promise::js_promise_resolved(value); + crate::value::js_nanbox_pointer(p as i64) +} + +extern "C" fn promise_reject_static( + _closure: *const crate::closure::ClosureHeader, + reason: f64, +) -> f64 { + let p = crate::promise::js_promise_rejected(reason); + crate::value::js_nanbox_pointer(p as i64) +} + +extern "C" fn promise_all_static( + _closure: *const crate::closure::ClosureHeader, + iterable: f64, +) -> f64 { + let p = crate::promise::combinators::js_promise_all_iterable(iterable); + crate::value::js_nanbox_pointer(p as i64) +} + +extern "C" fn promise_race_static( + _closure: *const crate::closure::ClosureHeader, + iterable: f64, +) -> f64 { + let p = crate::promise::combinators::js_promise_race_iterable(iterable); + crate::value::js_nanbox_pointer(p as i64) +} + +extern "C" fn promise_all_settled_static( + _closure: *const crate::closure::ClosureHeader, + iterable: f64, +) -> f64 { + let p = crate::promise::combinators::js_promise_all_settled_iterable(iterable); + crate::value::js_nanbox_pointer(p as i64) +} + +extern "C" fn promise_any_static( + _closure: *const crate::closure::ClosureHeader, + iterable: f64, +) -> f64 { + let p = crate::promise::combinators::js_promise_any_iterable(iterable); + crate::value::js_nanbox_pointer(p as i64) +} + +extern "C" fn promise_with_resolvers_static(_closure: *const crate::closure::ClosureHeader) -> f64 { + let obj = crate::promise::js_promise_with_resolvers(); + crate::value::js_nanbox_pointer(obj as i64) +} + +// `Promise.try(fn, ...args)`: call-arity 1 (callback) + rest (forwarded args). +extern "C" fn promise_try_static( + _closure: *const crate::closure::ClosureHeader, + callback: f64, + rest: f64, +) -> f64 { + let rest_ptr = crate::value::js_nanbox_get_pointer(rest) as *const crate::array::ArrayHeader; + let p = crate::promise::js_promise_try(callback, rest_ptr); + crate::value::js_nanbox_pointer(p as i64) +} + // #4627: reified `String.raw(callSite, ...substitutions)` tag function. One // fixed param (the template/cooked object) then a rest of substitutions, which // `js_string_raw` reads by numeric index — so `rest` (the collected array) is @@ -3696,6 +3769,53 @@ fn install_builtin_constructor_statics(name: &str, ctor: *mut crate::closure::Cl true, ); } + "Promise" => { + // #4521: reify the `Promise` statics as first-class function values + // (correct `.name` / `.length`, usable via reference / `.call` / + // `.apply` / spread). Direct calls (`Promise.all([...])`) keep the + // codegen fast path; these back value reads and rebound usage. + install_constructor_static( + ctor, + "resolve", + promise_resolve_static as *const u8, + 1, + false, + ); + install_constructor_static( + ctor, + "reject", + promise_reject_static as *const u8, + 1, + false, + ); + install_constructor_static(ctor, "all", promise_all_static as *const u8, 1, false); + install_constructor_static(ctor, "race", promise_race_static as *const u8, 1, false); + install_constructor_static( + ctor, + "allSettled", + promise_all_settled_static as *const u8, + 1, + false, + ); + install_constructor_static(ctor, "any", promise_any_static as *const u8, 1, false); + // `withResolvers` takes no arguments → spec `.length` 0. + install_constructor_static( + ctor, + "withResolvers", + promise_with_resolvers_static as *const u8, + 0, + false, + ); + // `try(fn, ...args)`: 1 fixed param (callback) + rest; spec `.length` 1. + install_constructor_static_with_call_arity( + ctor, + "try", + promise_try_static as *const u8, + 1, + 1, + true, + ); + } "ArrayBuffer" => { install_constructor_static( ctor,