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
137 changes: 41 additions & 96 deletions crates/perry-runtime/src/intl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ mod locale;
mod locales;
use locales::{get_canonical_locales_thunk, supported_values_of_thunk};
mod date_collator;
mod install;
use install::install_constructor;
mod list_relative_plural;
mod number_format;
mod number_format_options;
Expand Down Expand Up @@ -62,12 +64,12 @@ pub(crate) use number_format::{
intl_object_from_value, nf_coerce_number, nf_load, nf_resolved_default,
number_format_bound_format_thunk, number_format_bound_resolved_options_thunk,
number_format_bound_to_parts_thunk, number_format_format_getter_thunk,
number_format_format_object, number_format_resolved_options_object,
number_format_resolved_options_thunk, number_format_to_parts_thunk, number_instance_parts,
number_parts_from_resolved, parts_to_js_array, push_grouped_integer, push_sign,
push_style_suffix, round_integer_to_place, round_mode_code, round_to_fraction,
round_to_significant, rounding_up, set_round_ctx, significant_count, strip_leading_zeros,
this_intl_object, trim_fraction, NfResolved,
number_format_format_object, number_format_range_thunk, number_format_range_to_parts_thunk,
number_format_resolved_options_object, number_format_resolved_options_thunk,
number_format_to_parts_thunk, number_instance_parts, number_parts_from_resolved,
parts_to_js_array, push_grouped_integer, push_sign, push_style_suffix, round_integer_to_place,
round_mode_code, round_to_fraction, round_to_significant, rounding_up, set_round_ctx,
significant_count, strip_leading_zeros, this_intl_object, trim_fraction, NfResolved,
};
pub(crate) use number_format_options::{
configure_number_format, is_well_formed_currency_code, is_well_formed_unit_identifier,
Expand Down Expand Up @@ -1063,6 +1065,29 @@ fn make_instance(closure: *const ClosureHeader, kind: &str, locales: f64, option
number_format_bound_to_parts_thunk as *const u8,
1,
);
// `formatRange`/`formatRangeToParts` are installed as own instance
// properties (native Intl method dispatch resolves from own props,
// not the static prototype) but with a *this-based* closure rather
// than a bound one: a detached `nf.formatRange` reference therefore
// loses `this` and the `this_intl_object` guard throws a TypeError
// (formatRange/invoked-as-func.js), matching the non-bound prototype
// method these shadow.
install_function(
obj,
"formatRange",
number_format_range_thunk as *const u8,
2,
2,
false,
);
install_function(
obj,
"formatRangeToParts",
number_format_range_to_parts_thunk as *const u8,
2,
2,
false,
);
install_bound_instance_function(
obj,
"resolvedOptions",
Expand Down Expand Up @@ -1710,96 +1735,6 @@ fn set_proto_to_string_tag(proto: *mut ObjectHeader, tag: &str) {
);
}

fn install_constructor(
ns_obj: *mut ObjectHeader,
name: &str,
ctor_ptr: *const u8,
ctor_length: u32,
methods: &[(&str, *const u8, u32)],
getters: &[(&str, *const u8)],
) {
let ctor = crate::closure::js_closure_alloc(ctor_ptr, 0);
if ctor.is_null() {
return;
}
crate::closure::js_register_closure_rest(ctor_ptr, 0);
crate::object::set_bound_native_closure_name(ctor, name);
crate::object::set_builtin_closure_length(ctor as usize, ctor_length);
crate::object::set_builtin_property_attrs(
ctor as usize,
"name".to_string(),
PropertyAttrs::new(false, false, true),
);
crate::object::set_builtin_property_attrs(
ctor as usize,
"length".to_string(),
PropertyAttrs::new(false, false, true),
);

let ctor_value = js_nanbox_pointer(ctor as i64);
// Generous inline capacity so installing methods plus an accessor getter and
// the toStringTag symbol never bumps `field_count` past the physical slot
// count (which would expose an overflow slot — keys_array.rs #4099).
let proto = js_object_alloc(0, 16);
set_field(proto, "constructor", ctor_value);
set_builtin_attrs(proto, "constructor", PropertyAttrs::new(true, false, true));
for (method, ptr, arity) in methods.iter().copied() {
install_function(proto, method, ptr, arity, arity, false);
}
// Accessor properties (e.g. `get Intl.NumberFormat.prototype.format`): a
// getter-only descriptor on the prototype so reflection
// (`Object.getOwnPropertyDescriptor(proto, key).get`) sees a function whose
// name is `"get <key>"` and length 0. Instances still carry an own bound
// method for the hot dispatch path (native objects resolve from own props).
for (getter_name, ptr) in getters.iter().copied() {
let closure = crate::closure::js_closure_alloc(ptr, 0);
if closure.is_null() {
continue;
}
crate::closure::js_register_closure_arity(ptr, 0);
crate::object::set_bound_native_closure_name(closure, &format!("get {getter_name}"));
crate::object::set_builtin_closure_length(closure as usize, 0);
crate::object::set_builtin_property_attrs(
closure as usize,
"name".to_string(),
PropertyAttrs::new(false, false, true),
);
crate::object::set_builtin_property_attrs(
closure as usize,
"length".to_string(),
PropertyAttrs::new(false, false, true),
);
let getter_bits = js_nanbox_pointer(closure as i64).to_bits();
unsafe {
crate::object::install_builtin_getter(proto, getter_name, getter_bits);
}
}
set_proto_to_string_tag(proto, &format!("Intl.{name}"));
let proto_value = js_nanbox_pointer(proto as i64);
crate::closure::closure_set_dynamic_prop(ctor as usize, "prototype", proto_value);
crate::object::set_builtin_property_attrs(
ctor as usize,
"prototype".to_string(),
PropertyAttrs::new(false, false, false),
);

// `supportedLocalesOf(locales, options)` — `.length` is 1, but it reads a
// second `options` argument, so register it rest-style (all args collected)
// and pull both positionally.
let supported = install_function(
ctor as *mut ObjectHeader,
"supportedLocalesOf",
supported_locales_of_thunk as *const u8,
0,
1,
true,
);
crate::closure::closure_set_dynamic_prop(ctor as usize, "supportedLocalesOf", supported);

set_field(ns_obj, name, ctor_value);
set_builtin_attrs(ns_obj, name, PropertyAttrs::new(true, false, true));
}

pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) {
if ns_obj.is_null() {
return;
Expand Down Expand Up @@ -1834,6 +1769,16 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) {
number_format_to_parts_thunk as *const u8,
1,
),
// `formatRange`/`formatRangeToParts` are plain (non-bound) prototype
// methods (Intl.NumberFormat-v3): a detached reference loses `this`
// and the `this_intl_object` guard throws, so they are installed on
// the prototype only — never as own bound instance functions.
("formatRange", number_format_range_thunk as *const u8, 2),
(
"formatRangeToParts",
number_format_range_to_parts_thunk as *const u8,
2,
),
(
"resolvedOptions",
number_format_resolved_options_thunk as *const u8,
Expand Down
106 changes: 106 additions & 0 deletions crates/perry-runtime/src/intl/install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! Shared `Intl.*` constructor/prototype installation.
//!
//! `install_constructor` builds the constructor closure, its prototype (plain
//! methods, accessor getters, `Symbol.toStringTag`), the static
//! `supportedLocalesOf`, and registers the pair on the `Intl` namespace. Split
//! out of `intl.rs` to keep that namespace module under the file-size gate; the
//! private helpers it leans on (`set_field`, `install_function`, …) stay in the
//! parent and are reachable here as a descendant module.

use crate::object::{js_object_alloc, ObjectHeader, PropertyAttrs};
use crate::value::js_nanbox_pointer;

use super::{
install_function, set_builtin_attrs, set_field, set_proto_to_string_tag,
supported_locales_of_thunk,
};

pub(super) fn install_constructor(
ns_obj: *mut ObjectHeader,
name: &str,
ctor_ptr: *const u8,
ctor_length: u32,
methods: &[(&str, *const u8, u32)],
getters: &[(&str, *const u8)],
) {
let ctor = crate::closure::js_closure_alloc(ctor_ptr, 0);
if ctor.is_null() {
return;
}
crate::closure::js_register_closure_rest(ctor_ptr, 0);
crate::object::set_bound_native_closure_name(ctor, name);
crate::object::set_builtin_closure_length(ctor as usize, ctor_length);
crate::object::set_builtin_property_attrs(
ctor as usize,
"name".to_string(),
PropertyAttrs::new(false, false, true),
);
crate::object::set_builtin_property_attrs(
ctor as usize,
"length".to_string(),
PropertyAttrs::new(false, false, true),
);

let ctor_value = js_nanbox_pointer(ctor as i64);
// Generous inline capacity so installing methods plus an accessor getter and
// the toStringTag symbol never bumps `field_count` past the physical slot
// count (which would expose an overflow slot — keys_array.rs #4099).
let proto = js_object_alloc(0, 16);
set_field(proto, "constructor", ctor_value);
set_builtin_attrs(proto, "constructor", PropertyAttrs::new(true, false, true));
for (method, ptr, arity) in methods.iter().copied() {
install_function(proto, method, ptr, arity, arity, false);
}
// Accessor properties (e.g. `get Intl.NumberFormat.prototype.format`): a
// getter-only descriptor on the prototype so reflection
// (`Object.getOwnPropertyDescriptor(proto, key).get`) sees a function whose
// name is `"get <key>"` and length 0. Instances still carry an own bound
// method for the hot dispatch path (native objects resolve from own props).
for (getter_name, ptr) in getters.iter().copied() {
let closure = crate::closure::js_closure_alloc(ptr, 0);
if closure.is_null() {
continue;
}
crate::closure::js_register_closure_arity(ptr, 0);
crate::object::set_bound_native_closure_name(closure, &format!("get {getter_name}"));
crate::object::set_builtin_closure_length(closure as usize, 0);
crate::object::set_builtin_property_attrs(
closure as usize,
"name".to_string(),
PropertyAttrs::new(false, false, true),
);
crate::object::set_builtin_property_attrs(
closure as usize,
"length".to_string(),
PropertyAttrs::new(false, false, true),
);
let getter_bits = js_nanbox_pointer(closure as i64).to_bits();
unsafe {
crate::object::install_builtin_getter(proto, getter_name, getter_bits);
}
}
set_proto_to_string_tag(proto, &format!("Intl.{name}"));
let proto_value = js_nanbox_pointer(proto as i64);
crate::closure::closure_set_dynamic_prop(ctor as usize, "prototype", proto_value);
crate::object::set_builtin_property_attrs(
ctor as usize,
"prototype".to_string(),
PropertyAttrs::new(false, false, false),
);

// `supportedLocalesOf(locales, options)` — `.length` is 1, but it reads a
// second `options` argument, so register it rest-style (all args collected)
// and pull both positionally.
let supported = install_function(
ctor as *mut ObjectHeader,
"supportedLocalesOf",
supported_locales_of_thunk as *const u8,
0,
1,
true,
);
crate::closure::closure_set_dynamic_prop(ctor as usize, "supportedLocalesOf", supported);

set_field(ns_obj, name, ctor_value);
set_builtin_attrs(ns_obj, name, PropertyAttrs::new(true, false, true));
}
Loading
Loading