diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 0449790d7be..af27d458157 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -761,7 +761,10 @@ fn unwrap_number_format(nf: &JsValue, context: &mut Context) -> JsResult JsResult { +pub(crate) fn to_intl_mathematical_value( + value: &JsValue, + context: &mut Context, +) -> JsResult { // 1. Let primValue be ? ToPrimitive(value, number). let prim_value = value.to_primitive(context, PreferredType::Number)?; diff --git a/core/engine/src/builtins/number/mod.rs b/core/engine/src/builtins/number/mod.rs index a51e61ab28e..b780cb6b7b1 100644 --- a/core/engine/src/builtins/number/mod.rs +++ b/core/engine/src/builtins/number/mod.rs @@ -48,6 +48,19 @@ pub(crate) struct Number; impl IntrinsicObject for Number { fn init(realm: &Realm) { + let to_locale_string_function = BuiltInBuilder::callable_with_object( + realm, + realm + .intrinsics() + .objects() + .number_prototype_to_locale_string() + .into(), + Self::to_locale_string, + ) + .name(js_string!("toLocaleString")) + .length(0) + .build(); + let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; BuiltInBuilder::from_standard_constructor::(realm) @@ -87,7 +100,11 @@ impl IntrinsicObject for Number { .static_method(Self::number_is_integer, js_string!("isInteger"), 1) .method(Self::to_exponential, js_string!("toExponential"), 1) .method(Self::to_fixed, js_string!("toFixed"), 1) - .method(Self::to_locale_string, js_string!("toLocaleString"), 0) + .property( + js_string!("toLocaleString"), + to_locale_string_function, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .method(Self::to_precision, js_string!("toPrecision"), 1) .method(Self::to_string, js_string!("toString"), 1) .method(Self::value_of, js_string!("valueOf"), 0) diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index 391ca0f3f32..99e39182e94 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -3,7 +3,6 @@ use std::{ sync::atomic::Ordering, }; -use boa_macros::utf16; use num_traits::Zero; use super::{ @@ -1209,7 +1208,7 @@ impl BuiltinTypedArray { }; // 6. Let R be the empty String. - let mut r = Vec::new(); + let mut r = Vec::with_capacity(len as usize); // 7. Let k be 0. // 8. Repeat, while k < len, @@ -2501,24 +2500,44 @@ impl BuiltinTypedArray { (o.array_length(buf_len), is_fixed_len) }; + let locales = args.get_or_undefined(0); + let options = args.get_or_undefined(1); + let separator = { #[cfg(feature = "intl")] { - // TODO: this should eventually return a locale-sensitive separator. - utf16!(", ") + use crate::builtins::intl::locale::default_locale; + use icu_list::{ + ListFormatter, ListFormatterPreferences, options::ListFormatterOptions, + }; + + let locale = default_locale(context.intl_provider().locale_canonicalizer()?); + let preferences = ListFormatterPreferences::from(&locale); + let formatter = ListFormatter::try_new_unit_with_buffer_provider( + context.intl_provider().erased_provider(), + preferences, + ListFormatterOptions::default(), + ) + .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; + + // Ask ICU for the list pattern literal by formatting two empty elements. + // For many locales this yields ", ", but it may differ. + js_string!( + formatter.format_to_string(std::iter::once("").chain(std::iter::once(""))) + ) } #[cfg(not(feature = "intl"))] { - utf16!(", ") + js_string!(", ") } }; - let mut r = Vec::new(); + let mut r = Vec::with_capacity((len + len.saturating_sub(1)) as usize); for k in 0..len { if k > 0 { - r.extend_from_slice(separator); + r.extend(separator.iter()); } let next_element = array.get(k, context)?; @@ -2529,10 +2548,7 @@ impl BuiltinTypedArray { let s = next_element .invoke( js_string!("toLocaleString"), - &[ - args.get_or_undefined(0).clone(), - args.get_or_undefined(1).clone(), - ], + &[locales.clone(), options.clone()], context, )? .to_string(context)?; diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index b4dcc162e9d..c9619683597 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -1086,6 +1086,9 @@ pub struct IntrinsicObjects { /// [`%Array.prototype.toString%`](https://tc39.es/ecma262/#sec-array.prototype.tostring) array_prototype_to_string: JsFunction, + /// [`%Number.prototype.toLocaleString%`](https://tc39.es/ecma262/#sec-number.prototype.tolocalestring) + number_prototype_to_locale_string: JsFunction, + /// Cached iterator prototypes. iterator_prototypes: IteratorPrototypes, @@ -1158,6 +1161,7 @@ impl IntrinsicObjects { throw_type_error: JsFunction::empty_intrinsic_function(false), array_prototype_values: JsFunction::empty_intrinsic_function(false), array_prototype_to_string: JsFunction::empty_intrinsic_function(false), + number_prototype_to_locale_string: JsFunction::empty_intrinsic_function(false), iterator_prototypes: IteratorPrototypes::default(), generator: JsObject::with_null_proto(), async_generator: JsObject::with_null_proto(), @@ -1210,6 +1214,15 @@ impl IntrinsicObjects { self.array_prototype_to_string.clone() } + /// Gets the [`%Number.prototype.toLocaleString%`][spec] intrinsic function. + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring + #[inline] + #[must_use] + pub fn number_prototype_to_locale_string(&self) -> JsFunction { + self.number_prototype_to_locale_string.clone() + } + /// Gets the cached iterator prototypes. #[inline] #[must_use]