• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 6815798908

09 Nov 2023 05:17PM UTC coverage: 72.607% (-2.4%) from 75.01%
6815798908

push

github

web-flow
Implement `Any/BufferProvider` for some smart pointers (#4255)

Allows storing them as a `Box<dyn Any/BufferProvider>` without using a
wrapper type that implements the trait.

44281 of 60987 relevant lines covered (72.61%)

201375.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.97
/components/locid_transform/src/expander.rs
1
// This file is part of ICU4X. For terms of use, please see the file
17✔
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4

5
use crate::{provider::*, LocaleTransformError};
6

7
use core::mem;
8
use icu_locid::subtags::{Language, Region, Script};
9
use icu_locid::LanguageIdentifier;
10
use icu_provider::prelude::*;
11

12
use crate::TransformResult;
13

14
/// Implements the *Add Likely Subtags* and *Remove Likely Subtags*
15
/// algorithms as defined in *[UTS #35: Likely Subtags]*.
16
///
17
/// # Examples
18
///
19
/// Add likely subtags:
20
///
21
/// ```
22
/// use icu_locid::locale;
23
/// use icu_locid_transform::{LocaleExpander, TransformResult};
24
///
25
/// let lc = LocaleExpander::new();
26
///
27
/// let mut locale = locale!("zh-CN");
28
/// assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
29
/// assert_eq!(locale, locale!("zh-Hans-CN"));
30
///
31
/// let mut locale = locale!("zh-Hant-TW");
32
/// assert_eq!(lc.maximize(&mut locale), TransformResult::Unmodified);
33
/// assert_eq!(locale, locale!("zh-Hant-TW"));
34
/// ```
35
///
36
/// Remove likely subtags:
37
///
38
/// ```
39
/// use icu_locid::locale;
40
/// use icu_locid_transform::{LocaleExpander, TransformResult};
41
///
42
/// let lc = LocaleExpander::new();
43
///
44
/// let mut locale = locale!("zh-Hans-CN");
45
/// assert_eq!(lc.minimize(&mut locale), TransformResult::Modified);
46
/// assert_eq!(locale, locale!("zh"));
47
///
48
/// let mut locale = locale!("zh");
49
/// assert_eq!(lc.minimize(&mut locale), TransformResult::Unmodified);
50
/// assert_eq!(locale, locale!("zh"));
51
/// ```
52
///
53
/// Normally, only CLDR locales with Basic or higher coverage are included. To include more
54
/// locales for maximization, use [`try_new_extended`](Self::try_new_extended_unstable):
55
///
56
/// ```
57
/// use icu_locid::locale;
58
/// use icu_locid_transform::{LocaleExpander, TransformResult};
59
///
60
/// let lc = LocaleExpander::new_extended();
61
///
62
/// let mut locale = locale!("atj");
63
/// assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
64
/// assert_eq!(locale, locale!("atj-Latn-CA"));
65
/// ```
66
///
67
/// [UTS #35: Likely Subtags]: https://www.unicode.org/reports/tr35/#Likely_Subtags
68
#[derive(Debug, Clone)]
×
69
pub struct LocaleExpander {
70
    likely_subtags_l: DataPayload<LikelySubtagsForLanguageV1Marker>,
×
71
    likely_subtags_sr: DataPayload<LikelySubtagsForScriptRegionV1Marker>,
×
72
    likely_subtags_ext: Option<DataPayload<LikelySubtagsExtendedV1Marker>>,
×
73
}
74

75
struct LocaleExpanderBorrowed<'a> {
76
    likely_subtags_l: &'a LikelySubtagsForLanguageV1<'a>,
77
    likely_subtags_sr: &'a LikelySubtagsForScriptRegionV1<'a>,
78
    likely_subtags_ext: Option<&'a LikelySubtagsExtendedV1<'a>>,
79
}
80

81
impl LocaleExpanderBorrowed<'_> {
82
    fn get_l(&self, l: Language) -> Option<(Script, Region)> {
681✔
83
        let key = &l.into_tinystr().to_unvalidated();
681✔
84
        self.likely_subtags_l.language.get_copied(key).or_else(|| {
692✔
85
            self.likely_subtags_ext
22✔
86
                .and_then(|ext| ext.language.get_copied(key))
19✔
87
        })
11✔
88
    }
681✔
89

