From ea078933fc44a7a10b7dc22fe2e964aa99a0797b Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Sun, 23 Aug 2026 21:19:06 +0200 Subject: [PATCH] fix(intl): expose Collator compare as an accessor --- crates/perry-runtime/src/intl.rs | 47 +++++++++++-------- .../perry-runtime/src/intl/date_collator.rs | 17 ++++--- ...test_gap_intl_collator_compare_accessor.ts | 29 ++++++++++++ 3 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 test-files/test_gap_intl_collator_compare_accessor.ts diff --git a/crates/perry-runtime/src/intl.rs b/crates/perry-runtime/src/intl.rs index 9c3ca76339..57f26a4a3d 100644 --- a/crates/perry-runtime/src/intl.rs +++ b/crates/perry-runtime/src/intl.rs @@ -54,13 +54,14 @@ mod segmenter; use canon_aliases::canonicalize_unicode_extension_types; pub(crate) use date_collator::{ - collator_bound_compare_thunk, collator_bound_resolved_options_thunk, collator_compare_thunk, - collator_resolved_options_thunk, date_time_format_bound_format_thunk, - date_time_format_bound_range_thunk, date_time_format_bound_range_to_parts_thunk, - date_time_format_bound_resolved_options_thunk, date_time_format_bound_to_parts_thunk, - date_time_format_format_getter_thunk, date_time_format_range_thunk, - date_time_format_range_to_parts_thunk, date_time_format_resolved_options_thunk, - date_time_format_to_parts_thunk, temporal_locale_string, TemporalLocaleCtx, + collator_bound_compare_thunk, collator_bound_resolved_options_thunk, + collator_compare_getter_thunk, collator_resolved_options_thunk, + date_time_format_bound_format_thunk, date_time_format_bound_range_thunk, + date_time_format_bound_range_to_parts_thunk, date_time_format_bound_resolved_options_thunk, + date_time_format_bound_to_parts_thunk, date_time_format_format_getter_thunk, + date_time_format_range_thunk, date_time_format_range_to_parts_thunk, + date_time_format_resolved_options_thunk, date_time_format_to_parts_thunk, + temporal_locale_string, TemporalLocaleCtx, }; pub(crate) use list_relative_plural::{ canonicalize_calendar_id, canonicalize_offset_time_zone, is_valid_offset_time_zone, @@ -163,12 +164,13 @@ const KEY_NF_ROUNDING_INCREMENT: &str = "__intlNfRoundingIncrement"; const KEY_NF_ROUNDING_MODE: &str = "__intlNfRoundingMode"; const KEY_NF_ROUNDING_PRIORITY: &str = "__intlNfRoundingPriority"; const KEY_NF_TRAILING_ZERO: &str = "__intlNfTrailingZero"; -// Hidden [[BoundFormat]] slots. The bound format function is also installed as an -// own `format` property for the native dispatch fast path, but the prototype -// `format` getter reads it from here so user mutation/deletion of the public +// Hidden [[BoundFormat]] / [[BoundCompare]] slots. The bound function is also +// installed as an own property for the native dispatch fast path, but the +// prototype accessor reads it from here so user mutation/deletion of the public // property can't corrupt what the accessor returns. const KEY_NF_BOUND_FORMAT: &str = "__intlNfBoundFormat"; const KEY_DTF_BOUND_FORMAT: &str = "__intlDtfBoundFormat"; +const KEY_COL_BOUND_COMPARE: &str = "__intlColBoundCompare"; const KEY_COL_USAGE: &str = "__intlColUsage"; const KEY_COL_SENSITIVITY: &str = "__intlColSensitivity"; const KEY_COL_IGNORE_PUNCT: &str = "__intlColIgnorePunct"; @@ -1331,12 +1333,20 @@ fn make_instance(closure: *const ClosureHeader, kind: &str, locales: f64, option set_internal_field(obj, KEY_COL_COLLATION, string_value(&collation)); set_internal_field(obj, KEY_COL_NUMERIC, bool_value(numeric)); set_internal_field(obj, KEY_COL_CASE_FIRST, string_value(&case_first)); - install_bound_instance_function( + let compare_fn = install_bound_instance_function( obj, "compare", collator_bound_compare_thunk as *const u8, 2, ); + if !compare_fn.is_null() { + crate::object::set_bound_native_closure_name(compare_fn, ""); + set_internal_field( + obj, + KEY_COL_BOUND_COMPARE, + js_nanbox_pointer(compare_fn as i64), + ); + } install_bound_instance_function( obj, "resolvedOptions", @@ -1846,15 +1856,12 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { "Collator", collator_constructor_thunk as *const u8, 0, - &[ - ("compare", collator_compare_thunk as *const u8, 2), - ( - "resolvedOptions", - collator_resolved_options_thunk as *const u8, - 0, - ), - ], - &[], + &[( + "resolvedOptions", + collator_resolved_options_thunk as *const u8, + 0, + )], + &[("compare", collator_compare_getter_thunk as *const u8)], ); install_constructor( ns_obj, diff --git a/crates/perry-runtime/src/intl/date_collator.rs b/crates/perry-runtime/src/intl/date_collator.rs index 3e4d4ad7e0..a2e3cfab78 100644 --- a/crates/perry-runtime/src/intl/date_collator.rs +++ b/crates/perry-runtime/src/intl/date_collator.rs @@ -1668,15 +1668,6 @@ pub(super) fn validate_collator_options(options: f64) { let _ = get_option_value(options, "ignorePunctuation"); } -pub(crate) extern "C" fn collator_compare_thunk( - _closure: *const ClosureHeader, - left: f64, - right: f64, -) -> f64 { - let obj = this_intl_object("compare", KIND_COLLATOR); - collator_compare_object(obj, left, right) -} - pub(crate) extern "C" fn collator_bound_compare_thunk( closure: *const ClosureHeader, left: f64, @@ -1686,6 +1677,14 @@ pub(crate) extern "C" fn collator_bound_compare_thunk( collator_compare_object(obj, left, right) } +/// `get Intl.Collator.prototype.compare` — validate the receiver and return its +/// stable [[BoundCompare]] function. The constructor gives that function the +/// anonymous built-in shape required by ECMA-402 (`name: ""`, `length: 2`). +pub(crate) extern "C" fn collator_compare_getter_thunk(_closure: *const ClosureHeader) -> f64 { + let obj = this_intl_object("compare", KIND_COLLATOR); + get_field(obj, KEY_COL_BOUND_COMPARE) +} + /// Strip the code points a UCA `ignorePunctuation` collator treats as ignorable /// — whitespace and punctuation — so e.g. `compare("", " ")` and /// `compare("", "*")` are 0 (compare/ignorePunctuation.js). diff --git a/test-files/test_gap_intl_collator_compare_accessor.ts b/test-files/test_gap_intl_collator_compare_accessor.ts new file mode 100644 index 0000000000..5aa4082057 --- /dev/null +++ b/test-files/test_gap_intl_collator_compare_accessor.ts @@ -0,0 +1,29 @@ +const descriptor = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare")!; +const getter = descriptor.get!; +const collator = new Intl.Collator("en"); +const compare = collator.compare; + +console.log( + typeof getter, + descriptor.set, + descriptor.enumerable, + descriptor.configurable, +); +console.log( + getter.name, + getter.length, + Object.prototype.hasOwnProperty.call(getter, "prototype"), +); +console.log( + compare === collator.compare, + getter.call(collator) === compare, + compare.name, + compare.length, + Object.prototype.hasOwnProperty.call(compare, "prototype"), +); + +try { + getter.call({}); +} catch (error) { + console.log(error instanceof TypeError); +}