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                            {
1567                                // Avoid giving the zero the leading zero treatment.
1568                                break;
1569                            }
1570
1571                            // We're at a good boundary but could not make a quick primary comparison decision.
1572
1573                            // Note: It might look like a good idea to cache the CE32s, but
1574                            // doing so actually makes things slower.
1575                            break 'prefix;
1576                        }
1577                    }
1578                }
1579                let mut tail_first_c;
1580                let mut tail_first_ce32;
1581                let mut tail_first_ok;
1582                loop {
1583                    // Take a step back.
1584                    left.prepend_upcoming_before_init(head_last.clone());
1585                    right.prepend_upcoming_before_init(head_last.clone());
1586
1587                    tail_first_c = head_last_c;
1588                    tail_first_ce32 = head_last_ce32;
1589                    tail_first_ok = head_last_ok;
1590
1591                    let Some((head_last_c_new, decomposition)) = head_chars.next_back() else {
1592                        // We need to step back beyond the start of the prefix.
1593                        // Treat as good boundary.
1594                        break 'prefix;
1595                    };
1596                    head_last_c = head_last_c_new;
1597                    head_last = CharacterAndClassAndTrieValue::new_with_trie_val(
1598                        head_last_c,
1599                        decomposition,
1600                    );
1601                    head_last_ce32 = CollationElement32::default();
1602                    head_last_ok = false;
1603
1604                    if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1605                        // Intentionally empty block to keep
1606                        // the same structure as in the cases
1607                        // where something happens here.
1608                    } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1609                        && ((decomposition & LOW_ZEROS_MASK) != 0)
1610                    {
1611                        // Decomposition into two BMP characters: starter and non-starter
1612                        // Let's take the starter
1613                        head_last_c = char_from_u32(decomposition & 0x7FFF);
1614                    } else if decomposition == HANGUL_SYLLABLE_MARKER {
1615                        head_last_ce32 = FFFD_CE32;
1616                    } else {
1617                        continue;
1618                    }
1619                    head_last_ok = true;
1620                    if !tail_first_ok {
1621                        continue;
1622                    }
1623                    // The last character of the prefix is OK on the normalization
1624                    // level. Now let's check its ce32 unless it's a Hangul syllable.
1625                    if head_last_ce32 == CollationElement32::default() {
1626                        head_last_ce32 = self.tailoring.ce32_for_char(head_last_c);
1627                        if head_last_ce32 == FALLBACK_CE32 {
1628                            head_last_ce32 = self.root.ce32_for_char(head_last_c);
1629                        }
1630                        if head_last_ce32.tag_checked() == Some(Tag::Contraction)
1631                            && head_last_ce32.at_least_one_suffix_contains_starter()
1632                        {
1633                            continue;
1634                        }
1635                    }
1636                    // Check this _after_ `head_last_ce32` to make sure
1637                    // `head_last_ce32` is initialized for the next loop round
1638                    // trip if applicable.
1639                    if tail_first_ce32 == CollationElement32::default() {
1640                        tail_first_ce32 = self.tailoring.ce32_for_char(tail_first_c);
1641                        if tail_first_ce32 == FALLBACK_CE32 {
1642                            tail_first_ce32 = self.root.ce32_for_char(tail_first_c);
1643                        }
1644                    } // else we already have a trie value from the previous loop iteration or we have Hangul syllable
1645                    if tail_first_ce32.tag_checked() == Some(Tag::Prefix) {
1646                        continue;
1647                    }
1648                    if numeric_primary.is_some()
1649                        && head_last_ce32.tag_checked() == Some(Tag::Digit)
1650                        && tail_first_ce32.tag_checked() == Some(Tag::Digit)
1651                        && tail_first_ce32.digit() == 0
1652                    {
1653                        // Avoid giving the zero the leading zero treatment.
1654                        continue;
1655                    }
1656                    // We are at a good boundary!
1657                    break 'prefix;
1658                }
1659            } else {
1660                // The prefix is empty
1661                break 'prefix;
1662            }
1663            // Unreachable line
1664        }
1665
1666        // End identical prefix
1667
1668        left.init();
1669        right.init();
1670
1671        loop {
1672            let mut left_primary;
1673            'left_primary_loop: loop {
1674                let ce = left.next();
1675                left_primary = ce.primary();
1676                // TODO(#2008): Consider compiling out the variable handling when we know we aren't
1677                // shifting variable CEs.
1678                if !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY) {
1679                    left_ces.push(ce);
1680                } else {
1681                    // Variable CE, shift it to quaternary level.
1682                    // Ignore all following primary ignorables, and shift further variable CEs.
1683                    any_variable = true;
1684                    // Relative to ICU4C, the next line is hoisted out of the following loop
1685                    // in order to keep the variables called `ce` immutable to make it easier
1686                    // to reason about each assignment into `ce` resulting in exactly a single
1687                    // push into `left_ces`.
1688                    left_ces.push(ce.clone_with_non_primary_zeroed());
1689                    loop {
1690                        // This loop is simpler than in ICU4C; unlike in C++, we get to break by label.
1691                        let ce = left.next();
1692                        left_primary = ce.primary();
1693                        if left_primary != 0
1694                            && !(left_primary < variable_top
1695                                && left_primary > MERGE_SEPARATOR_PRIMARY)
1696                        {
1697                            // Neither a primary ignorable nor a variable CE.
1698                            left_ces.push(ce);
1699                            break 'left_primary_loop;
1700                        }
1701                        // If `left_primary == 0`, the following line ignores a primary-ignorable.
1702                        // Otherwise, it shifts a variable CE.
1703                        left_ces.push(ce.clone_with_non_primary_zeroed());
1704                    }
1705                }
1706                if left_primary != 0 {
1707                    break;
1708                }
1709            }
1710            let mut right_primary;
1711            'right_primary_loop: loop {
1712                let ce = right.next();
1713                right_primary = ce.primary();
1714                // TODO(#2008): Consider compiling out the variable handling when we know we aren't
1715                // shifting variable CEs.
1716                if !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY) {
1717                    right_ces.push(ce);
1718                } else {
1719                    // Variable CE, shift it to quaternary level.
1720                    // Ignore all following primary ignorables, and shift further variable CEs.
1721                    any_variable = true;
1722                    // Relative to ICU4C, the next line is hoisted out of the following loop
1723                    // in order to keep the variables called `ce` immutable to make it easier
1724                    // to reason about each assignment into `ce` resulting in exactly a single
1725                    // push into `right_ces`.
1726                    right_ces.push(ce.clone_with_non_primary_zeroed());
1727                    loop {
1728                        // This loop is simpler than in ICU4C; unlike in C++, we get to break by label.
1729                        let ce = right.next();
1730                        right_primary = ce.primary();
1731                        if right_primary != 0
1732                            && !(right_primary < variable_top
1733                                && right_primary > MERGE_SEPARATOR_PRIMARY)
1734                        {
1735                            // Neither a primary ignorable nor a variable CE.
1736                            right_ces.push(ce);
1737                            break 'right_primary_loop;
1738                        }
1739                        // If `right_primary == 0`, the following line ignores a primary-ignorable.
1740                        // Otherwise, it shifts a variable CE.
1741                        right_ces.push(ce.clone_with_non_primary_zeroed());
1742                    }
1743                }
1744                if right_primary != 0 {
1745                    break;
1746                }
1747            }
1748            if left_primary != right_primary {
1749                if let Some(reordering) = &self.reordering {
1750                    left_primary = reordering.reorder(left_primary);
1751                    right_primary = reordering.reorder(right_primary);
1752                }
1753                if left_primary < right_primary {
1754                    return Ordering::Less;
1755                }
1756                return Ordering::Greater;
1757            }
1758            if left_primary == NO_CE_PRIMARY {
1759                break;
1760            }
1761        }
1762
1763        // Sadly, we end up pushing the sentinel value, which means these
1764        // `SmallVec`s allocate more often than if we didn't actually
1765        // store the sentinel.
1766        debug_assert_eq!(left_ces.last(), Some(&NO_CE));
1767        debug_assert_eq!(right_ces.last(), Some(&NO_CE));
1768
1769        // Note: `unwrap_or_default` in the iterations below should never
1770        // actually end up using the "_or_default" part, because the sentinel
1771        // is in the `SmallVec`s. These could be changed to `unwrap()` if we
1772        // preferred panic in case of a bug.
1773        // TODO(#2009): Should we save one slot by not putting the sentinel in
1774        // the `SmallVec`s? So far, the answer seems "no", as it would complicate
1775        // the primary comparison above.
1776
1777        // Compare the buffered secondary & tertiary weights.
1778        // We might skip the secondary level but continue with the case level
1779        // which is turned on separately.
1780        if self.options.strength() >= Strength::Secondary {
1781            if !self.options.backward_second_level() {
1782                let mut left_iter = left_ces.iter();
1783                let mut right_iter = right_ces.iter();
1784                let mut left_secondary;
1785                let mut right_secondary;
1786                loop {
1787                    loop {
1788                        left_secondary = left_iter.next().unwrap_or_default().secondary();
1789                        if left_secondary != 0 {
1790                            break;
1791                        }
1792                    }
1793                    loop {
1794                        right_secondary = right_iter.next().unwrap_or_default().secondary();
1795                        if right_secondary != 0 {
1796                            break;
1797                        }
1798                    }
1799                    if left_secondary != right_secondary {
1800                        if left_secondary < right_secondary {
1801                            return Ordering::Less;
1802                        }
1803                        return Ordering::Greater;
1804                    }
1805                    if left_secondary == NO_CE_SECONDARY {
1806                        break;
1807                    }
1808                }
1809            } else {
1810                let mut left_remaining = &left_ces[..];
1811                let mut right_remaining = &right_ces[..];
1812                loop {
1813                    if left_remaining.is_empty() {
1814                        debug_assert!(right_remaining.is_empty());
1815                        break;
1816                    }
1817                    let (left_prefix, right_prefix) = {
1818                        let mut left_iter = left_remaining.iter();
1819                        loop {
1820                            let left_primary = left_iter.next().unwrap_or_default().primary();
1821                            if left_primary != 0 && left_primary <= MERGE_SEPARATOR_PRIMARY {
1822                                break;
1823                            }
1824                            debug_assert_ne!(left_primary, NO_CE_PRIMARY);
1825                        }
1826                        let left_new_remaining = left_iter.as_slice();
1827                        // Index in range by construction
1828                        #[expect(clippy::indexing_slicing)]
1829                        let left_prefix =
1830                            &left_remaining[..left_remaining.len() - 1 - left_new_remaining.len()];
1831                        left_remaining = left_new_remaining;
1832
1833                        let mut right_iter = right_remaining.iter();
1834                        loop {
1835                            let right_primary = right_iter.next().unwrap_or_default().primary();
1836                            if right_primary != 0 && right_primary <= MERGE_SEPARATOR_PRIMARY {
1837                                break;
1838                            }
1839                            debug_assert_ne!(right_primary, NO_CE_PRIMARY);
1840                        }
1841                        let right_new_remaining = right_iter.as_slice();
1842                        // Index in range by construction
1843                        #[expect(clippy::indexing_slicing)]
1844                        let right_prefix = &right_remaining
1845                            [..right_remaining.len() - 1 - right_new_remaining.len()];
1846                        right_remaining = right_new_remaining;
1847
1848                        (left_prefix, right_prefix)
1849                    };
1850                    let mut left_iter = left_prefix.iter();
1851                    let mut right_iter = right_prefix.iter();
1852
1853                    let mut left_secondary;
1854                    let mut right_secondary;
1855                    loop {
1856                        loop {
1857                            left_secondary = left_iter.next_back().unwrap_or_default().secondary();
1858                            if left_secondary != 0 {
1859                                break;
1860                            }
1861                        }
1862                        loop {
1863                            right_secondary =
1864                                right_iter.next_back().unwrap_or_default().secondary();
1865                            if right_secondary != 0 {
1866                                break;
1867                            }
1868                        }
1869                        if left_secondary != right_secondary {
1870                            if left_secondary < right_secondary {
1871                                return Ordering::Less;
1872                            }
1873                            return Ordering::Greater;
1874                        }
1875                        if left_secondary == NO_CE_SECONDARY {
1876                            break;
1877                        }
1878                    }
1879                }
1880            }
1881        }
1882
1883        if self.options.case_level() {
1884            let mut left_non_primary;
1885            let mut right_non_primary;
1886            let mut left_case;
1887            let mut right_case;
1888            let mut left_iter = left_ces.iter();
1889            let mut right_iter = right_ces.iter();
1890            if self.options.strength() == Strength::Primary {
1891                // Primary+caseLevel: Ignore case level weights of primary ignorables.
1892                // Otherwise we would get a-umlaut > a
1893                // which is not desirable for accent-insensitive sorting.
1894                // Check for (lower 32 bits) == 0 as well because variable CEs are stored
1895                // with only primary weights.
1896                loop {
1897                    loop {
1898                        let ce = left_iter.next().unwrap_or_default();
1899                        left_non_primary = ce.non_primary();
1900                        if !ce.either_half_zero() {
1901                            break;
1902                        }
1903                    }
1904                    left_case = left_non_primary.case();
1905                    loop {
1906                        let ce = right_iter.next().unwrap_or_default();
1907                        right_non_primary = ce.non_primary();
1908                        if !ce.either_half_zero() {
1909                            break;
1910                        }
1911                    }
1912                    right_case = right_non_primary.case();
1913                    // No need to handle NO_CE and MERGE_SEPARATOR specially:
1914                    // There is one case weight for each previous-level weight,
1915                    // so level length differences were handled there.
1916                    if left_case != right_case {
1917                        if !self.options.upper_first() {
1918                            if left_case < right_case {
1919                                return Ordering::Less;
1920                            }
1921                            return Ordering::Greater;
1922                        }
1923                        if left_case < right_case {
1924                            return Ordering::Greater;
1925                        }
1926                        return Ordering::Less;
1927                    }
1928                    if left_non_primary.secondary() == NO_CE_SECONDARY {
1929                        break;
1930                    }
1931                }
1932            } else {
1933                // Secondary+caseLevel: By analogy with the above,
1934                // ignore case level weights of secondary ignorables.
1935                //
1936                // Note: A tertiary CE has uppercase case bits (0.0.ut)
1937                // to keep tertiary+caseFirst well-formed.
1938                //
1939                // Tertiary+caseLevel: Also ignore case level weights of secondary ignorables.
1940                // Otherwise a tertiary CE's uppercase would be no greater than
1941                // a primary/secondary CE's uppercase.
1942                // (See UCA well-formedness condition 2.)
1943                // We could construct a special case weight higher than uppercase,
1944                // but it's simpler to always ignore case weights of secondary ignorables,
1945                // turning 0.0.ut into 0.0.0.t.
1946                // (See LDML Collation, Case Parameters.)
1947                loop {
1948                    loop {
1949                        left_non_primary = left_iter.next().unwrap_or_default().non_primary();
1950                        if left_non_primary.secondary() != 0 {
1951                            break;
1952                        }
1953                    }
1954                    left_case = left_non_primary.case();
1955                    loop {
1956                        right_non_primary = right_iter.next().unwrap_or_default().non_primary();
1957                        if right_non_primary.secondary() != 0 {
1958                            break;
1959                        }
1960                    }
1961                    right_case = right_non_primary.case();
1962                    // No need to handle NO_CE and MERGE_SEPARATOR specially:
1963                    // There is one case weight for each previous-level weight,
1964                    // so level length differences were handled there.
1965                    if left_case != right_case {
1966                        if !self.options.upper_first() {
1967                            if left_case < right_case {
1968                                return Ordering::Less;
1969                            }
1970                            return Ordering::Greater;
1971                        }
1972                        if left_case < right_case {
1973                            return Ordering::Greater;
1974                        }
1975                        return Ordering::Less;
1976                    }
1977                    if left_non_primary.secondary() == NO_CE_SECONDARY {
1978                        break;
1979                    }
1980                }
1981            }
1982        }
1983
1984        if let Some(tertiary_mask) = self.options.tertiary_mask() {
1985            let mut any_quaternaries = AnyQuaternaryAccumulator::new();
1986            let mut left_iter = left_ces.iter();
1987            let mut right_iter = right_ces.iter();
1988            loop {
1989                let mut left_non_primary;
1990                let mut left_tertiary;
1991                loop {
1992                    left_non_primary = left_iter.next().unwrap_or_default().non_primary();
1993                    any_quaternaries.accumulate(left_non_primary);
1994                    debug_assert!(
1995                        left_non_primary.tertiary() != 0 || left_non_primary.case_quaternary() == 0
1996                    );
1997                    left_tertiary = left_non_primary.tertiary_case_quarternary(tertiary_mask);
1998                    if left_tertiary != 0 {
1999                        break;
2000                    }
2001                }
2002
2003                let mut right_non_primary;
2004                let mut right_tertiary;
2005                loop {
2006                    right_non_primary = right_iter.next().unwrap_or_default().non_primary();
2007                    any_quaternaries.accumulate(right_non_primary);
2008                    debug_assert!(
2009                        right_non_primary.tertiary() != 0
2010                            || right_non_primary.case_quaternary() == 0
2011                    );
2012                    right_tertiary = right_non_primary.tertiary_case_quarternary(tertiary_mask);
2013                    if right_tertiary != 0 {
2014                        break;
2015                    }
2016                }
2017
2018                if left_tertiary != right_tertiary {
2019                    if self.options.upper_first() {
2020                        // Pass through NO_CE and keep real tertiary weights larger than that.
2021                        // Do not change the artificial uppercase weight of a tertiary CE (0.0.ut),
2022                        // to keep tertiary CEs well-formed.
2023                        // Their case+tertiary weights must be greater than those of
2024                        // primary and secondary CEs.
2025                        // Magic numbers from ICU4C.
2026                        if left_tertiary > NO_CE_TERTIARY {
2027                            if left_non_primary.secondary() != 0 {
2028                                left_tertiary ^= 0xC000;
2029                            } else {
2030                                left_tertiary += 0x4000;
2031                            }
2032                        }
2033                        if right_tertiary > NO_CE_TERTIARY {
2034                            if right_non_primary.secondary() != 0 {
2035                                right_tertiary ^= 0xC000;
2036                            } else {
2037                                right_tertiary += 0x4000;
2038                            }
2039                        }
2040                    }
2041                    if left_tertiary < right_tertiary {
2042                        return Ordering::Less;
2043                    }
2044                    return Ordering::Greater;
2045                }
2046
2047                if left_tertiary == NO_CE_TERTIARY {
2048                    break;
2049                }
2050            }
2051            if !any_variable && !any_quaternaries.has_quaternary() {
2052                return Ordering::Equal;
2053            }
2054        } else {
2055            return Ordering::Equal;
2056        }
2057
2058        if self.options.strength() <= Strength::Tertiary {
2059            return Ordering::Equal;
2060        }
2061
2062        let mut left_iter = left_ces.iter();
2063        let mut right_iter = right_ces.iter();
2064        loop {
2065            let mut left_quaternary;
2066            loop {
2067                let ce = left_iter.next().unwrap_or_default();
2068                if ce.tertiary_ignorable() {
2069                    left_quaternary = ce.primary();
2070                } else {
2071                    left_quaternary = ce.quaternary();
2072                }
2073                if left_quaternary != 0 {
2074                    break;
2075                }
2076            }
2077            let mut right_quaternary;
2078            loop {
2079                let ce = right_iter.next().unwrap_or_default();
2080                if ce.tertiary_ignorable() {
2081                    right_quaternary = ce.primary();
2082                } else {
2083                    right_quaternary = ce.quaternary();
2084                }
2085                if right_quaternary != 0 {
2086                    break;
2087                }
2088            }
2089            if left_quaternary != right_quaternary {
2090                if let Some(reordering) = &self.reordering {
2091                    left_quaternary = reordering.reorder(left_quaternary);
2092                    right_quaternary = reordering.reorder(right_quaternary);
2093                }
2094                if left_quaternary < right_quaternary {
2095                    return Ordering::Less;
2096                }
2097                return Ordering::Greater;
2098            }
2099            if left_quaternary == NO_CE_PRIMARY {
2100                break;
2101            }
2102        }
2103
2104        Ordering::Equal
2105    }
2106
2107    fn sort_key_levels(&self) -> u8 {
2108        #[expect(clippy::indexing_slicing)]
2109        let mut levels = LEVEL_MASKS[self.options.strength() as usize];
2110        if self.options.case_level() {
2111            levels |= CASE_LEVEL_FLAG;
2112        }
2113        levels
2114    }
2115
2116    /// Given valid UTF-8, write the sort key bytes up to the collator's strength.
2117    ///
2118    /// The bytes are written to an implementor of [`CollationKeySink`], a no-std version of `std::io::Write`.
2119    /// This trait is currently unstable, but it is implemented by `Vec<u8>`, `VecDeque<u8>`,
2120    /// `SmallVec<[u8; N]>`, and `&mut [u8]` (returning an error if the slice is too small).
2121    ///
2122    /// If two sort keys generated at the same strength are compared bytewise, the result is
2123    /// the same as a collation comparison of the original strings at that strength.
2124    ///
2125    /// For identical strength, the UTF-8 NFD normalization is appended for breaking ties.
2126    ///
2127    /// No terminating zero byte is written to the output, so the output is not a valid C
2128    /// string, but the caller may append a zero afterward if a C string is desired.
2129    ///
2130    /// ⚠️ Generating a sort key is expensive relative to comparison because to compare, the
2131    /// collator skips identical prefixes before doing more complex comparison.  Only use sort
2132    /// keys if you expect to compare them many times so as to amortize the cost of generating
2133    /// them.  Measurement of this performance trade-off would be a good idea.
2134    ///
2135    /// ⚠️ Sort keys, if stored durably, should be presumed to be invalidated by a CLDR update, a
2136    /// new version of Unicode, or an update to the ICU4X code.  Applications using sort keys
2137    /// *must* be prepared to recompute them if required and should take the performance of
2138    /// such an operation into account when deciding to use sort keys.
2139    ///
2140    /// ⚠️ If you should store sort keys in a database that is or becomes so large that
2141    /// regenerating sort keys becomes impractical, you should not expect ICU4X to support your
2142    /// using an older, frozen copy of the sort key generation algorithm with a later version
2143    /// of the library.
2144    ///
2145    /// # Example
2146    ///
2147    /// ```
2148    /// use icu_collator::{
2149    ///     options::{CollatorOptions, Strength},
2150    ///     Collator,
2151    /// };
2152    /// use icu_locale::locale;
2153    /// let locale = locale!("utf").into();
2154    /// let mut options = CollatorOptions::default();
2155    /// options.strength = Some(Strength::Primary);
2156    /// let collator = Collator::try_new(locale, options).unwrap();
2157    ///
2158    /// let mut k1 = Vec::new();
2159    /// let Ok(()) = collator.write_sort_key_to("hello", &mut k1);
2160    /// let mut k2 = Vec::new();
2161    /// let Ok(()) = collator.write_sort_key_to("Héłłö", &mut k2);
2162    /// assert_eq!(k1, k2);
2163    /// ```
2164    pub fn write_sort_key_to<S>(&self, s: &str, sink: &mut S) -> Result<S::Output, S::Error>
2165    where
2166        S: CollationKeySink + ?Sized,
2167        S::State: Default,
2168    {
2169        self.write_sort_key_impl(s.chars_with_trie_default_for_ascii(self.norm_trie()), sink)
2170    }
2171
2172    /// Given potentially invalid UTF-8, write the sort key bytes up to the collator's strength.
2173    ///
2174    /// For further details, see [`Self::write_sort_key_to`].
2175    pub fn write_sort_key_utf8_to<S>(&self, s: &[u8], sink: &mut S) -> Result<S::Output, S::Error>
2176    where
2177        S: CollationKeySink + ?Sized,
2178        S::State: Default,
2179    {
2180        self.write_sort_key_impl(s.chars_with_trie_default_for_ascii(self.norm_trie()), sink)
2181    }
2182
2183    /// Given potentially invalid UTF-16, write the sort key bytes up to the collator's strength.
2184    ///
2185    /// For further details, see [`Self::write_sort_key_to`].
2186    pub fn write_sort_key_utf16_to<S>(&self, s: &[u16], sink: &mut S) -> Result<S::Output, S::Error>
2187    where
2188        S: CollationKeySink + ?Sized,
2189        S::State: Default,
2190    {
2191        self.write_sort_key_impl(s.chars_with_trie(self.norm_trie()), sink)
2192    }
2193
2194    fn write_sort_key_impl<I, T, S>(
2195        &'data self,
2196        iter: I,
2197        sink: &mut S,
2198    ) -> Result<S::Output, S::Error>
2199    where
2200        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + Clone + 'data,
2201        T: AbstractCodePointTrie<'data, u32> + 'data,
2202        S: CollationKeySink + ?Sized,
2203        S::State: Default,
2204    {
2205        let identical = if self.options.strength() == Strength::Identical {
2206            Some(iter.clone())
2207        } else {
2208            None
2209        };
2210
2211        let mut state = S::State::default();
2212        self.write_sort_key_up_to_quaternary(iter, sink, &mut state)?;
2213
2214        if let Some(iter) = identical {
2215            sink.write_byte(&mut state, LEVEL_SEPARATOR_BYTE)?;
2216
2217            let mut iter = icu_normalizer::new_decomposition(iter, self.tables);
2218            let _ = iter.next(); // Discard the U+0000.
2219            write_identical_level(iter, sink, &mut state)?;
2220        }
2221
2222        sink.finish(state)
2223    }
2224
2225    /// Write the sort key bytes up to the collator's strength.
2226    ///
2227    /// Optionally write the case level.  Separate levels with the `LEVEL_SEPARATOR_BYTE`, but
2228    /// do not write a terminating zero as with a C string.
2229    fn write_sort_key_up_to_quaternary<I, S, T>(
2230        &'data self,
2231        iter: I,
2232        sink: &mut S,
2233        state: &mut S::State,
2234    ) -> Result<(), S::Error>
2235    where
2236        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + Clone + 'data,
2237        T: AbstractCodePointTrie<'data, u32> + 'data,
2238        S: CollationKeySink + ?Sized,
2239    {
2240        // This algorithm comes from `CollationKeys::writeSortKeyUpToQuaternary` in ICU4C.
2241        let levels = self.sort_key_levels();
2242
2243        let mut iter = collation_elements!(self, iter, self.numeric_primary());
2244        iter.init();
2245        let variable_top = self.variable_top();
2246
2247        let tertiary_mask = self.options.tertiary_mask().unwrap_or_default();
2248
2249        let mut cases = SortKeyLevel::default();
2250        let mut secondaries = SortKeyLevel::default();
2251        let mut tertiaries = SortKeyLevel::default();
2252        let mut quaternaries = SortKeyLevel::default();
2253
2254        let mut prev_reordered_primary = 0;
2255        let mut common_cases = 0usize;
2256        let mut common_secondaries = 0usize;
2257        let mut common_tertiaries = 0usize;
2258        let mut common_quaternaries = 0usize;
2259        let mut prev_secondary = 0;
2260        let mut sec_segment_start = 0;
2261
2262        loop {
2263            let mut ce = iter.next();
2264            let mut p = ce.primary();
2265            if p < variable_top && p > MERGE_SEPARATOR_PRIMARY {
2266                // Variable CE, shift it to quaternary level.  Ignore all following primary
2267                // ignorables, and shift further variable CEs.
2268                if common_quaternaries != 0 {
2269                    common_quaternaries -= 1;
2270                    while common_quaternaries >= QUAT_COMMON[WEIGHT_MAX_COUNT] as _ {
2271                        quaternaries.append_byte(QUAT_COMMON[WEIGHT_MIDDLE]);
2272                        common_quaternaries -= QUAT_COMMON[WEIGHT_MAX_COUNT] as usize;
2273                    }
2274                    // Shifted primary weights are lower than the common weight.
2275                    quaternaries.append_byte(QUAT_COMMON[WEIGHT_LOW] + common_quaternaries as u8);
2276                    common_quaternaries = 0;
2277                }
2278
2279                loop {
2280                    if levels & QUATERNARY_LEVEL_FLAG != 0 {
2281                        if let Some(reordering) = &self.reordering {
2282                            p = reordering.reorder(p);
2283                        }
2284                        if (p >> 24) as u8 >= QUAT_SHIFTED_LIMIT_BYTE {
2285                            // Prevent shifted primary lead bytes from overlapping with the
2286                            // common compression range.
2287                            quaternaries.append_byte(QUAT_SHIFTED_LIMIT_BYTE);
2288                        }
2289                        quaternaries.append_weight_32(p);
2290                    }
2291                    loop {
2292                        ce = iter.next();
2293                        p = ce.primary();
2294                        if p != 0 {
2295                            break;
2296                        }
2297                    }
2298                    if !(p < variable_top && p > MERGE_SEPARATOR_PRIMARY) {
2299                        break;
2300                    }
2301                }
2302            }
2303
2304            // ce could be primary ignorable, or NO_CE, or the merge separator, or a regular
2305            // primary CE, but it is not variable.  If ce == NO_CE, then write nothing for the
2306            // primary level but terminate compression on all levels and then exit the loop.
2307            if p > NO_CE_PRIMARY && levels & PRIMARY_LEVEL_FLAG != 0 {
2308                // Test the un-reordered primary for compressibility.
2309                let is_compressible = self.special_primaries.is_compressible((p >> 24) as _);
2310                if let Some(reordering) = &self.reordering {
2311                    p = reordering.reorder(p);
2312                }
2313                let p1 = (p >> 24) as u8;
2314                if !is_compressible || p1 != (prev_reordered_primary >> 24) as u8 {
2315                    if prev_reordered_primary != 0 {
2316                        if p < prev_reordered_primary {
2317                            // No primary compression terminator at the end of the level or
2318                            // merged segment.
2319                            if p1 > MERGE_SEPARATOR_BYTE {
2320                                sink.write(state, &[PRIMARY_COMPRESSION_LOW_BYTE])?;
2321                            }
2322                        } else {
2323                            sink.write(state, &[PRIMARY_COMPRESSION_HIGH_BYTE])?;
2324                        }
2325                    }
2326                    sink.write_byte(state, p1)?;
2327                    prev_reordered_primary = if is_compressible { p } else { 0 };
2328                }
2329
2330                let p2 = (p >> 16) as u8;
2331                if p2 != 0 {
2332                    let (b0, b1, b2) = (p2, (p >> 8) as _, p as _);
2333                    sink.write_byte(state, b0)?;
2334                    if b1 != 0 {
2335                        sink.write_byte(state, b1)?;
2336                        if b2 != 0 {
2337                            sink.write_byte(state, b2)?;
2338                        }
2339                    }
2340                }
2341            }
2342
2343            let non_primary = ce.non_primary();
2344            if non_primary.ignorable() {
2345                continue; // completely ignorable, no secondary/case/tertiary/quaternary
2346            }
2347
2348            macro_rules! handle_common {
2349                ($key:ident, $w:ident, $common:ident, $weights:ident, $lim:expr) => {
2350                    if $common != 0 {
2351                        $common -= 1;
2352                        while $common >= $weights[WEIGHT_MAX_COUNT] as _ {
2353                            $key.append_byte($weights[WEIGHT_MIDDLE]);
2354                            $common -= $weights[WEIGHT_MAX_COUNT] as usize;
2355                        }
2356                        let b = if $w < $lim {
2357                            $weights[WEIGHT_LOW] + ($common as u8)
2358                        } else {
2359                            $weights[WEIGHT_HIGH] - ($common as u8)
2360                        };
2361                        $key.append_byte(b);
2362                        $common = 0;
2363                    }
2364                };
2365                ($key:ident, $w:ident, $common:ident, $weights:ident) => {
2366                    handle_common!($key, $w, $common, $weights, COMMON_WEIGHT16);
2367                };
2368            }
2369
2370            if levels & SECONDARY_LEVEL_FLAG != 0 {
2371                let s = non_primary.secondary();
2372                if s == 0 {
2373                    // secondary ignorable
2374                } else if s == COMMON_WEIGHT16
2375                    && (!self.options.backward_second_level() || p != MERGE_SEPARATOR_PRIMARY)
2376                {
2377                    // s is a common secondary weight, and backwards-secondary is off or the ce
2378                    // is not the merge separator.
2379                    common_secondaries += 1;
2380                } else if !self.options.backward_second_level() {
2381                    handle_common!(secondaries, s, common_secondaries, SEC_COMMON);
2382                    secondaries.append_weight_16(s);
2383                } else {
2384                    if common_secondaries != 0 {
2385                        common_secondaries -= 1;
2386                        // Append reverse weights.  The level will be re-reversed later.
2387                        let remainder = common_secondaries % SEC_COMMON[WEIGHT_MAX_COUNT] as usize;
2388                        let b = if prev_secondary < COMMON_WEIGHT16 {
2389                            SEC_COMMON[WEIGHT_LOW] + remainder as u8
2390                        } else {
2391                            SEC_COMMON[WEIGHT_HIGH] - remainder as u8
2392                        };
2393                        secondaries.append_byte(b);
2394                        common_secondaries -= remainder;
2395                        // common_secondaries is now a multiple of SEC_COMMON[WEIGHT_MAX_COUNT]
2396                        while common_secondaries > 0 {
2397                            // same as >= SEC_COMMON[WEIGHT_MAX_COUNT]
2398                            secondaries.append_byte(SEC_COMMON[WEIGHT_MIDDLE]);
2399                            common_secondaries -= SEC_COMMON[WEIGHT_MAX_COUNT] as usize;
2400                        }
2401                        // commonSecondaries == 0
2402                    }
2403                    if 0 < p && p <= MERGE_SEPARATOR_PRIMARY {
2404                        // The backwards secondary level compares secondary weights backwards
2405                        // within segments separated by the merge separator (U+FFFE).
2406                        let secs = &mut secondaries.buf;
2407                        let last = secs.len() - 1;
2408                        if sec_segment_start < last {
2409                            let mut q = sec_segment_start;
2410                            let mut r = last;
2411
2412                            // these indices start at valid values and we stop when they cross
2413                            #[expect(clippy::indexing_slicing)]
2414                            while q < r {
2415                                let b = secs[q];
2416                                secs[q] = secs[r];
2417                                q += 1;
2418                                secs[r] = b;
2419                                r -= 1;
2420                            }
2421                        }
2422                        let b = if p == NO_CE_PRIMARY {
2423                            LEVEL_SEPARATOR_BYTE
2424                        } else {
2425                            MERGE_SEPARATOR_BYTE
2426                        };
2427                        secondaries.append_byte(b);
2428                        prev_secondary = 0;
2429                        sec_segment_start = secondaries.len();
2430                    } else {
2431                        secondaries.append_reverse_weight_16(s);
2432                        prev_secondary = s;
2433                    }
2434                }
2435            }
2436
2437            if levels & CASE_LEVEL_FLAG != 0 {
2438                if self.options.strength() == Strength::Primary && p == 0
2439                    || non_primary.bits() <= 0xffff
2440                {
2441                    // Primary+caseLevel: Ignore case level weights of primary ignorables.
2442                    // Otherwise: Ignore case level weights of secondary ignorables.  For
2443                    // details see the comments in the CollationCompare class.
2444                } else {
2445                    // case bits & tertiary lead byte
2446                    let mut c = ((non_primary.bits() >> 8) & 0xff) as u8;
2447                    debug_assert_ne!(c & 0xc0, 0xc0);
2448                    if c & 0xc0 == 0 && c > LEVEL_SEPARATOR_BYTE {
2449                        common_cases += 1;
2450                    } else {
2451                        if !self.options.upper_first() {
2452                            // lower first:  Compress common weights to nibbles 1..7..13,
2453                            // mixed=14, upper=15.  If there are only common (=lowest) weights
2454                            // in the whole level, then we need not write anything.  Level
2455                            // length differences are handled already on the next-higher level.
2456                            if common_cases != 0 && (c > LEVEL_SEPARATOR_BYTE || !cases.is_empty())
2457                            {
2458                                common_cases -= 1;
2459                                while common_cases >= CASE_LOWER_FIRST_COMMON[WEIGHT_MAX_COUNT] as _
2460                                {
2461                                    cases.append_byte(CASE_LOWER_FIRST_COMMON[WEIGHT_MIDDLE] << 4);
2462                                    common_cases -=
2463                                        CASE_LOWER_FIRST_COMMON[WEIGHT_MAX_COUNT] as usize;
2464                                }
2465                                let b = if c <= LEVEL_SEPARATOR_BYTE {
2466                                    CASE_LOWER_FIRST_COMMON[WEIGHT_LOW] + common_cases as u8
2467                                } else {
2468                                    CASE_LOWER_FIRST_COMMON[WEIGHT_HIGH] - common_cases as u8
2469                                };
2470                                cases.append_byte(b << 4);
2471                                common_cases = 0;
2472                            }
2473                            if c > LEVEL_SEPARATOR_BYTE {
2474                                // 14 or 15
2475                                c = (CASE_LOWER_FIRST_COMMON[WEIGHT_HIGH] + (c >> 6)) << 4;
2476                            }
2477                        } else {
2478                            // upper first:  Compress common weights to nibbles 3..15, mixed=2,
2479                            // upper=1.  The compressed common case weights only go up from the
2480                            // "low" value because with upperFirst the common weight is the
2481                            // highest one.
2482                            if common_cases != 0 {
2483                                common_cases -= 1;
2484                                while common_cases >= CASE_UPPER_FIRST_COMMON[WEIGHT_MAX_COUNT] as _
2485                                {
2486                                    cases.append_byte(CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] << 4);
2487                                    common_cases -=
2488                                        CASE_UPPER_FIRST_COMMON[WEIGHT_MAX_COUNT] as usize;
2489                                }
2490                                cases.append_byte(
2491                                    (CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] + common_cases as u8) << 4,
2492                                );
2493                                common_cases = 0;
2494                            }
2495                            if c > LEVEL_SEPARATOR_BYTE {
2496                                // 2 or 1
2497                                c = (CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] - (c >> 6)) << 4;
2498                            }
2499                        }
2500                        // c is a separator byte 01 or a left-shifted nibble 0x10, 0x20, ...
2501                        // 0xf0.
2502                        cases.append_byte(c);
2503                    }
2504                }
2505            }
2506
2507            if levels & TERTIARY_LEVEL_FLAG != 0 {
2508                let mut t = non_primary.tertiary_case_quarternary(tertiary_mask);
2509                debug_assert_ne!(non_primary.bits() & 0xc000, 0xc000);
2510                if t == COMMON_WEIGHT16 {
2511                    common_tertiaries += 1;
2512                } else if tertiary_mask & 0x8000 == 0 {
2513                    // Tertiary weights without case bits.  Move lead bytes 06..3F to C6..FF
2514                    // for a large common-weight range.
2515                    handle_common!(tertiaries, t, common_tertiaries, TER_ONLY_COMMON);
2516                    if t > COMMON_WEIGHT16 {
2517                        t += 0xc000;
2518                    }
2519                    tertiaries.append_weight_16(t);
2520                } else if !self.options.upper_first() {
2521                    // Tertiary weights with caseFirst=lowerFirst.  Move lead bytes 06..BF to
2522                    // 46..FF for the common-weight range.
2523                    handle_common!(tertiaries, t, common_tertiaries, TER_LOWER_FIRST_COMMON);
2524                    if t > COMMON_WEIGHT16 {
2525                        t += 0x4000;
2526                    }
2527                    tertiaries.append_weight_16(t);
2528                } else {
2529                    // Tertiary weights with caseFirst=upperFirst.  Do not change the
2530                    // artificial uppercase weight of a tertiary CE (0.0.ut), to keep tertiary
2531                    // CEs well-formed.  Their case+tertiary weights must be greater than those
2532                    // of primary and secondary CEs.
2533                    //
2534                    // Separator         01 -> 01      (unchanged)
2535                    // Lowercase     02..04 -> 82..84  (includes uncased)
2536                    // Common weight     05 -> 85..C5  (common-weight compression range)
2537                    // Lowercase     06..3F -> C6..FF
2538                    // Mixed case    42..7F -> 42..7F
2539                    // Uppercase     82..BF -> 02..3F
2540                    // Tertiary CE   86..BF -> C6..FF
2541                    if t <= NO_CE_TERTIARY {
2542                        // Keep separators unchanged.
2543                    } else if non_primary.bits() > 0xffff {
2544                        // Invert case bits of primary & secondary CEs.
2545                        t ^= 0xc000;
2546                        if t < (TER_UPPER_FIRST_COMMON[WEIGHT_HIGH] as u16) << 8 {
2547                            t -= 0x4000;
2548                        }
2549                    } else {
2550                        // Keep uppercase bits of tertiary CEs.
2551                        debug_assert!((0x8600..=0xbfff).contains(&t));
2552                        t += 0x4000;
2553                    }
2554                    handle_common!(
2555                        tertiaries,
2556                        t,
2557                        common_tertiaries,
2558                        TER_UPPER_FIRST_COMMON,
2559                        (TER_UPPER_FIRST_COMMON[WEIGHT_LOW] as u16) << 8
2560                    );
2561                    tertiaries.append_weight_16(t);
2562                }
2563            }
2564
2565            if levels & QUATERNARY_LEVEL_FLAG != 0 {
2566                let q = (non_primary.bits() & 0xffff) as u16;
2567                if q & 0xc0 == 0 && q > NO_CE_QUATERNARY {
2568                    common_quaternaries += 1;
2569                } else if q == NO_CE_QUATERNARY
2570                    && self.options.alternate_handling() == AlternateHandling::NonIgnorable
2571                    && quaternaries.is_empty()
2572                {
2573                    // If alternate=non-ignorable and there are only common quaternary weights,
2574                    // then we need not write anything.  The only weights greater than the
2575                    // merge separator and less than the common weight are shifted primary
2576                    // weights, which are not generated for alternate=non-ignorable.  There are
2577                    // also exactly as many quaternary weights as tertiary weights, so level
2578                    // length differences are handled already on tertiary level.  Any
2579                    // above-common quaternary weight will compare greater regardless.
2580                    quaternaries.append_byte(LEVEL_SEPARATOR_BYTE);
2581                } else {
2582                    let q = if q == NO_CE_QUATERNARY {
2583                        LEVEL_SEPARATOR_BYTE
2584                    } else {
2585                        (0xfc + ((q >> 6) & 3)) as u8
2586                    };
2587                    handle_common!(
2588                        quaternaries,
2589                        q,
2590                        common_quaternaries,
2591                        QUAT_COMMON,
2592                        QUAT_COMMON[WEIGHT_LOW]
2593                    );
2594                    quaternaries.append_byte(q);
2595                }
2596            }
2597
2598            if (non_primary.bits() >> 24) as u8 == LEVEL_SEPARATOR_BYTE {
2599                break; // ce == NO_CE
2600            }
2601        }
2602
2603        macro_rules! write_level {
2604            ($key:ident, $flag:ident) => {
2605                if levels & $flag != 0 {
2606                    sink.write(state, &[LEVEL_SEPARATOR_BYTE])?;
2607                    sink.write(state, &$key.buf)?;
2608                }
2609            };
2610        }
2611
2612        write_level!(secondaries, SECONDARY_LEVEL_FLAG);
2613
2614        if levels & CASE_LEVEL_FLAG != 0 {
2615            sink.write(state, &[LEVEL_SEPARATOR_BYTE])?;
2616
2617            // Write pairs of nibbles as bytes, except separator bytes as themselves.
2618            let mut b = 0;
2619            if let Some((last, head)) = cases.buf.split_last() {
2620                debug_assert_eq!(*last, 1); // The trailing NO_CE
2621                for c in head {
2622                    debug_assert_eq!(*c & 0xf, 0);
2623                    debug_assert_ne!(*c, 0);
2624                    if b == 0 {
2625                        b = *c;
2626                    } else {
2627                        sink.write_byte(state, b | (*c >> 4))?;
2628                        b = 0;
2629                    }
2630                }
2631            } else {
2632                debug_assert!(false);
2633            }
2634            if b != 0 {
2635                sink.write_byte(state, b)?;
2636            }
2637        }
2638
2639        write_level!(tertiaries, TERTIARY_LEVEL_FLAG);
2640        write_level!(quaternaries, QUATERNARY_LEVEL_FLAG);
2641
2642        Ok(())
2643    }
2644}
2645
2646/// Error indicating that a [`CollationKeySink`] with limited space ran out of space.
2647#[derive(Debug, PartialEq, Eq)]
2648pub struct TooSmall {
2649    /// The total length, in bytes, of the sort key.
2650    pub length: usize,
2651}
2652
2653impl TooSmall {
2654    pub fn new(length: usize) -> Self {
2655        Self { length }
2656    }
2657}
2658
2659/// A [`std::io::Write`]-like trait for writing to a buffer-like object.
2660///
2661/// (This crate does not have access to [`std`].)
2662///
2663/// <div class="stab unstable">
2664/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
2665/// including in SemVer minor releases. Do not implement or call methods on this trait
2666/// unless you are prepared for things to occasionally break.
2667///
2668/// Graduation tracking issue: [issue #7178](https://github.com/unicode-org/icu4x/issues/7178).
2669/// </div>
2670///
2671/// ✨ *Enabled with the `unstable` Cargo feature.*
2672pub trait CollationKeySink {
2673    /// The type of error the sink may return.
2674    type Error;
2675
2676    /// An intermediate state object used by the sink, which must implement [`Default`].
2677    type State;
2678
2679    /// A result value indicating the final state of the sink (e.g. a number of bytes written).
2680    type Output;
2681
2682    /// Writes a buffer into the writer.
2683    fn write(&mut self, state: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error>;
2684
2685    /// Write a single byte into the writer.
2686    fn write_byte(&mut self, state: &mut Self::State, b: u8) -> Result<(), Self::Error> {
2687        self.write(state, &[b])
2688    }
2689
2690    /// Finalize any internal sink state (perhaps by flushing a buffer) and return the final
2691    /// output value.
2692    fn finish(&mut self, state: Self::State) -> Result<Self::Output, Self::Error>;
2693}
2694
2695impl CollationKeySink for Vec<u8> {
2696    type Error = Infallible;
2697    type State = ();
2698    type Output = ();
2699
2700    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2701        self.extend_from_slice(buf);
2702        Ok(())
2703    }
2704
2705    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2706        Ok(())
2707    }
2708}
2709
2710impl CollationKeySink for VecDeque<u8> {
2711    type Error = Infallible;
2712    type State = ();
2713    type Output = ();
2714
2715    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2716        self.extend(buf.iter());
2717        Ok(())
2718    }
2719
2720    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2721        Ok(())
2722    }
2723}
2724
2725impl<const N: usize> CollationKeySink for SmallVec<[u8; N]> {
2726    type Error = Infallible;
2727    type State = ();
2728    type Output = ();
2729
2730    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2731        self.extend_from_slice(buf);
2732        Ok(())
2733    }
2734
2735    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2736        Ok(())
2737    }
2738}
2739
2740impl CollationKeySink for [u8] {
2741    type Error = TooSmall;
2742    type State = usize;
2743    type Output = usize;
2744
2745    fn write(&mut self, offset: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2746        if *offset + buf.len() <= self.len() {
2747            // just checked bounds
2748            #[expect(clippy::indexing_slicing)]
2749            self[*offset..*offset + buf.len()].copy_from_slice(buf);
2750        }
2751        *offset += buf.len();
2752        Ok(())
2753    }
2754
2755    fn finish(&mut self, offset: Self::State) -> Result<Self::Output, Self::Error> {
2756        if offset <= self.len() {
2757            Ok(offset)
2758        } else {
2759            Err(TooSmall::new(offset))
2760        }
2761    }
2762}
2763
2764#[derive(Default)]
2765struct SortKeyLevel {
2766    buf: SmallVec<[u8; 40]>,
2767}
2768
2769impl SortKeyLevel {
2770    fn len(&self) -> usize {
2771        self.buf.len()
2772    }
2773
2774    fn is_empty(&self) -> bool {
2775        self.buf.is_empty()
2776    }
2777
2778    fn append_byte(&mut self, x: u8) {
2779        self.buf.push(x);
2780    }
2781
2782    fn append_weight_16(&mut self, w: u16) {
2783        debug_assert_ne!(w, 0);
2784        let b0 = (w >> 8) as u8;
2785        let b1 = w as u8;
2786        self.append_byte(b0);
2787        if b1 != 0 {
2788            self.append_byte(b1);
2789        }
2790    }
2791
2792    fn append_reverse_weight_16(&mut self, w: u16) {
2793        debug_assert_ne!(w, 0);
2794        let b0 = (w >> 8) as u8;
2795        let b1 = w as u8;
2796        if b1 != 0 {
2797            self.append_byte(b1);
2798        }
2799        self.append_byte(b0);
2800    }
2801
2802    fn append_weight_32(&mut self, w: u32) {
2803        debug_assert_ne!(w, 0);
2804        let b0 = (w >> 24) as u8;
2805        let b1 = (w >> 16) as u8;
2806        let b2 = (w >> 8) as u8;
2807        let b3 = w as u8;
2808        self.append_byte(b0);
2809        if b1 != 0 {
2810            self.append_byte(b1);
2811            if b2 != 0 {
2812                self.append_byte(b2);
2813                if b3 != 0 {
2814                    self.append_byte(b3);
2815                }
2816            }
2817        }
2818    }
2819}
2820
2821// The algorithm below (BOCSU or Binary Ordered Compression Scheme for Unicode) is translated
2822// from the C++ code in ICU4C at icu4c/source/i18n/bocsu.{cpp,h}.  The algorithm works by
2823// converting a sequence of codepoints into a sequence of presumably small differences.  See
2824// the C++ code for a more detailed explanation.
2825
2826macro_rules! negdivmod {
2827    ($n:ident, $d:ident, $m:ident) => {
2828        $m = $n % $d;
2829        $n /= $d;
2830        if $m < 0 {
2831            $n -= 1;
2832            $m += $d;
2833        }
2834    };
2835}
2836
2837fn write_diff<S>(mut diff: i32, sink: &mut S, state: &mut S::State) -> Result<(), S::Error>
2838where
2839    S: CollationKeySink + ?Sized,
2840{
2841    let mut out = |b| sink.write_byte(state, b);
2842
2843    if diff >= SLOPE_REACH_NEG_1 {
2844        if diff <= SLOPE_REACH_POS_1 {
2845            out((SLOPE_MIDDLE + diff) as _)?;
2846        } else if diff <= SLOPE_REACH_POS_2 {
2847            out((SLOPE_START_POS_2 + (diff / SLOPE_TAIL_COUNT)) as _)?;
2848            out((SLOPE_MIN + diff % SLOPE_TAIL_COUNT) as _)?;
2849        } else if diff <= SLOPE_REACH_POS_3 {
2850            let p2 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2851            diff /= SLOPE_TAIL_COUNT;
2852            let p1 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2853            let p0 = SLOPE_START_POS_3 + (diff / SLOPE_TAIL_COUNT);
2854            out(p0 as _)?;
2855            out(p1 as _)?;
2856            out(p2 as _)?;
2857        } else {
2858            let p3 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2859            diff /= SLOPE_TAIL_COUNT;
2860            let p2 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2861            diff /= SLOPE_TAIL_COUNT;
2862            let p1 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2863            out(SLOPE_MAX as _)?;
2864            out(p1 as _)?;
2865            out(p2 as _)?;
2866            out(p3 as _)?;
2867        }
2868    } else {
2869        let mut m;
2870
2871        if diff >= SLOPE_REACH_NEG_2 {
2872            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2873            out((SLOPE_START_NEG_2 + diff) as _)?;
2874            out((SLOPE_MIN + m) as _)?;
2875        } else if diff >= SLOPE_REACH_NEG_3 {
2876            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2877            let p2 = SLOPE_MIN + m;
2878            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2879            let p1 = SLOPE_MIN + m;
2880            let p0 = SLOPE_START_NEG_3 + diff;
2881            out(p0 as _)?;
2882            out(p1 as _)?;
2883            out(p2 as _)?;
2884        } else {
2885            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2886            let p3 = SLOPE_MIN + m;
2887            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2888            let p2 = SLOPE_MIN + m;
2889            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2890            let p1 = SLOPE_MIN + m;
2891            let _ = diff;
2892            out(SLOPE_MIN as _)?;
2893            out(p1 as _)?;
2894            out(p2 as _)?;
2895            out(p3 as _)?;
2896        }
2897    }
2898
2899    Ok(())
2900}
2901
2902fn write_identical_level<I, S>(iter: I, sink: &mut S, state: &mut S::State) -> Result<(), S::Error>
2903where
2904    I: Iterator<Item = char>,
2905    S: CollationKeySink + ?Sized,
2906{
2907    let mut prev = 0i32;
2908
2909    for c in iter {
2910        if !(0x4e00..=0xa000).contains(&prev) {
2911            prev = (prev & !0x7f) - SLOPE_REACH_NEG_1;
2912        } else {
2913            // Unihan U+4e00..U+9fa5:  double-bytes down from the upper end
2914            prev = 0x9fff - SLOPE_REACH_POS_2;
2915        }
2916
2917        if c == MERGE_SEPARATOR {
2918            sink.write_byte(state, MERGE_SEPARATOR_BYTE)?;
2919            prev = 0;
2920        } else {
2921            let c = c as i32;
2922            write_diff(c - prev, sink, state)?;
2923            prev = c;
2924        }
2925    }
2926    Ok(())
2927}
2928
2929#[cfg(test)]
2930mod test {
2931    use super::*;
2932    use icu_locale::locale;
2933
2934    type Key = Vec<u8>;
2935
2936    fn collator_en(strength: Strength) -> CollatorBorrowed<'static> {
2937        let locale = locale!("en").into();
2938        let mut options = CollatorOptions::default();
2939        options.strength = Some(strength);
2940        Collator::try_new(locale, options).unwrap()
2941    }
2942
2943    fn collator_en_case_level(strength: Strength) -> CollatorBorrowed<'static> {
2944        let locale = locale!("en").into();
2945        let mut options = CollatorOptions::default();
2946        options.strength = Some(strength);
2947        options.case_level = Some(crate::options::CaseLevel::On);
2948        Collator::try_new(locale, options).unwrap()
2949    }
2950
2951    fn keys(strength: Strength) -> (Key, Key, Key) {
2952        let collator = collator_en(strength);
2953
2954        let mut k0 = Vec::new();
2955        let Ok(()) = collator.write_sort_key_to("aabc", &mut k0);
2956        let mut k1 = Vec::new();
2957        let Ok(()) = collator.write_sort_key_to("aAbc", &mut k1);
2958        let mut k2 = Vec::new();
2959        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k2);
2960
2961        (k0, k1, k2)
2962    }
2963
2964    #[test]
2965    fn sort_key_primary() {
2966        let (k0, k1, k2) = keys(Strength::Primary);
2967        assert_eq!(k0, k1);
2968        assert_eq!(k1, k2);
2969    }
2970
2971    #[test]
2972    fn sort_key_secondary() {
2973        let (k0, k1, k2) = keys(Strength::Secondary);
2974        assert_eq!(k0, k1);
2975        assert!(k1 < k2);
2976    }
2977
2978    #[test]
2979    fn sort_key_tertiary() {
2980        let (k0, k1, k2) = keys(Strength::Tertiary);
2981        assert!(k0 < k1);
2982        assert!(k1 < k2);
2983    }
2984
2985    fn collator_ja(strength: Strength) -> CollatorBorrowed<'static> {
2986        let locale = locale!("ja").into();
2987        let mut options = CollatorOptions::default();
2988        options.strength = Some(strength);
2989        Collator::try_new(locale, options).unwrap()
2990    }
2991
2992    fn keys_ja_strs(strength: Strength, s0: &str, s1: &str) -> (Key, Key) {
2993        let collator = collator_ja(strength);
2994
2995        let mut k0 = Vec::new();
2996        let Ok(()) = collator.write_sort_key_to(s0, &mut k0);
2997        let mut k1 = Vec::new();
2998        let Ok(()) = collator.write_sort_key_to(s1, &mut k1);
2999
3000        (k0, k1)
3001    }
3002
3003    fn keys_ja(strength: Strength) -> (Key, Key) {
3004        keys_ja_strs(strength, "あ", "ア")
3005    }
3006
3007    #[test]
3008    fn sort_keys_ja_to_quaternary() {
3009        let (k0, k1) = keys_ja(Strength::Primary);
3010        assert_eq!(k0, k1);
3011        let (k0, k1) = keys_ja(Strength::Secondary);
3012        assert_eq!(k0, k1);
3013        let (k0, k1) = keys_ja(Strength::Tertiary);
3014        assert_eq!(k0, k1);
3015        let (k0, k1) = keys_ja(Strength::Quaternary);
3016        assert!(k0 < k1);
3017    }
3018
3019    #[test]
3020    fn sort_keys_ja_identical() {
3021        let (k0, k1) = keys_ja_strs(Strength::Quaternary, "ア", "ア");
3022        assert_eq!(k0, k1);
3023        let (k0, k1) = keys_ja_strs(Strength::Identical, "ア", "ア");
3024        assert!(k0 < k1);
3025    }
3026
3027    #[test]
3028    fn sort_keys_utf16() {
3029        let collator = collator_en(Strength::Identical);
3030
3031        const STR8: &[u8] = b"hello world!";
3032        let mut k8 = Vec::new();
3033        let Ok(()) = collator.write_sort_key_utf8_to(STR8, &mut k8);
3034
3035        const STR16: &[u16] = &[
3036            0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21,
3037        ];
3038        let mut k16 = Vec::new();
3039        let Ok(()) = collator.write_sort_key_utf16_to(STR16, &mut k16);
3040        assert_eq!(k8, k16);
3041    }
3042
3043    #[test]
3044    fn sort_keys_invalid() {
3045        let collator = collator_en(Strength::Identical);
3046
3047        // some invalid strings
3048        let mut k = Vec::new();
3049        let Ok(()) = collator.write_sort_key_utf8_to(b"\xf0\x90", &mut k);
3050        let mut k = Vec::new();
3051        let Ok(()) = collator.write_sort_key_utf16_to(&[0xdd1e], &mut k);
3052    }
3053
3054    #[test]
3055    fn sort_key_to_vecdeque() {
3056        let collator = collator_en(Strength::Identical);
3057
3058        let mut k0 = Vec::new();
3059        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k0);
3060        let mut k1 = VecDeque::new();
3061        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k1);
3062        assert!(k0.iter().eq(k1.iter()));
3063    }
3064
3065    #[test]
3066    fn sort_key_to_slice() {
3067        let collator = collator_en(Strength::Identical);
3068
3069        let mut k0 = Vec::new();
3070        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k0);
3071        let mut k1 = [0u8; 100];
3072        let len = collator.write_sort_key_to("áAbc", &mut k1[..]).unwrap();
3073        assert_eq!(len, k0.len());
3074        assert!(k0.iter().eq(k1[..len].iter()));
3075    }
3076
3077    #[test]
3078    fn sort_key_to_slice_no_space() {
3079        let collator = collator_en(Strength::Identical);
3080        let mut k = [0u8; 0];
3081        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3082        assert!(matches!(res, Err(TooSmall { .. })));
3083    }
3084
3085    #[test]
3086    fn sort_key_to_slice_too_long() {
3087        // This runs out of space in write_sort_key_up_to_quaternary.
3088        let collator = collator_en(Strength::Identical);
3089        let mut k = [0u8; 5];
3090        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3091        assert!(matches!(res, Err(TooSmall { .. })));
3092    }
3093
3094    #[test]
3095    fn sort_key_to_slice_identical_too_long() {
3096        // This runs out of space while appending UTF-8 in the SinkAdapter.
3097        let collator = collator_en(Strength::Identical);
3098        let mut k = [0u8; 22];
3099        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3100        assert!(matches!(res, Err(TooSmall { .. })));
3101    }
3102
3103    #[test]
3104    fn sort_key_just_right() {
3105        // get the length needed
3106        let collator = collator_en(Strength::Identical);
3107        let mut k = [0u8; 0];
3108        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3109        let len = res.unwrap_err().length;
3110
3111        // almost enough
3112        let mut k = vec![0u8; len - 1];
3113        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3114        let len = res.unwrap_err().length;
3115
3116        // just right
3117        let mut k = vec![0u8; len];
3118        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3119        assert_eq!(res, Ok(len));
3120    }
3121
3122    #[test]
3123    fn sort_key_utf16_slice_too_small() {
3124        let collator = collator_en(Strength::Identical);
3125        const STR16: &[u16] = &[0x68, 0x65, 0x6c, 0x6c, 0x6f];
3126        let mut k = [0u8; 4];
3127        let res = collator.write_sort_key_utf16_to(STR16, &mut k[..]);
3128        assert!(matches!(res, Err(TooSmall { .. })));
3129    }
3130
3131    #[test]
3132    fn sort_key_very_long() {
3133        let collator = collator_en(Strength::Secondary);
3134        let mut k = Vec::new();
3135        let Ok(()) = collator.write_sort_key_to(&"a".repeat(300), &mut k);
3136    }
3137
3138    #[test]
3139    fn sort_key_case_level() {
3140        let collator = collator_en_case_level(Strength::Tertiary);
3141        let mut k = Vec::new();
3142        let Ok(()) = collator.write_sort_key_to("aBc", &mut k);
3143    }
3144
3145    #[test]
3146    fn sort_key_case_level_empty() {
3147        let collator = collator_en_case_level(Strength::Tertiary);
3148        let mut k = Vec::new();
3149        let Ok(()) = collator.write_sort_key_to("", &mut k);
3150    }
3151
3152    fn check_sort_key_less(a: &[u16], b: &[u16]) {
3153        let collator = collator_en(Strength::Identical);
3154        let mut ak = Vec::new();
3155        let Ok(()) = collator.write_sort_key_utf16_to(a, &mut ak);
3156        let mut bk = Vec::new();
3157        let Ok(()) = collator.write_sort_key_utf16_to(b, &mut bk);
3158        assert!(ak < bk, "failed: {a:04x?} - {b:04x?}");
3159    }
3160
3161    #[test]
3162    fn sort_key_fffe_bug_6811() {
3163        check_sort_key_less(
3164            &[0xfffe, 0x0001, 0x0002, 0x0003],
3165            &[0x0001, 0xfffe, 0x0002, 0x0003],
3166        );
3167        check_sort_key_less(
3168            &[0x0001, 0xfffe, 0x0002, 0x0003],
3169            &[0x0001, 0x0002, 0xfffe, 0x0003],
3170        );
3171        check_sort_key_less(
3172            &[0x0001, 0x0002, 0xfffe, 0x0003],
3173            &[0x0001, 0x0002, 0x0003, 0xfffe],
3174        );
3175        check_sort_key_less(&[0xfffe, 0x0000, 0x0000], &[0x0000, 0xfffe, 0x0000]);
3176        check_sort_key_less(&[0x0000, 0xfffe, 0x0000], &[0x0000, 0x0000, 0xfffe]);
3177    }
3178}