90
    fn get_ls(&self, l: Language, s: Script) -> Option<Region> {
324✔
91
        let key = &(
324✔
92
            l.into_tinystr().to_unvalidated(),
324✔
93
            s.into_tinystr().to_unvalidated(),
324✔
94
        );
95
        self.likely_subtags_l
648✔
96
            .language_script
97
            .get_copied(key)
98
            .or_else(|| {
639✔
99
                self.likely_subtags_ext
630✔
100
                    .and_then(|ext| ext.language_script.get_copied(key))
320✔
101
            })
315✔
102
    }
324✔
103

104
    fn get_lr(&self, l: Language, r: Region) -> Option<Script> {
181✔
105
        let key = &(
181✔
106
            l.into_tinystr().to_unvalidated(),
181✔
107
            r.into_tinystr().to_unvalidated(),
181✔
108
        );
109
        self.likely_subtags_l
362✔
110
            .language_region
111
            .get_copied(key)
112
            .or_else(|| {
355✔
113
                self.likely_subtags_ext
348✔
114
                    .and_then(|ext| ext.language_region.get_copied(key))
187✔
115
            })
174✔
116
    }
181✔
117

118
    fn get_s(&self, s: Script) -> Option<(Language, Region)> {
9✔
119
        let key = &s.into_tinystr().to_unvalidated();
9✔
120
        self.likely_subtags_sr.script.get_copied(key).or_else(|| {
9✔
121
            self.likely_subtags_ext
×
122
                .and_then(|ext| ext.script.get_copied(key))
×
123
        })
×
124
    }
9✔
125

126
    fn get_sr(&self, s: Script, r: Region) -> Option<Language> {
5✔
127
        let key = &(
5✔
128
            s.into_tinystr().to_unvalidated(),
5✔
129
            r.into_tinystr().to_unvalidated(),
5✔
130
        );
131
        self.likely_subtags_sr
10✔
132
            .script_region
133
            .get_copied(key)
134
            .or_else(|| {
9✔
135
                self.likely_subtags_ext
8✔
136
                    .and_then(|ext| ext.script_region.get_copied(key))
8✔
137
            })
4✔
138
    }
5✔
139

140
    fn get_r(&self, r: Region) -> Option<(Language, Script)> {
2✔
141
        let key = &r.into_tinystr().to_unvalidated();
2✔
142
        self.likely_subtags_sr.region.get_copied(key).or_else(|| {
2✔
143
            self.likely_subtags_ext
×
144
                .and_then(|ext| ext.region.get_copied(key))
×
145
        })
×
146
    }
2✔
147

148
    fn get_und(&self) -> (Language, Script, Region) {
15✔
149
        self.likely_subtags_l.und
15✔
150
    }
15✔
151
}
152

153
#[inline]
154
fn update_langid(
650✔
155
    language: Language,
156
    script: Option<Script>,
157
    region: Option<Region>,
158
    langid: &mut LanguageIdentifier,
159
) -> TransformResult {
160
    let mut modified = false;
650✔
161

162
    if langid.language.is_empty() && !language.is_empty() {
650✔
163
        langid.language = language;
8✔
164
        modified = true;
8✔
165
    }
166

167
    if langid.script.is_none() && script.is_some() {
650✔
168
        langid.script = script;
341✔
169
        modified = true;
341✔
170
    }
171

172
    if langid.region.is_none() && region.is_some() {
650✔
173
        langid.region = region;
495✔
174
        modified = true;
495✔
175
    }
176

177
    if modified {
650✔
178
        TransformResult::Modified
650✔
179
    } else {
180
        TransformResult::Unmodified
×
181
    }
182
}
650✔
183

