diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index a6520efeb79..3394c34ff94 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -17,8 +17,8 @@ use crate::{ OrdinaryObject, }, context::{ + icu::IntlProvider, intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - BoaProvider, }, js_string, native_function::NativeFunction, @@ -79,7 +79,7 @@ impl Service for Collator { type LocaleOptions = CollatorLocaleOptions; - fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &BoaProvider) { + fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &IntlProvider) { let collation = options .collation .take() @@ -274,8 +274,11 @@ impl BuiltInConstructor for Collator { // 18. Let relevantExtensionKeys be %Collator%.[[RelevantExtensionKeys]]. // 19. Let r be ResolveLocale(%Collator%.[[AvailableLocales]], requestedLocales, opt, relevantExtensionKeys, localeData). - let mut locale = - resolve_locale::(&requested_locales, &mut intl_options, context.icu()); + let mut locale = resolve_locale::( + &requested_locales, + &mut intl_options, + context.intl_provider(), + ); let collator_locale = { // `collator_locale` needs to be different from the resolved locale because ECMA402 doesn't @@ -332,7 +335,7 @@ impl BuiltInConstructor for Collator { .unzip(); let collator = - NativeCollator::try_new_unstable(context.icu().provider(), &collator_locale, { + NativeCollator::try_new_unstable(context.intl_provider(), &collator_locale, { let mut options = icu_collator::CollatorOptions::new(); options.strength = strength; options.case_level = case_level; diff --git a/boa_engine/src/builtins/intl/list_format/mod.rs b/boa_engine/src/builtins/intl/list_format/mod.rs index f126706818d..55c2105322e 100644 --- a/boa_engine/src/builtins/intl/list_format/mod.rs +++ b/boa_engine/src/builtins/intl/list_format/mod.rs @@ -126,7 +126,7 @@ impl BuiltInConstructor for ListFormat { matcher, ..Default::default() }, - context.icu(), + context.intl_provider(), ); // 11. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction"). @@ -144,17 +144,17 @@ impl BuiltInConstructor for ListFormat { let data_locale = DataLocale::from(&locale); let formatter = match typ { ListFormatType::Conjunction => ListFormatter::try_new_and_with_length_unstable( - context.icu().provider(), + context.intl_provider(), &data_locale, style, ), ListFormatType::Disjunction => ListFormatter::try_new_or_with_length_unstable( - context.icu().provider(), + context.intl_provider(), &data_locale, style, ), ListFormatType::Unit => ListFormatter::try_new_unit_with_length_unstable( - context.icu().provider(), + context.intl_provider(), &data_locale, style, ), diff --git a/boa_engine/src/builtins/intl/locale/mod.rs b/boa_engine/src/builtins/intl/locale/mod.rs index 15fd7f1b667..8f5a288e665 100644 --- a/boa_engine/src/builtins/intl/locale/mod.rs +++ b/boa_engine/src/builtins/intl/locale/mod.rs @@ -249,7 +249,10 @@ impl BuiltInConstructor for Locale { let region = get_option(options, utf16!("region"), context)?; // 10. Set tag to ! CanonicalizeUnicodeLocaleId(tag). - context.icu().locale_canonicalizer().canonicalize(&mut tag); + context + .intl_provider() + .locale_canonicalizer() + .canonicalize(&mut tag); // Skipping some boilerplate since this is easier to do using the `Locale` type, but putting the // spec for completion. @@ -280,7 +283,10 @@ impl BuiltInConstructor for Locale { } // 17. Return ! CanonicalizeUnicodeLocaleId(tag). - context.icu().locale_canonicalizer().canonicalize(&mut tag); + context + .intl_provider() + .locale_canonicalizer() + .canonicalize(&mut tag); } // 12. Let opt be a new Record. @@ -363,7 +369,10 @@ impl BuiltInConstructor for Locale { tag.extensions.unicode.keywords.set(key!("nu"), nu); } - context.icu().locale_canonicalizer().canonicalize(&mut tag); + context + .intl_provider() + .locale_canonicalizer() + .canonicalize(&mut tag); // 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, "%Locale.prototype%", internalSlotsList). let prototype = @@ -403,7 +412,7 @@ impl Locale { .clone(); // 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]]. If an error is signaled, set maximal to loc.[[Locale]]. - context.icu().locale_expander().maximize(&mut loc); + context.intl_provider().locale_expander().maximize(&mut loc); // 4. Return ! Construct(%Locale%, maximal). let prototype = context.intrinsics().constructors().locale().prototype(); @@ -439,7 +448,7 @@ impl Locale { .clone(); // 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]]. If an error is signaled, set minimal to loc.[[Locale]]. - context.icu().locale_expander().minimize(&mut loc); + context.intl_provider().locale_expander().minimize(&mut loc); // 4. Return ! Construct(%Locale%, minimal). let prototype = context.intrinsics().constructors().locale().prototype(); diff --git a/boa_engine/src/builtins/intl/locale/tests.rs b/boa_engine/src/builtins/intl/locale/tests.rs index 7c200786e1c..969c9cb4563 100644 --- a/boa_engine/src/builtins/intl/locale/tests.rs +++ b/boa_engine/src/builtins/intl/locale/tests.rs @@ -15,7 +15,7 @@ use crate::{ options::{IntlOptions, LocaleMatcher}, Service, }, - context::icu::{BoaProvider, Icu, StaticProviderAdapter}, + context::icu::IntlProvider, }; #[derive(Debug)] @@ -30,7 +30,7 @@ impl Service for TestService { type LocaleOptions = TestOptions; - fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &BoaProvider) { + fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &IntlProvider) { let loc_hc = locale .extensions .unicode @@ -73,11 +73,8 @@ impl Service for TestService { #[test] fn locale_resolution() { - let icu = Icu::new(BoaProvider::Buffer(Box::new(StaticProviderAdapter( - boa_icu_provider::buffer(), - )))) - .unwrap(); - let mut default = default_locale(icu.locale_canonicalizer()); + let provider = IntlProvider::try_new_with_buffer_provider(boa_icu_provider::buffer()).unwrap(); + let mut default = default_locale(provider.locale_canonicalizer()); default .extensions .unicode @@ -91,7 +88,7 @@ fn locale_resolution() { hc: Some(HourCycle::H11), }, }; - let locale = resolve_locale::(&[], &mut options, &icu); + let locale = resolve_locale::(&[], &mut options, &provider); assert_eq!(locale, default); // test best fit @@ -102,10 +99,10 @@ fn locale_resolution() { }, }; - let locale = resolve_locale::(&[], &mut options, &icu); + let locale = resolve_locale::(&[], &mut options, &provider); let best = best_locale_for_provider::<::LangMarker>( default.id.clone(), - icu.provider(), + &provider, ) .unwrap(); let mut best = Locale::from(best); @@ -118,6 +115,6 @@ fn locale_resolution() { service_options: TestOptions { hc: None }, }; - let locale = resolve_locale::(&[locale!("es-AR")], &mut options, &icu); + let locale = resolve_locale::(&[locale!("es-AR")], &mut options, &provider); assert_eq!(locale, "es-u-hc-h23".parse().unwrap()); } diff --git a/boa_engine/src/builtins/intl/locale/utils.rs b/boa_engine/src/builtins/intl/locale/utils.rs index 16194b07f89..423908cd461 100644 --- a/boa_engine/src/builtins/intl/locale/utils.rs +++ b/boa_engine/src/builtins/intl/locale/utils.rs @@ -7,7 +7,7 @@ use crate::{ options::get_option, Array, }, - context::{icu::Icu, BoaProvider}, + context::icu::IntlProvider, js_string, object::JsObject, string::utf16, @@ -138,7 +138,10 @@ pub(crate) fn canonicalize_locale_list( }; // vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag). - context.icu().locale_canonicalizer().canonicalize(&mut tag); + context + .intl_provider() + .locale_canonicalizer() + .canonicalize(&mut tag); // vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen. seen.insert(tag); @@ -314,9 +317,12 @@ pub(crate) fn best_locale_for_provider( /// in order to see if a certain [`Locale`] is supported. /// /// [spec]: https://tc39.es/ecma402/#sec-lookupmatcher -fn lookup_matcher(requested_locales: &[Locale], icu: &Icu) -> Locale +fn lookup_matcher( + requested_locales: &[Locale], + provider: &IntlProvider, +) -> Locale where - BoaProvider: DataProvider, + IntlProvider: DataProvider, { // 1. Let result be a new Record. // 2. For each element locale of requestedLocales, do @@ -329,7 +335,7 @@ where locale.extensions.private.clear(); // b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale). - let available_locale = best_available_locale::(id, icu.provider()); + let available_locale = best_available_locale::(id, provider); // c. If availableLocale is not undefined, then if let Some(available_locale) = available_locale { @@ -349,7 +355,7 @@ where // 3. Let defLocale be ! DefaultLocale(). // 4. Set result.[[locale]] to defLocale. // 5. Return result. - default_locale(icu.locale_canonicalizer()) + default_locale(provider.locale_canonicalizer()) } /// Abstract operation [`BestFitMatcher ( availableLocales, requestedLocales )`][spec] @@ -361,22 +367,25 @@ where /// produced by the `LookupMatcher` abstract operation. /// /// [spec]: https://tc39.es/ecma402/#sec-bestfitmatcher -fn best_fit_matcher(requested_locales: &[Locale], icu: &Icu) -> Locale +fn best_fit_matcher( + requested_locales: &[Locale], + provider: &IntlProvider, +) -> Locale where - BoaProvider: DataProvider, + IntlProvider: DataProvider, { for mut locale in requested_locales .iter() .cloned() .chain(std::iter::once_with(|| { - default_locale(icu.locale_canonicalizer()) + default_locale(provider.locale_canonicalizer()) })) { let id = std::mem::take(&mut locale.id); locale.extensions.transform.clear(); locale.extensions.private.clear(); - if let Some(available) = best_locale_for_provider(id, icu.provider()) { + if let Some(available) = best_locale_for_provider(id, provider) { locale.id = available; return locale; @@ -399,11 +408,11 @@ where pub(in crate::builtins::intl) fn resolve_locale( requested_locales: &[Locale], options: &mut IntlOptions, - icu: &Icu, + provider: &IntlProvider, ) -> Locale where S: Service, - BoaProvider: DataProvider, + IntlProvider: DataProvider, { // 1. Let matcher be options.[[localeMatcher]]. // 2. If matcher is "lookup", then @@ -412,9 +421,9 @@ where // a. Let r be ! BestFitMatcher(availableLocales, requestedLocales). // 4. Let foundLocale be r.[[locale]]. let mut found_locale = if options.matcher == LocaleMatcher::Lookup { - lookup_matcher::(requested_locales, icu) + lookup_matcher::(requested_locales, provider) } else { - best_fit_matcher::(requested_locales, icu) + best_fit_matcher::(requested_locales, provider) }; // From here, the spec differs significantly from the implementation, @@ -469,12 +478,10 @@ where // 11. Set result.[[locale]] to foundLocale. // 12. Return result. - S::resolve( - &mut found_locale, - &mut options.service_options, - icu.provider(), - ); - icu.locale_canonicalizer().canonicalize(&mut found_locale); + S::resolve(&mut found_locale, &mut options.service_options, provider); + provider + .locale_canonicalizer() + .canonicalize(&mut found_locale); found_locale } @@ -493,7 +500,7 @@ where /// [spec]: https://tc39.es/ecma402/#sec-lookupsupportedlocales fn lookup_supported_locales( requested_locales: &[Locale], - provider: &impl DataProvider, + provider: &(impl DataProvider + ?Sized), ) -> Vec { // 1. Let subset be a new empty List. // 2. For each element locale of requestedLocales, do @@ -517,7 +524,7 @@ fn lookup_supported_locales( /// [spec]: https://tc39.es/ecma402/#sec-bestfitsupportedlocales fn best_fit_supported_locales( requested_locales: &[Locale], - provider: &impl DataProvider, + provider: &(impl DataProvider + ?Sized), ) -> Vec { requested_locales .iter() @@ -538,7 +545,7 @@ pub(in crate::builtins::intl) fn supported_locales( context: &mut Context, ) -> JsResult where - BoaProvider: DataProvider, + IntlProvider: DataProvider, { // 1. Set options to ? CoerceOptionsToObject(options). let options = coerce_options_to_object(options, context)?; @@ -550,12 +557,12 @@ where // 4. Else, // a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales). LocaleMatcher::Lookup => { - lookup_supported_locales(requested_locales, context.icu().provider()) + lookup_supported_locales(requested_locales, context.intl_provider()) } // 3. If matcher is "best fit", then // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales). LocaleMatcher::BestFit => { - best_fit_supported_locales(requested_locales, context.icu().provider()) + best_fit_supported_locales(requested_locales, context.intl_provider()) } }; @@ -600,7 +607,7 @@ mod tests { builtins::intl::locale::utils::{ best_available_locale, best_fit_matcher, default_locale, lookup_matcher, }, - context::icu::{BoaProvider, Icu, StaticProviderAdapter}, + context::icu::IntlProvider, }; #[test] @@ -626,10 +633,7 @@ mod tests { #[test] fn lookup_match() { - let icu = Icu::new(BoaProvider::Buffer(Box::new(StaticProviderAdapter( - boa_icu_provider::buffer(), - )))) - .unwrap(); + let icu = IntlProvider::try_new_with_buffer_provider(boa_icu_provider::buffer()).unwrap(); // requested: [] diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 92c85e98c23..1a29e371106 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -15,7 +15,7 @@ use crate::{ builtins::{Array, BuiltInBuilder, BuiltInObject, IntrinsicObject}, - context::{intrinsics::Intrinsics, BoaProvider}, + context::{icu::IntlProvider, intrinsics::Intrinsics}, js_string, object::JsObject, property::Attribute, @@ -172,7 +172,7 @@ trait Service { fn resolve( _locale: &mut icu_locid::Locale, _options: &mut Self::LocaleOptions, - _provider: &BoaProvider, + _provider: &IntlProvider, ) { } } diff --git a/boa_engine/src/builtins/intl/plural_rules/mod.rs b/boa_engine/src/builtins/intl/plural_rules/mod.rs index 02aa1cf9a25..fa14b352cbd 100644 --- a/boa_engine/src/builtins/intl/plural_rules/mod.rs +++ b/boa_engine/src/builtins/intl/plural_rules/mod.rs @@ -143,16 +143,16 @@ impl BuiltInConstructor for PluralRules { matcher, ..Default::default() }, - context.icu(), + context.intl_provider(), ); let native = match rule_type { PluralRuleType::Cardinal => PluralRulesWithRanges::try_new_cardinal_unstable( - context.icu().provider(), + context.intl_provider(), &DataLocale::from(&locale), ), PluralRuleType::Ordinal => PluralRulesWithRanges::try_new_ordinal_unstable( - context.icu().provider(), + context.intl_provider(), &DataLocale::from(&locale), ), _ => { diff --git a/boa_engine/src/builtins/intl/segmenter/mod.rs b/boa_engine/src/builtins/intl/segmenter/mod.rs index 279c3052749..2ff7f03e56d 100644 --- a/boa_engine/src/builtins/intl/segmenter/mod.rs +++ b/boa_engine/src/builtins/intl/segmenter/mod.rs @@ -146,7 +146,7 @@ impl BuiltInConstructor for Segmenter { matcher, ..Default::default() }, - context.icu(), + context.intl_provider(), ); // 12. Let granularity be ? GetOption(options, "granularity", string, « "grapheme", "word", "sentence" », "grapheme"). @@ -155,14 +155,14 @@ impl BuiltInConstructor for Segmenter { let native = match granularity { Granularity::Grapheme => { - GraphemeClusterSegmenter::try_new_unstable(context.icu().provider()) + GraphemeClusterSegmenter::try_new_unstable(context.intl_provider()) .map(|s| NativeSegmenter::Grapheme(Box::new(s))) } - Granularity::Word => WordSegmenter::try_new_auto_unstable(context.icu().provider()) + Granularity::Word => WordSegmenter::try_new_auto_unstable(context.intl_provider()) .map(|s| NativeSegmenter::Word(Box::new(s))), - Granularity::Sentence => SentenceSegmenter::try_new_unstable(context.icu().provider()) + Granularity::Sentence => SentenceSegmenter::try_new_unstable(context.intl_provider()) .map(|s| NativeSegmenter::Sentence(Box::new(s))), } .map_err(|err| JsNativeError::typ().with_message(err.to_string()))?; diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 46777d6d0ed..ec888839be9 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -1754,16 +1754,16 @@ impl String { .next() // 3. Else, // a. Let requestedLocale be ! DefaultLocale(). - .unwrap_or_else(|| default_locale(context.icu().locale_canonicalizer())) + .unwrap_or_else(|| default_locale(context.intl_provider().locale_canonicalizer())) .id; // 4. Let noExtensionsLocale be the String value that is requestedLocale with any Unicode locale extension sequences (6.2.1) removed. // 5. Let availableLocales be a List with language tags that includes the languages for which the Unicode Character Database contains language sensitive case mappings. Implementations may add additional language tags if they support case mapping for additional locales. // 6. Let locale be ! BestAvailableLocale(availableLocales, noExtensionsLocale). // 7. If locale is undefined, set locale to "und". - let lang = best_available_locale::(lang, context.icu().provider()) + let lang = best_available_locale::(lang, context.intl_provider()) .unwrap_or(LanguageIdentifier::UND); - let casemapper = context.icu().case_mapper(); + let casemapper = context.intl_provider().case_mapper(); // 8. Let codePoints be StringToCodePoints(S). let result = string.map_valid_segments(|segment| { @@ -2143,7 +2143,7 @@ impl String { } #[cfg(feature = "intl")] { - context.icu().string_normalizers() + context.intl_provider().string_normalizers() } }; diff --git a/boa_engine/src/context/icu.rs b/boa_engine/src/context/icu.rs index f2307e7c5a4..68cabdb0833 100644 --- a/boa_engine/src/context/icu.rs +++ b/boa_engine/src/context/icu.rs @@ -4,8 +4,8 @@ use icu_casemap::CaseMapper; use icu_locid_transform::{LocaleCanonicalizer, LocaleExpander, LocaleTransformError}; use icu_normalizer::{ComposingNormalizer, DecomposingNormalizer, NormalizerError}; use icu_provider::{ - AnyProvider, AsDeserializingBufferProvider, AsDowncastingAnyProvider, BufferProvider, - DataError, DataProvider, DataRequest, DataResponse, KeyedDataMarker, MaybeSendSync, + AnyProvider, AnyResponse, BufferMarker, BufferProvider, DataError, DataKey, DataProvider, + DataRequest, DataResponse, KeyedDataMarker, MaybeSendSync, }; use serde::Deserialize; use thiserror::Error; @@ -14,41 +14,27 @@ use zerofrom::ZeroFrom; use crate::builtins::string::StringNormalizers; -/// ICU4X data provider used in boa. -/// -/// Providers can be either [`BufferProvider`]s or [`AnyProvider`]s. -/// -/// The [`icu_provider`] documentation has more information about data providers. -pub enum BoaProvider { - /// A [`BufferProvider`] data provider. - Buffer(Box), - /// An [`AnyProvider`] data provider. - Any(Box), +enum ErasedResponse { + Any(AnyResponse), + Buffer(DataResponse), } -impl Debug for BoaProvider { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Buffer(_) => f.debug_tuple("Buffer").field(&"..").finish(), - Self::Any(_) => f.debug_tuple("Any").field(&"..").finish(), - } +trait BoaProvider { + fn load_data(&self, key: DataKey, req: DataRequest<'_>) -> Result; +} + +struct BufferProviderWrapper(T); + +impl BoaProvider for BufferProviderWrapper { + fn load_data(&self, key: DataKey, req: DataRequest<'_>) -> Result { + self.0.load_buffer(key, req).map(ErasedResponse::Buffer) } } -// This blanket implementation mirrors the `DataProvider` implementations of `BufferProvider` and -// `AnyProvider`, which allows us to use `unstable` constructors in a stable way. -impl DataProvider for BoaProvider -where - M: KeyedDataMarker + 'static, - for<'de> YokeTraitHack<>::Output>: Deserialize<'de>, - for<'a> YokeTraitHack<>::Output>: Clone, - M::Yokeable: ZeroFrom<'static, M::Yokeable> + MaybeSendSync, -{ - fn load(&self, req: DataRequest<'_>) -> Result, DataError> { - match self { - BoaProvider::Buffer(provider) => provider.as_deserializing().load(req), - BoaProvider::Any(provider) => provider.as_downcasting().load(req), - } +struct AnyProviderWrapper(T); +impl BoaProvider for AnyProviderWrapper { + fn load_data(&self, key: DataKey, req: DataRequest<'_>) -> Result { + self.0.load_any(key, req).map(ErasedResponse::Any) } } @@ -66,46 +52,98 @@ pub enum IcuError { CaseMap(#[from] DataError), } -/// Collection of tools initialized from a [`DataProvider`] that are used for the functionality of +/// Collection of data initialized from a [`DataProvider`] that is used for the functionality of /// `Intl`. -pub(crate) struct Icu { - provider: BoaProvider, +pub(crate) struct IntlProvider { + inner_provider: Box, locale_canonicalizer: LocaleCanonicalizer, locale_expander: LocaleExpander, string_normalizers: StringNormalizers, case_mapper: CaseMapper, } -impl Debug for Icu { +impl DataProvider for IntlProvider +where + M: KeyedDataMarker + 'static, + for<'de> YokeTraitHack<>::Output>: Deserialize<'de> + Clone, + M::Yokeable: ZeroFrom<'static, M::Yokeable> + MaybeSendSync, +{ + fn load(&self, req: DataRequest<'_>) -> Result, DataError> { + match self.inner_provider.load_data(M::KEY, req)? { + ErasedResponse::Any(response) => { + response.downcast().map_err(|e| e.with_req(M::KEY, req)) + } + ErasedResponse::Buffer(response) => { + let buffer_format = response.metadata.buffer_format.ok_or_else(|| { + DataError::custom("BufferProvider didn't set BufferFormat") + .with_req(M::KEY, req) + })?; + Ok(DataResponse { + metadata: response.metadata, + payload: response + .payload + .map(|p| p.into_deserialized(buffer_format)) + .transpose() + .map_err(|e| e.with_req(M::KEY, req))?, + }) + } + } + } +} + +impl Debug for IntlProvider { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Icu") - .field("provider", &self.provider) .field("locale_canonicalizer", &self.locale_canonicalizer) .field("locale_expander", &self.locale_expander) .field("string_normalizers", &self.string_normalizers) .field("string_normalizercase_mapper", &self.case_mapper) - .finish() + .finish_non_exhaustive() } } -impl Icu { - /// Creates a new [`Icu`] from a valid [`BoaProvider`] +impl IntlProvider { + /// Creates a new [`Icu`] from a [`BufferProvider`]. /// /// # Errors /// /// Returns an error if any of the tools required cannot be constructed. - pub(crate) fn new(provider: BoaProvider) -> Result { + pub(crate) fn try_new_with_buffer_provider( + provider: (impl BufferProvider + 'static), + ) -> Result { Ok(Self { - locale_canonicalizer: LocaleCanonicalizer::try_new_unstable(&provider)?, - locale_expander: LocaleExpander::try_new_extended_unstable(&provider)?, + locale_canonicalizer: LocaleCanonicalizer::try_new_with_buffer_provider(&provider)?, + locale_expander: LocaleExpander::try_new_with_buffer_provider(&provider)?, string_normalizers: StringNormalizers { - nfc: ComposingNormalizer::try_new_nfc_unstable(&provider)?, - nfkc: ComposingNormalizer::try_new_nfkc_unstable(&provider)?, - nfd: DecomposingNormalizer::try_new_nfd_unstable(&provider)?, - nfkd: DecomposingNormalizer::try_new_nfkd_unstable(&provider)?, + nfc: ComposingNormalizer::try_new_nfc_with_buffer_provider(&provider)?, + nfkc: ComposingNormalizer::try_new_nfkc_with_buffer_provider(&provider)?, + nfd: DecomposingNormalizer::try_new_nfd_with_buffer_provider(&provider)?, + nfkd: DecomposingNormalizer::try_new_nfkd_with_buffer_provider(&provider)?, }, - case_mapper: CaseMapper::try_new_unstable(&provider)?, - provider, + case_mapper: CaseMapper::try_new_with_buffer_provider(&provider)?, + inner_provider: Box::new(BufferProviderWrapper(provider)), + }) + } + + /// Creates a new [`IntlProvider`] from an [`AnyProvider`]. + /// + /// # Errors + /// + /// Returns an error if any of the tools required cannot be constructed. + pub(crate) fn try_new_with_any_provider( + provider: (impl AnyProvider + 'static), + ) -> Result { + Ok(Self { + locale_canonicalizer: LocaleCanonicalizer::try_new_with_any_provider(&provider)?, + locale_expander: LocaleExpander::try_new_extended_with_any_provider(&provider)?, + string_normalizers: StringNormalizers { + nfc: ComposingNormalizer::try_new_nfc_with_any_provider(&provider)?, + nfkc: ComposingNormalizer::try_new_nfkc_with_any_provider(&provider)?, + nfd: DecomposingNormalizer::try_new_nfd_with_any_provider(&provider)?, + nfkd: DecomposingNormalizer::try_new_nfkd_with_any_provider(&provider)?, + }, + case_mapper: CaseMapper::try_new_with_any_provider(&provider)?, + inner_provider: Box::new(AnyProviderWrapper(provider)), }) } @@ -128,34 +166,4 @@ impl Icu { pub(crate) const fn case_mapper(&self) -> &CaseMapper { &self.case_mapper } - - /// Gets the inner icu data provider - pub(crate) const fn provider(&self) -> &BoaProvider { - &self.provider - } -} - -/// Adapter to allow creating a `Box` from -/// a &'static impl Provider. -#[derive(Debug)] -pub(crate) struct StaticProviderAdapter(pub(crate) &'static T); - -impl BufferProvider for StaticProviderAdapter { - fn load_buffer( - &self, - key: icu_provider::DataKey, - req: DataRequest<'_>, - ) -> Result, DataError> { - self.0.load_buffer(key, req) - } -} - -impl AnyProvider for StaticProviderAdapter { - fn load_any( - &self, - key: icu_provider::DataKey, - req: DataRequest<'_>, - ) -> Result { - self.0.load_any(key, req) - } } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 07cc6ef9475..ac39deeceb8 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -6,8 +6,10 @@ pub(crate) mod icu; pub mod intrinsics; pub use hooks::{DefaultHooks, HostHooks}; + #[cfg(feature = "intl")] -pub use icu::{BoaProvider, IcuError}; +pub use icu::IcuError; + use intrinsics::Intrinsics; #[cfg(not(feature = "intl"))] @@ -102,9 +104,9 @@ pub struct Context { can_block: bool, - /// ICU related utilities + /// Intl data provider. #[cfg(feature = "intl")] - icu: icu::Icu, + intl_provider: icu::IntlProvider, host_hooks: &'static dyn HostHooks, @@ -134,7 +136,7 @@ impl std::fmt::Debug for Context { .field("optimizer_options", &self.optimizer_options); #[cfg(feature = "intl")] - debug.field("icu", &self.icu); + debug.field("intl_provider", &self.intl_provider); debug.finish_non_exhaustive() } @@ -837,10 +839,10 @@ impl Context { ContextCleanupGuard::new(self, cleanup) } - /// Get the ICU related utilities + /// Get the Intl data provider. #[cfg(feature = "intl")] - pub(crate) const fn icu(&self) -> &icu::Icu { - &self.icu + pub(crate) const fn intl_provider(&self) -> &icu::IntlProvider { + &self.intl_provider } } @@ -863,7 +865,7 @@ pub struct ContextBuilder { module_loader: Option>, can_block: bool, #[cfg(feature = "intl")] - icu: Option, + icu: Option, #[cfg(feature = "fuzz")] instructions_remaining: usize, } @@ -917,7 +919,30 @@ impl ContextBuilder { self } - /// Provides an icu data provider to the [`Context`]. + /// Provides a [`BufferProvider`] data provider to the [`Context`]. + /// + /// This function is only available if the `intl` feature is enabled. + /// + /// # Errors + /// + /// This returns `Err` if the provided provider doesn't have the required locale information + /// to construct both a [`LocaleCanonicalizer`] and a [`LocaleExpander`]. Note that this doesn't + /// mean that the provider will successfully construct all `Intl` services; that check is made + /// until the creation of an instance of a service. + /// + /// [`LocaleCanonicalizer`]: icu_locid_transform::LocaleCanonicalizer + /// [`LocaleExpander`]: icu_locid_transform::LocaleExpander + /// [`BufferProvider`]: icu_provider::BufferProvider + #[cfg(feature = "intl")] + pub fn icu_buffer_provider( + mut self, + provider: T, + ) -> Result { + self.icu = Some(icu::IntlProvider::try_new_with_buffer_provider(provider)?); + Ok(self) + } + + /// Provides an [`AnyProvider`] data provider to the [`Context`]. /// /// This function is only available if the `intl` feature is enabled. /// @@ -930,9 +955,13 @@ impl ContextBuilder { /// /// [`LocaleCanonicalizer`]: icu_locid_transform::LocaleCanonicalizer /// [`LocaleExpander`]: icu_locid_transform::LocaleExpander + /// [`AnyProvider`]: icu_provider::AnyProvider #[cfg(feature = "intl")] - pub fn icu_provider(mut self, provider: BoaProvider) -> Result { - self.icu = Some(icu::Icu::new(provider)?); + pub fn icu_any_provider( + mut self, + provider: T, + ) -> Result { + self.icu = Some(icu::IntlProvider::try_new_with_any_provider(provider)?); Ok(self) } @@ -1027,11 +1056,9 @@ impl ContextBuilder { vm, strict: false, #[cfg(feature = "intl")] - icu: self.icu.unwrap_or_else(|| { - let provider = BoaProvider::Buffer(Box::new(icu::StaticProviderAdapter( - boa_icu_provider::buffer(), - ))); - icu::Icu::new(provider).expect("Failed to initialize default icu data.") + intl_provider: self.icu.unwrap_or_else(|| { + icu::IntlProvider::try_new_with_buffer_provider(boa_icu_provider::buffer()) + .expect("Failed to initialize default icu data.") }), #[cfg(feature = "fuzz")] instructions_remaining: self.instructions_remaining,