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                    let mut left_primary = left_ce32.to_primary_in_quick_check(self.tailoring);
1014                    // If `left_primary` is now zero, we don't really need to extract `right_primary`,
1015                    // but let's avoid the branch on the likely path.
1016                    let mut right_primary = right_ce32.to_primary_in_quick_check(self.tailoring);
1017                    quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1018                    // Try Hangul.
1019                    let right_hangul_offset = u32::from(right_c).wrapping_sub(HANGUL_S_BASE);
1020                    if right_hangul_offset < HANGUL_S_COUNT {
1021                        let left_hangul_offset = u32::from(left_c).wrapping_sub(HANGUL_S_BASE);
1022                        if left_hangul_offset < HANGUL_S_COUNT {
1023                            hangul_syllable_compare!(left_hangul_offset, right_hangul_offset,);
1024                        }
1025                    }
1026                }
1027            }
1028            // Note: It might look like a good idea to cache the CE32s, but
1029            // doing so actually makes things slower.
1030        },
1031        variable_top,
1032    );
1033
1034    compare!(
1035        /// Compare potentially ill-formed UTF-8 slices. Ill-formed input is compared
1036        /// as if errors had been replaced with REPLACEMENT CHARACTERs according
1037        /// to the WHATWG Encoding Standard.
1038        ,
1039        compare_utf8,
1040        [u8],
1041        [u8],
1042        split_prefix_u8,
1043        chars_with_trie_default_for_ascii,
1044        chars_with_trie_default_for_ascii,
1045        self,
1046        left_tail,
1047        right_tail,
1048        {
1049            // Direct copypaste from the `str` case.
1050            let tailoring_trie = &self.tailoring.trie;
1051            if let Some((left_c, left_u32)) = left_tail.chars_with_trie(tailoring_trie).next() {
1052                // Logically, there's a bunch of stuff we could do from the left side alone to determine
1053                // that reading from the right side at all or reading from the right trie is useless.
1054                // Doing that seems to be a pessimization, at least in the absence of PGO.
1055                if let Some((right_c, right_u32)) = right_tail.chars_with_trie(tailoring_trie).next() {
1056                    let left_ce32 = CollationElement32::new(left_u32);
1057                    let right_ce32 = CollationElement32::new(right_u32);
1058                    let mut left_primary = left_ce32.to_primary_in_quick_check(self.tailoring);
1059                    // If `left_primary` is now zero, we don't really need to extract `right_primary`,
1060                    // but let's avoid the branch on the likely path.
1061                    let mut right_primary = right_ce32.to_primary_in_quick_check(self.tailoring);
1062                    quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1063                    // Try Hangul.
1064                    let right_hangul_offset = u32::from(right_c).wrapping_sub(HANGUL_S_BASE);
1065                    if right_hangul_offset < HANGUL_S_COUNT {
1066                        let left_hangul_offset = u32::from(left_c).wrapping_sub(HANGUL_S_BASE);
1067                        if left_hangul_offset < HANGUL_S_COUNT {
1068                            hangul_syllable_compare!(left_hangul_offset, right_hangul_offset,);
1069                        }
1070                    }
1071                }
1072            }
1073            // Note: It might look like a good idea to cache the CE32s, but
1074            // doing so actually makes things slower.
1075        },
1076        variable_top,
1077    );
1078
1079    compare!(
1080        /// Compare potentially ill-formed UTF-16 slices. Unpaired surrogates
1081        /// are compared as if each one was a REPLACEMENT CHARACTER.
1082        ,
1083        compare_utf16,
1084        [u16],
1085        [u16],
1086        split_prefix_u16,
1087        chars_with_trie,
1088        chars_with_trie,
1089        self,
1090        left_tail,
1091        right_tail,
1092        {
1093            let tailoring_trie = &self.tailoring.trie;
1094            if let Some(left_u) = left_tail.first() {
1095                if let Some(right_u) = right_tail.first() {
1096                    let left_u16 = *left_u;
1097                    let right_u16 = *right_u;
1098                    let left_u32 = tailoring_trie.get16(left_u16);
1099                    let right_u32 = tailoring_trie.get16(right_u16);
1100                    let left_ce32 = CollationElement32::new(left_u32);
1101                    let right_ce32 = CollationElement32::new(right_u32);
1102                    let mut left_primary = left_ce32.to_primary_in_quick_check(self.tailoring);
1103                    // If `left_primary` is now zero, we don't really need to extract `right_primary`,
1104                    // but let's avoid the branch on the likely path.
1105                    let mut right_primary = right_ce32.to_primary_in_quick_check(self.tailoring);
1106                    quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1107                    // Try Hangul.
1108                    let left_hangul_offset = u32::from(left_u16).wrapping_sub(HANGUL_S_BASE);
1109                    if left_hangul_offset < HANGUL_S_COUNT {
1110                        let right_hangul_offset = u32::from(right_u16).wrapping_sub(HANGUL_S_BASE);
1111                        if right_hangul_offset < HANGUL_S_COUNT {
1112                            hangul_syllable_compare!(left_hangul_offset, right_hangul_offset,);
1113                        }
1114                    }
1115                }
1116            }
1117            // Note: It might look like a good idea to cache the CE32s, but
1118            // doing so actually makes things slower.
1119        },
1120        variable_top,
1121    );
1122
1123    compare!(
1124        /// Compare Latin1 slices.
1125        ///
1126        /// ✨ *Enabled with the `latin1` Cargo feature.*
1127        #[cfg(feature = "latin1")]
1128        ,
1129        compare_latin1,
1130        [u8],
1131        [u8],
1132        split_prefix_latin1,
1133        latin1_chars_with_trie,
1134        latin1_chars_with_trie,
1135        self,
1136        left_tail,
1137        right_tail,
1138        {
1139            let tailoring_trie = &self.tailoring.trie;
1140            if let Some(left_u) = left_tail.first() {
1141                if let Some(right_u) = right_tail.first() {
1142                    let left_u8 = *left_u;
1143                    let right_u8 = *right_u;
1144                    // The probability of getting a non-simple ce32 from
1145                    // the Latin1 part above ASCII is so high that let's
1146                    // only consider ASCII on the left for this fast path.
1147
1148                    // SAFETY: Checking the invariant of `get7` here.
1149                    if left_u8 < 0x80 {
1150                        // SAFETY: Invariant of `get7` checked above.
1151                        let left_u32 = unsafe { tailoring_trie.get7(left_u8) };
1152                        let left_ce32 = CollationElement32::new(left_u32);
1153                        // SAFETY: Checking the invariant of `get7` here for right.
1154                        if right_u8 < 0x80 {
1155                            // SAFETY: Invariant of `get7` checked above.
1156                            let right_u32 = unsafe { tailoring_trie.get7(right_u8) };
1157                            let right_ce32 = CollationElement32::new(right_u32);
1158                            // Should we use script reordering to cater to reordering
1159                            // digits or punctuation relative to letters?
1160                            let left_primary = left_ce32.to_primary_simple();
1161                            // If `left_primary` is now zero, we don't really need to extract `right_primary`,
1162                            // but let's avoid the branch on the likely path.
1163                            let right_primary = right_ce32.to_primary_simple();
1164                            if (left_primary != right_primary)
1165                                && (left_primary != 0)
1166                                && (right_primary != 0)
1167                                && !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY)
1168                                && !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY)
1169                            {
1170                                if left_primary < right_primary {
1171                                    return Ordering::Less;
1172                                }
1173                                return Ordering::Greater;
1174                            }
1175                        }
1176                    }
1177                }
1178            }
1179            // Note: It might look like a good idea to cache the CE32s, but
1180            // doing so actually makes things slower.
1181        },
1182        variable_top,
1183    );
1184
1185    compare!(
1186        /// Compare Latin1 slice with potentially ill-formed UTF-16
1187        /// slice.
1188        ///
1189        /// If you need to compare a potentially ill-formed UTF-16
1190        /// slice with a Latin1 slice, swap the arguments and
1191        /// call `reverse()` on the return value.
1192        ///
1193        /// ✨ *Enabled with the `latin1` Cargo feature.*
1194        #[cfg(feature = "latin1")]
1195        ,
1196        compare_latin1_utf16,
1197        [u8],
1198        [u16],
1199        split_prefix_latin1_utf16,
1200        latin1_chars_with_trie,
1201        chars_with_trie,
1202        self,
1203        left_tail,
1204        right_tail,
1205        {
1206            let tailoring_trie = &self.tailoring.trie;
1207            if let Some(left_u) = left_tail.first() {
1208                if let Some(right_u) = right_tail.first() {
1209                    let left_u8 = *left_u;
1210                    // The probability of getting a non-simple ce32 from
1211                    // the Latin1 part above ASCII is so high that let's
1212                    // only consider ASCII on the left for this fast path.
1213
1214                    // SAFETY: Checking the invariant of `get7` here.
1215                    if left_u8 < 0x80 {
1216                        // SAFETY: Invariant of `get7` checked above.
1217                        let left_u32 = unsafe { tailoring_trie.get7(left_u8) };
1218                        let left_ce32 = CollationElement32::new(left_u32);
1219                        let right_u16 = *right_u;
1220                        let right_u32 = tailoring_trie.get16(right_u16);
1221                        let right_ce32 = CollationElement32::new(right_u32);
1222                        let mut left_primary = left_ce32.to_primary_simple();
1223                        // If `left_primary` is now zero, we don't really need to
1224                        // extract `right_primary`, but let's avoid the branch on
1225                        // the likely path.
1226
1227                        // Don't use the macro to micro-optimize away the long primary
1228                        // case for ASCII.
1229                        let mut right_primary = right_ce32.to_primary_in_quick_check(self.tailoring);
1230                        if (left_primary != right_primary)
1231                            && (left_primary != 0)
1232                            && (right_primary != 0)
1233                            && !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY)
1234                            && !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY)
1235                        {
1236                            if let Some(reordering) = &self.reordering {
1237                                left_primary = reordering.reorder(left_primary);
1238                                right_primary = reordering.reorder(right_primary);
1239                            }
1240                            if left_primary < right_primary {
1241                                return Ordering::Less;
1242                            }
1243                            return Ordering::Greater;
1244                        }
1245                    }
1246                }
1247            }
1248            // Note: It might look like a good idea to cache the CE32s, but
1249            // doing so actually makes things slower.
1250        },
1251        variable_top,
1252    );
1253
1254    #[inline(always)]
1255    fn numeric_primary(&self) -> Option<u8> {
1256        if self.options.numeric() {
1257            Some(self.special_primaries.numeric_primary)
1258        } else {
1259            None
1260        }
1261    }
1262
1263    #[inline(always)]
1264    fn variable_top(&self) -> u32 {
1265        if self.options.alternate_handling() == AlternateHandling::NonIgnorable {
1266            0
1267        } else {
1268            // +1 so that we can use "<" and primary ignorables test out early.
1269            self.special_primaries
1270                .last_primary_for_group(self.options.max_variable())
1271                + 1
1272        }
1273    }
1274
1275    /// The implementation of the comparison operation.
1276    ///
1277    /// `head_chars` is an iterator _backward_ over the identical
1278    /// prefix and `left_chars` and `right_chars` are iterators
1279    /// _forward_ over the parts after the identical prefix.
1280    fn compare_impl<L, R, H, T>(
1281        &'data self,
1282        left_chars: L,
1283        right_chars: R,
1284        mut head_chars: H,
1285        variable_top: u32,
1286    ) -> Ordering
1287    where
1288        L: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
1289        R: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
1290        H: DoubleEndedIterator<Item = (char, u32)> + 'data,
1291        T: AbstractCodePointTrie<'data, u32> + 'data,
1292    {
1293        // Sadly, it looks like variable CEs and backward second level
1294        // require us to store the full 64-bit CEs instead of storing only
1295        // the NonPrimary part.
1296        //
1297        // TODO(#2008): Consider having two monomorphizations of this method:
1298        // one that can deal with variables shifted to quaternary and
1299        // backward second level and another that doesn't support that
1300        // and only stores `NonPrimary` in `left_ces` and `right_ces`
1301        // with double the number of stack allocated elements.
1302
1303        // Note: These are used only after the identical prefix skipping,
1304        // but initializing these up here improves performance at the time
1305        // of writing. Presumably the source order affects the stack frame
1306        // layout.
1307        let mut left_ces: SmallVec<[CollationElement; CE_BUFFER_SIZE]> = SmallVec::new();
1308        let mut right_ces: SmallVec<[CollationElement; CE_BUFFER_SIZE]> = SmallVec::new();
1309
1310        // The algorithm comes from CollationCompare::compareUpToQuaternary in ICU4C.
1311
1312        let mut any_variable = false;
1313
1314        let numeric_primary = self.numeric_primary();
1315
1316        let mut left = collation_elements!(self, left_chars, numeric_primary);
1317        let mut right = collation_elements!(self, right_chars, numeric_primary);
1318
1319        // Start identical prefix
1320
1321        // The logic here to check whether the boundary found by skipping
1322        // the identical prefix is safe is complicated compared to the ICU4C
1323        // approach of having a set of characters that are unsafe as the character
1324        // immediately following the identical prefix. However, the approach here
1325        // avoids extra data, and working on the main data avoids the bug
1326        // possibility of data structures not being mutually consistent.
1327
1328        // This code intentionally does not keep around the `CollationElement32`s
1329        // that have been read from the collation data tries, because keeping
1330        // them around turned out to be a pessimization: There would be added
1331        // branches on the hot path of the algorithm that maps characters to
1332        // collation elements, and the element size of the upcoming buffer
1333        // would grow.
1334        //
1335        // However, the values read from the normalization trie _are_ kept around,
1336        // since there is already a place where to put them.
1337
1338        // This loop is only broken out of as goto forward.
1339        #[expect(clippy::never_loop)]
1340        'prefix: loop {
1341            if let Some((mut head_last_c, head_last_trie_val)) = head_chars.next_back() {
1342                let mut head_last = CharacterAndClassAndTrieValue::new_with_trie_val(
1343                    head_last_c,
1344                    head_last_trie_val,
1345                );
1346                let mut head_last_ce32 = CollationElement32::default();
1347                let mut head_last_ok = false;
1348                if let Some(left_different) = left.iter_next_before_init() {
1349                    left.prepend_upcoming_before_init(left_different.clone());
1350                    if let Some(right_different) = right.iter_next_before_init() {
1351                        // Note: left_different and right_different may both be U+FFFD.
1352                        right.prepend_upcoming_before_init(right_different.clone());
1353
1354                        // The base logic is that a boundary between two starters
1355                        // that decompose to selves is safe iff the starter
1356                        // before the boundary can't contract a starter, the
1357                        // starter after the boundary doesn't have a prefix
1358                        // condition, and, with the numeric mode enabled,
1359                        // they aren't both numeric.
1360                        //
1361                        // This base logic is then extended with Hangul
1362                        // syllables and characters that decompose to a
1363                        // BMP starter followed by a BMP non-starter.
1364                        // The logic could be extended further, in
1365                        // particular to cover singleton decompositions
1366                        // to a BMP starter, but such characters can be
1367                        // expected to be rare enough in real-world input
1368                        // that it's not worthwhile to make this code more
1369                        // branchy.
1370                        //
1371                        // A Hangul syllable is safe on either side of the
1372                        // boundary, because Hangul syllables can't participate
1373                        // in contraction or have prefix conditions. They are
1374                        // also known not to be numeric.
1375                        //
1376                        // Hangul jamo is safe to look up from the main trie
1377                        // instead of the jamo table, because they aren't
1378                        // allowed to participate in contractions or prefix
1379                        // conditions, either, and are known not to be numeric.
1380                        //
1381                        // After a boundary, a decomposition to a BMP starter
1382                        // and a BMP non-starter can obviously be analyzed by
1383                        // considering the starter as if it was a starter
1384                        // that decomposes to self.
1385                        //
1386                        // Before a boundary the contraction condition considers
1387                        // whether the contraction can contract a starter.
1388                        // For the case of contracting a non-starter, it's
1389                        // fine for the BMP starter of the decomposition to
1390                        // contract the non-starter from the same decomposition:
1391                        // Since that would happen as part of the prefix that
1392                        // is identical, it wouldn't affect anything after.
1393                        //
1394                        // The case of contracting a non-starter other than
1395                        // the one that came from the decomposition itself
1396                        // is irrelevant, because we don't allow a non-starter
1397                        // right after the boundary regardless of the contraction
1398                        // status of what's before the boundary.
1399                        //
1400                        // Finally, decompositions to starter and non-starter
1401                        // are known not to be numeric.
1402
1403                        // The checks below are repetitive, but an attempt to factor
1404                        // repetitive code into an inlined function regressed,
1405                        // performance, so it seems that having the control flow
1406                        // right here without an intermediate enum from a
1407                        // function return to branch on is important.
1408
1409                        // This loop is only broken out of as goto forward. The control flow
1410                        // is much more readable this way.
1411                        #[expect(clippy::never_loop)]
1412                        loop {
1413                            // The two highest bits are about NFC, which we don't
1414                            // care about here.
1415                            let decomposition = head_last.trie_val;
1416                            if (decomposition
1417                                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1418                                == 0
1419                            {
1420                                // Intentionally empty block to keep
1421                                // the same structure as in the cases
1422                                // where something happens here.
1423                            } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1424                                && ((decomposition & LOW_ZEROS_MASK) != 0)
1425                            {
1426                                // Decomposition into two BMP characters: starter and non-starter
1427                                // Let's take the starter
1428                                head_last_c = char_from_u32(decomposition & 0x7FFF);
1429                            } else if decomposition == HANGUL_SYLLABLE_MARKER {
1430                                head_last_ce32 = IDENTICAL_PREFIX_HANGUL_MARKER_CE32;
1431                            } else {
1432                                break;
1433                            }
1434                            head_last_ok = true;
1435
1436                            let left_c;
1437                            let right_c;
1438
1439                            let decomposition = left_different.trie_val;
1440                            if (decomposition
1441                                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1442                                == 0
1443                            {
1444                                left_c = left_different.character();
1445                            } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1446                                && ((decomposition & LOW_ZEROS_MASK) != 0)
1447                            {
1448                                // Decomposition into two BMP characters: starter and non-starter
1449                                // Let's take the starter
1450                                left_c = char_from_u32(decomposition & 0x7FFF);
1451                            } else if decomposition == HANGUL_SYLLABLE_MARKER {
1452                                left_c = left_different.character();
1453                                if right_different.trie_val == HANGUL_SYLLABLE_MARKER {
1454                                    // Hangul syllable to Hangul syllable comparison can give us
1455                                    // a quick exit.
1456                                    let left_hangul_offset =
1457                                        u32::from(left_c).wrapping_sub(HANGUL_S_BASE);
1458                                    let right_hangul_offset =
1459                                        u32::from(right_different.character())
1460                                            .wrapping_sub(HANGUL_S_BASE);
1461                                    // If these are not in range, we have a memory-safe GIGO case.
1462                                    debug_assert!(left_hangul_offset < HANGUL_S_COUNT);
1463                                    debug_assert!(right_hangul_offset < HANGUL_S_COUNT);
1464                                    hangul_syllable_compare!(
1465                                        left_hangul_offset,
1466                                        right_hangul_offset,
1467                                    );
1468                                    // We had a two-jamo syllable whose jamo matched
1469                                    // the first two jamo of the other syllable.
1470                                    // Still, we're at a good boundary.
1471                                    break 'prefix;
1472                                }
1473                            } else {
1474                                break;
1475                            }
1476
1477                            let decomposition = right_different.trie_val;
1478                            if (decomposition
1479                                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1480                                == 0
1481                            {
1482                                right_c = right_different.character();
1483                            } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1484                                && ((decomposition & LOW_ZEROS_MASK) != 0)
1485                            {
1486                                // Decomposition into two BMP characters: starter and non-starter
1487                                // Let's take the starter
1488                                right_c = char_from_u32(decomposition & 0x7FFF);
1489                            } else if decomposition == HANGUL_SYLLABLE_MARKER {
1490                                right_c = right_different.character();
1491                            } else {
1492                                break;
1493                            }
1494
1495                            // The last character of the prefix is OK on the normalization
1496                            // level. Now let's check its ce32 unless it's a Hangul syllable,
1497                            // in which case `head_last_ce32` already is a non-default placeholder.
1498                            if head_last_ce32 == CollationElement32::default() {
1499                                head_last_ce32 = self.tailoring.ce32_for_char(head_last_c);
1500                                if head_last_ce32 == FALLBACK_CE32 {
1501                                    head_last_ce32 = self.root.ce32_for_char(head_last_c);
1502                                }
1503                                if head_last_ce32.tag_checked() == Some(Tag::Contraction)
1504                                    && head_last_ce32.at_least_one_suffix_contains_starter()
1505                                {
1506                                    break;
1507                                }
1508                            }
1509                            let mut left_data = self.tailoring;
1510                            let mut left_ce32 = self.tailoring.ce32_for_char(left_c);
1511                            if left_ce32 == FALLBACK_CE32 {
1512                                left_ce32 = self.root.ce32_for_char(left_c);
1513                                left_data = self.root;
1514                            }
1515                            let mut right_data = self.tailoring;
1516                            let mut right_ce32 = self.tailoring.ce32_for_char(right_c);
1517                            if right_ce32 == FALLBACK_CE32 {
1518                                right_ce32 = self.root.ce32_for_char(right_c);
1519                                right_data = self.root;
1520                            }
1521
1522                            // We might be at at a good boundary unless either ce32 is a
1523                            // prefix ce32. Let's check for the happy path first, though.
1524
1525                            // Now check if the ce32s we have are simple enough to
1526                            // make a quick decision here.
1527                            let mut left_primary = left_ce32.to_primary_in_quick_check_numeric(
1528                                left_data,
1529                                numeric_primary.is_some(),
1530                            );
1531                            // If `left_primary` is now zero, we don't really need
1532                            // to extract `right_primary`, but let's avoid the branch
1533                            // on the likely path.
1534                            let mut right_primary = right_ce32.to_primary_in_quick_check_numeric(
1535                                right_data,
1536                                numeric_primary.is_some(),
1537                            );
1538                            quick_primary_compare!(left_primary, right_primary, variable_top, self,);
1539
1540                            if left_ce32.tag_checked() == Some(Tag::Prefix)
1541                                || right_ce32.tag_checked() == Some(Tag::Prefix)
1542                            {
1543                                // TODO: For the Japanese tailoring, it might actually be worthwhile
1544                                // to handle the prefix ce32s inline above the quick check. We've already
1545                                // read the last character from `head` anyway.
1546                                break;
1547                            }
1548
1549                            if numeric_primary.is_some()
1550                                && head_last_ce32.tag_checked() == Some(Tag::Digit)
1551                                && ((left_ce32.tag_checked() == Some(Tag::Digit)
1552                                    && left_ce32.digit() == 0)
1553                                    || (right_ce32.tag_checked() == Some(Tag::Digit)
1554                                        && right_ce32.digit() == 0)
1555                                    || ((left_ce32.tag_checked() == Some(Tag::Digit))
1556                                        ^ (right_ce32.tag_checked() == Some(Tag::Digit))))
1557                            {
1558                                // Avoid giving the zero the leading zero treatment
1559                                // and also reject the case where only one of
1560                                // right and left is a digit.
1561                                break;
1562                            }
1563
1564                            // We're at a good boundary but could not make a quick primary comparison decision.
1565
1566                            // Note: It might look like a good idea to cache the CE32s, but
1567                            // doing so actually makes things slower.
1568                            break 'prefix;
1569                        }
1570                    }
1571                }
1572                let mut tail_first_c;
1573                let mut tail_first_ce32;
1574                let mut tail_first_ok;
1575                loop {
1576                    // Take a step back.
1577                    left.prepend_upcoming_before_init(head_last.clone());
1578                    right.prepend_upcoming_before_init(head_last.clone());
1579
1580                    tail_first_c = head_last_c;
1581                    tail_first_ce32 = head_last_ce32;
1582                    tail_first_ok = head_last_ok;
1583
1584                    let Some((head_last_c_new, decomposition)) = head_chars.next_back() else {
1585                        // We need to step back beyond the start of the prefix.
1586                        // Treat as good boundary.
1587                        break 'prefix;
1588                    };
1589                    head_last_c = head_last_c_new;
1590                    head_last = CharacterAndClassAndTrieValue::new_with_trie_val(
1591                        head_last_c,
1592                        decomposition,
1593                    );
1594                    head_last_ce32 = CollationElement32::default();
1595                    head_last_ok = false;
1596
1597                    if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1598                        // Intentionally empty block to keep
1599                        // the same structure as in the cases
1600                        // where something happens here.
1601                    } else if ((decomposition & HIGH_ZEROS_MASK) != 0)
1602                        && ((decomposition & LOW_ZEROS_MASK) != 0)
1603                    {
1604                        // Decomposition into two BMP characters: starter and non-starter
1605                        // Let's take the starter
1606                        head_last_c = char_from_u32(decomposition & 0x7FFF);
1607                    } else if decomposition == HANGUL_SYLLABLE_MARKER {
1608                        head_last_ce32 = FFFD_CE32;
1609                    } else {
1610                        continue;
1611                    }
1612                    head_last_ok = true;
1613                    if !tail_first_ok {
1614                        continue;
1615                    }
1616                    // The last character of the prefix is OK on the normalization
1617                    // level. Now let's check its ce32 unless it's a Hangul syllable.
1618                    if head_last_ce32 == CollationElement32::default() {
1619                        head_last_ce32 = self.tailoring.ce32_for_char(head_last_c);
1620                        if head_last_ce32 == FALLBACK_CE32 {
1621                            head_last_ce32 = self.root.ce32_for_char(head_last_c);
1622                        }
1623                        if head_last_ce32.tag_checked() == Some(Tag::Contraction)
1624                            && head_last_ce32.at_least_one_suffix_contains_starter()
1625                        {
1626                            continue;
1627                        }
1628                    }
1629                    // Check this _after_ `head_last_ce32` to make sure
1630                    // `head_last_ce32` is initialized for the next loop round
1631                    // trip if applicable.
1632                    if tail_first_ce32 == CollationElement32::default() {
1633                        tail_first_ce32 = self.tailoring.ce32_for_char(tail_first_c);
1634                        if tail_first_ce32 == FALLBACK_CE32 {
1635                            tail_first_ce32 = self.root.ce32_for_char(tail_first_c);
1636                        }
1637                    } // else we already have a trie value from the previous loop iteration or we have Hangul syllable
1638                    if tail_first_ce32.tag_checked() == Some(Tag::Prefix) {
1639                        continue;
1640                    }
1641                    if numeric_primary.is_some()
1642                        && head_last_ce32.tag_checked() == Some(Tag::Digit)
1643                        && tail_first_ce32.tag_checked() == Some(Tag::Digit)
1644                        && tail_first_ce32.digit() == 0
1645                    {
1646                        // Avoid giving the zero the leading zero treatment.
1647                        continue;
1648                    }
1649                    // We are at a good boundary!
1650                    break 'prefix;
1651                }
1652            } else {
1653                // The prefix is empty
1654                break 'prefix;
1655            }
1656            // Unreachable line
1657        }
1658
1659        // End identical prefix
1660
1661        left.init();
1662        right.init();
1663
1664        loop {
1665            let mut left_primary;
1666            'left_primary_loop: loop {
1667                let ce = left.next();
1668                left_primary = ce.primary();
1669                // TODO(#2008): Consider compiling out the variable handling when we know we aren't
1670                // shifting variable CEs.
1671                if !(left_primary < variable_top && left_primary > MERGE_SEPARATOR_PRIMARY) {
1672                    left_ces.push(ce);
1673                } else {
1674                    // Variable CE, shift it to quaternary level.
1675                    // Ignore all following primary ignorables, and shift further variable CEs.
1676                    any_variable = true;
1677                    // Relative to ICU4C, the next line is hoisted out of the following loop
1678                    // in order to keep the variables called `ce` immutable to make it easier
1679                    // to reason about each assignment into `ce` resulting in exactly a single
1680                    // push into `left_ces`.
1681                    left_ces.push(ce.clone_with_non_primary_zeroed());
1682                    loop {
1683                        // This loop is simpler than in ICU4C; unlike in C++, we get to break by label.
1684                        let ce = left.next();
1685                        left_primary = ce.primary();
1686                        if left_primary != 0
1687                            && !(left_primary < variable_top
1688                                && left_primary > MERGE_SEPARATOR_PRIMARY)
1689                        {
1690                            // Neither a primary ignorable nor a variable CE.
1691                            left_ces.push(ce);
1692                            break 'left_primary_loop;
1693                        }
1694                        // If `left_primary == 0`, the following line ignores a primary-ignorable.
1695                        // Otherwise, it shifts a variable CE.
1696                        left_ces.push(ce.clone_with_non_primary_zeroed());
1697                    }
1698                }
1699                if left_primary != 0 {
1700                    break;
1701                }
1702            }
1703            let mut right_primary;
1704            'right_primary_loop: loop {
1705                let ce = right.next();
1706                right_primary = ce.primary();
1707                // TODO(#2008): Consider compiling out the variable handling when we know we aren't
1708                // shifting variable CEs.
1709                if !(right_primary < variable_top && right_primary > MERGE_SEPARATOR_PRIMARY) {
1710                    right_ces.push(ce);
1711                } else {
1712                    // Variable CE, shift it to quaternary level.
1713                    // Ignore all following primary ignorables, and shift further variable CEs.
1714                    any_variable = true;
1715                    // Relative to ICU4C, the next line is hoisted out of the following loop
1716                    // in order to keep the variables called `ce` immutable to make it easier
1717                    // to reason about each assignment into `ce` resulting in exactly a single
1718                    // push into `right_ces`.
1719                    right_ces.push(ce.clone_with_non_primary_zeroed());
1720                    loop {
1721                        // This loop is simpler than in ICU4C; unlike in C++, we get to break by label.
1722                        let ce = right.next();
1723                        right_primary = ce.primary();
1724                        if right_primary != 0
1725                            && !(right_primary < variable_top
1726                                && right_primary > MERGE_SEPARATOR_PRIMARY)
1727                        {
1728                            // Neither a primary ignorable nor a variable CE.
1729                            right_ces.push(ce);
1730                            break 'right_primary_loop;
1731                        }
1732                        // If `right_primary == 0`, the following line ignores a primary-ignorable.
1733                        // Otherwise, it shifts a variable CE.
1734                        right_ces.push(ce.clone_with_non_primary_zeroed());
1735                    }
1736                }
1737                if right_primary != 0 {
1738                    break;
1739                }
1740            }
1741            if left_primary != right_primary {
1742                if let Some(reordering) = &self.reordering {
1743                    left_primary = reordering.reorder(left_primary);
1744                    right_primary = reordering.reorder(right_primary);
1745                }
1746                if left_primary < right_primary {
1747                    return Ordering::Less;
1748                }
1749                return Ordering::Greater;
1750            }
1751            if left_primary == NO_CE_PRIMARY {
1752                break;
1753            }
1754        }
1755
1756        // Sadly, we end up pushing the sentinel value, which means these
1757        // `SmallVec`s allocate more often than if we didn't actually
1758        // store the sentinel.
1759        debug_assert_eq!(left_ces.last(), Some(&NO_CE));
1760        debug_assert_eq!(right_ces.last(), Some(&NO_CE));
1761
1762        // Note: `unwrap_or_default` in the iterations below should never
1763        // actually end up using the "_or_default" part, because the sentinel
1764        // is in the `SmallVec`s. These could be changed to `unwrap()` if we
1765        // preferred panic in case of a bug.
1766        // TODO(#2009): Should we save one slot by not putting the sentinel in
1767        // the `SmallVec`s? So far, the answer seems "no", as it would complicate
1768        // the primary comparison above.
1769
1770        // Compare the buffered secondary & tertiary weights.
1771        // We might skip the secondary level but continue with the case level
1772        // which is turned on separately.
1773        if self.options.strength() >= Strength::Secondary {
1774            if !self.options.backward_second_level() {
1775                let mut left_iter = left_ces.iter();
1776                let mut right_iter = right_ces.iter();
1777                let mut left_secondary;
1778                let mut right_secondary;
1779                loop {
1780                    loop {
1781                        left_secondary = left_iter.next().unwrap_or_default().secondary();
1782                        if left_secondary != 0 {
1783                            break;
1784                        }
1785                    }
1786                    loop {
1787                        right_secondary = right_iter.next().unwrap_or_default().secondary();
1788                        if right_secondary != 0 {
1789                            break;
1790                        }
1791                    }
1792                    if left_secondary != right_secondary {
1793                        if left_secondary < right_secondary {
1794                            return Ordering::Less;
1795                        }
1796                        return Ordering::Greater;
1797                    }
1798                    if left_secondary == NO_CE_SECONDARY {
1799                        break;
1800                    }
1801                }
1802            } else {
1803                let mut left_remaining = &left_ces[..];
1804                let mut right_remaining = &right_ces[..];
1805                loop {
1806                    if left_remaining.is_empty() {
1807                        debug_assert!(right_remaining.is_empty());
1808                        break;
1809                    }
1810                    let (left_prefix, right_prefix) = {
1811                        let mut left_iter = left_remaining.iter();
1812                        loop {
1813                            let left_primary = left_iter.next().unwrap_or_default().primary();
1814                            if left_primary != 0 && left_primary <= MERGE_SEPARATOR_PRIMARY {
1815                                break;
1816                            }
1817                            debug_assert_ne!(left_primary, NO_CE_PRIMARY);
1818                        }
1819                        let left_new_remaining = left_iter.as_slice();
1820                        // Index in range by construction
1821                        #[expect(clippy::indexing_slicing)]
1822                        let left_prefix =
1823                            &left_remaining[..left_remaining.len() - 1 - left_new_remaining.len()];
1824                        left_remaining = left_new_remaining;
1825
1826                        let mut right_iter = right_remaining.iter();
1827                        loop {
1828                            let right_primary = right_iter.next().unwrap_or_default().primary();
1829                            if right_primary != 0 && right_primary <= MERGE_SEPARATOR_PRIMARY {
1830                                break;
1831                            }
1832                            debug_assert_ne!(right_primary, NO_CE_PRIMARY);
1833                        }
1834                        let right_new_remaining = right_iter.as_slice();
1835                        // Index in range by construction
1836                        #[expect(clippy::indexing_slicing)]
1837                        let right_prefix = &right_remaining
1838                            [..right_remaining.len() - 1 - right_new_remaining.len()];
1839                        right_remaining = right_new_remaining;
1840
1841                        (left_prefix, right_prefix)
1842                    };
1843                    let mut left_iter = left_prefix.iter();
1844                    let mut right_iter = right_prefix.iter();
1845
1846                    let mut left_secondary;
1847                    let mut right_secondary;
1848                    loop {
1849                        loop {
1850                            left_secondary = left_iter.next_back().unwrap_or_default().secondary();
1851                            if left_secondary != 0 {
1852                                break;
1853                            }
1854                        }
1855                        loop {
1856                            right_secondary =
1857                                right_iter.next_back().unwrap_or_default().secondary();
1858                            if right_secondary != 0 {
1859                                break;
1860                            }
1861                        }
1862                        if left_secondary != right_secondary {
1863                            if left_secondary < right_secondary {
1864                                return Ordering::Less;
1865                            }
1866                            return Ordering::Greater;
1867                        }
1868                        if left_secondary == NO_CE_SECONDARY {
1869                            break;
1870                        }
1871                    }
1872                }
1873            }
1874        }
1875
1876        if self.options.case_level() {
1877            let mut left_non_primary;
1878            let mut right_non_primary;
1879            let mut left_case;
1880            let mut right_case;
1881            let mut left_iter = left_ces.iter();
1882            let mut right_iter = right_ces.iter();
1883            if self.options.strength() == Strength::Primary {
1884                // Primary+caseLevel: Ignore case level weights of primary ignorables.
1885                // Otherwise we would get a-umlaut > a
1886                // which is not desirable for accent-insensitive sorting.
1887                // Check for (lower 32 bits) == 0 as well because variable CEs are stored
1888                // with only primary weights.
1889                loop {
1890                    loop {
1891                        let ce = left_iter.next().unwrap_or_default();
1892                        left_non_primary = ce.non_primary();
1893                        if !ce.either_half_zero() {
1894                            break;
1895                        }
1896                    }
1897                    left_case = left_non_primary.case();
1898                    loop {
1899                        let ce = right_iter.next().unwrap_or_default();
1900                        right_non_primary = ce.non_primary();
1901                        if !ce.either_half_zero() {
1902                            break;
1903                        }
1904                    }
1905                    right_case = right_non_primary.case();
1906                    // No need to handle NO_CE and MERGE_SEPARATOR specially:
1907                    // There is one case weight for each previous-level weight,
1908                    // so level length differences were handled there.
1909                    if left_case != right_case {
1910                        if !self.options.upper_first() {
1911                            if left_case < right_case {
1912                                return Ordering::Less;
1913                            }
1914                            return Ordering::Greater;
1915                        }
1916                        if left_case < right_case {
1917                            return Ordering::Greater;
1918                        }
1919                        return Ordering::Less;
1920                    }
1921                    if left_non_primary.secondary() == NO_CE_SECONDARY {
1922                        break;
1923                    }
1924                }
1925            } else {
1926                // Secondary+caseLevel: By analogy with the above,
1927                // ignore case level weights of secondary ignorables.
1928                //
1929                // Note: A tertiary CE has uppercase case bits (0.0.ut)
1930                // to keep tertiary+caseFirst well-formed.
1931                //
1932                // Tertiary+caseLevel: Also ignore case level weights of secondary ignorables.
1933                // Otherwise a tertiary CE's uppercase would be no greater than
1934                // a primary/secondary CE's uppercase.
1935                // (See UCA well-formedness condition 2.)
1936                // We could construct a special case weight higher than uppercase,
1937                // but it's simpler to always ignore case weights of secondary ignorables,
1938                // turning 0.0.ut into 0.0.0.t.
1939                // (See LDML Collation, Case Parameters.)
1940                loop {
1941                    loop {
1942                        left_non_primary = left_iter.next().unwrap_or_default().non_primary();
1943                        if left_non_primary.secondary() != 0 {
1944                            break;
1945                        }
1946                    }
1947                    left_case = left_non_primary.case();
1948                    loop {
1949                        right_non_primary = right_iter.next().unwrap_or_default().non_primary();
1950                        if right_non_primary.secondary() != 0 {
1951                            break;
1952                        }
1953                    }
1954                    right_case = right_non_primary.case();
1955                    // No need to handle NO_CE and MERGE_SEPARATOR specially:
1956                    // There is one case weight for each previous-level weight,
1957                    // so level length differences were handled there.
1958                    if left_case != right_case {
1959                        if !self.options.upper_first() {
1960                            if left_case < right_case {
1961                                return Ordering::Less;
1962                            }
1963                            return Ordering::Greater;
1964                        }
1965                        if left_case < right_case {
1966                            return Ordering::Greater;
1967                        }
1968                        return Ordering::Less;
1969                    }
1970                    if left_non_primary.secondary() == NO_CE_SECONDARY {
1971                        break;
1972                    }
1973                }
1974            }
1975        }
1976
1977        if let Some(tertiary_mask) = self.options.tertiary_mask() {
1978            let mut any_quaternaries = AnyQuaternaryAccumulator::new();
1979            let mut left_iter = left_ces.iter();
1980            let mut right_iter = right_ces.iter();
1981            loop {
1982                let mut left_non_primary;
1983                let mut left_tertiary;
1984                loop {
1985                    left_non_primary = left_iter.next().unwrap_or_default().non_primary();
1986                    any_quaternaries.accumulate(left_non_primary);
1987                    debug_assert!(
1988                        left_non_primary.tertiary() != 0 || left_non_primary.case_quaternary() == 0
1989                    );
1990                    left_tertiary = left_non_primary.tertiary_case_quarternary(tertiary_mask);
1991                    if left_tertiary != 0 {
1992                        break;
1993                    }
1994                }
1995
1996                let mut right_non_primary;
1997                let mut right_tertiary;
1998                loop {
1999                    right_non_primary = right_iter.next().unwrap_or_default().non_primary();
2000                    any_quaternaries.accumulate(right_non_primary);
2001                    debug_assert!(
2002                        right_non_primary.tertiary() != 0
2003                            || right_non_primary.case_quaternary() == 0
2004                    );
2005                    right_tertiary = right_non_primary.tertiary_case_quarternary(tertiary_mask);
2006                    if right_tertiary != 0 {
2007                        break;
2008                    }
2009                }
2010
2011                if left_tertiary != right_tertiary {
2012                    if self.options.upper_first() {
2013                        // Pass through NO_CE and keep real tertiary weights larger than that.
2014                        // Do not change the artificial uppercase weight of a tertiary CE (0.0.ut),
2015                        // to keep tertiary CEs well-formed.
2016                        // Their case+tertiary weights must be greater than those of
2017                        // primary and secondary CEs.
2018                        // Magic numbers from ICU4C.
2019                        if left_tertiary > NO_CE_TERTIARY {
2020                            if left_non_primary.secondary() != 0 {
2021                                left_tertiary ^= 0xC000;
2022                            } else {
2023                                left_tertiary += 0x4000;
2024                            }
2025                        }
2026                        if right_tertiary > NO_CE_TERTIARY {
2027                            if right_non_primary.secondary() != 0 {
2028                                right_tertiary ^= 0xC000;
2029                            } else {
2030                                right_tertiary += 0x4000;
2031                            }
2032                        }
2033                    }
2034                    if left_tertiary < right_tertiary {
2035                        return Ordering::Less;
2036                    }
2037                    return Ordering::Greater;
2038                }
2039
2040                if left_tertiary == NO_CE_TERTIARY {
2041                    break;
2042                }
2043            }
2044            if !any_variable && !any_quaternaries.has_quaternary() {
2045                return Ordering::Equal;
2046            }
2047        } else {
2048            return Ordering::Equal;
2049        }
2050
2051        if self.options.strength() <= Strength::Tertiary {
2052            return Ordering::Equal;
2053        }
2054
2055        let mut left_iter = left_ces.iter();
2056        let mut right_iter = right_ces.iter();
2057        loop {
2058            let mut left_quaternary;
2059            loop {
2060                let ce = left_iter.next().unwrap_or_default();
2061                if ce.tertiary_ignorable() {
2062                    left_quaternary = ce.primary();
2063                } else {
2064                    left_quaternary = ce.quaternary();
2065                }
2066                if left_quaternary != 0 {
2067                    break;
2068                }
2069            }
2070            let mut right_quaternary;
2071            loop {
2072                let ce = right_iter.next().unwrap_or_default();
2073                if ce.tertiary_ignorable() {
2074                    right_quaternary = ce.primary();
2075                } else {
2076                    right_quaternary = ce.quaternary();
2077                }
2078                if right_quaternary != 0 {
2079                    break;
2080                }
2081            }
2082            if left_quaternary != right_quaternary {
2083                if let Some(reordering) = &self.reordering {
2084                    left_quaternary = reordering.reorder(left_quaternary);
2085                    right_quaternary = reordering.reorder(right_quaternary);
2086                }
2087                if left_quaternary < right_quaternary {
2088                    return Ordering::Less;
2089                }
2090                return Ordering::Greater;
2091            }
2092            if left_quaternary == NO_CE_PRIMARY {
2093                break;
2094            }
2095        }
2096
2097        Ordering::Equal
2098    }
2099
2100    fn sort_key_levels(&self) -> u8 {
2101        #[expect(clippy::indexing_slicing)]
2102        let mut levels = LEVEL_MASKS[self.options.strength() as usize];
2103        if self.options.case_level() {
2104            levels |= CASE_LEVEL_FLAG;
2105        }
2106        levels
2107    }
2108
2109    /// Given valid UTF-8, write the sort key bytes up to the collator's strength.
2110    ///
2111    /// The bytes are written to an implementor of [`CollationKeySink`], a no-std version of `std::io::Write`.
2112    /// This trait is currently unstable, but it is implemented by `Vec<u8>`, `VecDeque<u8>`,
2113    /// `SmallVec<[u8; N]>`, and `&mut [u8]` (returning an error if the slice is too small).
2114    ///
2115    /// If two sort keys generated at the same strength are compared bytewise, the result is
2116    /// the same as a collation comparison of the original strings at that strength.
2117    ///
2118    /// For identical strength, the UTF-8 NFD normalization is appended for breaking ties.
2119    ///
2120    /// No terminating zero byte is written to the output, so the output is not a valid C
2121    /// string, but the caller may append a zero afterward if a C string is desired.
2122    ///
2123    /// ⚠️ Generating a sort key is expensive relative to comparison because to compare, the
2124    /// collator skips identical prefixes before doing more complex comparison.  Only use sort
2125    /// keys if you expect to compare them many times so as to amortize the cost of generating
2126    /// them.  Measurement of this performance trade-off would be a good idea.
2127    ///
2128    /// ⚠️ Sort keys, if stored durably, should be presumed to be invalidated by a CLDR update, a
2129    /// new version of Unicode, or an update to the ICU4X code.  Applications using sort keys
2130    /// *must* be prepared to recompute them if required and should take the performance of
2131    /// such an operation into account when deciding to use sort keys.
2132    ///
2133    /// ⚠️ If you should store sort keys in a database that is or becomes so large that
2134    /// regenerating sort keys becomes impractical, you should not expect ICU4X to support your
2135    /// using an older, frozen copy of the sort key generation algorithm with a later version
2136    /// of the library.
2137    ///
2138    /// # Example
2139    ///
2140    /// ```
2141    /// use icu_collator::{
2142    ///     options::{CollatorOptions, Strength},
2143    ///     Collator,
2144    /// };
2145    /// use icu_locale::locale;
2146    /// let locale = locale!("utf").into();
2147    /// let mut options = CollatorOptions::default();
2148    /// options.strength = Some(Strength::Primary);
2149    /// let collator = Collator::try_new(locale, options).unwrap();
2150    ///
2151    /// let mut k1 = Vec::new();
2152    /// let Ok(()) = collator.write_sort_key_to("hello", &mut k1);
2153    /// let mut k2 = Vec::new();
2154    /// let Ok(()) = collator.write_sort_key_to("Héłłö", &mut k2);
2155    /// assert_eq!(k1, k2);
2156    /// ```
2157    pub fn write_sort_key_to<S>(&self, s: &str, sink: &mut S) -> Result<S::Output, S::Error>
2158    where
2159        S: CollationKeySink + ?Sized,
2160        S::State: Default,
2161    {
2162        self.write_sort_key_impl(s.chars_with_trie_default_for_ascii(self.norm_trie()), sink)
2163    }
2164
2165    /// Given potentially invalid UTF-8, write the sort key bytes up to the collator's strength.
2166    ///
2167    /// For further details, see [`Self::write_sort_key_to`].
2168    pub fn write_sort_key_utf8_to<S>(&self, s: &[u8], sink: &mut S) -> Result<S::Output, S::Error>
2169    where
2170        S: CollationKeySink + ?Sized,
2171        S::State: Default,
2172    {
2173        self.write_sort_key_impl(s.chars_with_trie_default_for_ascii(self.norm_trie()), sink)
2174    }
2175
2176    /// Given potentially invalid UTF-16, write the sort key bytes up to the collator's strength.
2177    ///
2178    /// For further details, see [`Self::write_sort_key_to`].
2179    pub fn write_sort_key_utf16_to<S>(&self, s: &[u16], sink: &mut S) -> Result<S::Output, S::Error>
2180    where
2181        S: CollationKeySink + ?Sized,
2182        S::State: Default,
2183    {
2184        self.write_sort_key_impl(s.chars_with_trie(self.norm_trie()), sink)
2185    }
2186
2187    fn write_sort_key_impl<I, T, S>(
2188        &'data self,
2189        iter: I,
2190        sink: &mut S,
2191    ) -> Result<S::Output, S::Error>
2192    where
2193        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + Clone + 'data,
2194        T: AbstractCodePointTrie<'data, u32> + 'data,
2195        S: CollationKeySink + ?Sized,
2196        S::State: Default,
2197    {
2198        let identical = if self.options.strength() == Strength::Identical {
2199            Some(iter.clone())
2200        } else {
2201            None
2202        };
2203
2204        let mut state = S::State::default();
2205        self.write_sort_key_up_to_quaternary(iter, sink, &mut state)?;
2206
2207        if let Some(iter) = identical {
2208            sink.write_byte(&mut state, LEVEL_SEPARATOR_BYTE)?;
2209
2210            let mut iter = icu_normalizer::new_decomposition(iter, self.tables);
2211            let _ = iter.next(); // Discard the U+0000.
2212            write_identical_level(iter, sink, &mut state)?;
2213        }
2214
2215        sink.finish(state)
2216    }
2217
2218    /// Write the sort key bytes up to the collator's strength.
2219    ///
2220    /// Optionally write the case level.  Separate levels with the `LEVEL_SEPARATOR_BYTE`, but
2221    /// do not write a terminating zero as with a C string.
2222    fn write_sort_key_up_to_quaternary<I, S, T>(
2223        &'data self,
2224        iter: I,
2225        sink: &mut S,
2226        state: &mut S::State,
2227    ) -> Result<(), S::Error>
2228    where
2229        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + Clone + 'data,
2230        T: AbstractCodePointTrie<'data, u32> + 'data,
2231        S: CollationKeySink + ?Sized,
2232    {
2233        // This algorithm comes from `CollationKeys::writeSortKeyUpToQuaternary` in ICU4C.
2234        let levels = self.sort_key_levels();
2235
2236        let mut iter = collation_elements!(self, iter, self.numeric_primary());
2237        iter.init();
2238        let variable_top = self.variable_top();
2239
2240        let tertiary_mask = self.options.tertiary_mask().unwrap_or_default();
2241
2242        let mut cases = SortKeyLevel::default();
2243        let mut secondaries = SortKeyLevel::default();
2244        let mut tertiaries = SortKeyLevel::default();
2245        let mut quaternaries = SortKeyLevel::default();
2246
2247        let mut prev_reordered_primary = 0;
2248        let mut common_cases = 0usize;
2249        let mut common_secondaries = 0usize;
2250        let mut common_tertiaries = 0usize;
2251        let mut common_quaternaries = 0usize;
2252        let mut prev_secondary = 0;
2253        let mut sec_segment_start = 0;
2254
2255        loop {
2256            let mut ce = iter.next();
2257            let mut p = ce.primary();
2258            if p < variable_top && p > MERGE_SEPARATOR_PRIMARY {
2259                // Variable CE, shift it to quaternary level.  Ignore all following primary
2260                // ignorables, and shift further variable CEs.
2261                if common_quaternaries != 0 {
2262                    common_quaternaries -= 1;
2263                    while common_quaternaries >= QUAT_COMMON[WEIGHT_MAX_COUNT] as _ {
2264                        quaternaries.append_byte(QUAT_COMMON[WEIGHT_MIDDLE]);
2265                        common_quaternaries -= QUAT_COMMON[WEIGHT_MAX_COUNT] as usize;
2266                    }
2267                    // Shifted primary weights are lower than the common weight.
2268                    quaternaries.append_byte(QUAT_COMMON[WEIGHT_LOW] + common_quaternaries as u8);
2269                    common_quaternaries = 0;
2270                }
2271
2272                loop {
2273                    if levels & QUATERNARY_LEVEL_FLAG != 0 {
2274                        if let Some(reordering) = &self.reordering {
2275                            p = reordering.reorder(p);
2276                        }
2277                        if (p >> 24) as u8 >= QUAT_SHIFTED_LIMIT_BYTE {
2278                            // Prevent shifted primary lead bytes from overlapping with the
2279                            // common compression range.
2280                            quaternaries.append_byte(QUAT_SHIFTED_LIMIT_BYTE);
2281                        }
2282                        quaternaries.append_weight_32(p);
2283                    }
2284                    loop {
2285                        ce = iter.next();
2286                        p = ce.primary();
2287                        if p != 0 {
2288                            break;
2289                        }
2290                    }
2291                    if !(p < variable_top && p > MERGE_SEPARATOR_PRIMARY) {
2292                        break;
2293                    }
2294                }
2295            }
2296
2297            // ce could be primary ignorable, or NO_CE, or the merge separator, or a regular
2298            // primary CE, but it is not variable.  If ce == NO_CE, then write nothing for the
2299            // primary level but terminate compression on all levels and then exit the loop.
2300            if p > NO_CE_PRIMARY && levels & PRIMARY_LEVEL_FLAG != 0 {
2301                // Test the un-reordered primary for compressibility.
2302                let is_compressible = self.special_primaries.is_compressible((p >> 24) as _);
2303                if let Some(reordering) = &self.reordering {
2304                    p = reordering.reorder(p);
2305                }
2306                let p1 = (p >> 24) as u8;
2307                if !is_compressible || p1 != (prev_reordered_primary >> 24) as u8 {
2308                    if prev_reordered_primary != 0 {
2309                        if p < prev_reordered_primary {
2310                            // No primary compression terminator at the end of the level or
2311                            // merged segment.
2312                            if p1 > MERGE_SEPARATOR_BYTE {
2313                                sink.write(state, &[PRIMARY_COMPRESSION_LOW_BYTE])?;
2314                            }
2315                        } else {
2316                            sink.write(state, &[PRIMARY_COMPRESSION_HIGH_BYTE])?;
2317                        }
2318                    }
2319                    sink.write_byte(state, p1)?;
2320                    prev_reordered_primary = if is_compressible { p } else { 0 };
2321                }
2322
2323                let p2 = (p >> 16) as u8;
2324                if p2 != 0 {
2325                    let (b0, b1, b2) = (p2, (p >> 8) as _, p as _);
2326                    sink.write_byte(state, b0)?;
2327                    if b1 != 0 {
2328                        sink.write_byte(state, b1)?;
2329                        if b2 != 0 {
2330                            sink.write_byte(state, b2)?;
2331                        }
2332                    }
2333                }
2334            }
2335
2336            let non_primary = ce.non_primary();
2337            if non_primary.ignorable() {
2338                continue; // completely ignorable, no secondary/case/tertiary/quaternary
2339            }
2340
2341            macro_rules! handle_common {
2342                ($key:ident, $w:ident, $common:ident, $weights:ident, $lim:expr) => {
2343                    if $common != 0 {
2344                        $common -= 1;
2345                        while $common >= $weights[WEIGHT_MAX_COUNT] as _ {
2346                            $key.append_byte($weights[WEIGHT_MIDDLE]);
2347                            $common -= $weights[WEIGHT_MAX_COUNT] as usize;
2348                        }
2349                        let b = if $w < $lim {
2350                            $weights[WEIGHT_LOW] + ($common as u8)
2351                        } else {
2352                            $weights[WEIGHT_HIGH] - ($common as u8)
2353                        };
2354                        $key.append_byte(b);
2355                        $common = 0;
2356                    }
2357                };
2358                ($key:ident, $w:ident, $common:ident, $weights:ident) => {
2359                    handle_common!($key, $w, $common, $weights, COMMON_WEIGHT16);
2360                };
2361            }
2362
2363            if levels & SECONDARY_LEVEL_FLAG != 0 {
2364                let s = non_primary.secondary();
2365                if s == 0 {
2366                    // secondary ignorable
2367                } else if s == COMMON_WEIGHT16
2368                    && (!self.options.backward_second_level() || p != MERGE_SEPARATOR_PRIMARY)
2369                {
2370                    // s is a common secondary weight, and backwards-secondary is off or the ce
2371                    // is not the merge separator.
2372                    common_secondaries += 1;
2373                } else if !self.options.backward_second_level() {
2374                    handle_common!(secondaries, s, common_secondaries, SEC_COMMON);
2375                    secondaries.append_weight_16(s);
2376                } else {
2377                    if common_secondaries != 0 {
2378                        common_secondaries -= 1;
2379                        // Append reverse weights.  The level will be re-reversed later.
2380                        let remainder = common_secondaries % SEC_COMMON[WEIGHT_MAX_COUNT] as usize;
2381                        let b = if prev_secondary < COMMON_WEIGHT16 {
2382                            SEC_COMMON[WEIGHT_LOW] + remainder as u8
2383                        } else {
2384                            SEC_COMMON[WEIGHT_HIGH] - remainder as u8
2385                        };
2386                        secondaries.append_byte(b);
2387                        common_secondaries -= remainder;
2388                        // common_secondaries is now a multiple of SEC_COMMON[WEIGHT_MAX_COUNT]
2389                        while common_secondaries > 0 {
2390                            // same as >= SEC_COMMON[WEIGHT_MAX_COUNT]
2391                            secondaries.append_byte(SEC_COMMON[WEIGHT_MIDDLE]);
2392                            common_secondaries -= SEC_COMMON[WEIGHT_MAX_COUNT] as usize;
2393                        }
2394                        // commonSecondaries == 0
2395                    }
2396                    if 0 < p && p <= MERGE_SEPARATOR_PRIMARY {
2397                        // The backwards secondary level compares secondary weights backwards
2398                        // within segments separated by the merge separator (U+FFFE).
2399                        let secs = &mut secondaries.buf;
2400                        let last = secs.len() - 1;
2401                        if sec_segment_start < last {
2402                            let mut q = sec_segment_start;
2403                            let mut r = last;
2404
2405                            // these indices start at valid values and we stop when they cross
2406                            #[expect(clippy::indexing_slicing)]
2407                            while q < r {
2408                                let b = secs[q];
2409                                secs[q] = secs[r];
2410                                q += 1;
2411                                secs[r] = b;
2412                                r -= 1;
2413                            }
2414                        }
2415                        let b = if p == NO_CE_PRIMARY {
2416                            LEVEL_SEPARATOR_BYTE
2417                        } else {
2418                            MERGE_SEPARATOR_BYTE
2419                        };
2420                        secondaries.append_byte(b);
2421                        prev_secondary = 0;
2422                        sec_segment_start = secondaries.len();
2423                    } else {
2424                        secondaries.append_reverse_weight_16(s);
2425                        prev_secondary = s;
2426                    }
2427                }
2428            }
2429
2430            if levels & CASE_LEVEL_FLAG != 0 {
2431                if self.options.strength() == Strength::Primary && p == 0
2432                    || non_primary.bits() <= 0xffff
2433                {
2434                    // Primary+caseLevel: Ignore case level weights of primary ignorables.
2435                    // Otherwise: Ignore case level weights of secondary ignorables.  For
2436                    // details see the comments in the CollationCompare class.
2437                } else {
2438                    // case bits & tertiary lead byte
2439                    let mut c = ((non_primary.bits() >> 8) & 0xff) as u8;
2440                    debug_assert_ne!(c & 0xc0, 0xc0);
2441                    if c & 0xc0 == 0 && c > LEVEL_SEPARATOR_BYTE {
2442                        common_cases += 1;
2443                    } else {
2444                        if !self.options.upper_first() {
2445                            // lower first:  Compress common weights to nibbles 1..7..13,
2446                            // mixed=14, upper=15.  If there are only common (=lowest) weights
2447                            // in the whole level, then we need not write anything.  Level
2448                            // length differences are handled already on the next-higher level.
2449                            if common_cases != 0 && (c > LEVEL_SEPARATOR_BYTE || !cases.is_empty())
2450                            {
2451                                common_cases -= 1;
2452                                while common_cases >= CASE_LOWER_FIRST_COMMON[WEIGHT_MAX_COUNT] as _
2453                                {
2454                                    cases.append_byte(CASE_LOWER_FIRST_COMMON[WEIGHT_MIDDLE] << 4);
2455                                    common_cases -=
2456                                        CASE_LOWER_FIRST_COMMON[WEIGHT_MAX_COUNT] as usize;
2457                                }
2458                                let b = if c <= LEVEL_SEPARATOR_BYTE {
2459                                    CASE_LOWER_FIRST_COMMON[WEIGHT_LOW] + common_cases as u8
2460                                } else {
2461                                    CASE_LOWER_FIRST_COMMON[WEIGHT_HIGH] - common_cases as u8
2462                                };
2463                                cases.append_byte(b << 4);
2464                                common_cases = 0;
2465                            }
2466                            if c > LEVEL_SEPARATOR_BYTE {
2467                                // 14 or 15
2468                                c = (CASE_LOWER_FIRST_COMMON[WEIGHT_HIGH] + (c >> 6)) << 4;
2469                            }
2470                        } else {
2471                            // upper first:  Compress common weights to nibbles 3..15, mixed=2,
2472                            // upper=1.  The compressed common case weights only go up from the
2473                            // "low" value because with upperFirst the common weight is the
2474                            // highest one.
2475                            if common_cases != 0 {
2476                                common_cases -= 1;
2477                                while common_cases >= CASE_UPPER_FIRST_COMMON[WEIGHT_MAX_COUNT] as _
2478                                {
2479                                    cases.append_byte(CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] << 4);
2480                                    common_cases -=
2481                                        CASE_UPPER_FIRST_COMMON[WEIGHT_MAX_COUNT] as usize;
2482                                }
2483                                cases.append_byte(
2484                                    (CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] + common_cases as u8) << 4,
2485                                );
2486                                common_cases = 0;
2487                            }
2488                            if c > LEVEL_SEPARATOR_BYTE {
2489                                // 2 or 1
2490                                c = (CASE_UPPER_FIRST_COMMON[WEIGHT_LOW] - (c >> 6)) << 4;
2491                            }
2492                        }
2493                        // c is a separator byte 01 or a left-shifted nibble 0x10, 0x20, ...
2494                        // 0xf0.
2495                        cases.append_byte(c);
2496                    }
2497                }
2498            }
2499
2500            if levels & TERTIARY_LEVEL_FLAG != 0 {
2501                let mut t = non_primary.tertiary_case_quarternary(tertiary_mask);
2502                debug_assert_ne!(non_primary.bits() & 0xc000, 0xc000);
2503                if t == COMMON_WEIGHT16 {
2504                    common_tertiaries += 1;
2505                } else if tertiary_mask & 0x8000 == 0 {
2506                    // Tertiary weights without case bits.  Move lead bytes 06..3F to C6..FF
2507                    // for a large common-weight range.
2508                    handle_common!(tertiaries, t, common_tertiaries, TER_ONLY_COMMON);
2509                    if t > COMMON_WEIGHT16 {
2510                        t += 0xc000;
2511                    }
2512                    tertiaries.append_weight_16(t);
2513                } else if !self.options.upper_first() {
2514                    // Tertiary weights with caseFirst=lowerFirst.  Move lead bytes 06..BF to
2515                    // 46..FF for the common-weight range.
2516                    handle_common!(tertiaries, t, common_tertiaries, TER_LOWER_FIRST_COMMON);
2517                    if t > COMMON_WEIGHT16 {
2518                        t += 0x4000;
2519                    }
2520                    tertiaries.append_weight_16(t);
2521                } else {
2522                    // Tertiary weights with caseFirst=upperFirst.  Do not change the
2523                    // artificial uppercase weight of a tertiary CE (0.0.ut), to keep tertiary
2524                    // CEs well-formed.  Their case+tertiary weights must be greater than those
2525                    // of primary and secondary CEs.
2526                    //
2527                    // Separator         01 -> 01      (unchanged)
2528                    // Lowercase     02..04 -> 82..84  (includes uncased)
2529                    // Common weight     05 -> 85..C5  (common-weight compression range)
2530                    // Lowercase     06..3F -> C6..FF
2531                    // Mixed case    42..7F -> 42..7F
2532                    // Uppercase     82..BF -> 02..3F
2533                    // Tertiary CE   86..BF -> C6..FF
2534                    if t <= NO_CE_TERTIARY {
2535                        // Keep separators unchanged.
2536                    } else if non_primary.bits() > 0xffff {
2537                        // Invert case bits of primary & secondary CEs.
2538                        t ^= 0xc000;
2539                        if t < (TER_UPPER_FIRST_COMMON[WEIGHT_HIGH] as u16) << 8 {
2540                            t -= 0x4000;
2541                        }
2542                    } else {
2543                        // Keep uppercase bits of tertiary CEs.
2544                        debug_assert!((0x8600..=0xbfff).contains(&t));
2545                        t += 0x4000;
2546                    }
2547                    handle_common!(
2548                        tertiaries,
2549                        t,
2550                        common_tertiaries,
2551                        TER_UPPER_FIRST_COMMON,
2552                        (TER_UPPER_FIRST_COMMON[WEIGHT_LOW] as u16) << 8
2553                    );
2554                    tertiaries.append_weight_16(t);
2555                }
2556            }
2557
2558            if levels & QUATERNARY_LEVEL_FLAG != 0 {
2559                let q = (non_primary.bits() & 0xffff) as u16;
2560                if q & 0xc0 == 0 && q > NO_CE_QUATERNARY {
2561                    common_quaternaries += 1;
2562                } else if q == NO_CE_QUATERNARY
2563                    && self.options.alternate_handling() == AlternateHandling::NonIgnorable
2564                    && quaternaries.is_empty()
2565                {
2566                    // If alternate=non-ignorable and there are only common quaternary weights,
2567                    // then we need not write anything.  The only weights greater than the
2568                    // merge separator and less than the common weight are shifted primary
2569                    // weights, which are not generated for alternate=non-ignorable.  There are
2570                    // also exactly as many quaternary weights as tertiary weights, so level
2571                    // length differences are handled already on tertiary level.  Any
2572                    // above-common quaternary weight will compare greater regardless.
2573                    quaternaries.append_byte(LEVEL_SEPARATOR_BYTE);
2574                } else {
2575                    let q = if q == NO_CE_QUATERNARY {
2576                        LEVEL_SEPARATOR_BYTE
2577                    } else {
2578                        (0xfc + ((q >> 6) & 3)) as u8
2579                    };
2580                    handle_common!(
2581                        quaternaries,
2582                        q,
2583                        common_quaternaries,
2584                        QUAT_COMMON,
2585                        QUAT_COMMON[WEIGHT_LOW]
2586                    );
2587                    quaternaries.append_byte(q);
2588                }
2589            }
2590
2591            if (non_primary.bits() >> 24) as u8 == LEVEL_SEPARATOR_BYTE {
2592                break; // ce == NO_CE
2593            }
2594        }
2595
2596        macro_rules! write_level {
2597            ($key:ident, $flag:ident) => {
2598                if levels & $flag != 0 {
2599                    sink.write(state, &[LEVEL_SEPARATOR_BYTE])?;
2600                    sink.write(state, &$key.buf)?;
2601                }
2602            };
2603        }
2604
2605        write_level!(secondaries, SECONDARY_LEVEL_FLAG);
2606
2607        if levels & CASE_LEVEL_FLAG != 0 {
2608            sink.write(state, &[LEVEL_SEPARATOR_BYTE])?;
2609
2610            // Write pairs of nibbles as bytes, except separator bytes as themselves.
2611            let mut b = 0;
2612            if let Some((last, head)) = cases.buf.split_last() {
2613                debug_assert_eq!(*last, 1); // The trailing NO_CE
2614                for c in head {
2615                    debug_assert_eq!(*c & 0xf, 0);
2616                    debug_assert_ne!(*c, 0);
2617                    if b == 0 {
2618                        b = *c;
2619                    } else {
2620                        sink.write_byte(state, b | (*c >> 4))?;
2621                        b = 0;
2622                    }
2623                }
2624            } else {
2625                debug_assert!(false);
2626            }
2627            if b != 0 {
2628                sink.write_byte(state, b)?;
2629            }
2630        }
2631
2632        write_level!(tertiaries, TERTIARY_LEVEL_FLAG);
2633        write_level!(quaternaries, QUATERNARY_LEVEL_FLAG);
2634
2635        Ok(())
2636    }
2637}
2638
2639/// Error indicating that a [`CollationKeySink`] with limited space ran out of space.
2640#[derive(Debug, PartialEq, Eq)]
2641pub struct TooSmall {
2642    /// The total length, in bytes, of the sort key.
2643    pub length: usize,
2644}
2645
2646impl TooSmall {
2647    pub fn new(length: usize) -> Self {
2648        Self { length }
2649    }
2650}
2651
2652/// A [`std::io::Write`]-like trait for writing to a buffer-like object.
2653///
2654/// (This crate does not have access to [`std`].)
2655///
2656/// <div class="stab unstable">
2657/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
2658/// including in SemVer minor releases. Do not implement or call methods on this trait
2659/// unless you are prepared for things to occasionally break.
2660///
2661/// Graduation tracking issue: [issue #7178](https://github.com/unicode-org/icu4x/issues/7178).
2662/// </div>
2663///
2664/// ✨ *Enabled with the `unstable` Cargo feature.*
2665pub trait CollationKeySink {
2666    /// The type of error the sink may return.
2667    type Error;
2668
2669    /// An intermediate state object used by the sink, which must implement [`Default`].
2670    type State;
2671
2672    /// A result value indicating the final state of the sink (e.g. a number of bytes written).
2673    type Output;
2674
2675    /// Writes a buffer into the writer.
2676    fn write(&mut self, state: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error>;
2677
2678    /// Write a single byte into the writer.
2679    fn write_byte(&mut self, state: &mut Self::State, b: u8) -> Result<(), Self::Error> {
2680        self.write(state, &[b])
2681    }
2682
2683    /// Finalize any internal sink state (perhaps by flushing a buffer) and return the final
2684    /// output value.
2685    fn finish(&mut self, state: Self::State) -> Result<Self::Output, Self::Error>;
2686}
2687
2688impl CollationKeySink for Vec<u8> {
2689    type Error = Infallible;
2690    type State = ();
2691    type Output = ();
2692
2693    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2694        self.extend_from_slice(buf);
2695        Ok(())
2696    }
2697
2698    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2699        Ok(())
2700    }
2701}
2702
2703impl CollationKeySink for VecDeque<u8> {
2704    type Error = Infallible;
2705    type State = ();
2706    type Output = ();
2707
2708    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2709        self.extend(buf.iter());
2710        Ok(())
2711    }
2712
2713    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2714        Ok(())
2715    }
2716}
2717
2718impl<const N: usize> CollationKeySink for SmallVec<[u8; N]> {
2719    type Error = Infallible;
2720    type State = ();
2721    type Output = ();
2722
2723    fn write(&mut self, _: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2724        self.extend_from_slice(buf);
2725        Ok(())
2726    }
2727
2728    fn finish(&mut self, _: Self::State) -> Result<Self::Output, Self::Error> {
2729        Ok(())
2730    }
2731}
2732
2733impl CollationKeySink for [u8] {
2734    type Error = TooSmall;
2735    type State = usize;
2736    type Output = usize;
2737
2738    fn write(&mut self, offset: &mut Self::State, buf: &[u8]) -> Result<(), Self::Error> {
2739        if *offset + buf.len() <= self.len() {
2740            // just checked bounds
2741            #[expect(clippy::indexing_slicing)]
2742            self[*offset..*offset + buf.len()].copy_from_slice(buf);
2743        }
2744        *offset += buf.len();
2745        Ok(())
2746    }
2747
2748    fn finish(&mut self, offset: Self::State) -> Result<Self::Output, Self::Error> {
2749        if offset <= self.len() {
2750            Ok(offset)
2751        } else {
2752            Err(TooSmall::new(offset))
2753        }
2754    }
2755}
2756
2757#[derive(Default)]
2758struct SortKeyLevel {
2759    buf: SmallVec<[u8; 40]>,
2760}
2761
2762impl SortKeyLevel {
2763    fn len(&self) -> usize {
2764        self.buf.len()
2765    }
2766
2767    fn is_empty(&self) -> bool {
2768        self.buf.is_empty()
2769    }
2770
2771    fn append_byte(&mut self, x: u8) {
2772        self.buf.push(x);
2773    }
2774
2775    fn append_weight_16(&mut self, w: u16) {
2776        debug_assert_ne!(w, 0);
2777        let b0 = (w >> 8) as u8;
2778        let b1 = w as u8;
2779        self.append_byte(b0);
2780        if b1 != 0 {
2781            self.append_byte(b1);
2782        }
2783    }
2784
2785    fn append_reverse_weight_16(&mut self, w: u16) {
2786        debug_assert_ne!(w, 0);
2787        let b0 = (w >> 8) as u8;
2788        let b1 = w as u8;
2789        if b1 != 0 {
2790            self.append_byte(b1);
2791        }
2792        self.append_byte(b0);
2793    }
2794
2795    fn append_weight_32(&mut self, w: u32) {
2796        debug_assert_ne!(w, 0);
2797        let b0 = (w >> 24) as u8;
2798        let b1 = (w >> 16) as u8;
2799        let b2 = (w >> 8) as u8;
2800        let b3 = w as u8;
2801        self.append_byte(b0);
2802        if b1 != 0 {
2803            self.append_byte(b1);
2804            if b2 != 0 {
2805                self.append_byte(b2);
2806                if b3 != 0 {
2807                    self.append_byte(b3);
2808                }
2809            }
2810        }
2811    }
2812}
2813
2814// The algorithm below (BOCSU or Binary Ordered Compression Scheme for Unicode) is translated
2815// from the C++ code in ICU4C at icu4c/source/i18n/bocsu.{cpp,h}.  The algorithm works by
2816// converting a sequence of codepoints into a sequence of presumably small differences.  See
2817// the C++ code for a more detailed explanation.
2818
2819macro_rules! negdivmod {
2820    ($n:ident, $d:ident, $m:ident) => {
2821        $m = $n % $d;
2822        $n /= $d;
2823        if $m < 0 {
2824            $n -= 1;
2825            $m += $d;
2826        }
2827    };
2828}
2829
2830fn write_diff<S>(mut diff: i32, sink: &mut S, state: &mut S::State) -> Result<(), S::Error>
2831where
2832    S: CollationKeySink + ?Sized,
2833{
2834    let mut out = |b| sink.write_byte(state, b);
2835
2836    if diff >= SLOPE_REACH_NEG_1 {
2837        if diff <= SLOPE_REACH_POS_1 {
2838            out((SLOPE_MIDDLE + diff) as _)?;
2839        } else if diff <= SLOPE_REACH_POS_2 {
2840            out((SLOPE_START_POS_2 + (diff / SLOPE_TAIL_COUNT)) as _)?;
2841            out((SLOPE_MIN + diff % SLOPE_TAIL_COUNT) as _)?;
2842        } else if diff <= SLOPE_REACH_POS_3 {
2843            let p2 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2844            diff /= SLOPE_TAIL_COUNT;
2845            let p1 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2846            let p0 = SLOPE_START_POS_3 + (diff / SLOPE_TAIL_COUNT);
2847            out(p0 as _)?;
2848            out(p1 as _)?;
2849            out(p2 as _)?;
2850        } else {
2851            let p3 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2852            diff /= SLOPE_TAIL_COUNT;
2853            let p2 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2854            diff /= SLOPE_TAIL_COUNT;
2855            let p1 = SLOPE_MIN + diff % SLOPE_TAIL_COUNT;
2856            out(SLOPE_MAX as _)?;
2857            out(p1 as _)?;
2858            out(p2 as _)?;
2859            out(p3 as _)?;
2860        }
2861    } else {
2862        let mut m;
2863
2864        if diff >= SLOPE_REACH_NEG_2 {
2865            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2866            out((SLOPE_START_NEG_2 + diff) as _)?;
2867            out((SLOPE_MIN + m) as _)?;
2868        } else if diff >= SLOPE_REACH_NEG_3 {
2869            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2870            let p2 = SLOPE_MIN + m;
2871            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2872            let p1 = SLOPE_MIN + m;
2873            let p0 = SLOPE_START_NEG_3 + diff;
2874            out(p0 as _)?;
2875            out(p1 as _)?;
2876            out(p2 as _)?;
2877        } else {
2878            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2879            let p3 = SLOPE_MIN + m;
2880            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2881            let p2 = SLOPE_MIN + m;
2882            negdivmod!(diff, SLOPE_TAIL_COUNT, m);
2883            let p1 = SLOPE_MIN + m;
2884            let _ = diff;
2885            out(SLOPE_MIN as _)?;
2886            out(p1 as _)?;
2887            out(p2 as _)?;
2888            out(p3 as _)?;
2889        }
2890    }
2891
2892    Ok(())
2893}
2894
2895fn write_identical_level<I, S>(iter: I, sink: &mut S, state: &mut S::State) -> Result<(), S::Error>
2896where
2897    I: Iterator<Item = char>,
2898    S: CollationKeySink + ?Sized,
2899{
2900    let mut prev = 0i32;
2901
2902    for c in iter {
2903        if !(0x4e00..=0xa000).contains(&prev) {
2904            prev = (prev & !0x7f) - SLOPE_REACH_NEG_1;
2905        } else {
2906            // Unihan U+4e00..U+9fa5:  double-bytes down from the upper end
2907            prev = 0x9fff - SLOPE_REACH_POS_2;
2908        }
2909
2910        if c == MERGE_SEPARATOR {
2911            sink.write_byte(state, MERGE_SEPARATOR_BYTE)?;
2912            prev = 0;
2913        } else {
2914            let c = c as i32;
2915            write_diff(c - prev, sink, state)?;
2916            prev = c;
2917        }
2918    }
2919    Ok(())
2920}
2921
2922#[cfg(test)]
2923mod test {
2924    use super::*;
2925    use icu_locale::locale;
2926
2927    type Key = Vec<u8>;
2928
2929    fn collator_en(strength: Strength) -> CollatorBorrowed<'static> {
2930        let locale = locale!("en").into();
2931        let mut options = CollatorOptions::default();
2932        options.strength = Some(strength);
2933        Collator::try_new(locale, options).unwrap()
2934    }
2935
2936    fn collator_en_case_level(strength: Strength) -> CollatorBorrowed<'static> {
2937        let locale = locale!("en").into();
2938        let mut options = CollatorOptions::default();
2939        options.strength = Some(strength);
2940        options.case_level = Some(crate::options::CaseLevel::On);
2941        Collator::try_new(locale, options).unwrap()
2942    }
2943
2944    fn keys(strength: Strength) -> (Key, Key, Key) {
2945        let collator = collator_en(strength);
2946
2947        let mut k0 = Vec::new();
2948        let Ok(()) = collator.write_sort_key_to("aabc", &mut k0);
2949        let mut k1 = Vec::new();
2950        let Ok(()) = collator.write_sort_key_to("aAbc", &mut k1);
2951        let mut k2 = Vec::new();
2952        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k2);
2953
2954        (k0, k1, k2)
2955    }
2956
2957    #[test]
2958    fn sort_key_primary() {
2959        let (k0, k1, k2) = keys(Strength::Primary);
2960        assert_eq!(k0, k1);
2961        assert_eq!(k1, k2);
2962    }
2963
2964    #[test]
2965    fn sort_key_secondary() {
2966        let (k0, k1, k2) = keys(Strength::Secondary);
2967        assert_eq!(k0, k1);
2968        assert!(k1 < k2);
2969    }
2970
2971    #[test]
2972    fn sort_key_tertiary() {
2973        let (k0, k1, k2) = keys(Strength::Tertiary);
2974        assert!(k0 < k1);
2975        assert!(k1 < k2);
2976    }
2977
2978    fn collator_ja(strength: Strength) -> CollatorBorrowed<'static> {
2979        let locale = locale!("ja").into();
2980        let mut options = CollatorOptions::default();
2981        options.strength = Some(strength);
2982        Collator::try_new(locale, options).unwrap()
2983    }
2984
2985    fn keys_ja_strs(strength: Strength, s0: &str, s1: &str) -> (Key, Key) {
2986        let collator = collator_ja(strength);
2987
2988        let mut k0 = Vec::new();
2989        let Ok(()) = collator.write_sort_key_to(s0, &mut k0);
2990        let mut k1 = Vec::new();
2991        let Ok(()) = collator.write_sort_key_to(s1, &mut k1);
2992
2993        (k0, k1)
2994    }
2995
2996    fn keys_ja(strength: Strength) -> (Key, Key) {
2997        keys_ja_strs(strength, "あ", "ア")
2998    }
2999
3000    #[test]
3001    fn sort_keys_ja_to_quaternary() {
3002        let (k0, k1) = keys_ja(Strength::Primary);
3003        assert_eq!(k0, k1);
3004        let (k0, k1) = keys_ja(Strength::Secondary);
3005        assert_eq!(k0, k1);
3006        let (k0, k1) = keys_ja(Strength::Tertiary);
3007        assert_eq!(k0, k1);
3008        let (k0, k1) = keys_ja(Strength::Quaternary);
3009        assert!(k0 < k1);
3010    }
3011
3012    #[test]
3013    fn sort_keys_ja_identical() {
3014        let (k0, k1) = keys_ja_strs(Strength::Quaternary, "ア", "ア");
3015        assert_eq!(k0, k1);
3016        let (k0, k1) = keys_ja_strs(Strength::Identical, "ア", "ア");
3017        assert!(k0 < k1);
3018    }
3019
3020    #[test]
3021    fn sort_keys_utf16() {
3022        let collator = collator_en(Strength::Identical);
3023
3024        const STR8: &[u8] = b"hello world!";
3025        let mut k8 = Vec::new();
3026        let Ok(()) = collator.write_sort_key_utf8_to(STR8, &mut k8);
3027
3028        const STR16: &[u16] = &[
3029            0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21,
3030        ];
3031        let mut k16 = Vec::new();
3032        let Ok(()) = collator.write_sort_key_utf16_to(STR16, &mut k16);
3033        assert_eq!(k8, k16);
3034    }
3035
3036    #[test]
3037    fn sort_keys_invalid() {
3038        let collator = collator_en(Strength::Identical);
3039
3040        // some invalid strings
3041        let mut k = Vec::new();
3042        let Ok(()) = collator.write_sort_key_utf8_to(b"\xf0\x90", &mut k);
3043        let mut k = Vec::new();
3044        let Ok(()) = collator.write_sort_key_utf16_to(&[0xdd1e], &mut k);
3045    }
3046
3047    #[test]
3048    fn sort_key_to_vecdeque() {
3049        let collator = collator_en(Strength::Identical);
3050
3051        let mut k0 = Vec::new();
3052        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k0);
3053        let mut k1 = VecDeque::new();
3054        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k1);
3055        assert!(k0.iter().eq(k1.iter()));
3056    }
3057
3058    #[test]
3059    fn sort_key_to_slice() {
3060        let collator = collator_en(Strength::Identical);
3061
3062        let mut k0 = Vec::new();
3063        let Ok(()) = collator.write_sort_key_to("áAbc", &mut k0);
3064        let mut k1 = [0u8; 100];
3065        let len = collator.write_sort_key_to("áAbc", &mut k1[..]).unwrap();
3066        assert_eq!(len, k0.len());
3067        assert!(k0.iter().eq(k1[..len].iter()));
3068    }
3069
3070    #[test]
3071    fn sort_key_to_slice_no_space() {
3072        let collator = collator_en(Strength::Identical);
3073        let mut k = [0u8; 0];
3074        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3075        assert!(matches!(res, Err(TooSmall { .. })));
3076    }
3077
3078    #[test]
3079    fn sort_key_to_slice_too_long() {
3080        // This runs out of space in write_sort_key_up_to_quaternary.
3081        let collator = collator_en(Strength::Identical);
3082        let mut k = [0u8; 5];
3083        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3084        assert!(matches!(res, Err(TooSmall { .. })));
3085    }
3086
3087    #[test]
3088    fn sort_key_to_slice_identical_too_long() {
3089        // This runs out of space while appending UTF-8 in the SinkAdapter.
3090        let collator = collator_en(Strength::Identical);
3091        let mut k = [0u8; 22];
3092        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3093        assert!(matches!(res, Err(TooSmall { .. })));
3094    }
3095
3096    #[test]
3097    fn sort_key_just_right() {
3098        // get the length needed
3099        let collator = collator_en(Strength::Identical);
3100        let mut k = [0u8; 0];
3101        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3102        let len = res.unwrap_err().length;
3103
3104        // almost enough
3105        let mut k = vec![0u8; len - 1];
3106        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3107        let len = res.unwrap_err().length;
3108
3109        // just right
3110        let mut k = vec![0u8; len];
3111        let res = collator.write_sort_key_to("áAbc", &mut k[..]);
3112        assert_eq!(res, Ok(len));
3113    }
3114
3115    #[test]
3116    fn sort_key_utf16_slice_too_small() {
3117        let collator = collator_en(Strength::Identical);
3118        const STR16: &[u16] = &[0x68, 0x65, 0x6c, 0x6c, 0x6f];
3119        let mut k = [0u8; 4];
3120        let res = collator.write_sort_key_utf16_to(STR16, &mut k[..]);
3121        assert!(matches!(res, Err(TooSmall { .. })));
3122    }
3123
3124    #[test]
3125    fn sort_key_very_long() {
3126        let collator = collator_en(Strength::Secondary);
3127        let mut k = Vec::new();
3128        let Ok(()) = collator.write_sort_key_to(&"a".repeat(300), &mut k);
3129    }
3130
3131    #[test]
3132    fn sort_key_case_level() {
3133        let collator = collator_en_case_level(Strength::Tertiary);
3134        let mut k = Vec::new();
3135        let Ok(()) = collator.write_sort_key_to("aBc", &mut k);
3136    }
3137
3138    #[test]
3139    fn sort_key_case_level_empty() {
3140        let collator = collator_en_case_level(Strength::Tertiary);
3141        let mut k = Vec::new();
3142        let Ok(()) = collator.write_sort_key_to("", &mut k);
3143    }
3144
3145    fn check_sort_key_less(a: &[u16], b: &[u16]) {
3146        let collator = collator_en(Strength::Identical);
3147        let mut ak = Vec::new();
3148        let Ok(()) = collator.write_sort_key_utf16_to(a, &mut ak);
3149        let mut bk = Vec::new();
3150        let Ok(()) = collator.write_sort_key_utf16_to(b, &mut bk);
3151        assert!(ak < bk, "failed: {a:04x?} - {b:04x?}");
3152    }
3153
3154    #[test]
3155    fn sort_key_fffe_bug_6811() {
3156        check_sort_key_less(
3157            &[0xfffe, 0x0001, 0x0002, 0x0003],
3158            &[0x0001, 0xfffe, 0x0002, 0x0003],
3159        );
3160        check_sort_key_less(
3161            &[0x0001, 0xfffe, 0x0002, 0x0003],
3162            &[0x0001, 0x0002, 0xfffe, 0x0003],
3163        );
3164        check_sort_key_less(
3165            &[0x0001, 0x0002, 0xfffe, 0x0003],
3166            &[0x0001, 0x0002, 0x0003, 0xfffe],
3167        );
3168        check_sort_key_less(&[0xfffe, 0x0000, 0x0000], &[0x0000, 0xfffe, 0x0000]);
3169        check_sort_key_less(&[0x0000, 0xfffe, 0x0000], &[0x0000, 0x0000, 0xfffe]);
3170    }
3171}