184
impl LocaleExpander {
185
    /// Creates a [`LocaleExpander`] with compiled data for commonly-used locales
186
    /// (locales with *Basic* or higher [CLDR coverage]).
187
    ///
188
    /// Use this constructor if you want limited likely subtags for data-oriented use cases.
189
    ///
190
    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
191
    ///
192
    /// [📚 Help choosing a constructor](icu_provider::constructors)
193
    ///
194
    /// [CLDR coverage]: https://www.unicode.org/reports/tr35/tr35-info.html#Coverage_Levels
195
    #[cfg(feature = "compiled_data")]
196
    pub const fn new() -> Self {
10✔
197
        LocaleExpander {
10✔
198
            likely_subtags_l: DataPayload::from_static_ref(
10✔
199
                crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_L_V1,
200
            ),
201
            likely_subtags_sr: DataPayload::from_static_ref(
10✔
202
                crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_SR_V1,
203
            ),
204
            likely_subtags_ext: None,
10✔
205
        }
×
206
    }
10✔
207

208
    /// Creates a [`LocaleExpander`] with compiled data for all locales.
209
    ///
210
    /// Use this constructor if you want to include data for all locales, including ones
211
    /// that may not have data for other services (i.e. [CLDR coverage] below *Basic*).
212
    ///
213
    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
214
    ///
215
    /// [📚 Help choosing a constructor](icu_provider::constructors)
216
    ///
217
    /// [CLDR coverage]: https://www.unicode.org/reports/tr35/tr35-info.html#Coverage_Levels
218
    #[cfg(feature = "compiled_data")]
219
    pub const fn new_extended() -> Self {
8✔
220
        LocaleExpander {
8✔
221
            likely_subtags_l: DataPayload::from_static_ref(
8✔
222
                crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_L_V1,
223
            ),
224
            likely_subtags_sr: DataPayload::from_static_ref(
8✔
225
                crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_SR_V1,
226
            ),
227
            likely_subtags_ext: Some(DataPayload::from_static_ref(
8✔
228
                crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_EXT_V1,
229
            )),
230
        }
×
231
    }
8✔
232

233
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new_extended)]
234
    pub fn try_new_extended_unstable<P>(
235
        provider: &P,
236
    ) -> Result<LocaleExpander, LocaleTransformError>
237
    where
238
        P: DataProvider<LikelySubtagsForLanguageV1Marker>
239
            + DataProvider<LikelySubtagsForScriptRegionV1Marker>
240
            + DataProvider<LikelySubtagsExtendedV1Marker>
241
            + ?Sized,
242
    {
243
        let likely_subtags_l = provider.load(Default::default())?.take_payload()?;
244
        let likely_subtags_sr = provider.load(Default::default())?.take_payload()?;
245
        let likely_subtags_ext = Some(provider.load(Default::default())?.take_payload()?);
246

247
        Ok(LocaleExpander {
248
            likely_subtags_l,
249
            likely_subtags_sr,
250
            likely_subtags_ext,
251
        })
252
    }
253

254
    icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: LocaleTransformError,
255
        #[cfg(skip)]
256
        functions: [
257
        new_extended,
258
        try_new_extended_with_any_provider,
259
        try_new_extended_with_buffer_provider,
260
        try_new_extended_unstable,
261
        Self
262
    ]);
263

264
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(ANY, Self::new)]
265
    pub fn try_new_with_any_provider(
7✔
266
        provider: &(impl AnyProvider + ?Sized),
267
    ) -> Result<LocaleExpander, LocaleTransformError> {
268
        Self::try_new_compat(&provider.as_downcasting())
7✔
269
    }
7✔
270

271
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(BUFFER, Self::new)]
272
    #[cfg(feature = "serde")]
273
    pub fn try_new_with_buffer_provider(
×
274
        provider: &(impl BufferProvider + ?Sized),
275
    ) -> Result<LocaleExpander, LocaleTransformError> {
276
        Self::try_new_compat(&provider.as_deserializing())
×
277
    }
×
278

279
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
280
    pub fn try_new_unstable<P>(provider: &P) -> Result<LocaleExpander, LocaleTransformError>
281
    where
282
        P: DataProvider<LikelySubtagsForLanguageV1Marker>
283
            + DataProvider<LikelySubtagsForScriptRegionV1Marker>
284
            + ?Sized,
285
    {
286
        let likely_subtags_l = provider.load(Default::default())?.take_payload()?;
287
        let likely_subtags_sr = provider.load(Default::default())?.take_payload()?;
288

289
        Ok(LocaleExpander {
290
            likely_subtags_l,
291
            likely_subtags_sr,
292
            likely_subtags_ext: None,
293
        })
294
    }
295

296
    fn try_new_compat<P>(provider: &P) -> Result<LocaleExpander, LocaleTransformError>
7✔
297
    where
298
        P: DataProvider<LikelySubtagsForLanguageV1Marker>
299
            + DataProvider<LikelySubtagsForScriptRegionV1Marker>
300
            + DataProvider<LikelySubtagsExtendedV1Marker>
301
            + DataProvider<LikelySubtagsV1Marker>
302
            + ?Sized,
