Skip to main content

icu_collator/
comparison.rs

1// This file is part of ICU4X. For terms of use, please see the file
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// Various collation-related algorithms and constants in this file are
6// adapted from ICU4C and, therefore, are subject to the ICU license as
7// described in LICENSE.
8
9//! This module holds the `Collator` struct whose `compare_impl()` contains
10//! the comparison of collation element sequences.
11
12use alloc::collections::VecDeque;
13use alloc::vec::Vec;
14
15use crate::elements::CharacterAndClassAndTrieValue;
16use crate::elements::CollationElement32;
17use crate::elements::Tag;
18use crate::elements::BACKWARD_COMBINING_MARKER;
19use crate::elements::CE_BUFFER_SIZE;
20use crate::elements::FALLBACK_CE32;
21use crate::elements::HANGUL_N_COUNT;
22use crate::elements::HANGUL_S_BASE;
23use crate::elements::HANGUL_S_COUNT;
24use crate::elements::HANGUL_T_COUNT;
25use crate::elements::IDENTICAL_PREFIX_HANGUL_MARKER_CE32;
26use crate::elements::NON_ROUND_TRIP_MARKER;
27use crate::elements::{
28    char_from_u32, CollationElement, CollationElements, NonPrimary, FFFD_CE32,
29    HANGUL_SYLLABLE_MARKER, HIGH_ZEROS_MASK, JAMO_COUNT, LOW_ZEROS_MASK, NO_CE, NO_CE_PRIMARY,
30    NO_CE_QUATERNARY, NO_CE_SECONDARY, NO_CE_TERTIARY, OPTIMIZED_DIACRITICS_MAX_COUNT,
31    QUATERNARY_MASK,
32};
33use crate::options::CollatorOptionsBitField;
34use crate::options::{
35    AlternateHandling, CollatorOptions, MaxVariable, ResolvedCollatorOptions, Strength,
36};
37use crate::preferences::{CollationCaseFirst, CollationNumericOrdering, CollationType};
38use crate::provider::CollationData;
39use crate::provider::CollationDiacritics;
40use crate::provider::CollationDiacriticsV1;
41use crate::provider::CollationJamo;
42use crate::provider::CollationJamoV1;
43use crate::provider::CollationMetadataV1;
44use crate::provider::CollationReordering;
45use crate::provider::CollationReorderingV1;
46use crate::provider::CollationRootV1;
47use crate::provider::CollationSpecialPrimariesV1;
48use crate::provider::CollationSpecialPrimariesValidated;
49use crate::provider::CollationTailoringV1;
50use core::cmp::Ordering;
51use core::convert::{Infallible, TryFrom};
52use icu_collections::codepointtrie::AbstractCodePointTrie;
53use icu_collections::codepointtrie::CharsWithTrieDefaultForAsciiEx;
54use icu_collections::codepointtrie::CharsWithTrieEx;
55#[cfg(feature = "serde")]
56use icu_collections::codepointtrie::CodePointTrie;
57#[cfg(not(feature = "serde"))]
58use icu_collections::codepointtrie::FastCodePointTrie;
59#[cfg(feature = "latin1")]
60use icu_collections::codepointtrie::Latin1CharsWithTrieEx;
61use icu_collections::codepointtrie::TypedCodePointTrie;
62use icu_collections::codepointtrie::WithTrie;
63use icu_normalizer::provider::DecompositionData;
64use icu_normalizer::provider::DecompositionTables;
65use icu_normalizer::provider::NormalizerNfdDataV1;
66use icu_normalizer::provider::NormalizerNfdTablesV1;
67use icu_provider::marker::ErasedMarker;
68use icu_provider::prelude::*;
69use smallvec::SmallVec;
70use utf16_iter::Utf16CharsWithTrieEx;
71use utf8_iter::Utf8CharsWithTrieDefaultForAsciiEx;
72use utf8_iter::Utf8CharsWithTrieEx;
73use zerovec::ule::AsULE;
74
75#[cfg(feature = "serde")]
76type NormTrie<'trie> = CodePointTrie<'trie, u32>;
77
78#[cfg(not(feature = "serde"))]
79type NormTrie<'trie> = FastCodePointTrie<'trie, u32>;
80
81// Special sort key bytes for all levels.
82const LEVEL_SEPARATOR_BYTE: u8 = 1;
83
84/// Merge-sort-key separator.
85///
86/// Same as the unique primary and identical-level weights of U+FFFE.  Must not
87/// be used as primary compression low terminator.  Otherwise usable.
88const MERGE_SEPARATOR: char = '\u{fffe}';
89const MERGE_SEPARATOR_BYTE: u8 = 2;
90const MERGE_SEPARATOR_PRIMARY: u32 = 0x02000000;
91
92/// Primary compression low terminator, must be greater than [`MERGE_SEPARATOR_BYTE`].
93///
94/// Reserved value in primary second byte if the lead byte is compressible.
95/// Otherwise usable in all CE weight bytes.
96const PRIMARY_COMPRESSION_LOW_BYTE: u8 = 3;
97
98/// Primary compression high terminator.
99///
100/// Reserved value in primary second byte if the lead byte is compressible.
101/// Otherwise usable in all CE weight bytes.
102const PRIMARY_COMPRESSION_HIGH_BYTE: u8 = 0xff;
103
104/// Default secondary/tertiary weight lead byte.
105const COMMON_BYTE: u8 = 5;
106const COMMON_WEIGHT16: u16 = 0x0500;
107
108// Internal flags for sort key generation
109const PRIMARY_LEVEL_FLAG: u8 = 0x01;
110const SECONDARY_LEVEL_FLAG: u8 = 0x02;
111const CASE_LEVEL_FLAG: u8 = 0x04;
112const TERTIARY_LEVEL_FLAG: u8 = 0x08;
113const QUATERNARY_LEVEL_FLAG: u8 = 0x10;
114
115const LEVEL_MASKS: [u8; Strength::Identical as usize + 1] = [
116    PRIMARY_LEVEL_FLAG,
117    PRIMARY_LEVEL_FLAG | SECONDARY_LEVEL_FLAG,
118    PRIMARY_LEVEL_FLAG | SECONDARY_LEVEL_FLAG | TERTIARY_LEVEL_FLAG,
119    PRIMARY_LEVEL_FLAG | SECONDARY_LEVEL_FLAG | TERTIARY_LEVEL_FLAG | QUATERNARY_LEVEL_FLAG,
120    0,
121    0,
122    0,
123    PRIMARY_LEVEL_FLAG | SECONDARY_LEVEL_FLAG | TERTIARY_LEVEL_FLAG | QUATERNARY_LEVEL_FLAG,
124];
125
126// Internal constants for indexing into the below compression configurations
127const WEIGHT_LOW: usize = 0;
128const WEIGHT_MIDDLE: usize = 1;
129const WEIGHT_HIGH: usize = 2;
130const WEIGHT_MAX_COUNT: usize = 3;
131
132// Secondary level: Compress up to 33 common weights as 05..25 or 25..45.
133const SEC_COMMON: [u8; 4] = [COMMON_BYTE, COMMON_BYTE + 0x20, COMMON_BYTE + 0x40, 0x21];
134
135// Case level, lowerFirst: Compress up to 7 common weights as 1..7 or 7..13.
136const CASE_LOWER_FIRST_COMMON: [u8; 4] = [1, 7, 13, 7];
137
138// Case level, upperFirst: Compress up to 13 common weights as 3..15.
139const CASE_UPPER_FIRST_COMMON: [u8; 4] = [3, 0 /* unused */, 15, 13];
140
141// Tertiary level only (no case): Compress up to 97 common weights as 05..65 or 65..C5.
142const TER_ONLY_COMMON: [u8; 4] = [COMMON_BYTE, COMMON_BYTE + 0x60, COMMON_BYTE + 0xc0, 0x61];
143
144// Tertiary with case, lowerFirst: Compress up to 33 common weights as 05..25 or 25..45.
145const TER_LOWER_FIRST_COMMON: [u8; 4] = [COMMON_BYTE, COMMON_BYTE + 0x20, COMMON_BYTE + 0x40, 0x21];
146
147// Tertiary with case, upperFirst: Compress up to 33 common weights as 85..A5 or A5..C5.
148const TER_UPPER_FIRST_COMMON: [u8; 4] = [
149    COMMON_BYTE + 0x80,
150    COMMON_BYTE + 0x80 + 0x20,
151    COMMON_BYTE + 0x80 + 0x40,
152    0x21,
153];
154
155const QUAT_COMMON: [u8; 4] = [0x1c, 0x1c + 0x70, 0x1c + 0xe0, 0x71];
156const QUAT_SHIFTED_LIMIT_BYTE: u8 = QUAT_COMMON[WEIGHT_LOW] - 1; // 0x1b
157
158// Do not use byte values 0, 1, 2 because they are separators in sort keys.
159const SLOPE_MIN: i32 = 3;
160const SLOPE_MAX: i32 = 0xff;
161const SLOPE_MIDDLE: i32 = 0x81;
162const SLOPE_TAIL_COUNT: i32 = SLOPE_MAX - SLOPE_MIN + 1;
163const SLOPE_SINGLE: i32 = 80;
164const SLOPE_LEAD_2: i32 = 42;
165const SLOPE_LEAD_3: i32 = 3;
166
167// The difference value range for single-byters.
168const SLOPE_REACH_POS_1: i32 = SLOPE_SINGLE;
169const SLOPE_REACH_NEG_1: i32 = -SLOPE_SINGLE;
170
171// The difference value range for double-byters.
172const SLOPE_REACH_POS_2: i32 = SLOPE_LEAD_2 * SLOPE_TAIL_COUNT + (SLOPE_LEAD_2 - 1);
173const SLOPE_REACH_NEG_2: i32 = -SLOPE_REACH_POS_2 - 1;
174
175// The difference value range for 3-byters.
176const SLOPE_REACH_POS_3: i32 = SLOPE_LEAD_3 * SLOPE_TAIL_COUNT * SLOPE_TAIL_COUNT
177    + (SLOPE_LEAD_3 - 1) * SLOPE_TAIL_COUNT
178    + (SLOPE_TAIL_COUNT - 1);
179const SLOPE_REACH_NEG_3: i32 = -SLOPE_REACH_POS_3 - 1;
180
181// The lead byte start values.
182const SLOPE_START_POS_2: i32 = SLOPE_MIDDLE + SLOPE_SINGLE + 1;
183const SLOPE_START_POS_3: i32 = SLOPE_START_POS_2 + SLOPE_LEAD_2;
184const SLOPE_START_NEG_2: i32 = SLOPE_MIDDLE + SLOPE_REACH_NEG_1;
185const SLOPE_START_NEG_3: i32 = SLOPE_START_NEG_2 - SLOPE_LEAD_2;
186
187struct AnyQuaternaryAccumulator(u32);
188
189impl AnyQuaternaryAccumulator {
190    #[inline(always)]
191    pub fn new() -> Self {
192        AnyQuaternaryAccumulator(0)
193    }
194    #[inline(always)]
195    pub fn accumulate(&mut self, non_primary: NonPrimary) {
196        self.0 |= non_primary.bits()
197    }
198    #[inline(always)]
199    pub fn has_quaternary(&self) -> bool {
200        self.0 & u32::from(QUATERNARY_MASK) != 0
201    }
202}
203
204/// `true` iff `i` is greater or equal to `start` and less or equal
205/// to `end`.
206#[inline(always)]
207fn in_inclusive_range16(i: u16, start: u16, end: u16) -> bool {
208    i.wrapping_sub(start) <= (end - start)
209}
210
211/// Finds the identical prefix of `left` and `right` containing
212/// Latin1.
213///
214/// Returns the identical prefix, the part of `left` after the
215/// prefix, and the part of `right` after the prefix.
216///
217/// ✨ *Enabled with the `latin1` Cargo feature.*
218#[cfg(feature = "latin1")]
219fn split_prefix_latin1<'a, 'b>(left: &'a [u8], right: &'b [u8]) -> (&'a [u8], &'a [u8], &'b [u8]) {
220    let i = left
221        .iter()
222        .zip(right.iter())
223        .take_while(|(l, r)| l == r)
224        .count();
225    if let Some((head, left_tail)) = left.split_at_checked(i) {
226        if let Some(right_tail) = right.get(i..) {
227            return (head, left_tail, right_tail);
228        }
229    }
230    (&[], left, right)
231}
232
233/// Finds the identical prefix of `left` containing Latin1
234/// and `right` containing potentially ill-formed UTF-16.
235///
236/// Returns the identical prefix, the part of `left` after the
237/// prefix, and the part of `right` after the prefix.
238///
239/// ✨ *Enabled with the `latin1` Cargo feature.*
240#[cfg(feature = "latin1")]
241fn split_prefix_latin1_utf16<'a, 'b>(
242    left: &'a [u8],
243    right: &'b [u16],
244) -> (&'a [u8], &'a [u8], &'b [u16]) {
245    let i = left
246        .iter()
247        .zip(right.iter())
248        .take_while(|(l, r)| u16::from(**l) == **r)
249        .count();
250    if let Some((head, left_tail)) = left.split_at_checked(i) {
251        if let Some(right_tail) = right.get(i..) {
252            return (head, left_tail, right_tail);
253        }
254    }
255    (&[], left, right)
256}
257
258/// Finds the identical prefix of `left` and `right` containing
259/// potentially ill-formed UTF-16, while avoiding splitting a
260/// well-formed surrogate pair. In case of ill-formed
261/// UTF-16, the prefix is not guaranteed to be maximal.
262///
263/// Returns the identical prefix, the part of `left` after the
264/// prefix, and the part of `right` after the prefix.
265fn split_prefix_u16<'a, 'b>(
266    left: &'a [u16],
267    right: &'b [u16],
268) -> (&'a [u16], &'a [u16], &'b [u16]) {
269    let mut i = left
270        .iter()
271        .zip(right.iter())
272        .take_while(|(l, r)| l == r)
273        .count();
274    if i != 0 {
275        if let Some(&last) = left.get(i.wrapping_sub(1)) {
276            if in_inclusive_range16(last, 0xD800, 0xDBFF) {
277                i -= 1;
278            }
279            if let Some((head, left_tail)) = left.split_at_checked(i) {
280                if let Some(right_tail) = right.get(i..) {
281                    return (head, left_tail, right_tail);
282                }
283            }
284        }
285    }
286    (&[], left, right)
287}
288
289/// Finds the identical prefix of `left` and `right` containing
290/// potentially ill-formed UTF-8, while avoiding splitting a UTF-8
291/// byte sequence. In case of ill-formed UTF-8, the prefix is
292/// not guaranteed to be maximal.
293///
294/// Returns the identical prefix, the part of `left` after the
295/// prefix, and the part of `right` after the prefix.
296fn split_prefix_u8<'a, 'b>(left: &'a [u8], right: &'b [u8]) -> (&'a [u8], &'a [u8], &'b [u8]) {
297    let mut i = left
298        .iter()
299        .zip(right.iter())
300        .take_while(|(l, r)| l == r)
301        .count();
302    if i != 0 {
303        // Tails must not start with a UTF-8 continuation
304        // byte unless it's the first byte of the original
305        // slice.
306
307        // First, left and right differ, but since they
308        // are the same afterwards, one of them needs checking
309        // only once.
310        if let Some(right_first) = right.get(i) {
311            if (right_first & 0b1100_0000) == 0b1000_0000 {
312                i -= 1;
313            }
314        }
315        while i != 0 {
316            if let Some(left_first) = left.get(i) {
317                if (left_first & 0b1100_0000) == 0b1000_0000 {
318                    i -= 1;
319                    continue;
320                }
321            }
322            break;
323        }
324        if let Some((head, left_tail)) = left.split_at_checked(i) {
325            if let Some(right_tail) = right.get(i..) {
326                return (head, left_tail, right_tail);
327            }
328        }
329    }
330    (&[], left, right)
331}
332
333/// Finds the identical prefix of `left` and `right` containing
334/// guaranteed well-format UTF-8.
335///
336/// Returns the identical prefix, the part of `left` after the
337/// prefix, and the part of `right` after the prefix.
338fn split_prefix<'a, 'b>(left: &'a str, right: &'b str) -> (&'a str, &'a str, &'b str) {
339    let left_bytes = left.as_bytes();
340    let right_bytes = right.as_bytes();
341    let mut i = left_bytes
342        .iter()
343        .zip(right_bytes.iter())
344        .take_while(|(l, r)| l == r)
345        .count();
346    if i != 0 {
347        // Tails must not start with a UTF-8 continuation
348        // byte.
349
350        // Since the inputs are valid UTF-8, the first byte
351        // of either input slice cannot be a contination slice,
352        // so we may rely on finding a lead byte when walking
353        // backwards.
354
355        // Since the inputs are valid UTF-8, if a tail starts
356        // with a continuation, both tails must start with a
357        // continuation, since the most recent lead byte must
358        // be equal, so the difference is within valid UTF-8
359        // sequences of equal length.
360
361        // Therefore, it's sufficient to examine only one of
362        // the sides.
363        loop {
364            if let Some(left_first) = left_bytes.get(i) {
365                if (left_first & 0b1100_0000) == 0b1000_0000 {
366                    i -= 1;
367                    continue;
368                }
369            }
370            break;
371        }
372        // The methods below perform useless UTF-8 boundary checks,
373        // since we just checked. However, avoiding `unsafe` to
374        // make this code easier to audit.
375        if let Some((head, left_tail)) = left.split_at_checked(i) {
376            if let Some(right_tail) = right.get(i..) {
377                return (head, left_tail, right_tail);
378            }
379        }
380    }
381    ("", left, right)
382}
383
384/// Holder struct for payloads that are locale-dependent. (For code
385/// reuse between owned and borrowed cases.)
386#[derive(Debug)]
387struct LocaleSpecificDataHolder {
388    tailoring: Option<DataPayload<CollationTailoringV1>>,
389    diacritics: DataPayload<CollationDiacriticsV1>,
390    reordering: Option<DataPayload<CollationReorderingV1>>,
391    merged_options: CollatorOptionsBitField,
392    lithuanian_dot_above: bool,
393}
394
395icu_locale_core::preferences::define_preferences!(
396    /// The preferences for collation.
397    ///
398    /// # Preferences
399    ///
400    /// Examples for using the different preferences below can be found in the [crate-level docs](crate).
401    ///
402    /// ## Case First
403    ///
404    /// See the [spec](https://www.unicode.org/reports/tr35/tr35-collation.html#Case_Parameters).
405    /// This is the BCP47 key `kf`. Three possibilities: [`CollationCaseFirst::False`] (default,
406    /// except for Danish and Maltese), [`CollationCaseFirst::Lower`], and [`CollationCaseFirst::Upper`]
407    /// (default for Danish and Maltese).
408    ///
409    /// ## Numeric
410    ///
411    /// This is the BCP47 key `kn`. When set to [`CollationNumericOrdering::True`], any sequence of decimal
412    /// digits (General_Category = Nd) is sorted at the primary level according to the
413    /// numeric value. The default is [`CollationNumericOrdering::False`].
414    [Copy]
415    CollatorPreferences,
416    {
417        /// The collation type. This corresponds to the `-u-co` BCP-47 tag.
418        collation_type: CollationType,
419        /// Treatment of case. (Large and small kana differences are treated as case differences.)
420        /// This corresponds to the `-u-kf` BCP-47 tag.
421        case_first: CollationCaseFirst,
422        /// When set to `True`, any sequence of decimal digits is sorted at a primary level according
423        /// to the numeric value.
424        /// This corresponds to the `-u-kn` BPC-47 tag.
425        numeric_ordering: CollationNumericOrdering
426    }
427);
428
429impl LocaleSpecificDataHolder {
430    /// The constructor code reused between owned and borrowed cases.
431    fn try_new_unstable_internal<D>(
432        provider: &D,
433        prefs: CollatorPreferences,
434        options: CollatorOptions,
435    ) -> Result<Self, DataError>
436    where
437        D: DataProvider<CollationTailoringV1>
438            + DataProvider<CollationDiacriticsV1>
439            + DataProvider<CollationMetadataV1>
440            + DataProvider<CollationReorderingV1>
441            + ?Sized,
442    {
443        let marker_attributes = prefs
444            .collation_type
445            .as_ref()
446            // all collation types are valid marker attributes
447            .map(|c| DataMarkerAttributes::from_str_or_panic(c.as_str()))
448            .unwrap_or_default();
449
450        let data_locale = CollationTailoringV1::make_locale(prefs.locale_preferences);
451        let req = DataRequest {
452            id: DataIdentifierBorrowed::for_marker_attributes_and_locale(
453                marker_attributes,
454                &data_locale,
455            ),
456            metadata: {
457                let mut metadata = DataRequestMetadata::default();
458                metadata.silent = true;
459                metadata
460            },
461        };
462
463        let fallback_req = DataRequest {
464            id: DataIdentifierBorrowed::for_marker_attributes_and_locale(
465                Default::default(),
466                &data_locale,
467            ),
468            ..Default::default()
469        };
470
471        let metadata_payload: DataPayload<CollationMetadataV1> = provider
472            .load(req)
473            .or_else(|_| provider.load(fallback_req))?
474            .payload;
475
476        let metadata = metadata_payload.get();
477
478        let tailoring: Option<DataPayload<CollationTailoringV1>> = if metadata.tailored() {
479            Some(
480                provider
481                    .load(req)
482                    .or_else(|_| provider.load(fallback_req))?
483                    .payload,
484            )
485        } else {
486            None
487        };
488
489        let reordering: Option<DataPayload<CollationReorderingV1>> = if metadata.reordering() {
490            Some(
491                provider
492                    .load(req)
493                    .or_else(|_| provider.load(fallback_req))?
494                    .payload,
495            )
496        } else {
497            None
498        };
499
500        if let Some(reordering) = &reordering {
501            if reordering.get().reorder_table.len() != 256 {
502                return Err(DataError::custom("invalid").with_marker(CollationReorderingV1::INFO));
503            }
504        }
505
506        let tailored_diacritics = metadata.tailored_diacritics();
507        let diacritics: DataPayload<CollationDiacriticsV1> = provider
508            .load(if tailored_diacritics {
509                req
510            } else {
511                Default::default()
512            })?
513            .payload;
514
515        if tailored_diacritics {
516            // In the tailored case we accept a shorter table in which case the tailoring is
517            // responsible for supplying the missing values in the trie.
518            // As of June 2022, none of the collations actually use a shortened table.
519            // Vietnamese and Ewe load a full-length alternative table and the rest use
520            // the default one.
521            if diacritics.get().secondaries.len() > OPTIMIZED_DIACRITICS_MAX_COUNT {
522                return Err(DataError::custom("invalid").with_marker(CollationDiacriticsV1::INFO));
523            }
524        } else if diacritics.get().secondaries.len() != OPTIMIZED_DIACRITICS_MAX_COUNT {
525            return Err(DataError::custom("invalid").with_marker(CollationDiacriticsV1::INFO));
526        }
527
528        let mut altered_defaults = CollatorOptionsBitField::default();
529
530        if metadata.alternate_shifted() {
531            altered_defaults.set_alternate_handling(Some(AlternateHandling::Shifted));
532        }
533        if metadata.backward_second_level() {
534            altered_defaults.set_backward_second_level(Some(true));
535        }
536
537        altered_defaults.set_case_first(Some(metadata.case_first()));
538        altered_defaults.set_max_variable(Some(metadata.max_variable()));
539
540        let mut merged_options = CollatorOptionsBitField::from(options);
541        merged_options.set_case_first(prefs.case_first);
542        merged_options.set_numeric_from_enum(prefs.numeric_ordering);
543        merged_options.set_defaults(altered_defaults);
544
545        Ok(LocaleSpecificDataHolder {
546            tailoring,
547            diacritics,
548            merged_options,
549            reordering,
550            lithuanian_dot_above: metadata.lithuanian_dot_above(),
551        })
552    }
553}
554
555/// Compares strings according to culturally-relevant ordering.
556#[derive(Debug)]
557pub struct Collator {
558    special_primaries: DataPayload<ErasedMarker<CollationSpecialPrimariesValidated<'static>>>,
559    root: DataPayload<CollationRootV1>,
560    tailoring: Option<DataPayload<CollationTailoringV1>>,
561    jamo: DataPayload<CollationJamoV1>,
562    diacritics: DataPayload<CollationDiacriticsV1>,
563    options: CollatorOptionsBitField,
564    reordering: Option<DataPayload<CollationReorderingV1>>,
565    decompositions: DataPayload<NormalizerNfdDataV1>,
566    tables: DataPayload<NormalizerNfdTablesV1>,
567    lithuanian_dot_above: bool,
568}
569
570impl Collator {
571    /// Constructs a borrowed version of this type for more efficient querying.
572    pub fn as_borrowed(&self) -> CollatorBorrowed<'_> {
573        CollatorBorrowed {
574            special_primaries: self.special_primaries.get(),
575            root: self.root.get(),
576            tailoring: if let Some(t) = self.tailoring.as_ref() {
577                t.get()
578            } else {
579                self.root.get()
580            },
581            jamo: self.jamo.get(),
582            diacritics: self.diacritics.get(),
583            options: self.options,
584            reordering: self.reordering.as_ref().map(|s| s.get()),
585            decompositions: self.decompositions.get(),
586            tables: self.tables.get(),
587            lithuanian_dot_above: self.lithuanian_dot_above,
588        }
589    }
590
591    /// Creates `CollatorBorrowed` for the given locale and options from compiled data.
592    #[cfg(feature = "compiled_data")]
593    pub fn try_new(
594        prefs: CollatorPreferences,
595        options: CollatorOptions,
596    ) -> Result<CollatorBorrowed<'static>, DataError> {
597        CollatorBorrowed::try_new(prefs, options)
598    }
599
600    icu_provider::gen_buffer_data_constructors!(
601        (prefs: CollatorPreferences, options: CollatorOptions) -> error: DataError,
602        functions: [
603            try_new: skip,
604            try_new_with_buffer_provider,
605            try_new_unstable,
606            Self
607        ]
608    );
609
610    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
611    pub fn try_new_unstable<D>(
612        provider: &D,
613        prefs: CollatorPreferences,
614        options: CollatorOptions,
615    ) -> Result<Self, DataError>
616    where
617        D: DataProvider<CollationSpecialPrimariesV1>
618            + DataProvider<CollationRootV1>
619            + DataProvider<CollationTailoringV1>
620            + DataProvider<CollationDiacriticsV1>
621            + DataProvider<CollationJamoV1>
622            + DataProvider<CollationMetadataV1>
623            + DataProvider<CollationReorderingV1>
624            + DataProvider<NormalizerNfdDataV1>
625            + DataProvider<NormalizerNfdTablesV1>
626            + ?Sized,
627    {
628        Self::try_new_unstable_internal(
629            provider,
630            provider.load(Default::default())?.payload,
631            provider.load(Default::default())?.payload,
632            provider.load(Default::default())?.payload,
633            provider.load(Default::default())?.payload,
634            provider.load(Default::default())?.payload,
635            prefs,
636            options,
637        )
638    }
639
640    #[expect(clippy::too_many_arguments)]
641    fn try_new_unstable_internal<D>(
642        provider: &D,
643        root: DataPayload<CollationRootV1>,
644        decompositions: DataPayload<NormalizerNfdDataV1>,
645        tables: DataPayload<NormalizerNfdTablesV1>,
646        jamo: DataPayload<CollationJamoV1>,
647        special_primaries: DataPayload<CollationSpecialPrimariesV1>,
648        prefs: CollatorPreferences,
649        options: CollatorOptions,
650    ) -> Result<Self, DataError>
651    where
652        D: DataProvider<CollationRootV1>
653            + DataProvider<CollationTailoringV1>
654            + DataProvider<CollationDiacriticsV1>
655            + DataProvider<CollationMetadataV1>
656            + DataProvider<CollationReorderingV1>
657            + ?Sized,
658    {
659        let locale_dependent =
660            LocaleSpecificDataHolder::try_new_unstable_internal(provider, prefs, options)?;
661
662        // TODO: redesign Korean search collation handling
663        if jamo.get().ce32s.len() != JAMO_COUNT {
664            return Err(DataError::custom("invalid").with_marker(CollationJamoV1::INFO));
665        }
666
667        // `variant_count` isn't stable yet:
668        // https://github.com/rust-lang/rust/issues/73662
669        if special_primaries.get().last_primaries.len() <= (MaxVariable::Currency as usize) {
670            return Err(DataError::custom("invalid").with_marker(CollationSpecialPrimariesV1::INFO));
671        }
672        let special_primaries = special_primaries.map_project(|csp, _| {
673            let compressible_bytes = (csp.last_primaries.len()
674                == MaxVariable::Currency as usize + 16)
675                .then(|| {
676                    csp.last_primaries
677                        .as_maybe_borrowed()?
678                        .as_ule_slice()
679                        .get((MaxVariable::Currency as usize)..)?
680                        .try_into()
681                        .ok()
682                })
683                .flatten()
684                .unwrap_or(
685                    CollationSpecialPrimariesValidated::HARDCODED_COMPRESSIBLE_BYTES_FALLBACK,
686                );
687
688            CollationSpecialPrimariesValidated {
689                last_primaries: csp.last_primaries.truncated(MaxVariable::Currency as usize),
690                numeric_primary: csp.numeric_primary,
691                compressible_bytes,
692            }
693        });
694
695        Ok(Collator {
696            special_primaries,
697            root,
698            tailoring: locale_dependent.tailoring,
699            jamo,
700            diacritics: locale_dependent.diacritics,
701            options: locale_dependent.merged_options,
702            reordering: locale_dependent.reordering,
703            decompositions,
704            tables,
705            lithuanian_dot_above: locale_dependent.lithuanian_dot_above,
706        })
707    }
708}
709
710macro_rules! quick_primary_compare {
711    ($left_primary:ident,
712     $right_primary:ident,
713     $variable_top:ident,
714     $self:ident,
715    ) => {
716        if ($left_primary != $right_primary)
717            && ($left_primary != 0)
718            && ($right_primary != 0)
719            && !($left_primary < $variable_top && $left_primary > MERGE_SEPARATOR_PRIMARY)
720            && !($right_primary < $variable_top && $right_primary > MERGE_SEPARATOR_PRIMARY)
721        {
722            if let Some(reordering) = &$self.reordering {
723                $left_primary = reordering.reorder($left_primary);
724                $right_primary = reordering.reorder($right_primary);
725            }
726            if $left_primary < $right_primary {
727                return Ordering::Less;
728            }
729            return Ordering::Greater;
730        }
731    };
732}
733
734macro_rules! hangul_syllable_compare {
735    ($left_hangul_offset:ident,
736     $right_hangul_offset:ident,
737    ) => {
738        let left_l = $left_hangul_offset / HANGUL_N_COUNT;
739        let right_l = $right_hangul_offset / HANGUL_N_COUNT;
740        if left_l != right_l {
741            // We don't really support jamo tailoring, so let's
742            // compare the jamo directly.
743            if left_l < right_l {
744                return Ordering::Less;
745            }
746            return Ordering::Greater;
747        }
748        let left_v = ($left_hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
749        let right_v = ($right_hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
750        if left_v != right_v {
751            // We don't really support jamo tailoring, so let's
752            // compare the jamo directly.
753            if left_v < right_v {
754                return Ordering::Less;
755            }
756            return Ordering::Greater;
757        }
758        let left_t = $left_hangul_offset % HANGUL_T_COUNT;
759        let right_t = $right_hangul_offset % HANGUL_T_COUNT;
760        // If either syllable is a two-jamo syllable, we fall through.
761        if left_t != right_t && left_t != 0 && right_t != 0 {
762            // We don't really support jamo tailoring, so let's
763            // compare the jamo directly.
764            if left_t < right_t {
765                return Ordering::Less;
766            }
767            return Ordering::Greater;
768        }
769    };
770}
771
772macro_rules! compare {
773    ($(#[$meta:meta])*,
774     $compare:ident,
775     $left_slice:ty,
776     $right_slice:ty,
777     $split_prefix:ident,
778     $left_to_iter:ident,
779     $right_to_iter:ident,
780     $self:ident,
781     $left_tail:ident,
782     $right_tail:ident,
783     $primary_check:block,
784     $variable_top:ident,
785    ) => {
786        $(#[$meta])*
787        pub fn $compare(&$self, left: &$left_slice, right: &$right_slice) -> Ordering {
788            let (head, $left_tail, $right_tail) = $split_prefix(left, right);
789            if $left_tail.is_empty() && $right_tail.is_empty() {
790                return Ordering::Equal;
791            }
792            let $variable_top = $self.variable_top();
793            if head.is_empty() {
794                $primary_check
795            }
796            let norm_trie = $self.norm_trie();
797            let ret = $self.compare_impl($left_tail.$left_to_iter(norm_trie), $right_tail.$right_to_iter(norm_trie), head.$left_to_iter(norm_trie), $variable_top);
798            if $self.options.strength() == Strength::Identical && ret == Ordering::Equal {
799                // We don't need to remove the leading U+0000, because it compares equal anyway.
800                return icu_normalizer::new_decomposition($left_tail.$left_to_iter(norm_trie), $self.tables).map(|c| if c != MERGE_SEPARATOR { c as i32 } else { -1i32 }).cmp(
801                    icu_normalizer::new_decomposition($right_tail.$right_to_iter(norm_trie), $self.tables).map(|c| if c != MERGE_SEPARATOR { c as i32 } else { -1i32 }),
802                );
803            }
804            ret
805        }
806    }
807}
808
809/// Compares strings according to culturally-relevant ordering,
810/// borrowed version.
811#[derive(Debug)]
812pub struct CollatorBorrowed<'a> {
813    special_primaries: &'a CollationSpecialPrimariesValidated<'a>,
814    root: &'a CollationData<'a>,
815    tailoring: &'a CollationData<'a>,
816    jamo: &'a CollationJamo<'a>,
817    diacritics: &'a CollationDiacritics<'a>,
818    options: CollatorOptionsBitField,
819    reordering: Option<&'a CollationReordering<'a>>,
820    decompositions: &'a DecompositionData<'a>,
821    tables: &'a DecompositionTables<'a>,
822    lithuanian_dot_above: bool,
823}
824
825impl CollatorBorrowed<'static> {
826    /// Creates a collator for the given locale and options from compiled data.
827    #[cfg(feature = "compiled_data")]
828    pub fn try_new(
829        prefs: CollatorPreferences,
830        options: CollatorOptions,
831    ) -> Result<Self, DataError> {
832        // These are assigned to locals in order to keep the code after these assignments
833        // copypaste-compatible with `Collator::try_new_unstable_internal`.
834
835        let provider = &crate::provider::Baked;
836        let decompositions = icu_normalizer::provider::Baked::SINGLETON_NORMALIZER_NFD_DATA_V1;
837        let tables = icu_normalizer::provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1;
838        let root = crate::provider::Baked::SINGLETON_COLLATION_ROOT_V1;
839        let jamo = crate::provider::Baked::SINGLETON_COLLATION_JAMO_V1;
840
841        let locale_dependent =
842            LocaleSpecificDataHolder::try_new_unstable_internal(provider, prefs, options)?;
843
844        // TODO: redesign Korean search collation handling
845        const _: () = assert!(
846            crate::provider::Baked::SINGLETON_COLLATION_JAMO_V1
847                .ce32s
848                .as_slice()
849                .len()
850                == JAMO_COUNT
851        );
852
853        // `variant_count` isn't stable yet:
854        // https://github.com/rust-lang/rust/issues/73662
855        const _: () = assert!(
856            crate::provider::Baked::SINGLETON_COLLATION_SPECIAL_PRIMARIES_V1
857                .last_primaries
858                .as_slice()
859                .len()
860                > (MaxVariable::Currency as usize)
861        );
862
863        let special_primaries = const {
864            &CollationSpecialPrimariesValidated {
865                last_primaries: zerovec::ZeroSlice::from_ule_slice(
866                    crate::provider::Baked::SINGLETON_COLLATION_SPECIAL_PRIMARIES_V1
867                        .last_primaries
868                        .as_slice()
869                        .as_ule_slice()
870                        .split_at(MaxVariable::Currency as usize)
871                        .0,
872                )
873                .as_zerovec(),
874                numeric_primary: crate::provider::Baked::SINGLETON_COLLATION_SPECIAL_PRIMARIES_V1
875                    .numeric_primary,
876                compressible_bytes: {
877                    const C: &[<u16 as AsULE>::ULE] =
878                        crate::provider::Baked::SINGLETON_COLLATION_SPECIAL_PRIMARIES_V1
879                            .last_primaries
880                            .as_slice()
881                            .as_ule_slice();
882                    if C.len() == MaxVariable::Currency as usize + 16 {
883                        let i = MaxVariable::Currency as usize;
884                        #[allow(clippy::indexing_slicing)] // protected, const
885                        &[
886                            C[i],
887                            C[i + 1],
888                            C[i + 2],
889                            C[i + 3],
890                            C[i + 4],
891                            C[i + 5],
892                            C[i + 6],
893                            C[i + 7],
894                            C[i + 8],
895                            C[i + 9],
896                            C[i + 10],
897                            C[i + 11],
898                            C[i + 12],
899                            C[i + 13],
900                            C[i + 14],
901                            C[i + 15],
902                        ]
903                    } else {
904                        CollationSpecialPrimariesValidated::HARDCODED_COMPRESSIBLE_BYTES_FALLBACK
905                    }
906                },
907            }
908        };
909
910        // Attribute belongs closer to `unwrap`, but
911        // https://github.com/rust-lang/rust/issues/15701
912        #[expect(clippy::unwrap_used)]
913        Ok(CollatorBorrowed {
914            special_primaries,
915            root,
916            // Unwrap is OK, because we know we have the baked provider.
917            tailoring: if let Some(s) = locale_dependent.tailoring {
918                s.get_static().unwrap()
919            } else {
920                root
921            },
922            jamo,
923            // Unwrap is OK, because we know we have the baked provider.
924            diacritics: locale_dependent.diacritics.get_static().unwrap(),
925            options: locale_dependent.merged_options,
926            // Unwrap is OK, because we know we have the baked provider.
927            reordering: locale_dependent.reordering.map(|s| s.get_static().unwrap()),
928            decompositions,
929            tables,
930            lithuanian_dot_above: locale_dependent.lithuanian_dot_above,
931        })
932    }
933
934    /// Cheaply converts a [`CollatorBorrowed<'static>`] into a [`Collator`].
935    ///
936    /// Note: Due to branching and indirection, using [`Collator`] might inhibit some
937    /// compile-time optimizations that are possible with [`CollatorBorrowed`].
938    pub const fn static_to_owned(self) -> Collator {
939        Collator {
940            special_primaries: DataPayload::from_static_ref(self.special_primaries),
941            root: DataPayload::from_static_ref(self.root),
942            tailoring: Some(DataPayload::from_static_ref(self.tailoring)),
943            jamo: DataPayload::from_static_ref(self.jamo),
944            diacritics: DataPayload::from_static_ref(self.diacritics),
945            options: self.options,
946            reordering: if let Some(s) = self.reordering {
947                // `map` not available in const context
948                Some(DataPayload::from_static_ref(s))
949            } else {
950                None
951            },
952            decompositions: DataPayload::from_static_ref(self.decompositions),
953            tables: DataPayload::from_static_ref(self.tables),
954            lithuanian_dot_above: self.lithuanian_dot_above,
955        }
956    }
957}
958
959macro_rules! collation_elements {
960    ($self:expr, $chars:expr, $numeric_primary:expr) => {{
961        let jamo = <&[<u32 as AsULE>::ULE; JAMO_COUNT]>::try_from($self.jamo.ce32s.as_ule_slice());
962
963        let jamo = jamo.unwrap();
964
965        CollationElements::new(
966            $chars,
967            $self.root,
968            $self.tailoring,
969            jamo,
970            &$self.diacritics.secondaries,
971            $self.tables,
972            $numeric_primary,
973            $self.lithuanian_dot_above,
974        )
975    }};
976}
977
978impl<'data> CollatorBorrowed<'data> {
979    /// The resolved options showing how the default options, the requested options,
980    /// and the options from locale data were combined.
981    pub fn resolved_options(&self) -> ResolvedCollatorOptions {
982        self.options.into()
983    }
984
985    fn norm_trie(&self) -> &'data NormTrie<'data> {
986        #[allow(clippy::useless_conversion)]
987        <&NormTrie<'data>>::try_from(&self.decompositions.trie)
988            .unwrap_or_else(|_| unreachable!("Incompatible data"))
989    }
990
991    compare!(
992        /// Compare guaranteed well-formed UTF-8 slices.
993        ,
994        compare,
995        str,
996        str,
997        split_prefix,
998        chars_with_trie_default_for_ascii,
999        chars_with_trie_default_for_ascii,
1000        self,
1001        left_tail,
1002        right_tail,
1003        {
1004            // Not macroized: Copy and paste to the other UTF-8 case below.
1005            let tailoring_trie = &self.tailoring.trie;
1006            if let Some((left_c, left_u32)) = left_tail.chars_with_trie(tailoring_trie).next() {
1007                // Logically, there's a bunch of stuff we could do from the left side alone to determine
1008                // that reading from the right side at all or reading from the right trie is useless.
1009                // Doing that seems to be a pessimization, at least in the absence of PGO.
1010                if let Some((right_c, right_u32)) = right_tail.chars_with_trie(tailoring_trie).next() {
1011                    let left_ce32 = CollationElement32::new(left_u32);
1012                    let right_ce32 = CollationElement32::new(right_u32);
1013                    if let Some(mut left_primary) = left_ce32.to_primary_in_quick_check(self.tailoring) {
1014                        if let Some(mut right_primary) = right_ce32.to_primary_in_quick_check(self.tailoring) {
1015                            quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1016                        }
1017                    }
1018                    // Try Hangul. (At least in the absence of PGO, it's better _not_ to put
1019                    // this into an `else` branch of the `left_primary` `if`.)
1020                    let right_hangul_offset = u32::from(right_c).wrapping_sub(HANGUL_S_BASE);
1021                    if right_hangul_offset < HANGUL_S_COUNT {
1022                        let left_hangul_offset = u32::from(left_c).wrapping_sub(HANGUL_S_BASE);
1023                        if left_hangul_offset < HANGUL_S_COUNT {
1024                            hangul_syllable_compare!(left_hangul_offset, right_hangul_offset,);
1025                        }
1026                    }
1027                }
1028            }
1029            // Note: It might look like a good idea to cache the CE32s, but
1030            // doing so actually makes things slower.
1031        },
1032        variable_top,
1033    );
1034
1035    compare!(
1036        /// Compare potentially ill-formed UTF-8 slices. Ill-formed input is compared
1037        /// as if errors had been replaced with REPLACEMENT CHARACTERs according
1038        /// to the WHATWG Encoding Standard.
1039        ,
1040        compare_utf8,
1041        [u8],
1042        [u8],
1043        split_prefix_u8,
1044        chars_with_trie_default_for_ascii,
1045        chars_with_trie_default_for_ascii,
1046        self,
1047        left_tail,
1048        right_tail,
1049        {
1050            // Direct copypaste from the `str` case.
1051            let tailoring_trie = &self.tailoring.trie;
1052            if let Some((left_c, left_u32)) = left_tail.chars_with_trie(tailoring_trie).next() {
1053                // Logically, there's a bunch of stuff we could do from the left side alone to determine
1054                // that reading from the right side at all or reading from the right trie is useless.
1055                // Doing that seems to be a pessimization, at least in the absence of PGO.
1056                if let Some((right_c, right_u32)) = right_tail.chars_with_trie(tailoring_trie).next() {
1057                    let left_ce32 = CollationElement32::new(left_u32);
1058                    let right_ce32 = CollationElement32::new(right_u32);
1059                    if let Some(mut left_primary) = left_ce32.to_primary_in_quick_check(self.tailoring) {
1060                        if let Some(mut right_primary) = right_ce32.to_primary_in_quick_check(self.tailoring) {
1061                            quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1062                        }
1063                    }
1064                    // Try Hangul. (At least in the absence of PGO, it's better _not_ to put
1065                    // this into an `else` branch of the `left_primary` `if`.)
1066                    let right_hangul_offset = u32::from(right_c).wrapping_sub(HANGUL_S_BASE);
1067                    if right_hangul_offset < HANGUL_S_COUNT {
1068                        let left_hangul_offset = u32::from(left_c).wrapping_sub(HANGUL_S_BASE);
1069                        if left_hangul_offset < HANGUL_S_COUNT {
1070                            hangul_syllable_compare!(left_hangul_offset, right_hangul_offset,);
1071                        }
1072                    }
1073                }
1074            }
1075            // Note: It might look like a good idea to cache the CE32s, but
1076            // doing so actually makes things slower.
1077        },
1078        variable_top,
1079    );
1080
1081    compare!(
1082        /// Compare potentially ill-formed UTF-16 slices. Unpaired surrogates
1083        /// are compared as if each one was a REPLACEMENT CHARACTER.
1084        ,
1085        compare_utf16,
1086        [u16],
1087        [u16],
1088        split_prefix_u16,
1089        chars_with_trie,
1090        chars_with_trie,
1091        self,
1092        left_tail,
1093        right_tail,
1094        {
1095            let tailoring_trie = &self.tailoring.trie;
1096            if let Some(left_u) = left_tail.first() {
1097                if let Some(right_u) = right_tail.first() {
1098                    let left_u16 = *left_u;
1099                    let right_u16 = *right_u;
1100                    let left_u32 = tailoring_trie.get16(left_u16);
1101                    let right_u32 = tailoring_trie.get16(right_u16);
1102                    let left_ce32 = CollationElement32::new(left_u32);
1103                    let right_ce32 = CollationElement32::new(right_u32);
1104                    if let Some(mut left_primary) = left_ce32.to_primary_in_quick_check(self.tailoring) {
1105                        if let Some(mut right_primary) = right_ce32.to_primary_in_quick_check(self.tailoring) {
1106                            quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1107                        }
1108                    }
1109                    // Try Hangul. Not putting in an `else` of the above consistent with UTF-8.
1110                    let left_hangul_offset = u32::from(left_u16).wrapping_sub(HANGUL_S_BASE);
1111                    if left_hangul_offset < HANGUL_S_COUNT {
1112                        if let Some(right_u) = right_tail.first() {
1113                            let right_u16 = *right_u;
1114                            let right_hangul_offset = u32::from(right_u16).wrapping_sub(HANGUL_S_BASE);
1115                            if right_hangul_offset < HANGUL_S_COUNT {
1116                                hangul_syllable_compare!(left_hangul_offset, right_hangul_offset,);
1117                            }
1118                        }
1119                    }
1120                }
1121            }
1122            // Note: It might look like a good idea to cache the CE32s, but
1123            // doing so actually makes things slower.
1124        },
1125        variable_top,
1126    );
1127
1128    compare!(
1129        /// Compare Latin1 slices.
1130        ///
1131        /// ✨ *Enabled with the `latin1` Cargo feature.*
1132        #[cfg(feature = "latin1")]
1133        ,
1134        compare_latin1,
1135        [u8],
1136        [u8],
1137        split_prefix_latin1,
1138        latin1_chars_with_trie,
1139        latin1_chars_with_trie,
1140        self,
1141        left_tail,
1142        right_tail,
1143        {
1144            let tailoring_trie = &self.tailoring.trie;
1145            if let Some(left_u) = left_tail.first() {
1146                if let Some(right_u) = right_tail.first() {
1147                    let left_u8 = *left_u;
1148                    let right_u8 = *right_u;
1149                    // The probability of getting a non-simple ce32 from
1150                    // the Latin1 part above ASCII is so high that let's
1151                    // only consider ASCII on the left for this fast path.
1152
1153                    // SAFETY: Checking the invariant of `get7` here.
1154                    if left_u8 < 0x80 {
1155                        // SAFETY: Invariant of `get7` checked above.
1156                        let left_u32 = unsafe { tailoring_trie.get7(left_u8) };
1157                        let left_ce32 = CollationElement32::new(left_u32);
1158                        // SAFETY: Checking the invariant of `get7` here for right.
1159                        if right_u8 < 0x80 {
1160                            // SAFETY: Invariant of `get7` checked above.
1161                            let right_u32 = unsafe { tailoring_trie.get7(right_u8) };
1162                            let right_ce32 = CollationElement32::new(right_u32);
1163                            // Should be use script reordering to cater to reordering
1164                            // digets or punctuation relative to letters?
1165                            if let Some(left_primary) = left_ce32.to_primary_simple() {
1166                                if let Some(right_primary) = right_ce32.to_primary_simple() {
1167                                    if (left_primary != right_primary)
1168                                        && (left_primary != 0)
1169                                        && (right_primary != 0)
1170                                        && !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY)
1171                                        && !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY)
1172                                    {
1173                                        if left_primary < right_primary {
1174                                            return Ordering::Less;
1175                                        }
1176                                        return Ordering::Greater;
1177                                    }
1178                                }
1179                            }
1180                        }
1181                    }
1182                }
1183            }
1184            // Note: It might look like a good idea to cache the CE32s, but
1185            // doing so actually makes things slower.
1186        },
1187        variable_top,
1188    );
1189
1190    compare!(
1191        /// Compare Latin1 slice with potentially ill-formed UTF-16
1192        /// slice.
1193        ///
1194        /// If you need to compare a potentially ill-formed UTF-16
1195        /// slice with a Latin1 slice, swap the arguments and
1196        /// call `reverse()` on the return value.
1197        ///
1198        /// ✨ *Enabled with the `latin1` Cargo feature.*
1199        #[cfg(feature = "latin1")]
1200        ,
1201        compare_latin1_utf16,
1202        [u8],
1203        [u16],
1204        split_prefix_latin1_utf16,
1205        latin1_chars_with_trie,
1206        chars_with_trie,
1207        self,
1208        left_tail,
1209        right_tail,
1210        {
1211            let tailoring_trie = &self.tailoring.trie;
1212            if let Some(left_u) = left_tail.first() {
1213                if let Some(right_u) = right_tail.first() {
1214                    let left_u8 = *left_u;
1215                    // The probability of getting a non-simple ce32 from
1216                    // the Latin1 part above ASCII is so high that let's
1217                    // only consider ASCII on the left for this fast path.
1218
1219                    // SAFETY: Checking the invariant of `get7` here.
1220                    if left_u8 < 0x80 {
1221                        // SAFETY: Invariant of `get7` checked above.
1222                        let left_u32 = unsafe { tailoring_trie.get7(left_u8) };
1223                        let left_ce32 = CollationElement32::new(left_u32);
1224                        let right_u16 = *right_u;
1225                        let right_u32 = tailoring_trie.get16(right_u16);
1226                        let right_ce32 = CollationElement32::new(right_u32);
1227                        if let Some(mut left_primary) = left_ce32.to_primary_simple() {
1228                            // Don't use the macro to micro-optimize away the long primary
1229                            // case for ASCII.
1230                            if let Some(mut right_primary) = right_ce32.to_primary_in_quick_check(self.tailoring) {
1231                                if (left_primary != right_primary)
1232                                    && (left_primary != 0)
1233                                    && (right_primary != 0)
1234                                    && !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY)
1235                                    && !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY)
1236                                {
1237                                    if let Some(reordering) = &self.reordering {
1238                                        left_primary = reordering.reorder(left_primary);
1239                                        right_primary = reordering.reorder(right_primary);
1240                                    }
1241                                    if left_primary < right_primary {
1242                                        return Ordering::Less;
1243                                    }
1244                                    return Ordering::Greater;
1245                                }
1246                            }
1247                        }
1248                    }
1249                }
1250            }
1251            // Note: It might look like a good idea to cache the CE32s, but
1252            // doing so actually makes things slower.
1253        },
1254        variable_top,
1255    );
1256
1257    #[inline(always)]
1258    fn numeric_primary(&self) -> Option<u8> {
1259        if self.options.numeric() {
1260            Some(self.special_primaries.numeric_primary)
1261        } else {
1262            None
1263        }
1264    }
1265
1266    #[inline(always)]
1267    fn variable_top(&self) -> u32 {
1268        if self.options.alternate_handling() == AlternateHandling::NonIgnorable {
1269            0
1270        } else {
1271            // +1 so that we can use "<" and primary ignorables test out early.
1272            self.special_primaries
1273                .last_primary_for_group(self.options.max_variable())
1274                + 1
1275        }
1276    }
1277
1278    /// The implementation of the comparison operation.
1279    ///
1280    /// `head_chars` is an iterator _backward_ over the identical
1281    /// prefix and `left_chars` and `right_chars` are iterators
1282    /// _forward_ over the parts after the identical prefix.
1283    fn compare_impl<L, R, H, T>(
1284        &'data self,
1285        left_chars: L,
1286        right_chars: R,
1287        mut head_chars: H,
1288        variable_top: u32,
1289    ) -> Ordering
1290    where
1291        L: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
1292        R: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
1293        H: DoubleEndedIterator<Item = (char, u32)> + 'data,
1294        T: AbstractCodePointTrie<'data, u32> + 'data,
1295    {
1296        // Sadly, it looks like variable CEs and backward second level
1297        // require us to store the full 64-bit CEs instead of storing only
1298        // the NonPrimary part.
1299        //
1300        // TODO(#2008): Consider having two monomorphizations of this method:
1301        // one that can deal with variables shifted to quaternary and
1302        // backward second level and another that doesn't support that
1303        // and only stores `NonPrimary` in `left_ces` and `right_ces`
1304        // with double the number of stack allocated elements.
1305
1306        // Note: These are used only after the identical prefix skipping,
1307        // but initializing these up here improves performance at the time
1308        // of writing. Presumably the source order affects the stack frame
1309        // layout.
1310        let mut left_ces: SmallVec<[CollationElement; CE_BUFFER_SIZE]> = SmallVec::new();
1311        let mut right_ces: SmallVec<[CollationElement; CE_BUFFER_SIZE]> = SmallVec::new();
1312
1313        // The algorithm comes from CollationCompare::compareUpToQuaternary in ICU4C.
1314
1315        let mut any_variable = false;
1316
1317        let numeric_primary = self.numeric_primary();
1318
1319        let mut left = collation_elements!(self, left_chars, numeric_primary);
1320        let mut right = collation_elements!(self, right_chars, numeric_primary);
1321
1322        // Start identical prefix
1323
1324        // The logic here to check whether the boundary found by skipping
1325        // the identical prefix is safe is complicated compared to the ICU4C
1326        // approach of having a set of characters that are unsafe as the character
1327        // immediately following the identical prefix. However, the approach here
1328        // avoids extra data, and working on the main data avoids the bug
1329        // possibility of data structures not being mutually consistent.
1330
1331        // This code intentionally does not keep around the `CollationElement32`s
1332        // that have been read from the collation data tries, because keeping
1333        // them around turned out to be a pessimization: There would be added
1334        // branches on the hot path of the algorithm that maps characters to
1335        // collation elements, and the element size of the upcoming buffer
1336        // would grow.
1337        //
1338        // However, the values read from the normalization trie _are_ kept around,
1339        // since there is already a place where to put them.
1340
1341        // This loop is only broken out of as goto forward.
1342        #[expect(clippy::never_loop)]
1343        'prefix: loop {
1344            if let Some((mut head_last_c, head_last_trie_val)) = head_chars.next_back() {
1345                let mut head_last = CharacterAndClassAndTrieValue::new_with_trie_val(
1346                    head_last_c,
1347                    head_last_trie_val,
1348                );
1349                let mut head_last_ce32 = CollationElement32::default();
1350                let mut head_last_ok = false;
1351                if let Some(left_different) = left.iter_next_before_init() {
1352                    left.prepend_upcoming_before_init(left_different.clone());
1353                    if let Some(right_different) = right.iter_next_before_init() {
1354                        // Note: left_different and right_different may both be U+FFFD.
1355                        right.prepend_upcoming_before_init(right_different.clone());
1356
1357                        // The base logic is that a boundary between two starters
1358                        // that decompose to selves is safe iff the starter
1359                        // before the boundary can't contract a starter, the
1360                        // starter after the boundary doesn't have a prefix
1361                        // condition, and, with the numeric mode enabled,
1362                        // they aren't both numeric.
1363                        //
1364                        // This base logic is then extended with Hangul
1365                        // syllables and characters that decompose to a
1366                        // BMP starter followed by a BMP non-starter.
1367                        // The logic could be extended further, in
1368                        // particular to cover singleton decompositions
1369                        // to a BMP starter, but such characters can be
1370                        // expected to be rare enough in real-world input
1371                        // that it's not worthwhile to make this code more
1372                        // branchy.
1373                        //
1374                        // A Hangul syllable is safe on either side of the
1375                        // boundary, because Hangul syllables can't participate
1376                        // in contraction or have prefix conditions. They are
1377                        // also known not to be numeric.
1378                        //
1379                        // Hangul jamo is safe to look up from the main trie
1380                        // instead of the jamo table, because they aren't
1381                        // allowed to participate in contractions or prefix
1382                        // conditions, either, and are known not to be numeric.
1383                        //
1384                        // After a boundary, a decomposition to a BMP starter
1385                        // and a BMP non-starter can obviously be analyzed by
1386                        // considering the starter as if it was a starter
1387                        // that decomposes to self.
1388                        //
1389                        // Before a boundary the contraction condition considers
1390                        // whether the contraction can contract a starter.
1391                        // For the case of contracting a non-starter, it's
1392                        // fine for the BMP starter of the decomposition to
1393                        // contract the non-starter from the same decomposition:
1394                        // Since that would happen as part of the prefix that
1395                        // is identical, it wouldn't affect anything after.
1396                        //
1397                        // The case of contracting a non-starter other than
1398                        // the one that came from the decomposition itself
1399                        // is irrelevant, because we don't allow a non-starter
1400                        // right after the boundary regardless of the contraction
1401                        // status of what's before the boundary.
1402                        //
1403                        // Finally, decompositions to starter and non-starter
1404                        // are known not to be numeric.
1405
1406                        // The checks below are repetitive, but an attempt to factor
1407                        // repetitive code into an inlined function regressed,
1408                        // performance, so it seems that having the control flow
1409                        // right here without an intermediate enum from a
1410                        // function return to branch on is important.
1411
1412                        // This loop is only broken out of as goto forward. The control flow
1413                        // is much more readable this way.
1414                        #[expect(clippy::never_loop)]
1415                        loop {
1416                            // The two highest bits are about NFC, which we don't
1417                            // care about here.
1418                            let decomposition = head_last.trie_val;
1419                            if (decomposition
1420                                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1421                                == 0
1422                            {
1423                                // Intentionally empty block to keep
1424                                // the same structure as in the cases
1425                                // where something happens here.
1426                            } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1427                                && ((decomposition & LOW_ZEROS_MASK) != 0)
1428                            {
1429                                // Decomposition into two BMP characters: starter and non-starter
1430                                // Let's take the starter
1431                                head_last_c = char_from_u32(decomposition & 0x7FFF);
1432                            } else if decomposition == HANGUL_SYLLABLE_MARKER {
1433                                head_last_ce32 = IDENTICAL_PREFIX_HANGUL_MARKER_CE32;
1434                            } else {
1435                                break;
1436                            }
1437                            head_last_ok = true;
1438
1439                            let left_c;
1440                            let right_c;
1441
1442                            let decomposition = left_different.trie_val;
1443                            if (decomposition
1444                                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1445                                == 0
1446                            {
1447                                left_c = left_different.character();
1448                            } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1449                                && ((decomposition & LOW_ZEROS_MASK) != 0)
1450                            {
1451                                // Decomposition into two BMP characters: starter and non-starter
1452                                // Let's take the starter
1453                                left_c = char_from_u32(decomposition & 0x7FFF);
1454                            } else if decomposition == HANGUL_SYLLABLE_MARKER {
1455                                left_c = left_different.character();
1456                                if right_different.trie_val == HANGUL_SYLLABLE_MARKER {
1457                                    // Hangul syllable to Hangul syllable comparison can give us
1458                                    // a quick exit.
1459                                    let left_hangul_offset =
1460                                        u32::from(left_c).wrapping_sub(HANGUL_S_BASE);
1461                                    let right_hangul_offset =
1462                                        u32::from(right_different.character())
1463                                            .wrapping_sub(HANGUL_S_BASE);
1464                                    // If these are not in range, we have a memory-safe GIGO case.
1465                                    debug_assert!(left_hangul_offset < HANGUL_S_COUNT);
1466                                    debug_assert!(right_hangul_offset < HANGUL_S_COUNT);
1467                                    hangul_syllable_compare!(
1468                                        left_hangul_offset,
1469                                        right_hangul_offset,
1470                                    );
1471                                    // We had a two-jamo syllable whose jamo matched
1472                                    // the first two jamo of the other syllable.
1473                                    // Still, we're at a good boundary.
1474                                    break 'prefix;
1475                                }
1476                            } else {
1477                                break;
1478                            }
1479
1480                            let decomposition = right_different.trie_val;
1481                            if (decomposition
1482                                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1483                                == 0
1484                            {
1485                                right_c = right_different.character();
1486                            } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1487                                && ((decomposition & LOW_ZEROS_MASK) != 0)
1488                            {
1489                                // Decomposition into two BMP characters: starter and non-starter
1490                                // Let's take the starter
1491                                right_c = char_from_u32(decomposition & 0x7FFF);
1492                            } else if decomposition == HANGUL_SYLLABLE_MARKER {
1493                                right_c = right_different.character();
1494                            } else {
1495                                break;
1496                            }
1497
1498                            // The last character of the prefix is OK on the normalization
1499                            // level. Now let's check its ce32 unless it's a Hangul syllable,
1500                            // in which case `head_last_ce32` already is a non-default placeholder.
1501                            if head_last_ce32 == CollationElement32::default() {
1502                                head_last_ce32 = self.tailoring.ce32_for_char(head_last_c);
1503                                if head_last_ce32 == FALLBACK_CE32 {
1504                                    head_last_ce32 = self.root.ce32_for_char(head_last_c);
1505                                }
1506                                if head_last_ce32.tag_checked() == Some(Tag::Contraction)
1507                                    && head_last_ce32.at_least_one_suffix_contains_starter()
1508                                {
1509                                    break;
1510                                }
1511                            }
1512                            let mut left_data = self.tailoring;
1513                            let mut left_ce32 = self.tailoring.ce32_for_char(left_c);
1514                            if left_ce32 == FALLBACK_CE32 {
1515                                left_ce32 = self.root.ce32_for_char(left_c);
1516                                left_data = self.root;
1517                            }
1518                            let mut right_data = self.tailoring;
1519                            let mut right_ce32 = self.tailoring.ce32_for_char(right_c);
1520                            if right_ce32 == FALLBACK_CE32 {
1521                                right_ce32 = self.root.ce32_for_char(right_c);
1522                                right_data = self.root;
1523                            }
1524
1525                            // We might be at at a good boundary unless either ce32 is a
1526                            // prefix ce32. Let's check for the happy path first, though.
1527
1528                            // Now check if the ce32s we have are simple enough to
1529                            // make a quick decision here.
1530                            if let Some(mut left_primary) = left_ce32
1531                                .to_primary_in_quick_check_numeric(
1532                                    left_data,
1533                                    numeric_primary.is_some(),
1534                                )
1535                            {
1536                                if let Some(mut right_primary) = right_ce32
1537                                    .to_primary_in_quick_check_numeric(
1538                                        right_data,
1539                                        numeric_primary.is_some(),
1540                                    )
1541                                {
1542                                    quick_primary_compare!(
1543                                        left_primary,
1544                                        right_primary,
1545                                        variable_top,
1546                                        self,
1547                                    );
1548                                }
1549                            }
1550
1551                            if left_ce32.tag_checked() == Some(Tag::Prefix)
1552                                || right_ce32.tag_checked() == Some(Tag::Prefix)
1553                            {
1554                                // TODO: For the Japanese tailoring, it might actually be worthwhile
1555                                // to handle the prefix ce32s inline above the quick check. We've already
1556                                // read the last character from `head` anyway.
1557                                break;
1558                            }
1559
1560                            if numeric_primary.is_some()
1561                                && head_last_ce32.tag_checked() == Some(Tag::Digit)
1562                                && ((left_ce32.tag_checked() == Some(Tag::Digit)
1563                                    && left_ce32.digit() == 0)
1564                                    || (right_ce32.tag_checked() == Some(Tag::Digit)
1565                                        && right_ce32.digit() == 0)
1566                                    || ((left_ce32.tag_checked() == Some(Tag::Digit))
1567                                        ^ (right_ce32.tag_checked() == Some(Tag::Digit))))
1568                            {
1569                                // Avoid giving the zero the leading zero treatment
1570                                // and also reject the case where only one of
1571                                // right and left is a digit.
1572                                break;
1573                            }
1574
1575                            // We're at a good boundary but could not make a quick primary comparison decision.
1576
1577                            // Note: It might look like a good idea to cache the CE32s, but
1578                            // doing so actually makes things slower.
1579                            break 'prefix;
1580                        }
1581                    }
1582                }
1583                let mut tail_first_c;
1584                let mut tail_first_ce32;
1585                let mut tail_first_ok;
1586                loop {
1587                    // Take a step back.
1588                    left.prepend_upcoming_before_init(head_last.clone());
1589                    right.prepend_upcoming_before_init(head_last.clone());
1590
1591                    tail_first_c = head_last_c;
1592                    tail_first_ce32 = head_last_ce32;
1593                    tail_first_ok = head_last_ok;
1594
1595                    let Some((head_last_c_new, decomposition)) = head_chars.next_back() else {
1596                        // We need to step back beyond the start of the prefix.
1597                        // Treat as good boundary.
1598                        break 'prefix;
1599                    };
1600                    head_last_c = head_last_c_new;
1601                    head_last = CharacterAndClassAndTrieValue::new_with_trie_val(
1602                        head_last_c,
1603                        decomposition,
1604                    );
1605                    head_last_ce32 = CollationElement32::default();
1606                    head_last_ok = false;
1607
1608                    if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1609                        // Intentionally empty block to keep
1610                        // the same structure as in the cases
1611                        // where something happens here.
1612                    } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1613                        && ((decomposition & LOW_ZEROS_MASK) != 0)
1614                    {
1615                        // Decomposition into two BMP characters: starter and non-starter
1616                        // Let's take the starter
1617                        head_last_c = char_from_u32(decomposition & 0x7FFF);
1618                    } else if decomposition == HANGUL_SYLLABLE_MARKER {
1619                        head_last_ce32 = FFFD_CE32;
1620                    } else {
1621                        continue;
1622                    }
1623                    head_last_ok = true;
1624                    if !tail_first_ok {
1625                        continue;
1626                    }
1627                    // The last character of the prefix is OK on the normalization
1628                    // level. Now let's check its ce32 unless it's a Hangul syllable.
1629                    if head_last_ce32 == CollationElement32::default() {
1630                        head_last_ce32 = self.tailoring.ce32_for_char(head_last_c);
1631                        if head_last_ce32 == FALLBACK_CE32 {
1632                            head_last_ce32 = self.root.ce32_for_char(head_last_c);
1633                        }
1634                        if head_last_ce32.tag_checked() == Some(Tag::Contraction)
1635                            && head_last_ce32.at_least_one_suffix_contains_starter()
1636                        {
1637                            continue;
1638                        }
1639                    }
1640                    // Check this _after_ `head_last_ce32` to make sure
1641                    // `head_last_ce32` is initialized for the next loop round
1642                    // trip if applicable.
1643                    if tail_first_ce32 == CollationElement32::default() {
1644                        tail_first_ce32 = self.tailoring.ce32_for_char(tail_first_c);
1645                        if tail_first_ce32 == FALLBACK_CE32 {
1646                            tail_first_ce32 = self.root.ce32_for_char(tail_first_c);
1647                        }
1648                    } // else we already have a trie value from the previous loop iteration or we have Hangul syllable
1649                    if tail_first_ce32.tag_checked() == Some(Tag::Prefix) {
1650                        continue;
1651                    }
1652                    if numeric_primary.is_some()
1653                        && head_last_ce32.tag_checked() == Some(Tag::Digit)
1654                        && tail_first_ce32.tag_checked() == Some(Tag::Digit)
1655                        && tail_first_ce32.digit() == 0
1656                    {
1657                        // Avoid giving the zero the leading zero treatment.
1658                        continue;
1659                    }
1660                    // We are at a good boundary!
1661                    break 'prefix;
1662                }
1663            } else {
1664                // The prefix is empty
1665                break 'prefix;
1666            }
1667            // Unreachable line
1668        }
1669
1670        // End identical prefix
1671
1672        left.init();
1673        right.init();
1674
1675        loop {
1676            let mut left_primary;
1677            'left_primary_loop: loop {
1678                let ce = left.next();
1679                left_primary = ce.primary();
1680                // TODO(#2008): Consider compiling out the variable handling when we know we aren't
1681                // shifting variable CEs.
1682                if !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY) {
1683                    left_ces.push(ce);
1684                } else {
1685                    // Variable CE, shift it to quaternary level.
1686                    // Ignore all following primary ignorables, and shift further variable CEs.
1687                    any_variable = true;
1688                    // Relative to ICU4C, the next line is hoisted out of the following loop
1689                    // in order to keep the variables called `ce` immutable to make it easier
1690                    // to reason about each assignment into `ce` resulting in exactly a single
1691                    // push into `left_ces`.
1692                    left_ces.push(ce.clone_with_non_primary_zeroed());
1693                    loop {
1694                        // This loop is simpler than in ICU4C; unlike in C++, we get to break by label.
1695                        let ce = left.next();
1696                        left_primary = ce.primary();
1697                        if left_primary != 0
1698                            && !(left_primary < variable_top
1699                                && left_primary > MERGE_SEPARATOR_PRIMARY)
1700                        {
1701                            // Neither a primary ignorable nor a variable CE.
1702                            left_ces.push(ce);
1703                            break 'left_primary_loop;
1704                        }
1705                        // If `left_primary == 0`, the following line ignores a primary-ignorable.
1706                        // Otherwise, it shifts a variable CE.
1707                        left_ces.push(ce.clone_with_non_primary_zeroed());
1708                    }
1709                }
1710                if left_primary != 0 {
1711                    break;
1712                }
1713            }
1714            let mut right_primary;
1715            'right_primary_loop: loop {
1716                let ce = right.next();
1717                right_primary = ce.primary();
1718                // TODO(#2008): Consider compiling out the variable handling when we know we aren't
1719                // shifting variable CEs.
1720                if !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY) {
1721                    right_ces.push(ce);
1722                } else {
1723                    // Variable CE, shift it to quaternary level.
1724                    // Ignore all following primary ignorables, and shift further variable CEs.
1725                    any_variable = true;
1726                    // Relative to ICU4C, the next line is hoisted out of the following loop
1727                    // in order to keep the variables called `ce` immutable to make it easier
1728                    // to reason about each assignment into `ce` resulting in exactly a single
1729                    // push into `right_ces`.
1730                    right_ces.push(ce.clone_with_non_primary_zeroed());
1731                    loop {
1732                        // This loop is simpler than in ICU4C; unlike in C++, we get to break by label.
1733                        let ce = right.next();
1734                        right_primary = ce.primary();
1735                        if right_primary != 0
1736                            && !(right_primary < variable_top
1737                                && right_primary > MERGE_SEPARATOR_PRIMARY)
1738                        {
1739                            // Neither a primary ignorable nor a variable CE.
1740                            right_ces.push(ce);
1741                            break 'right_primary_loop;
1742                        }
1743                        // If `right_primary == 0`, the following line ignores a primary-ignorable.
1744                        // Otherwise, it shifts a variable CE.
1745                        right_ces.push(ce.clone_with_non_primary_zeroed());
1746                    }
1747                }
1748                if right_primary != 0 {
1749                    break;
1750                }
1751            }
1752            if left_primary != right_primary {
1753                if let Some(reordering) = &self.reordering {
1754                    left_primary = reordering.reorder(left_primary);
1755                    right_primary = reordering.reorder(right_primary);
1756                }
1757                if left_primary < right_primary {
1758                    return Ordering::Less;
1759                }
1760                return Ordering::Greater;
1761            }
1762            if left_primary == NO_CE_PRIMARY {
1763                break;
1764            }
1765        }
1766
1767        // Sadly, we end up pushing the sentinel value, which means these
1768        // `SmallVec`s allocate more often than if we didn't actually
1769        // store the sentinel.
1770        debug_assert_eq!(left_ces.last(), Some(&NO_CE));
1771        debug_assert_eq!(right_ces.last(), Some(&NO_CE));
1772
1773        // Note: `unwrap_or_default` in the iterations below should never
1774        // actually end up using the "_or_default" part, because the sentinel
1775        // is in the `SmallVec`s. These could be changed to `unwrap()` if we
1776        // preferred panic in case of a bug.
1777        // TODO(#2009): Should we save one slot by not putting the sentinel in
1778        // the `SmallVec`s? So far, the answer seems "no", as it would complicate
1779        // the primary comparison above.
1780
1781        // Compare the buffered secondary & tertiary weights.
1782        // We might skip the secondary level but continue with the case level
1783        // which is turned on separately.
1784        if self.options.strength() >= Strength::Secondary {
1785            if !self.options.backward_second_level() {
1786                let mut left_iter = left_ces.iter();
1787                let mut right_iter = right_ces.iter();
1788                let mut left_secondary;
1789                let mut right_secondary;
1790                loop {
1791                    loop {
1792                        left_secondary = left_iter.next().unwrap_or_default().secondary();
1793                        if left_secondary != 0 {
1794                            break;
1795                        }
1796                    }
1797                    loop {
1798                        right_secondary = right_iter.next().unwrap_or_default().secondary();
1799                        if right_secondary != 0 {
1800                            break;
1801                        }
1802                    }
1803                    if left_secondary != right_secondary {
1804                        if left_secondary < right_secondary {
1805                            return Ordering::Less;
1806                        }
1807                        return Ordering::Greater;
1808                    }
1809                    if left_secondary == NO_CE_SECONDARY {
1810                        break;
1811                    }
1812                }
1813            } else {
1814                let mut left_remaining = &left_ces[..];
1815                let mut right_remaining = &right_ces[..];
1816                loop {
1817                    if left_remaining.is_empty() {
1818                        debug_assert!(right_remaining.is_empty());
1819                        break;
1820                    }
1821                    let (left_prefix, right_prefix) = {
1822                        let mut left_iter = left_remaining.iter();
1823                        loop {
1824                            let left_primary = left_iter.next().unwrap_or_default().primary();
1825                            if left_primary != 0 && left_primary <= MERGE_SEPARATOR_PRIMARY {
1826                                break;
1827                            }
1828                            debug_assert_ne!(left_primary, NO_CE_PRIMARY);
1829                        }
1830                        let left_new_remaining = left_iter.as_slice();
1831                        // Index in range by construction
1832                        #[expect(clippy::indexing_slicing)]
1833                        let left_prefix =
1834                            &left_remaining[..left_remaining.len() - 1 - left_new_remaining.len()];
1835                        left_remaining = left_new_remaining;
1836
1837                        let mut right_iter = right_remaining.iter();
1838                        loop {
1839                            let right_primary = right_iter.next().unwrap_or_default().primary();
1840                            if right_primary != 0 && right_primary <= MERGE_SEPARATOR_PRIMARY {
1841                                break;
1842                            }
1843                            debug_assert_ne!(right_primary, NO_CE_PRIMARY);
1844                        }
1845                        let right_new_remaining = right_iter.as_slice();
1846                        // Index in range by construction
1847                        #[expect(clippy::indexing_slicing)]
1848                        let right_prefix = &right_remaining
1849                            [..right_remaining.len() - 1 - right_new_remaining.len()];
1850                        right_remaining = right_new_remaining;
1851
1852                        (left_prefix, right_prefix)
1853                    };
1854                    let mut left_iter = left_prefix.iter();
1855                    let mut right_iter = right_prefix.iter();
1856
1857                    let mut left_secondary;
1858                    let mut right_secondary;
1859                    loop {
1860                        loop {
1861                            left_secondary = left_iter.next_back().unwrap_or_default().secondary();
1862                            if left_secondary != 0 {
1863                                break;
1864                            }
1865                        }
1866                        loop {
1867                            right_secondary =
1868                                right_iter.next_back().unwrap_or_default().secondary();
1869                            if right_secondary != 0 {
1870                                break;
1871                            }
1872                        }
1873                        if left_secondary != right_secondary {
1874                            if left_secondary < right_secondary {
1875                                return Ordering::Less;
1876                            }
1877                            return Ordering::Greater;
1878                        }
1879                        if left_secondary == NO_CE_SECONDARY {
1880                            break;
1881                        }
1882                    }
1883                }
1884            }
1885        }
1886
1887        if self.options.case_level() {
1888            let mut left_non_primary;
1889            let mut right_non_primary;
1890            let mut left_case;
1891            let mut right_case;
1892            let mut left_iter = left_ces.iter();
1893            let mut right_iter = right_ces.iter();
1894            if self.options.strength() == Strength::Primary {
1895                // Primary+caseLevel: Ignore case level weights of primary ignorables.
1896                // Otherwise we would get a-umlaut > a
1897                // which is not desirable for accent-insensitive sorting.
1898                // Check for (lower 32 bits) == 0 as well because variable CEs are stored
1899                // with only primary weights.
1900                loop {
1901                    loop {
1902                        let ce = left_iter.next().unwrap_or_default();
1903                        left_non_primary = ce.non_primary();
1904                        if !ce.either_half_zero() {
1905                            break;
1906                        }
1907                    }
1908                    left_case = left_non_primary.case();
1909                    loop {
1910                        let ce = right_iter.next().unwrap_or_default();
1911                        right_non_primary = ce.non_primary();
1912                        if !ce.either_half_zero() {
1913                            break;
1914                        }
1915                    }
1916                    right_case = right_non_primary.case();
1917                    // No need to handle NO_CE and MERGE_SEPARATOR specially:
1918                    // There is one case weight for each previous-level weight,
1919                    // so level length differences were handled there.
1920                    if left_case != right_case {
1921                        if !self.options.upper_first() {
1922                            if left_case < right_case {
1923                                return Ordering::Less;
1924                            }
1925                            return Ordering::Greater;
1926                        }
1927                        if left_case < right_case {
1928                            return Ordering::Greater;
1929                        }
1930                        return Ordering::Less;
1931                    }
1932                    if left_non_primary.secondary() == NO_CE_SECONDARY {
1933                        break;
1934                    }
1935                }
1936            } else {
1937                // Secondary+caseLevel: By analogy with the above,
1938                // ignore case level weights of secondary ignorables.
1939                //
1940                // Note: A tertiary CE has uppercase case bits (0.0.ut)
1941                // to keep tertiary+caseFirst well-formed.
1942                //
1943                // Tertiary+caseLevel: Also ignore case level weights of secondary ignorables.
1944                // Otherwise a tertiary CE's uppercase would be no greater than
1945                // a primary/secondary CE's uppercase.
1946                // (See UCA well-formedness condition 2.)
1947                // We could construct a special case weight higher than uppercase,
1948                // but it's simpler to always ignore case weights of secondary ignorables,
1949                // turning 0.0.ut into 0.0.0.t.
1950                // (See LDML Collation, Case Parameters.)
1951                loop {
1952                    loop {
1953                        left_non_primary = left_iter.next().unwrap_or_default().non_primary();
1954                        if left_non_primary.secondary() != 0 {
1955                            break;
1956                        }
1957                    }
1958                    left_case = left_non_primary.case();
1959                    loop {
1960                        right_non_primary = right_iter.next().unwrap_or_default().non_primary();
1961                        if right_non_primary.secondary() != 0 {
1962                            break;
1963                        }
1964                    }
1965                    right_case = right_non_primary.case();
1966                    // No need to handle NO_CE and MERGE_SEPARATOR specially:
1967                    // There is one case weight for each previous-level weight,
1968                    // so level length differences were handled there.
1969                    if left_case != right_case {
1970                        if !self.options.upper_first() {
1971                            if left_case < right_case {
1972                                return Ordering::Less;
1973                            }
1974                            return Ordering::Greater;
1975                        }
1976                        if left_case < right_case {
1977                            return Ordering::Greater;
1978                        }
1979                        return Ordering::Less;
1980                    }
1981                    if left_non_primary.secondary() == NO_CE_SECONDARY {
1982                        break;
1983                    }
1984                }
1985            }
1986        }
1987
1988        if let Some(tertiary_mask) = self.options.tertiary_mask() {
1989            let mut any_quaternaries = AnyQuaternaryAccumulator::new();
1990            let mut left_iter = left_ces.iter();
1991            let mut right_iter = right_ces.iter();
1992            loop {
1993                let mut left_non_primary;
1994                let mut left_tertiary;
1995                loop {
1996                    left_non_primary = left_iter.next().unwrap_or_default().non_primary();
1997                    any_quaternaries.accumulate(left_non_primary);
1998                    debug_assert!(
1999                        left_non_primary.tertiary() != 0 || left_non_primary.case_quaternary() == 0
2000                    );
2001                    left_tertiary = left_non_primary.tertiary_case_quarternary(tertiary_mask);
2002                    if left_tertiary != 0 {
2003                        break;
2004                    }
2005                }
2006
2007                let mut right_non_primary;
2008                let mut right_tertiary;
2009                loop {
2010                    right_non_primary = right_iter.next().unwrap_or_default().non_primary();
2011                    any_quaternaries.accumulate(right_non_primary);
2012                    debug_assert!(
2013                        right_non_primary.tertiary() != 0
2014                            || right_non_primary.case_quaternary() == 0
2015                    );
2016                    right_tertiary = right_non_primary.tertiary_case_quarternary(tertiary_mask);
2017                    if right_tertiary != 0 {
2018                        break;
2019                    }
2020                }
2021
2022                if left_tertiary != right_tertiary {
2023                    if self.options.upper_first() {
2024                        // Pass through NO_CE and keep real tertiary weights larger than that.
2025                        // Do not change the artificial uppercase weight of a tertiary CE (0.0.ut),
2026                        // to keep tertiary CEs well-formed.
2027                        // Their case+tertiary weights must be greater than those of
2028                        // primary and secondary CEs.
2029                        // Magic numbers from ICU4C.
2030                        if left_tertiary > NO_CE_TERTIARY {
2031                            if left_non_primary.secondary() != 0 {
2032                                left_tertiary ^= 0xC000;
2033                            } else {
2034                                left_tertiary += 0x4000;
2035                            }
2036                        }
2037                        if right_tertiary > NO_CE_TERTIARY {
2038                            if right_non_primary.secondary() != 0 {
2039                                right_tertiary ^= 0xC000;
2040                            } else {
2041                                right_tertiary += 0x4000;
2042                            }
2043                        }
2044                    }
2045                    if left_tertiary < right_tertiary {
2046                        return Ordering::Less;
2047                    }
2048                    return Ordering::Greater;
2049                }
2050
2051                if left_tertiary == NO_CE_TERTIARY {
2052                    break;
2053                }
2054            }
2055            if !any_variable && !any_quaternaries.has_quaternary() {
2056                return Ordering::Equal;
2057            }
2058        } else {
2059            return Ordering::Equal;
2060        }
2061
2062        if self.options.strength() <= Strength::Tertiary {
2063            return Ordering::Equal;
2064        }
2065
2066        let mut left_iter = left_ces.iter();
2067        let mut right_iter = right_ces.iter();
2068        loop {
2069            let mut left_quaternary;
2070            loop {
2071                let ce = left_iter.next().unwrap_or_default();
2072                if ce.tertiary_ignorable() {
2073                    left_quaternary = ce.primary();
2074                } else {
2075                    left_quaternary = ce.quaternary();
2076                }
2077                if left_quaternary != 0 {
2078                    break;
2079                }
2080            }
2081            let mut right_quaternary;
2082            loop {
2083                let ce = right_iter.next().unwrap_or_default();
2084                if ce.tertiary_ignorable() {
2085                    right_quaternary = ce.primary();
2086                } else {
2087                    right_quaternary = ce.quaternary();
2088                }
2089                if right_quaternary != 0 {
2090                    break;
2091                }
2092            }
2093            if left_quaternary != right_quaternary {
2094                if let Some(reordering) = &self.reordering {
2095                    left_quaternary = reordering.reorder(left_quaternary);
2096                    right_quaternary = reordering.reorder(right_quaternary);
2097                }
2098                if left_quaternary < right_quaternary {
2099                    return Ordering::Less;
2100                }
2101                return Ordering::Greater;
2102            }
2103            if left_quaternary == NO_CE_PRIMARY {
2104                break;
2105            }
2106        }
2107
2108        Ordering::Equal
2109    }
2110
2111    fn sort_key_levels(&self) -> u8 {
2112        #[expect(clippy::indexing_slicing)]
2113        let mut levels = LEVEL_MASKS[self.options.strength() as usize];
2114        if self.options.case_level() {
2115            levels |= CASE_LEVEL_FLAG;
2116        }
2117        levels
2118    }
2119
2120    /// Given valid UTF-8, write the sort key bytes up to the collator's strength.
2121    ///
2122    /// The bytes are written to an implementor of [`CollationKeySink`], a no-std version of `std::io::Write`.
2123    /// This trait is currently unstable, but it is implemented by `Vec<u8>`, `VecDeque<u8>`,
2124    /// `SmallVec<[u8; N]>`, and `&mut [u8]` (returning an error if the slice is too small).
2125    ///
2126    /// If two sort keys generated at the same strength are compared bytewise, the result is
2127    /// the same as a collation comparison of the original strings at that strength.
2128    ///
2129    /// For identical strength, the UTF-8 NFD normalization is appended for breaking ties.
2130    ///
2131    /// No terminating zero byte is written to the output, so the output is not a valid C
2132    /// string, but the caller may append a zero afterward if a C string is desired.
2133    ///
2134    /// ⚠️ Generating a sort key is expensive relative to comparison because to compare, the
2135    /// collator skips identical prefixes before doing more complex comparison.  Only use sort
2136    /// keys if you expect to compare them many times so as to amortize the cost of generating
2137    /// them.  Measurement of this performance trade-off would be a good idea.
2138    ///
2139    /// ⚠️ Sort keys, if stored durably, should be presumed to be invalidated by a CLDR update, a
2140    /// new version of Unicode, or an update to the ICU4X code.  Applications using sort keys
2141    /// *must* be prepared to recompute them if required and should take the performance of
2142    /// such an operation into account when deciding to use sort keys.
2143    ///
2144    /// ⚠️ If you should store sort keys in a database that is or becomes so large that
2145    /// regenerating sort keys becomes impractical, you should not expect ICU4X to support your
2146    /// using an older, frozen copy of the sort key generation algorithm with a later version
2147    /// of the library.
2148    ///
2149    /// # Example
2150    ///
2151    /// ```
2152    /// use icu_collator::{
2153    ///     options::{CollatorOptions, Strength},
2154    ///     Collator,
2155    /// };
2156    /// use icu_locale::locale;
2157    /// let locale = locale!("utf").into();
2158    /// let mut options = CollatorOptions::default();
2159    /// options.strength = Some(Strength::Primary);
2160    /// let collator = Collator::try_new(locale, options).unwrap();
2161    ///
2162    /// let mut k1 = Vec::new();
2163    /// let Ok(()) = collator.write_sort_key_to("hello", &mut k1);
2164    /// let mut k2 = Vec::new();
2165    /// let Ok(()) = collator.write_sort_key_to("Héłłö", &mut k2);
2166    /// assert_eq!(k1, k2);
2167    /// ```
2168    pub fn write_sort_key_to<S>(&self, s: &str, sink: &mut S) -> Result<S::Output, S::Error>
2169    where
2170        S: CollationKeySink + ?Sized,
2171        S::State: Default,
2172    {
2173        self.write_sort_key_impl(s.chars_with_trie_default_for_ascii(self.norm_trie()), sink)
2174    }
2175
2176    /// Given potentially invalid UTF-8, write the sort key bytes up to the collator's strength.
2177    ///
2178    /// For further details, see [`Self::write_sort_key_to`].
2179    pub fn write_sort_key_utf8_to<S>(&self, s: &[u8], sink: &mut S) -> Result<S::Output, S::Error>
2180    where
2181        S: CollationKeySink + ?Sized,
2182        S::State: Default,
2183    {
2184        self.write_sort_key_impl(s.chars_with_trie_default_for_ascii(self.norm_trie()), sink)
2185    }
2186
2187    /// Given potentially invalid UTF-16, write the sort key bytes up to the collator's strength.
2188    ///
2189    /// For further details, see [`Self::write_sort_key_to`].
2190    pub fn write_sort_key_utf16_to<S>(&self, s: &[u16], sink: &mut S) -> Result<S::Output, S::Error>
2191    where
2192        S: CollationKeySink + ?Sized,
2193        S::State: Default,
2194    {
2195        self.write_sort_key_impl(s.chars_with_trie(self.norm_trie()), sink)
2196    }
2197
2198    fn write_sort_key_impl<I, T, S>(
2199        &'data self,
2200        iter: I,
2201        sink: &mut S,
2202    ) -> Result<S::Output, S::Error>
2203    where
2204        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + Clone + 'data,
2205        T: AbstractCodePointTrie<'data, u32> + 'data,
2206        S: CollationKeySink + ?Sized,
2207        S::State: Default,
2208    {
2209        let identical = if self.options.strength() == Strength::Identical {
2210            Some(iter.clone())
2211        } else {
2212            None
2213        };
2214
2215        let mut state = S::State::default();
2216        self.write_sort_key_up_to_quaternary(iter, sink, &mut state)?;
2217
2218        if let Some(iter) = identical {
2219            sink.write_byte(&mut state, LEVEL_SEPARATOR_BYTE)?;
2220
2221            let mut iter = icu_normalizer::new_decomposition(iter, self.tables);
2222            let _ = iter.next(); // Discard the U+0000.
2223            write_identical_level(iter, sink, &mut state)?;
2224        }
2225
2226        sink.finish(state)
2227    }
2228
2229    /// Write the sort key bytes up to the collator's strength.
2230    ///
2231    /// Optionally write the case level.  Separate levels with the `LEVEL_SEPARATOR_BYTE`, but
2232    /// do not write a terminating zero as with a C string.
2233    fn write_sort_key_up_to_quaternary<I, S, T>(
2234        &'data self,
2235        iter: I,
2236        sink: &mut S,
2237        state: &mut S::State,
2238    ) -> Result<(), S::Error>
2239    where
2240        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + Clone + 'data,
2241        T: AbstractCodePointTrie<'data, u32> + 'data,
2242        S: CollationKeySink + ?Sized,
2243    {
2244        // This algorithm comes from `CollationKeys::writeSortKeyUpToQuaternary` in ICU4C.
2245        let levels = self.sort_key_levels();
2246
2247        let mut iter = collation_elements!(self, iter, self.numeric_primary());
2248        iter.init();
2249        let variable_top = self.variable_top();
2250
2251        let tertiary_mask = self.options.tertiary_mask().unwrap_or_default();
2252
2253        let mut cases = SortKeyLevel::default();
2254        let mut secondaries = SortKeyLevel::default();
2255        let mut tertiaries = SortKeyLevel::default();
2256        let mut quaternaries = SortKeyLevel::default();
2257
2258        let mut prev_reordered_primary = 0;
2259        let mut common_cases = 0usize;
2260        let mut common_secondaries = 0usize;
2261        let mut common_tertiaries = 0usize;
2262        let mut common_quaternaries = 0usize;
2263        let mut prev_secondary = 0;
2264        let mut sec_segment_start = 0;
2265
2266        loop {
2267            let mut ce = iter.next();
2268            let mut p = ce.primary();
2269            if p < variable_top && p > MERGE_SEPARATOR_PRIMARY {
2270                // Variable CE, shift it to quaternary level.  Ignore all following primary
2271                // ignorables, and shift further variable CEs.
2272                if common_quaternaries != 0 {
2273                    common_quaternaries -= 1;
2274                    while common_quaternaries >= QUAT_COMMON[WEIGHT_MAX_COUNT] as _ {
2275                        quaternaries.append_byte(QUAT_COMMON[WEIGHT_MIDDLE]);
2276                        common_quaternaries -= QUAT_COMMON[WEIGHT_MAX_COUNT] as usize;
2277                    }
2278                    // Shifted primary weights are lower than the common weight.
2279                    quaternaries.append_byte(QUAT_COMMON[WEIGHT_LOW] + common_quaternaries as u8);
2280                    common_quaternaries = 0;
2281                }
2282
2283                loop {
2284                    if levels & QUATERNARY_LEVEL_FLAG != 0 {
2285                        if let Some(reordering) = &self.reordering {
2286                            p = reordering.reorder(p);
2287                        }
2288                        if (p >> 24) as u8 >= QUAT_SHIFTED_LIMIT_BYTE {
2289                            // Prevent shifted primary lead bytes from overlapping with the
2290                            // common compression range.
2291                            quaternaries.append_byte(QUAT_SHIFTED_LIMIT_BYTE);
2292                        }
2293                        quaternaries.append_weight_32(p);
2294                    }
2295                    loop {
2296                        ce = iter.next();
2297                        p = ce.primary();
2298                        if p != 0 {
2299                            break;
2300                        }
2301                    }
2302                    if !(p < variable_top && p > MERGE_SEPARATOR_PRIMARY) {
2303                        break;
2304                    }
2305                }
2306            }
2307
2308            // ce could be primary ignorable, or NO_CE, or the merge separator, or a regular
2309            // primary CE, but it is not variable.  If ce == NO_CE, then write nothing for the
2310            // primary level but terminate compression on all levels and then exit the loop.
2311            if p > NO_CE_PRIMARY && levels & PRIMARY_LEVEL_FLAG != 0 {
2312                // Test the un-reordered primary for compressibility.
2313                let is_compressible = self.special_primaries.is_compressible((p >> 24) as _);
2314                if let Some(reordering) = &self.reordering {
2315                    p = reordering.reorder(p);
2316                }
2317                let p1 = (p >> 24) as u8;
2318                if !is_compressible || p1 != (prev_reordered_primary >> 24) as u8 {
2319                    if prev_reordered_primary != 0 {
2320                        if p < prev_reordered_primary {
2321                            // No primary compression terminator at the end of the level or
2322                            // merged segment.
2323                            if p1 > MERGE_SEPARATOR_BYTE {
2324                                sink.write(state, &[PRIMARY_COMPRESSION_LOW_BYTE])?;
2325                            }
2326                        } else {
2327                            sink.write(state, &[PRIMARY_COMPRESSION_HIGH_BYTE])?;
2328                        }
2329                    }
2330                    sink.write_byte(state, p1)?;
2331                    prev_reordered_primary = if is_compressible { p } else { 0 };
2332                }
2333
2334                let p2 = (p >> 16) as u8;
2335                if p2 != 0 {
2336                    let (b0, b1, b2) = (p2, (p >> 8) as _, p as _);
2337                    sink.write_byte(state, b0)?;
2338                    if b1 != 0 {
2339                        sink.write_byte(state, b1)?;
2340                        if b2 != 0 {
2341                            sink.write_byte(state, b2)?;
2342                        }
2343                    }
2344                }
2345            }
2346
2347            let non_primary = ce.non_primary();
2348            if non_primary.ignorable() {
2349                continue; // completely ignorable, no secondary/case/tertiary/quaternary
2350            }
2351
2352            macro_rules! handle_common {
2353                ($key:ident, $w:ident, $common:ident, $weights:ident, $lim:expr) => {
2354                    if $common != 0 {
2355                        $common -= 1;
2356                        while $common >= $weights[WEIGHT_MAX_COUNT] as _ {
2357                            $key.append_byte($weights[WEIGHT_MIDDLE]);
2358                            $common -= $weights[WEIGHT_MAX_COUNT] as usize;
2359                        }
2360                        let b = if $w < $lim {
2361                            $weights[WEIGHT_LOW] + ($common as u8)
2362                        } else {
2363                            $weights[WEIGHT_HIGH] - ($common as u8)
2364                        };
2365                        $key.append_byte(b);
2366                        $common = 0;
2367                    }
2368                };
2369                ($key:ident, $w:ident, $common:ident, $weights:ident) => {
2370                    handle_common!($key, $w, $common, $weights, COMMON_WEIGHT16);
2371                };
2372            }
2373
2374            if levels & SECONDARY_LEVEL_FLAG != 0 {
2375                let s = non_primary.secondary();
2376                if s == 0 {
2377                    // secondary ignorable
2378                } else if s == COMMON_WEIGHT16
2379                    && (!self.options.backward_second_level() || p != MERGE_SEPARATOR_PRIMARY)
2380                {
2381                    // s is a common secondary weight, and backwards-secondary is off or the ce
2382                    // is not the merge separator.
2383                    common_secondaries += 1;
2384                } else if !self.options.backward_second_level() {
2385                    handle_common!(secondaries, s, common_secondaries, SEC_COMMON);
2386                    secondaries.append_weight_16(s);
2387                } else {
2388                    if common_secondaries != 0 {
2389                        common_secondaries -= 1;
2390                        // Append reverse weights.  The level will be re-reversed later.
2391                        let remainder = common_secondaries % SEC_COMMON[WEIGHT_MAX_COUNT] as usize;
2392                        let b = if prev_secondary < COMMON_WEIGHT16 {
2393                            SEC_COMMON[WEIGHT_LOW] + remainder as u8
2394                        } else {
2395                            SEC_COMMON[WEIGHT_HIGH] - remainder as u8
2396                        };
2397                        secondaries.append_byte(b);
2398                        common_secondaries -= remainder;
2399                        // common_secondaries is now a multiple of SEC_COMMON[WEIGHT_MAX_COUNT]
2400                        while common_secondaries > 0 {
2401                            // same as >= SEC_COMMON[WEIGHT_MAX_COUNT]
2402                            secondaries.append_byte(SEC_COMMON[WEIGHT_MIDDLE]);
2403                            common_secondaries -= SEC_COMMON[WEIGHT_MAX_COUNT] as usize;
2404                        }
2405                        // commonSecondaries == 0
2406                    }
2407                    if 0 < p && p <= MERGE_SEPARATOR_PRIMARY {
2408                        // The backwards secondary level compares secondary weights backwards
2409                        // within segments separated by the merge separator (U+FFFE).
2410                        let secs = &mut secondaries.buf;
2411                        let last = secs.len() - 1;
2412                        if sec_segment_start < last {
2413                            let mut q = sec_segment_start;
2414                            let mut r = last;
2415
2416                            // these indices start at valid values and we stop when they cross
2417                            #[expect(clippy::indexing_slicing)]
2418                            while q < r {
2419                                let b = secs[q];
2420                                secs[q] = secs[r];
2421                                q += 1;
2422                                secs[r] = b;
2423                                r -= 1;
2424                            }
2425                        }
2426                        let b = if p == NO_CE_PRIMARY {
2427                            LEVEL_SEPARATOR_BYTE
2428                        } else {
2429                            MERGE_SEPARATOR_BYTE
2430                        };
2431                        secondaries.append_byte(b);
2432                        prev_secondary = 0;
2433                        sec_segment_start = secondaries.len();
2434                    } else {
2435                        secondaries.append_reverse_weight_16(s);
2436                        prev_secondary = s;
2437                    }
2438                }
2439            }
2440
2441            if levels & CASE_LEVEL_FLAG != 0 {
2442                if self.options.strength() == Strength::Primary && p == 0
2443                    || non_primary.bits() <= 0xffff
2444                {
2445                    // Primary+caseLevel: Ignore case level weights of primary ignorables.
2446                    // Otherwise: Ignore case level weights of secondary ignorables.  For
2447                    // details see the comments in the CollationCompare class.
2448                } else {
2449                    // case bits & tertiary lead byte
2450                    let mut c = ((non_primary.bits() >> 8) & 0xff) as u8;
2451                    debug_assert_ne!(c & 0xc0, 0xc0);
2452                    if c & 0xc0 == 0 && c > LEVEL_SEPARATOR_BYTE {
2453                        common_cases += 1;
2454                    } else {
2455                        if !self.options.upper_first() {
2456                            // lower first:  Compress common weights to nibbles 1..7..13,
2457                            // mixed=14, upper=15.  If there are only common (=lowest) weights
2458                            // in the whole level, then we need not write anything.  Level
2459                            // length differences are handled already on the next-higher level.
2460                            if common_cases != 0 && (c > LEVEL_SEPARATOR_BYTE || !cases.is_empty())
2461                            {
2462                                common_cases -= 1;
2463                                while common_cases >= CASE_LOWER_FIRST_COMMON[WEIGHT_MAX_COUNT] as _
2464                                {
2465                                    cases.append_byte(CASE_LOWER_FIRST_COMMON[WEIGHT_MIDDLE] << 4);
2466                                    common_cases -=
2467                                        CASE_LOWER_FIRST_COMMON[WEIGHT_MAX_COUNT] as usize;
2468                                }
2469                                let b = if c <= LEVEL_SEPARATOR_BYTE {
2470                                    CASE_LOWER_FIRST_COMMON[WEIGHT_LOW] + common_cases as u8
2471                                } else {
2472                                    CASE_LOWER_FIRST_COMMON[WEIGHT_HIGH] - common_cases as u8
2473                                };
2474                                cases.append_byte(b << 4);
2475                                common_cases = 0;
2476                            }
2477                            if c > LEVEL_SEPARATOR_BYTE {
2478                                // 14 or 15
2479                                c = (CASE_LOWER_FIRST_COMMON[WEIGHT_HIGH] + (c >> 6)) << 4;
2480                            }
2481                        } else {
2482                            // upper first:  Compress common weights to nibbles 3..15, mixed=2,
2483                            // upper=1.  The compressed common case weights only go up from the
2484                            // "low" value because with upperFirst the common weight is the
2485                            // highest one.
2486                            if common_cases != 0 {
2487                                common_cases -= 1;
2488                                while common_cases >= CASE_UPPER_FIRST_COMMON[WEIGHT_MAX_COUNT] as _
2489                                {
2490                                    cases.append_byte(CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] << 4);
2491                                    common_cases -=
2492                                        CASE_UPPER_FIRST_COMMON[WEIGHT_MAX_COUNT] as usize;
2493                                }
2494                                cases.append_byte(
2495                                    (CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] + common_cases as u8) << 4,
2496                                );
2497                                common_cases = 0;
2498                            }
2499                            if c > LEVEL_SEPARATOR_BYTE {
2500                                // 2 or 1
2501                                c = (CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] - (c >> 6)) << 4;
2502                            }
2503                        }
2504                        // c is a separator byte 01 or a left-shifted nibble 0x10, 0x20, ...
2505                        // 0xf0.
2506                        cases.append_byte(c);
2507                    }
2508                }
2509            }
2510
2511            if levels & TERTIARY_LEVEL_FLAG != 0 {
2512                let mut t = non_primary.tertiary_case_quarternary(tertiary_mask);
2513                debug_assert_ne!(non_primary.bits() & 0xc000, 0xc000);
2514                if t == COMMON_WEIGHT16 {
2515                    common_tertiaries += 1;
2516                } else if tertiary_mask & 0x8000 == 0 {
2517                    // Tertiary weights without case bits.  Move lead bytes 06..3F to C6..FF
2518                    // for a large common-weight range.
2519                    handle_common!(tertiaries, t, common_tertiaries, TER_ONLY_COMMON);
2520                    if t > COMMON_WEIGHT16 {
2521                        t += 0xc000;
2522                    }
2523                    tertiaries.append_weight_16(t);
2524                } else if !self.options.upper_first() {
2525                    // Tertiary weights with caseFirst=lowerFirst.  Move lead bytes 06..BF to
2526                    // 46..FF for the common-weight range.
2527                    handle_common!(tertiaries, t, common_tertiaries, TER_LOWER_FIRST_COMMON);
2528                    if t > COMMON_WEIGHT16 {
2529                        t += 0x4000;
2530                    }
2531                    tertiaries.append_weight_16(t);
2532                } else {
2533                    // Tertiary weights with caseFirst=upperFirst.  Do not change the
2534                    // artificial uppercase weight of a tertiary CE (0.0.ut), to keep tertiary
2535                    // CEs well-formed.  Their case+tertiary weights must be greater than those
2536                    // of primary and secondary CEs.
2537                    //
2538                    // Separator         01 -> 01      (unchanged)
2539                    // Lowercase     02..04 -> 82..84  (includes uncased)
2540                    // Common weight     05 -> 85..C5  (common-weight compression range)
2541                    // Lowercase     06..3F -> C6..FF
2542                    // Mixed case    42..7F -> 42..7F
2543                    // Uppercase     82..BF -> 02..3F
2544                    // Tertiary CE   86..BF -> C6..FF
2545                    if t <= NO_CE_TERTIARY {
2546                        // Keep separators unchanged.
2547                    } else if non_primary.bits() > 0xffff {
2548                        // Invert case bits of primary & secondary CEs.
2549                        t ^= 0xc000;
2550                        if t < (TER_UPPER_FIRST_COMMON[WEIGHT_HIGH] as u16) << 8 {
2551                            t -= 0x4000;
2552                        }
2553                    } else {
2554                        // Keep uppercase bits of tertiary CEs.
2555                        debug_assert!((0x8600..=0xbfff).contains(&t));
2556                        t += 0x4000;
2557                    }
2558                    handle_common!(
2559                        tertiaries,
2560                        t,
2561                        common_tertiaries,
2562                        TER_UPPER_FIRST_COMMON,
2563                        (TER_UPPER_FIRST_COMMON[WEIGHT_LOW] as u16) << 8
2564                    );
2565                    tertiaries.append_weight_16(t);
2566                }
2567            }
2568
2569            if levels & QUATERNARY_LEVEL_FLAG != 0 {
2570                let q = (non_primary.bits() & 0xffff) as u16;
2571                if q & 0xc0 == 0 && q > NO_CE_QUATERNARY {
2572                    common_quaternaries += 1;
2573                } else if q == NO_CE_QUATERNARY
2574                    && self.options.alternate_handling() == AlternateHandling::NonIgnorable
2575                    && quaternaries.is_empty()
2576                {
2577                    // If alternate=non-ignorable and there are only common quaternary weights,
2578                    // then we need not write anything.  The only weights greater than the
2579                    // merge separator and less than the common weight are shifted primary
2580                    // weights, which are not generated for alternate=non-ignorable.  There are
2581                    // also exactly as many quaternary weights as tertiary weights, so level
2582                    // length differences are handled already on tertiary level.  Any
2583                    // above-common quaternary weight will compare greater regardless.
2584                    quaternaries.append_byte(LEVEL_SEPARATOR_BYTE);
2585                } else {
2586                    let q = if q == NO_CE_QUATERNARY {
2587                        LEVEL_SEPARATOR_BYTE
2588                    } else {
2589                        (0xfc + ((q >> 6) & 3)) as u8
2590                    };
2591                    handle_common!(
2592                        quaternaries,
2593                        q,
2594                        common_quaternaries,
2595                        QUAT_COMMON,
2596                        QUAT_COMMON[WEIGHT_LOW]
2597                    );
2598                    quaternaries.append_byte(q);
2599                }
2600            }
2601
2602            if (non_primary.bits() >> 24) as u8 == LEVEL_SEPARATOR_BYTE {
2603                break; // ce == NO_CE
2604            }
2605        }
2606
2607        macro_rules! write_level {
2608            ($key:ident, $flag:ident) => {
2609                if levels & $flag != 0 {
2610                    sink.write(state, &[LEVEL_SEPARATOR_BYTE])?;
2611                    sink.write(state, &$key.buf)?;
2612                }
2613            };
2614        }
2615
2616        write_level!(secondaries, SECONDARY_LEVEL_FLAG);
2617
2618        if levels & CASE_LEVEL_FLAG != 0 {
2619            sink.write(state, &[LEVEL_SEPARATOR_BYTE])?;
2620
2621            // Write pairs of nibbles as bytes, except separator bytes as themselves.
2622            let mut b = 0;
2623            if let Some((last, head)) = cases.buf.split_last() {
2624                debug_assert_eq!(*last, 1); // The trailing NO_CE
2625                for c in head {
2626                    debug_assert_eq!(*c & 0xf, 0);
2627                    debug_assert_ne!(*c, 0);
2628                    if b == 0 {
2629                        b = *c;
2630                    } else {
2631                        sink.write_byte(state, b | (*c >> 4))?;
2632                        b = 0;
2633                    }
2634                }
2635            } else {
2636                debug_assert!(false);
2637            }
2638            if b != 0 {
2639                sink.write_byte(state, b)?;
2640            }
2641        }
2642
2643        write_level!(tertiaries, TERTIARY_LEVEL_FLAG);
2644        write_level!(quaternaries, QUATERNARY_LEVEL_FLAG);
2645
2646        Ok(())
2647    }
2648}
2649
2650/// Error indicating that a [`CollationKeySink`] with limited space ran out of space.
2651#[derive(Debug, PartialEq, Eq)]
2652pub struct TooSmall {
2653    /// The total length, in bytes, of the sort key.
2654    pub length: usize,
2655}
2656
2657impl TooSmall {
2658    pub fn new(length: usize) -> Self {
2659        Self { length }
2660    }
2661}
2662
2663/// A [`std::io::Write`]-like trait for writing to a buffer-like object.
2664///
2665/// (This crate does not have access to [`std`].)
2666///
2667/// <div class="stab unstable">
2668/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
2669/// including in SemVer minor releases. Do not implement or call methods on this trait
2670/// unless you are prepared for things to occasionally break.
2671///
2672/// Graduation tracking issue: [issue #7178](https://github.com/unicode-org/icu4x/issues/7178).
2673/// </div>
2674///
2675/// ✨ *Enabled with the `unstable` Cargo feature.*
2676pub trait CollationKeySink {
2677    /// The type of error the sink may return.
2678    type Error;
2679
2680    /// An intermediate state object used by the sink, which must implement [`Default`].
2681    type State;
2682
2683    /// A result value indicating the final state of the sink (e.g. a number of bytes written).
2684    type Output;
2685
2686    /// Writes a buffer into the writer.
2687    fn write(&mut self, state: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error>;
2688
2689    /// Write a single byte into the writer.
2690    fn write_byte(&mut self, state: &mut Self::State, b: u8) -> Result<(), Self::Error> {
2691        self.write(state, &[b])
2692    }
2693
2694    /// Finalize any internal sink state (perhaps by flushing a buffer) and return the final
2695    /// output value.
2696    fn finish(&mut self, state: Self::State) -> Result<Self::Output, Self::Error>;
2697}
2698
2699impl CollationKeySink for Vec<u8> {
2700    type Error = Infallible;
2701    type State = ();
2702    type Output = ();
2703
2704    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2705        self.extend_from_slice(buf);
2706        Ok(())
2707    }
2708
2709    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2710        Ok(())
2711    }
2712}
2713
2714impl CollationKeySink for VecDeque<u8> {
2715    type Error = Infallible;
2716    type State = ();
2717    type Output = ();
2718
2719    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2720        self.extend(buf.iter());
2721        Ok(())
2722    }
2723
2724    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2725        Ok(())
2726    }
2727}
2728
2729impl<const N: usize> CollationKeySink for SmallVec<[u8; N]> {
2730    type Error = Infallible;
2731    type State = ();
2732    type Output = ();
2733
2734    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2735        self.extend_from_slice(buf);
2736        Ok(())
2737    }
2738
2739    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2740        Ok(())
2741    }
2742}
2743
2744impl CollationKeySink for [u8] {
2745    type Error = TooSmall;
2746    type State = usize;
2747    type Output = usize;
2748
2749    fn write(&mut self, offset: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2750        if *offset + buf.len() <= self.len() {
2751            // just checked bounds
2752            #[expect(clippy::indexing_slicing)]
2753            self[*offset..*offset + buf.len()].copy_from_slice(buf);
2754        }
2755        *offset += buf.len();
2756        Ok(())
2757    }
2758
2759    fn finish(&mut self, offset: Self::State) -> Result<Self::Output, Self::Error> {
2760        if offset <= self.len() {
2761            Ok(offset)
2762        } else {
2763            Err(TooSmall::new(offset))
2764        }
2765    }
2766}
2767
2768#[derive(Default)]
2769struct SortKeyLevel {
2770    buf: SmallVec<[u8; 40]>,
2771}
2772
2773impl SortKeyLevel {
2774    fn len(&self) -> usize {
2775        self.buf.len()
2776    }
2777
2778    fn is_empty(&self) -> bool {
2779        self.buf.is_empty()
2780    }
2781
2782    fn append_byte(&mut self, x: u8) {
2783        self.buf.push(x);
2784    }
2785
2786    fn append_weight_16(&mut self, w: u16) {
2787        debug_assert_ne!(w, 0);
2788        let b0 = (w >> 8) as u8;
2789        let b1 = w as u8;
2790        self.append_byte(b0);
2791        if b1 != 0 {
2792            self.append_byte(b1);
2793        }
2794    }
2795
2796    fn append_reverse_weight_16(&mut self, w: u16) {
2797        debug_assert_ne!(w, 0);
2798        let b0 = (w >> 8) as u8;
2799        let b1 = w as u8;
2800        if b1 != 0 {
2801            self.append_byte(b1);
2802        }
2803        self.append_byte(b0);
2804    }
2805
2806    fn append_weight_32(&mut self, w: u32) {
2807        debug_assert_ne!(w, 0);
2808        let b0 = (w >> 24) as u8;
2809        let b1 = (w >> 16) as u8;
2810        let b2 = (w >> 8) as u8;
2811        let b3 = w as u8;
2812        self.append_byte(b0);
2813        if b1 != 0 {
2814            self.append_byte(b1);
2815            if b2 != 0 {
2816                self.append_byte(b2);
2817                if b3 != 0 {
2818                    self.append_byte(b3);
2819                }
2820            }
2821        }
2822    }
2823}
2824
2825// The algorithm below (BOCSU or Binary Ordered Compression Scheme for Unicode) is translated
2826// from the C++ code in ICU4C at icu4c/source/i18n/bocsu.{cpp,h}.  The algorithm works by
2827// converting a sequence of codepoints into a sequence of presumably small differences.  See
2828// the C++ code for a more detailed explanation.
2829
2830macro_rules! negdivmod {
2831    ($n:ident, $d:ident, $m:ident) => {
2832        $m = $n % $d;
2833        $n /= $d;
2834        if $m < 0 {
2835            $n -= 1;
2836            $m += $d;
2837        }
2838    };
2839}
2840
2841fn write_diff<S>(mut diff: i32, sink: &mut S, state: &mut S::State) -> Result<(), S::Error>
2842where
2843    S: CollationKeySink + ?Sized,
2844{
2845    let mut out = |b| sink.write_byte(state, b);
2846
2847    if diff >= SLOPE_REACH_NEG_1 {
2848        if diff <= SLOPE_REACH_POS_1 {
2849            out((SLOPE_MIDDLE + diff) as _)?;
2850        } else if diff <= SLOPE_REACH_POS_2 {
2851            out((SLOPE_START_POS_2 + (diff / SLOPE_TAIL_COUNT)) as _)?;
2852            out((SLOPE_MIN + diff % SLOPE_TAIL_COUNT) as _)?;
2853        } else if diff <= SLOPE_REACH_POS_3 {
2854            let p2 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2855            diff /= SLOPE_TAIL_COUNT;
2856            let p1 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2857            let p0 = SLOPE_START_POS_3 + (diff / SLOPE_TAIL_COUNT);
2858            out(p0 as _)?;
2859            out(p1 as _)?;
2860            out(p2 as _)?;
2861        } else {
2862            let p3 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2863            diff /= SLOPE_TAIL_COUNT;
2864            let p2 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2865            diff /= SLOPE_TAIL_COUNT;
2866            let p1 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2867            out(SLOPE_MAX as _)?;
2868            out(p1 as _)?;
2869            out(p2 as _)?;
2870            out(p3 as _)?;
2871        }
2872    } else {
2873        let mut m;
2874
2875        if diff >= SLOPE_REACH_NEG_2 {
2876            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2877            out((SLOPE_START_NEG_2 + diff) as _)?;
2878            out((SLOPE_MIN + m) as _)?;
2879        } else if diff >= SLOPE_REACH_NEG_3 {
2880            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2881            let p2 = SLOPE_MIN + m;
2882            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2883            let p1 = SLOPE_MIN + m;
2884            let p0 = SLOPE_START_NEG_3 + diff;
2885            out(p0 as _)?;
2886            out(p1 as _)?;
2887            out(p2 as _)?;
2888        } else {
2889            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2890            let p3 = SLOPE_MIN + m;
2891            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2892            let p2 = SLOPE_MIN + m;
2893            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2894            let p1 = SLOPE_MIN + m;
2895            let _ = diff;
2896            out(SLOPE_MIN as _)?;
2897            out(p1 as _)?;
2898            out(p2 as _)?;
2899            out(p3 as _)?;
2900        }
2901    }
2902
2903    Ok(())
2904}
2905
2906fn write_identical_level<I, S>(iter: I, sink: &mut S, state: &mut S::State) -> Result<(), S::Error>
2907where
2908    I: Iterator<Item = char>,
2909    S: CollationKeySink + ?Sized,
2910{
2911    let mut prev = 0i32;
2912
2913    for c in iter {
2914        if !(0x4e00..=0xa000).contains(&prev) {
2915            prev = (prev & !0x7f) - SLOPE_REACH_NEG_1;
2916        } else {
2917            // Unihan U+4e00..U+9fa5:  double-bytes down from the upper end
2918            prev = 0x9fff - SLOPE_REACH_POS_2;
2919        }
2920
2921        if c == MERGE_SEPARATOR {
2922            sink.write_byte(state, MERGE_SEPARATOR_BYTE)?;
2923            prev = 0;
2924        } else {
2925            let c = c as i32;
2926            write_diff(c - prev, sink, state)?;
2927            prev = c;
2928        }
2929    }
2930    Ok(())
2931}
2932
2933#[cfg(test)]
2934mod test {
2935    use super::*;
2936    use icu_locale::locale;
2937
2938    type Key = Vec<u8>;
2939
2940    fn collator_en(strength: Strength) -> CollatorBorrowed<'static> {
2941        let locale = locale!("en").into();
2942        let mut options = CollatorOptions::default();
2943        options.strength = Some(strength);
2944        Collator::try_new(locale, options).unwrap()
2945    }
2946
2947    fn collator_en_case_level(strength: Strength) -> CollatorBorrowed<'static> {
2948        let locale = locale!("en").into();
2949        let mut options = CollatorOptions::default();
2950        options.strength = Some(strength);
2951        options.case_level = Some(crate::options::CaseLevel::On);
2952        Collator::try_new(locale, options).unwrap()
2953    }
2954
2955    fn keys(strength: Strength) -> (Key, Key, Key) {
2956        let collator = collator_en(strength);
2957
2958        let mut k0 = Vec::new();
2959        let Ok(()) = collator.write_sort_key_to("aabc", &mut k0);
2960        let mut k1 = Vec::new();
2961        let Ok(()) = collator.write_sort_key_to("aAbc", &mut k1);
2962        let mut k2 = Vec::new();
2963        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k2);
2964
2965        (k0, k1, k2)
2966    }
2967
2968    #[test]
2969    fn sort_key_primary() {
2970        let (k0, k1, k2) = keys(Strength::Primary);
2971        assert_eq!(k0, k1);
2972        assert_eq!(k1, k2);
2973    }
2974
2975    #[test]
2976    fn sort_key_secondary() {
2977        let (k0, k1, k2) = keys(Strength::Secondary);
2978        assert_eq!(k0, k1);
2979        assert!(k1 < k2);
2980    }
2981
2982    #[test]
2983    fn sort_key_tertiary() {
2984        let (k0, k1, k2) = keys(Strength::Tertiary);
2985        assert!(k0 < k1);
2986        assert!(k1 < k2);
2987    }
2988
2989    fn collator_ja(strength: Strength) -> CollatorBorrowed<'static> {
2990        let locale = locale!("ja").into();
2991        let mut options = CollatorOptions::default();
2992        options.strength = Some(strength);
2993        Collator::try_new(locale, options).unwrap()
2994    }
2995
2996    fn keys_ja_strs(strength: Strength, s0: &str, s1: &str) -> (Key, Key) {
2997        let collator = collator_ja(strength);
2998
2999        let mut k0 = Vec::new();
3000        let Ok(()) = collator.write_sort_key_to(s0, &mut k0);
3001        let mut k1 = Vec::new();
3002        let Ok(()) = collator.write_sort_key_to(s1, &mut k1);
3003
3004        (k0, k1)
3005    }
3006
3007    fn keys_ja(strength: Strength) -> (Key, Key) {
3008        keys_ja_strs(strength, "あ", "ア")
3009    }
3010
3011    #[test]
3012    fn sort_keys_ja_to_quaternary() {
3013        let (k0, k1) = keys_ja(Strength::Primary);
3014        assert_eq!(k0, k1);
3015        let (k0, k1) = keys_ja(Strength::Secondary);
3016        assert_eq!(k0, k1);
3017        let (k0, k1) = keys_ja(Strength::Tertiary);
3018        assert_eq!(k0, k1);
3019        let (k0, k1) = keys_ja(Strength::Quaternary);
3020        assert!(k0 < k1);
3021    }
3022
3023    #[test]
3024    fn sort_keys_ja_identical() {
3025        let (k0, k1) = keys_ja_strs(Strength::Quaternary, "ア", "ア");
3026        assert_eq!(k0, k1);
3027        let (k0, k1) = keys_ja_strs(Strength::Identical, "ア", "ア");
3028        assert!(k0 < k1);
3029    }
3030
3031    #[test]
3032    fn sort_keys_utf16() {
3033        let collator = collator_en(Strength::Identical);
3034
3035        const STR8: &[u8] = b"hello world!";
3036        let mut k8 = Vec::new();
3037        let Ok(()) = collator.write_sort_key_utf8_to(STR8, &mut k8);
3038
3039        const STR16: &[u16] = &[
3040            0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21,
3041        ];
3042        let mut k16 = Vec::new();
3043        let Ok(()) = collator.write_sort_key_utf16_to(STR16, &mut k16);
3044        assert_eq!(k8, k16);
3045    }
3046
3047    #[test]
3048    fn sort_keys_invalid() {
3049        let collator = collator_en(Strength::Identical);
3050
3051        // some invalid strings
3052        let mut k = Vec::new();
3053        let Ok(()) = collator.write_sort_key_utf8_to(b"\xf0\x90", &mut k);
3054        let mut k = Vec::new();
3055        let Ok(()) = collator.write_sort_key_utf16_to(&[0xdd1e], &mut k);
3056    }
3057
3058    #[test]
3059    fn sort_key_to_vecdeque() {
3060        let collator = collator_en(Strength::Identical);
3061
3062        let mut k0 = Vec::new();
3063        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k0);
3064        let mut k1 = VecDeque::new();
3065        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k1);
3066        assert!(k0.iter().eq(k1.iter()));
3067    }
3068
3069    #[test]
3070    fn sort_key_to_slice() {
3071        let collator = collator_en(Strength::Identical);
3072
3073        let mut k0 = Vec::new();
3074        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k0);
3075        let mut k1 = [0u8; 100];
3076        let len = collator.write_sort_key_to("áAbc", &mut k1[..]).unwrap();
3077        assert_eq!(len, k0.len());
3078        assert!(k0.iter().eq(k1[..len].iter()));
3079    }
3080
3081    #[test]
3082    fn sort_key_to_slice_no_space() {
3083        let collator = collator_en(Strength::Identical);
3084        let mut k = [0u8; 0];
3085        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3086        assert!(matches!(res, Err(TooSmall { .. })));
3087    }
3088
3089    #[test]
3090    fn sort_key_to_slice_too_long() {
3091        // This runs out of space in write_sort_key_up_to_quaternary.
3092        let collator = collator_en(Strength::Identical);
3093        let mut k = [0u8; 5];
3094        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3095        assert!(matches!(res, Err(TooSmall { .. })));
3096    }
3097
3098    #[test]
3099    fn sort_key_to_slice_identical_too_long() {
3100        // This runs out of space while appending UTF-8 in the SinkAdapter.
3101        let collator = collator_en(Strength::Identical);
3102        let mut k = [0u8; 22];
3103        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3104        assert!(matches!(res, Err(TooSmall { .. })));
3105    }
3106
3107    #[test]
3108    fn sort_key_just_right() {
3109        // get the length needed
3110        let collator = collator_en(Strength::Identical);
3111        let mut k = [0u8; 0];
3112        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3113        let len = res.unwrap_err().length;
3114
3115        // almost enough
3116        let mut k = vec![0u8; len - 1];
3117        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3118        let len = res.unwrap_err().length;
3119
3120        // just right
3121        let mut k = vec![0u8; len];
3122        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3123        assert_eq!(res, Ok(len));
3124    }
3125
3126    #[test]
3127    fn sort_key_utf16_slice_too_small() {
3128        let collator = collator_en(Strength::Identical);
3129        const STR16: &[u16] = &[0x68, 0x65, 0x6c, 0x6c, 0x6f];
3130        let mut k = [0u8; 4];
3131        let res = collator.write_sort_key_utf16_to(STR16, &mut k[..]);
3132        assert!(matches!(res, Err(TooSmall { .. })));
3133    }
3134
3135    #[test]
3136    fn sort_key_very_long() {
3137        let collator = collator_en(Strength::Secondary);
3138        let mut k = Vec::new();
3139        let Ok(()) = collator.write_sort_key_to(&"a".repeat(300), &mut k);
3140    }
3141
3142    #[test]
3143    fn sort_key_case_level() {
3144        let collator = collator_en_case_level(Strength::Tertiary);
3145        let mut k = Vec::new();
3146        let Ok(()) = collator.write_sort_key_to("aBc", &mut k);
3147    }
3148
3149    #[test]
3150    fn sort_key_case_level_empty() {
3151        let collator = collator_en_case_level(Strength::Tertiary);
3152        let mut k = Vec::new();
3153        let Ok(()) = collator.write_sort_key_to("", &mut k);
3154    }
3155
3156    fn check_sort_key_less(a: &[u16], b: &[u16]) {
3157        let collator = collator_en(Strength::Identical);
3158        let mut ak = Vec::new();
3159        let Ok(()) = collator.write_sort_key_utf16_to(a, &mut ak);
3160        let mut bk = Vec::new();
3161        let Ok(()) = collator.write_sort_key_utf16_to(b, &mut bk);
3162        assert!(ak < bk, "failed: {a:04x?} - {b:04x?}");
3163    }
3164
3165    #[test]
3166    fn sort_key_fffe_bug_6811() {
3167        check_sort_key_less(
3168            &[0xfffe, 0x0001, 0x0002, 0x0003],
3169            &[0x0001, 0xfffe, 0x0002, 0x0003],
3170        );
3171        check_sort_key_less(
3172            &[0x0001, 0xfffe, 0x0002, 0x0003],
3173            &[0x0001, 0x0002, 0xfffe, 0x0003],
3174        );
3175        check_sort_key_less(
3176            &[0x0001, 0x0002, 0xfffe, 0x0003],
3177            &[0x0001, 0x0002, 0x0003, 0xfffe],
3178        );
3179        check_sort_key_less(&[0xfffe, 0x0000, 0x0000], &[0x0000, 0xfffe, 0x0000]);
3180        check_sort_key_less(&[0x0000, 0xfffe, 0x0000], &[0x0000, 0x0000, 0xfffe]);
3181    }
3182}