303
    {
304
        let payload_l = provider
14✔
305
            .load(Default::default())
7✔
306
            .and_then(DataResponse::take_payload);
7✔
307
        let payload_sr = provider
7✔
308
            .load(Default::default())
7✔
309
            .and_then(DataResponse::take_payload);
7✔
310
        let payload_ext = provider
7✔
311
            .load(Default::default())
7✔
312
            .and_then(DataResponse::take_payload);
313

314
        let (likely_subtags_l, likely_subtags_sr, likely_subtags_ext) =
6✔
315
            match (payload_l, payload_sr, payload_ext) {
7✔
316
                (Ok(l), Ok(sr), Err(_)) => (l, sr, None),
3✔
317
                (Ok(l), Ok(sr), Ok(ext)) => (l, sr, Some(ext)),
1✔
318
                _ => {
319
                    let result: DataPayload<LikelySubtagsV1Marker> =
320
                        provider.load(Default::default())?.take_payload()?;
3✔
321
                    (
2✔
322
                        result.map_project_cloned(|st, _| {
4✔
323
                            LikelySubtagsForLanguageV1::clone_from_borrowed(st)
2✔
324
                        }),
2✔
325
                        result.map_project(|st, _| st.into()),
4✔
326
                        None,
2✔
327
                    )
×
328
                }
3✔
329
            };
7✔
330

331
        Ok(LocaleExpander {
6✔
332
            likely_subtags_l,
6✔
333
            likely_subtags_sr,
6✔
334
            likely_subtags_ext,
6✔
335
        })
336
    }
7✔
337

338
    fn as_borrowed(&self) -> LocaleExpanderBorrowed {
721✔
339
        LocaleExpanderBorrowed {
721✔
340
            likely_subtags_l: self.likely_subtags_l.get(),
721✔
341
            likely_subtags_sr: self.likely_subtags_sr.get(),
721✔
342
            likely_subtags_ext: self.likely_subtags_ext.as_ref().map(|p| p.get()),
800✔
343
        }
344
    }
721✔
345

346
    /// The maximize method potentially updates a passed in locale in place
347
    /// depending up the results of running the 'Add Likely Subtags' algorithm
348
    /// from <https://www.unicode.org/reports/tr35/#Likely_Subtags>.
349
    ///
350
    /// If the result of running the algorithm would result in a new locale, the
351
    /// locale argument is updated in place to match the result, and the method
352
    /// returns [`TransformResult::Modified`]. Otherwise, the method
353
    /// returns [`TransformResult::Unmodified`] and the locale argument is
354
    /// unchanged.
355
    ///
356
    /// # Examples
357
    ///
358
    /// ```
359
    /// use icu_locid::locale;
360
    /// use icu_locid_transform::{LocaleExpander, TransformResult};
361
    ///
362
    /// let lc = LocaleExpander::new();
363
    ///
364
    /// let mut locale = locale!("zh-CN");
365
    /// assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
366
    /// assert_eq!(locale, locale!("zh-Hans-CN"));
367
    ///
368
    /// let mut locale = locale!("zh-Hant-TW");
369
    /// assert_eq!(lc.maximize(&mut locale), TransformResult::Unmodified);
370
    /// assert_eq!(locale, locale!("zh-Hant-TW"));
371
    /// ```
372
    pub fn maximize<T: AsMut<LanguageIdentifier>>(&self, mut langid: T) -> TransformResult {
704✔
373
        let langid = langid.as_mut();
704✔
374
        let data = self.as_borrowed();
664✔
375

376
        if !langid.language.is_empty() && langid.script.is_some() && langid.region.is_some() {
662✔
377
            return TransformResult::Unmodified;
5✔
378
        }
379

380
        if !langid.language.is_empty() {
661✔
381
            if let Some(region) = langid.region {
656✔
382
                if let Some(script) = data.get_lr(langid.language, region) {
163✔
383
                    return update_langid(Language::UND, Some(script), None, langid);
2✔
384
                }
385
            }
386
            if let Some(script) = langid.script {
654✔
387
                if let Some(region) = data.get_ls(langid.language, script) {
314✔
388
                    return update_langid(Language::UND, None, Some(region), langid);
1✔
389
                }
390
            }
391
            if let Some((script, region)) = data.get_l(langid.language) {
649✔
392
                return update_langid(Language::UND, Some(script), Some(region), langid);
657✔
393
            }
394
        }
395
        if let Some(script) = langid.script {
8✔
396
            if let Some(region) = langid.region {
5✔
397
                if let Some(language) = data.get_sr(script, region) {
×
398
                    return update_langid(language, None, None, langid);
×
399
                }
400
            }
401
            if let Some((language, region)) = data.get_s(script) {
5✔
402
                return update_langid(language, None, Some(region), langid);
5✔
403
            }
404
        }
405
        if let Some(region) = langid.region {
3✔
406
            if let Some((language, script)) = data.get_r(region) {
×
407
                return update_langid(language, Some(script), None, langid);
×
408
            }
409
        }
410

411
        update_langid(
4✔
412
            data.get_und().0,
3✔
413
            Some(data.get_und().1),
4✔
414
            Some(data.get_und().2),
4✔
415
            langid,
416
        )
417
    }
658✔
418

419
    /// This returns a new Locale that is the result of running the
420
    /// 'Remove Likely Subtags' algorithm from
421
    /// <https://www.unicode.org/reports/tr35/#Likely_Subtags>.
422
    ///
423
    /// If the result of running the algorithm would result in a new locale, the
424
    /// locale argument is updated in place to match the result, and the method
425
    /// returns [`TransformResult::Modified`]. Otherwise, the method
426
    /// returns [`TransformResult::Unmodified`] and the locale argument is
427
    /// unchanged.
428
    ///
429
    /// # Examples
430
    ///
431
    /// ```
432
    /// use icu_locid::locale;
433
    /// use icu_locid_transform::{LocaleExpander, TransformResult};
434
    ///
435
    /// let lc = LocaleExpander::new();
436
    ///
437
    /// let mut locale = locale!("zh-Hans-CN");
438
    /// assert_eq!(lc.minimize(&mut locale), TransformResult::Modified);
439
    /// assert_eq!(locale, locale!("zh"));
440
    ///
441
    /// let mut locale = locale!("zh");
442
    /// assert_eq!(lc.minimize(&mut locale), TransformResult::Unmodified);
443
    /// assert_eq!(locale, locale!("zh"));
444
    /// ```
445
    pub fn minimize<T: AsMut<LanguageIdentifier>>(&self, mut langid: T) -> TransformResult {
193✔
446
        let langid = langid.as_mut();
193✔
447

448
        let mut max = langid.clone();
156✔
449
        self.maximize(&mut max);
157✔
450
        let variants = mem::take(&mut max.variants);
154✔
451
        max.variants.clear();
186✔
452
        let mut trial = max.clone();
199✔
453

454
        trial.script = None;
175✔
455
        trial.region = None;
175✔
456
        self.maximize(&mut trial);
175✔
457
        if trial == max {
154✔
458
            if langid.language != max.language || langid.script.is_some() || langid.region.is_some()
×
459
            {
460
                if langid.language != max.language {
×
461
                    langid.language = max.language
×
462
                }
463
                if langid.script.is_some() {
×
464
                    langid.script = None;
×
465
                }
466
                if langid.region.is_some() {
×
467
                    langid.region = None;
×
468
                }
469
                langid.variants = variants;
×
470
                return TransformResult::Modified;
×
471
            } else {
472
                return TransformResult::Unmodified;
×
473
            }
474
        }
475

476
        trial.script = None;
156✔
477
        trial.region = max.region;
156✔
478
        self.maximize(&mut trial);
156✔
479
        if trial == max {
155✔
480
            if langid.language != max.language
×
481
                || langid.script.is_some()
×
482
                || langid.region != max.region
×
483
            {
484
                if langid.language != max.language {
×
485
                    langid.language = max.language
×
486
                }
487
                if langid.script.is_some() {
×
488
                    langid.script = None;
×
489
                }
490
                if langid.region != max.region {
×
491
                    langid.region = max.region;
×
492
                }
493
                langid.variants = variants;
×
494
                return TransformResult::Modified;
×
495
            } else {
496
                return TransformResult::Unmodified;
×
497
            }
498
        }
499

500
        trial.script = max.script;
155✔
501
        trial.region = None;
155✔
502
        self.maximize(&mut trial);
155✔
503
        if trial == max {
154✔
504
            if langid.language != max.language
459✔
505
                || langid.script != max.script
156✔
506
                || langid.region.is_some()
157✔
507
            {
508
                if langid.language != max.language {
×
509
                    langid.language = max.language
×
510
                }
511
                if langid.script != max.script {
×
512
                    langid.script = max.script;
×
513
                }
514
                if langid.region.is_some() {
×
515
                    langid.region = None;
×
516
                }
517
                langid.variants = variants;
×
518
                return TransformResult::Modified;
×
519
            } else {
520
                return TransformResult::Unmodified;
155✔
521
            }
522
        }
523

524
        if langid.language != max.language
×
525
            || langid.script != max.script
×
526
            || langid.region != max.region
×
527
        {
528
            if langid.language != max.language {
×
529
                langid.language = max.language
×
530
            }
531
            if langid.script != max.script {
×
532
                langid.script = max.script;
×
533
            }
534
            if langid.region != max.region {
×
535
                langid.region = max.region;
×
536
            }
537
            TransformResult::Modified
×
538
        } else {
539
            TransformResult::Unmodified
×
540
        }
541
    }
155✔
542

543
    // TODO(3492): consider turning this and a future get_likely_region/get_likely_language public
544
    #[inline]
545
    pub(crate) fn get_likely_script<T: AsRef<LanguageIdentifier>>(
×
546
        &self,
547
        langid: T,
548
    ) -> Option<Script> {
549
        let langid = langid.as_ref();
×
550
        langid
×
551
            .script
552
            .or_else(|| self.infer_likely_script(langid.language, langid.region))
×
553
    }
×
554

555
    fn infer_likely_script(&self, language: Language, region: Option<Region>) -> Option<Script> {
6✔
556
        let data = self.as_borrowed();
6✔
557

558
        // proceed through _all possible cases_ in order of specificity
559
        // (borrowed from LocaleExpander::maximize):
560
        // 1. language + region
561
        // 2. language
562
        // 3. region
563
        // we need to check all cases, because e.g. for "en-US" the default script is associated
564
        // with "en" but not "en-US"
565
        if language != Language::UND {
6✔
566
            if let Some(region) = region {
6✔
567
                // 1. we know both language and region
568
                if let Some(script) = data.get_lr(language, region) {
1✔
569
                    return Some(script);
×
570
                }
571
            }
572
            // 2. we know language, but we either do not know region or knowing region did not help
573
            if let Some((script, _)) = data.get_l(language) {
6✔
574
                return Some(script);
4✔
575
            }
576
        }
577
        if let Some(region) = region {
2✔
578
            // 3. we know region, but we either do not know language or knowing language did not help
579
            if let Some((_, script)) = data.get_r(region) {
×
580
                return Some(script);
×
581
            }
582
        }
583
        // we could not figure out the script from the given locale
584
        None
2✔
585
    }
6✔
586
}
587

588
#[cfg(feature = "serde")]
589
#[cfg(test)]
590
mod tests {
591
    use super::*;
592
    use icu_locid::locale;
593

594
    struct RejectByKeyProvider {
595
        keys: Vec<DataKey>,
596
    }
597

598
    impl AnyProvider for RejectByKeyProvider {
599
        fn load_any(&self, key: DataKey, _: DataRequest) -> Result<AnyResponse, DataError> {
18✔
600
            if self.keys.contains(&key) {
18✔
601
                return Err(DataErrorKind::MissingDataKey.with_str_context("rejected"));
8✔
602
            }
603

604
            let l = crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_L_V1;
10✔
605
            let ext = crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_EXT_V1;
10✔
606
            let sr = crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_SR_V1;
10✔
607

608
            let payload = if key.hashed() == LikelySubtagsV1Marker::KEY.hashed() {
10✔
609
                DataPayload::<LikelySubtagsV1Marker>::from_owned(LikelySubtagsV1 {
2✔
610
                    language_script: l
4✔
611
                        .language_script
612
                        .iter_copied()
613
                        .chain(ext.language_script.iter_copied())
2✔
614
                        .collect(),
615
                    language_region: l
4✔
616
                        .language_region
617
                        .iter_copied()
618
                        .chain(ext.language_region.iter_copied())
2✔
619
                        .collect(),
620
                    language: l
4✔
621
                        .language
622
                        .iter_copied()
623
                        .chain(ext.language.iter_copied())
2✔
624
                        .collect(),
625
                    script_region: ext.script_region.clone(),
2✔
626
                    script: ext.script.clone(),
2✔
627
                    region: ext.region.clone(),
2✔
628
                    und: l.und,
2✔
629
                })
×
630
                .wrap_into_any_payload()
631
            } else if key.hashed() == LikelySubtagsForLanguageV1Marker::KEY.hashed() {
8✔
632
                DataPayload::<LikelySubtagsForLanguageV1Marker>::from_static_ref(l)
3✔
633
                    .wrap_into_any_payload()
634
            } else if key.hashed() == LikelySubtagsExtendedV1Marker::KEY.hashed() {
5✔
635
                DataPayload::<LikelySubtagsExtendedV1Marker>::from_static_ref(ext)
3✔
636
                    .wrap_into_any_payload()
637
            } else if key.hashed() == LikelySubtagsForScriptRegionV1Marker::KEY.hashed() {
2✔
638
                DataPayload::<LikelySubtagsForScriptRegionV1Marker>::from_static_ref(sr)
2✔
639
                    .wrap_into_any_payload()
640
            } else {
641
                return Err(DataErrorKind::MissingDataKey.into_error());
×
642
            };
643

644
            Ok(AnyResponse {
10✔
645
                payload: Some(payload),
10✔
646
                metadata: Default::default(),
10✔
647
            })
×
648
        }
18✔
649
    }
650

651
    #[test]
652
    fn test_old_keys() {
2✔
653
        let provider = RejectByKeyProvider {
1✔
654
            keys: vec![
1✔
655
                LikelySubtagsForLanguageV1Marker::KEY,
656
                LikelySubtagsForScriptRegionV1Marker::KEY,
657
                LikelySubtagsExtendedV1Marker::KEY,
658
            ],
659
        };
660
        let lc = LocaleExpander::try_new_with_any_provider(&provider)
1✔
661
            .expect("should create with old keys");
662
        let mut locale = locale!("zh-CN");
1✔
663
        assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
1✔
664
        assert_eq!(locale, locale!("zh-Hans-CN"));
1✔
665
    }
2✔
666

667
    #[test]
668
    fn test_new_keys() {
2✔
669
        let provider = RejectByKeyProvider {
1✔
670
            keys: vec![LikelySubtagsV1Marker::KEY],
1✔
671
        };
672
        let lc = LocaleExpander::try_new_with_any_provider(&provider)
1✔
673
            .expect("should create with new keys");
674
        let mut locale = locale!("zh-CN");
1✔
675
        assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
1✔
676
        assert_eq!(locale, locale!("zh-Hans-CN"));
1✔
677
    }
2✔
678

679
    #[test]
680
    fn test_mixed_keys() {
2✔
681
        // Include the old key and one of the new keys but not both new keys.
682
        // Not sure if this is a useful test.
683
        let provider = RejectByKeyProvider {
1✔
684
            keys: vec![LikelySubtagsForScriptRegionV1Marker::KEY],
1✔
685
        };
686
        let lc = LocaleExpander::try_new_with_any_provider(&provider)
1✔
687
            .expect("should create with mixed keys");
688
        let mut locale = locale!("zh-CN");
1✔
689
        assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
1✔
690
        assert_eq!(locale, locale!("zh-Hans-CN"));
1✔
691
    }
2✔
692

693
    #[test]
694
    fn test_no_keys() {
2✔
695
        let provider = RejectByKeyProvider {
1✔
696
            keys: vec![
1✔
697
                LikelySubtagsForLanguageV1Marker::KEY,
698
                LikelySubtagsForScriptRegionV1Marker::KEY,
699
                LikelySubtagsV1Marker::KEY,
700
            ],
701
        };
702
        if LocaleExpander::try_new_with_any_provider(&provider).is_ok() {
1✔
703
            panic!("should not create: no data present")
×
704
        };
705
    }
2✔
706

707
    #[test]
708
    fn test_new_small_keys() {
2✔
709
        // Include the new small keys but not the extended key
710
        let provider = RejectByKeyProvider {
1✔
711
            keys: vec![
1✔
712
                LikelySubtagsExtendedV1Marker::KEY,
713
                LikelySubtagsV1Marker::KEY,
714
            ],
715
        };
716
        let lc = LocaleExpander::try_new_with_any_provider(&provider)
1✔
717
            .expect("should create with mixed keys");
718
        let mut locale = locale!("zh-CN");
1✔
719
        assert_eq!(lc.maximize(&mut locale), TransformResult::Modified);
1✔
720
        assert_eq!(locale, locale!("zh-Hans-CN"));
1✔
721
    }
2✔
722
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc