Skip to main content

icu_collator/
elements.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 64-bit `CollationElement` struct used for
10//! the actual comparison, the 32-bit `CollationElement32` struct
11//! that's used for storage. (Strictly speaking, the storage is
12//! `RawBytesULE<4>`.) And the `CollationElements` iterator adapter
13//! that turns an iterator over `char` into an iterator over
14//! `CollationElement`. (To match the structure of ICU4C, this isn't
15//! a real Rust `Iterator`. Instead of signaling end by returning
16//! `None`, it signals end by returning `NO_CE`.)
17//!
18//! This module also declares various constants that are also used
19//! by the `comparison` module.
20
21use core::char::REPLACEMENT_CHARACTER;
22use core::marker::PhantomData;
23use icu_collections::char16trie::TrieResult;
24use icu_collections::codepointtrie::AbstractCodePointTrie;
25use icu_collections::codepointtrie::WithTrie;
26use icu_normalizer::provider::DecompositionTables;
27use icu_properties::props::CanonicalCombiningClass;
28use smallvec::SmallVec;
29use zerovec::ule::AsULE;
30use zerovec::ule::RawBytesULE;
31use zerovec::{zeroslice, ZeroSlice};
32
33use crate::provider::CollationData;
34
35/// `true` iff `ce32`, when interpreted as `CollationElement32`,
36/// is self-contained.
37#[cfg(feature = "datagen")]
38pub fn is_self_contained(ce32: u32) -> bool {
39    CollationElement32::new(ce32)
40        .to_ce_self_contained()
41        .is_some()
42}
43
44// Start `SmallVec` size constants.
45//
46// These are the on-stack buffer sizes. If the buffers need
47// to grow larger, they are spilled to the heap.
48//
49// TODO(#2005): Figure out good sizes for these.
50
51/// The number of full 64-bit collation units that get buffered
52/// in the primary comparison loop so that they can be examined
53/// by the subsequent comparison stregths.
54///
55/// Note 1: If a primary difference is found, the comparison
56/// returns early, so these buffers end up holding all the
57/// collation elements only if there is no primary difference.
58///
59/// Note 2: Unfortunately for now, a sentinel value signaling
60/// the end of input gets written into the buffer in addition
61/// to the real collation elements.
62///
63/// This should probably either be halved to 4 on the logic
64/// that especially in the presence of the identical prefix
65/// optimization, most comparisons return after a couple of
66/// primary comparisons or increased to 32 on the logic that
67/// such a buffer could better hold a file or human name that
68/// differs on secordary or higher level.
69pub(crate) const CE_BUFFER_SIZE: usize = 8;
70
71/// The number of extra full 64-bit collation units that have
72/// already been computed as part of an operation that yields
73/// multiple collation units at a time.
74const PENDING_CE_BUFFER_SIZE: usize = 6;
75
76/// Either the identical prefix or the lookahead plus the next
77/// upcoming character.
78///
79/// The longest contraction suffix in CLDR 40 is 7 characters long.
80const UPCOMING_CHARACTER_BUFFER_SIZE: usize = 10;
81
82/// The contiguous sequence of combining characters.
83const COMBINING_CHARACTER_BUFFER_SIZE: usize = 7;
84
85/// The sequence of digits in the numeric mode.
86const DIGIT_BUFFER_SIZE: usize = 8;
87
88/// The number of combining characters that a contraction has
89/// matched.
90const PENDING_REMOVALS_SIZE: usize = 1;
91
92// End `SmallVec` constants
93
94/// Marker that the decomposition does not round trip via NFC.
95///
96/// See components/normalizer/trie-value-format.md
97pub(crate) const NON_ROUND_TRIP_MARKER: u32 = 1 << 30;
98
99/// Marker that the first character of the decomposition
100/// can combine backwards.
101///
102/// See components/normalizer/trie-value-format.md
103pub(crate) const BACKWARD_COMBINING_MARKER: u32 = 1 << 31;
104
105/// Mask for the bits have to be zero for this to be a BMP
106/// singleton decomposition, or value baked into the surrogate
107/// range.
108///
109/// See components/normalizer/trie-value-format.md
110pub(crate) const HIGH_ZEROS_MASK: u32 = 0x3FFF0000;
111
112/// Mask for the bits have to be zero for this to be a complex
113/// decomposition.
114///
115/// See components/normalizer/trie-value-format.md
116pub(crate) const LOW_ZEROS_MASK: u32 = 0xFFE0;
117
118/// Marker value for U+FDFA in NFKD. (Unified with
119/// `HANGUL_SYLLABLE_MARKER`, but they differ by
120/// `NON_ROUND_TRIP_MARKER`.)
121///
122/// See components/normalizer/trie-value-format.md
123const FDFA_MARKER: u16 = 1;
124
125/// Marker value for Hangul syllables. (Unified with `FDFA_MARKER`,
126/// but they differ by `NON_ROUND_TRIP_MARKER`.)
127///
128/// See components/normalizer/trie-value-format.md
129pub(crate) const HANGUL_SYLLABLE_MARKER: u32 = 1;
130
131/// Checks if a trie value carries a (non-zero) canonical
132/// combining class.
133///
134/// See components/normalizer/trie-value-format.md
135fn trie_value_has_ccc(trie_value: u32) -> bool {
136    (trie_value & 0x3FFFFE00) == 0xD800
137}
138
139/// Checks if the trie signifies a special non-starter decomposition.
140///
141/// See components/normalizer/trie-value-format.md
142fn trie_value_indicates_special_non_starter_decomposition(trie_value: u32) -> bool {
143    (trie_value & 0x3FFFFF00) == 0xD900
144}
145
146/// Checks if a trie value signifies a character whose decomposition
147/// starts with a non-starter.
148///
149/// See components/normalizer/trie-value-format.md
150fn decomposition_starts_with_non_starter(trie_value: u32) -> bool {
151    trie_value_has_ccc(trie_value)
152}
153
154/// Extracts a canonical combining class (possibly zero) from a trie value.
155///
156/// See components/normalizer/trie-value-format.md
157fn ccc_from_trie_value(trie_value: u32) -> CanonicalCombiningClass {
158    if trie_value_has_ccc(trie_value) {
159        CanonicalCombiningClass::from_icu4c_value(trie_value as u8)
160    } else {
161        CanonicalCombiningClass::NotReordered
162    }
163}
164
165// These constants originate from page 143 of Unicode 14.0
166pub(crate) const HANGUL_S_BASE: u32 = 0xAC00;
167pub(crate) const HANGUL_L_BASE: u32 = 0x1100;
168pub(crate) const HANGUL_V_BASE: u32 = 0x1161;
169pub(crate) const HANGUL_T_BASE: u32 = 0x11A7;
170pub(crate) const HANGUL_T_COUNT: u32 = 28;
171pub(crate) const HANGUL_N_COUNT: u32 = 588;
172pub(crate) const HANGUL_S_COUNT: u32 = 11172;
173
174pub(crate) const JAMO_COUNT: usize = 256; // 0x1200 - 0x1100
175
176const COMBINING_DIACRITICS_BASE: usize = 0x0300;
177const OPTIMIZED_DIACRITICS_LIMIT: usize = 0x034F;
178pub(crate) const OPTIMIZED_DIACRITICS_MAX_COUNT: usize =
179    OPTIMIZED_DIACRITICS_LIMIT - COMBINING_DIACRITICS_BASE;
180
181pub(crate) const CASE_MASK: u16 = 0xC000;
182pub(crate) const TERTIARY_MASK: u16 = 0x3F3F; // ONLY_TERTIARY_MASK in ICU4C
183pub(crate) const QUATERNARY_MASK: u16 = 0xC0;
184
185/// A CE32 is special if its low byte is this or greater.
186/// Impossible case bits 11 mark special CE32s.
187pub const SPECIAL_CE32_LOW_BYTE: u8 = 0xC0;
188/// This value itself is used to indicate a fallback to the root collation.
189pub(crate) const FALLBACK_CE32: CollationElement32 =
190    CollationElement32(SPECIAL_CE32_LOW_BYTE as u32);
191const LONG_PRIMARY_CE32_LOW_BYTE: u8 = 0xC1; // SPECIAL_CE32_LOW_BYTE | LONG_PRIMARY_TAG
192/// Like `LONG_PRIMARY_CE32_LOW_BYTE` but for reorderables.
193pub const LONG_PRIMARY_FOR_REORDERABLE_CE32_LOW_BYTE: u8 = 0xCB; // SPECIAL_CE32_LOW_BYTE | LONG_PRIMARY_FOR_REORDERABLE_TAG
194/// Used only as a placeholder on the indentical prefix path.
195/// The requirement is that this CE32 fails the quick mapping to a primary,
196/// which is does, because the tag byte is higher than
197/// `LONG_PRIMARY_CE32_LOW_BYTE`.
198pub(crate) const IDENTICAL_PREFIX_HANGUL_MARKER_CE32: CollationElement32 = CollationElement32(0xC2);
199const COMMON_SECONDARY_CE: u64 = 0x05000000;
200const COMMON_TERTIARY_CE: u64 = 0x0500;
201const COMMON_SEC_AND_TER_CE: u64 = COMMON_SECONDARY_CE | COMMON_TERTIARY_CE;
202
203/// The ce32 packing of `COMMON_SEC_AND_TER_CE`.
204#[cfg(feature = "datagen")]
205pub const COMMON_SEC_AND_TER_CE32: u16 = 0x0505;
206
207/// Largest index that can be represented in expansion(32)s.
208#[cfg(feature = "datagen")]
209pub const MAX_INDEX: usize = 0x7FFFF;
210
211const UNASSIGNED_IMPLICIT_BYTE: u8 = 0xFE;
212
213// /// Set if there is no match for the single (no-suffix) character itself.
214// /// This is only possible if there is a prefix.
215// /// In this case, discontiguous contraction matching cannot add combining marks
216// /// starting from an empty suffix.
217// /// The default CE32 is used anyway if there is no suffix match.
218// const CONTRACT_SINGLE_CP_NO_MATCH: u32 = 0x100;
219
220/// Set if the first character of every contraction suffix has lccc!=0.
221const CONTRACT_NEXT_CCC: u32 = 0x200;
222/// Set if any contraction suffix ends with lccc!=0.
223const CONTRACT_TRAILING_CCC: u32 = 0x400;
224/// Set if at least one contraction suffix contains a starter
225const CONTRACT_HAS_STARTER: u32 = 0x800;
226
227// const NO_CE32: CollationElement32 = CollationElement32::default();
228// constants named NO_CE* : End of input. Only used in runtime code, not stored in data.
229pub(crate) const NO_CE: CollationElement = CollationElement::default();
230pub(crate) const NO_CE_PRIMARY: u32 = 1; // not a left-adjusted weight
231                                         // const NO_CE_NON_PRIMARY: NonPrimary = NonPrimary::default();
232pub(crate) const NO_CE_SECONDARY: u16 = 0x0100;
233pub(crate) const NO_CE_TERTIARY: u16 = 0x0100;
234pub(crate) const NO_CE_QUATERNARY: u16 = 0x0100;
235const NO_CE_VALUE: u64 =
236    ((NO_CE_PRIMARY as u64) << 32) | ((NO_CE_SECONDARY as u64) << 16) | (NO_CE_TERTIARY as u64); // 0x101000100
237
238// See ICU4C collation.h and https://www.unicode.org/reports/tr10/#Trailing_Weights
239pub(crate) const FFFD_PRIMARY: u32 = 0xFFFD0000; // U+FFFD
240pub(crate) const FFFD_CE_VALUE: u64 = ((FFFD_PRIMARY as u64) << 32) | COMMON_SEC_AND_TER_CE;
241pub(crate) const FFFD_CE: CollationElement = CollationElement(FFFD_CE_VALUE);
242pub(crate) const FFFD_CE32_VALUE: u32 = 0xFFFD0505;
243pub(crate) const FFFD_CE32: CollationElement32 = CollationElement32(FFFD_CE32_VALUE);
244
245pub(crate) const EMPTY_U16: &ZeroSlice<u16> = zeroslice![];
246const SINGLE_REPLACEMENT_CHARACTER_U16: &ZeroSlice<u16> =
247    zeroslice!(u16; <u16 as AsULE>::ULE::from_unsigned; [REPLACEMENT_CHARACTER as u16]);
248
249pub(crate) const EMPTY_CHAR: &ZeroSlice<char> = zeroslice![];
250const SINGLE_REPLACEMENT_CHARACTER_CHAR: &ZeroSlice<char> =
251    zeroslice!(char; <char as AsULE>::ULE::from_aligned; [REPLACEMENT_CHARACTER]);
252
253/// If `opt` is `Some`, unwrap it. If `None`, panic if debug assertions
254/// are enabled and return `default` if debug assertions are not enabled.
255///
256/// Use this only if the only reason why `opt` could be `None` is bogus
257/// data from the provider.
258#[inline(always)]
259pub(crate) fn unwrap_or_gigo<T>(opt: Option<T>, default: T) -> T {
260    if let Some(val) = opt {
261        val
262    } else {
263        // GIGO case
264        debug_assert!(false);
265        default
266    }
267}
268
269/// Convert a `u32` _obtained from data provider data_ to `char`.
270#[inline(always)]
271pub(crate) fn char_from_u32(u: u32) -> char {
272    unwrap_or_gigo(core::char::from_u32(u), REPLACEMENT_CHARACTER)
273}
274
275/// Convert a `u16` _obtained from data provider data_ to `char`.
276#[inline(always)]
277fn char_from_u16(u: u16) -> char {
278    char_from_u32(u32::from(u))
279}
280
281#[inline(always)]
282fn in_inclusive_range(c: char, start: char, end: char) -> bool {
283    u32::from(c).wrapping_sub(u32::from(start)) <= (u32::from(end) - u32::from(start))
284}
285
286/// Special-CE32 tags, from bits 3..0 of a special 32-bit CE.
287/// Bits 31..8 are available for tag-specific data.
288/// Bits  5..4: Reserved. May be used in the future to indicate lccc!=0 and tccc!=0.
289#[derive(Eq, PartialEq, Debug)]
290#[allow(dead_code)]
291#[repr(u8)] // This repr is necessary for transmute safety
292pub enum Tag {
293    /// Fall back to the base collator.
294    /// This is the tag value in [`SPECIAL_CE32_LOW_BYTE`] and [`FALLBACK_CE32`].
295    /// Bits 31..8: Unused, 0.
296    Fallback = 0,
297    /// Long-primary CE with [`COMMON_SEC_AND_TER_CE`].
298    ///
299    /// When this occurs directly in the trie (not behind an further lookup like
300    /// `Expansion32`) and we know we haven't loaded old-format data, this further
301    /// signals that the canonical combining class of the character is zero.
302    /// See also [`LongPrimaryForReorderable`].
303    ///
304    /// Bits 31..8: Three-byte primary.
305    LongPrimary = 1,
306    /// Long-secondary CE with zero primary.
307    /// Bits 31..16: Secondary weight.
308    /// Bits 15.. 8: Tertiary weight.
309    LongSecondary = 2,
310    /// Unused.
311    /// May be used in the future for single-byte secondary CEs (`SHORT_SECONDARY_TAG`),
312    /// storing the secondary in bits 31..24, the ccc in bits 23..16,
313    /// and the tertiary in bits 15..8.
314    Reserved3 = 3,
315    /// Latin mini expansions of two simple CEs [pp, 05, tt] [00, ss, 05].
316    /// Bits 31..24: Single-byte primary weight pp of the first CE.
317    /// Bits 23..16: Tertiary weight tt of the first CE.
318    /// Bits 15.. 8: Secondary weight ss of the second CE.
319    /// Unused by ICU4X, may get repurposed for jamo expansions is Korean search.
320    LatinExpansion = 4,
321    /// Points to one or more simple/long-primary/long-secondary 32-bit CE32s.
322    /// Bits 31..13: Index into `uint32_t` table.
323    /// Bits 12.. 8: Length=1..31.
324    Expansion32 = 5,
325    /// Points to one or more 64-bit CEs.
326    ///
327    /// When this occurs directly in the trie (not behind an further lookup like
328    /// `Expansion32`) and we know we haven't loaded old-format data, this further
329    /// signals that the canonical combining class of the character is zero.
330    /// See also [`ExpansionForReorderable`].
331    ///
332    /// Bits 31..13: Index into CE table.
333    /// Bits 12.. 8: Length=1..31.
334    Expansion = 6,
335    /// Builder data, used only in the `CollationDataBuilder`, not in runtime data.
336    ///
337    /// If bit 8 is 0: Builder context, points to a list of context-sensitive mappings.
338    /// Bits 31..13: Index to the builder's list of `ConditionalCE32` for this character.
339    /// Bits 12.. 9: Unused, 0.
340    ///
341    /// If bit 8 is 1 (`IS_BUILDER_JAMO_CE32`): Builder-only jamoCE32 value.
342    /// The builder fetches the Jamo CE32 from the trie.
343    /// Bits 31..13: Jamo code point.
344    /// Bits 12.. 9: Unused, 0.
345    BuilderData = 7,
346    /// Points to prefix trie.
347    /// Bits 31..13: Index into prefix/contraction data.
348    /// Bits 12.. 8: Unused, 0.
349    Prefix = 8,
350    /// Points to contraction data.
351    /// Bits 31..13: Index into prefix/contraction data.
352    /// Bits 12..11: Unused, 0.
353    /// Bit      10: `CONTRACT_TRAILING_CCC` flag.
354    /// Bit       9: `CONTRACT_NEXT_CCC` flag.
355    /// Bit       8: `CONTRACT_SINGLE_CP_NO_MATCH` flag.
356    Contraction = 9,
357    /// Decimal digit.
358    /// Bits 31..13: Index into `uint32_t` table for non-numeric-collation CE32.
359    /// Bit      12: Unused, 0.
360    /// Bits 11.. 8: Digit value 0..9.
361    Digit = 10,
362    /// The same as `LongPrimary` but if ccc!=0.
363    ///
364    /// In ICU4C, this is `U0000_TAG`, which is not used by ICU4X:
365    /// Tag for U+0000, for moving the NUL-termination handling
366    /// from the regular fastpath into specials-handling code.
367    /// Bits 31..8: Unused, 0.
368    LongPrimaryForReorderable = 11,
369    /// Tag for a Hangul syllable.
370    /// Bits 31..9: Unused, 0.
371    /// Bit      8: `HANGUL_NO_SPECIAL_JAMO` flag.
372    /// Not used by ICU4X, may get reused for compressing Hanja expansions.
373    Hangul = 12,
374    /// The same as `Expansion` but if ccc!=0.
375    ///
376    /// In ICU4C, this is `LEAD_SURROGATE_TAG`, which is not used by ICU4X:
377    /// Optional optimization for UTF-16 string processing.
378    /// Bits 31..10: Unused, 0.
379    ///       9.. 8: =0: All associated supplementary code points are unassigned-implicit.
380    ///              =1: All associated supplementary code points fall back to the base data.
381    ///              else: (Normally 2) Look up the data for the supplementary code point.
382    ExpansionForReorderable = 13,
383    /// Tag for CEs with primary weights in code point order.
384    /// Bits 31..13: Index into CE table, for one data "CE".
385    /// Bits 12.. 8: Unused, 0.
386    ///
387    /// This data "CE" has the following bit fields:
388    /// Bits 63..32: Three-byte primary pppppp00.
389    ///      31.. 8: Start/base code point of the in-order range.
390    ///           7: Flag isCompressible primary.
391    ///       6.. 0: Per-code point primary-weight increment.
392    Offset = 14,
393    /// Implicit CE tag. Compute an unassigned-implicit CE.
394    /// All bits are set (`UNASSIGNED_CE32=0xffffffff`).
395    Implicit = 15,
396}
397
398/// A compressed form of a collation element as stored in the collation
399/// data.
400///
401/// A `CollationElement32` can be "normal" or "special".
402/// Bits 7 and 6 are case bits for the "normal" case and setting
403/// both is an impossible case bit combination. Hence, "special"
404/// `CollationElement32`s are marked by setting both case bits
405/// to 1. This is equivalent with the low byte being less than
406/// `SPECIAL_CE32_LOW_BYTE` (0xC0, i.e. 0b11000000) in the "normal"
407/// case and equal to or greater in the "special" case.
408///
409/// For the normal case:
410/// Bits: 31..16: Primary weight
411/// Bits: 15..8: Secondary weight
412/// Bits:  7..6: Case bits (cannot both be 1 simultaneously)
413/// Bits:  5..0: The high part of the discontiguous tertiary weight
414/// (The quaternary weight and the low part of the discontiguous
415/// tertiary weight are zero.)
416///
417/// For the special case:
418/// Bits 31..8: tag-specific; see the documentation for `Tag`.
419/// Bits  7..6: The specialness marker; both bits set to 1
420/// Bits  5..4: Reserved. May be used in the future to indicate lccc!=0 and tccc!=0.
421/// Bits  3..0: the tag (bit-compatible with `Tag`)
422#[derive(Copy, Clone, PartialEq, Debug)]
423pub struct CollationElement32(u32);
424
425impl CollationElement32 {
426    /// Wrap a `u32` in the newtype.
427    #[inline(always)]
428    pub fn new(bits: u32) -> Self {
429        CollationElement32(bits)
430    }
431
432    #[inline(always)]
433    pub(crate) fn new_from_ule(ule: RawBytesULE<4>) -> Self {
434        CollationElement32(u32::from_unaligned(ule))
435    }
436
437    /// Extract the wrapped `u32`.
438    #[cfg(feature = "datagen")]
439    #[inline(always)]
440    pub fn bits(self) -> u32 {
441        self.0
442    }
443
444    #[inline(always)]
445    fn low_byte(self) -> u8 {
446        self.0 as u8
447    }
448
449    /// Return the tag in `Some` if special or `None` if not special.
450    #[inline(always)]
451    pub fn tag_checked(self) -> Option<Tag> {
452        let t = self.low_byte();
453        if t < SPECIAL_CE32_LOW_BYTE {
454            None
455        } else {
456            Some(self.tag())
457        }
458    }
459
460    /// Returns the tag if this element is special.
461    /// Non-specialness should first be checked by seeing if either
462    /// `to_ce_simple_or_long_primary()` or `to_ce_self_contained()`
463    /// returns non-`None`.
464    ///
465    /// # Panics
466    ///
467    /// Panics in debug mode if called on a non-special element.
468    #[inline(always)]
469    pub(crate) fn tag(self) -> Tag {
470        debug_assert!(self.low_byte() >= SPECIAL_CE32_LOW_BYTE);
471        // Safety: Tag has values 0 to 15, which are filtered for with the 0xF mask.
472        unsafe { core::mem::transmute(self.low_byte() & 0xF) }
473    }
474
475    /// Simplest possible check for the Latin1 fast path.
476    ///
477    /// A return value of zero means no primary.
478    #[cfg(feature = "latin1")]
479    #[inline(always)]
480    pub(crate) fn to_primary_simple(self) -> u32 {
481        let t = self.low_byte();
482        if t < SPECIAL_CE32_LOW_BYTE {
483            // Not special
484            self.0 & 0xFFFF0000
485        } else {
486            0
487        }
488    }
489
490    /// Extract only the first primary in the quick check without identical
491    /// prefix.
492    ///
493    /// A return value of zero means no primary.
494    #[inline(always)]
495    pub(crate) fn to_primary_in_quick_check(self, data: &CollationData) -> u32 {
496        let t = self.low_byte();
497        if t < SPECIAL_CE32_LOW_BYTE {
498            // Not special
499            self.0 & 0xFFFF0000
500        } else if t == LONG_PRIMARY_CE32_LOW_BYTE {
501            self.0 - u32::from(t)
502        } else {
503            let tag = self.tag();
504            if tag == Tag::Expansion {
505                // Hiragana in `ja` tailoring
506                data.get_primary_from_ces(self.index())
507            } else {
508                0
509            }
510            // Do not add support for additional tag types here
511            // without ensuring in datagen that reorderable characters
512            // don't end up with CE32s with tags that are handled here.
513        }
514    }
515
516    /// Extract only the first primary in the quick check after the identical
517    /// prefix. Unlike `to_primary_in_quick_check`, this method variant can
518    /// handle `Tag::Digit` if the numeric mode is not enabled. (The numeric
519    /// mode requires looking ahead.)
520    ///
521    /// A return value of zero means no primary.
522    #[inline(always)]
523    pub(crate) fn to_primary_in_quick_check_numeric(
524        self,
525        data: &CollationData,
526        numeric: bool,
527    ) -> u32 {
528        let mut ce32 = self;
529        loop {
530            let t = ce32.low_byte();
531            if t < SPECIAL_CE32_LOW_BYTE {
532                // Not special
533                return ce32.0 & 0xFFFF0000;
534            }
535            if t == LONG_PRIMARY_CE32_LOW_BYTE {
536                return ce32.0 - u32::from(t);
537            }
538            let tag = ce32.tag();
539            if tag == Tag::Expansion {
540                // Hiragana in `ja` tailoring
541                return data.get_primary_from_ces(ce32.index());
542            }
543            // Digit case for JetStream 3; see https://github.com/WebKit/JetStream/issues/294
544            if tag == Tag::Digit && !numeric {
545                ce32 = data.get_ce32(ce32.index());
546                continue;
547            }
548            return 0;
549            // Do not add support for additional tag types here
550            // without ensuring in datagen that reorderable characters
551            // don't end up with CE32s with tags that are handled here.
552        }
553    }
554
555    /// Expands to 64 bits if the expansion is to a single 64-bit collation
556    /// element and is not a long-secondary expansion.
557    #[inline(always)]
558    pub(crate) fn to_ce_simple_or_long_primary(self) -> Option<CollationElement> {
559        let t = self.low_byte();
560        if t < SPECIAL_CE32_LOW_BYTE {
561            // Not special
562            let as64 = u64::from(self.0);
563            Some(CollationElement::new(
564                ((as64 & 0xFFFF0000) << 32) | ((as64 & 0xFF00) << 16) | (u64::from(t) << 8),
565            ))
566        } else if t == LONG_PRIMARY_CE32_LOW_BYTE || t == LONG_PRIMARY_FOR_REORDERABLE_CE32_LOW_BYTE
567        {
568            let as64 = u64::from(self.0);
569            Some(CollationElement::new(
570                ((as64 - u64::from(t)) << 32) | COMMON_SEC_AND_TER_CE,
571            ))
572        } else {
573            // Could still be long secondary (or not self-contained at all).
574            // See `to_ce_self_contained()`.
575            None
576        }
577    }
578
579    /// Expands to 64 bits if the expansion is to a single 64-bit collation
580    /// element.
581    #[inline(always)]
582    pub(crate) fn to_ce_self_contained(self) -> Option<CollationElement> {
583        if let Some(ce) = self.to_ce_simple_or_long_primary() {
584            return Some(ce);
585        }
586        if self.tag() == Tag::LongSecondary {
587            Some(CollationElement::new(u64::from(self.0 & 0xffffff00)))
588        } else {
589            None
590        }
591    }
592
593    /// Expands to 64 bits if the expansion is to a single 64-bit collation
594    /// element or otherwise returns the collation element for U+FFFD.
595    #[inline(always)]
596    pub(crate) fn to_ce_self_contained_or_gigo(self) -> CollationElement {
597        unwrap_or_gigo(self.to_ce_self_contained(), FFFD_CE)
598    }
599
600    /// Gets the length from this element.
601    ///
602    /// # Panics
603    ///
604    /// In debug builds if this element doesn't have a length.
605    #[inline(always)]
606    pub(crate) fn len(self) -> usize {
607        debug_assert!(
608            self.tag() == Tag::Expansion32
609                || self.tag() == Tag::Expansion
610                || self.tag() == Tag::ExpansionForReorderable
611        );
612        ((self.0 >> 8) & 31) as usize
613    }
614
615    /// Gets the index from this element.
616    ///
617    /// # Panics
618    ///
619    /// In debug builds if this element doesn't have an index.
620    #[inline(always)]
621    pub(crate) fn index(self) -> usize {
622        debug_assert!(
623            self.tag() == Tag::Expansion32
624                || self.tag() == Tag::Expansion
625                || self.tag() == Tag::ExpansionForReorderable
626                || self.tag() == Tag::Contraction
627                || self.tag() == Tag::Digit
628                || self.tag() == Tag::Prefix
629                || self.tag() == Tag::Offset
630        );
631        (self.0 >> 13) as usize
632    }
633
634    #[inline(always)]
635    pub(crate) fn digit(self) -> u8 {
636        debug_assert!(self.tag() == Tag::Digit);
637        ((self.0 >> 8) & 0xF) as u8
638    }
639
640    #[inline(always)]
641    pub(crate) fn every_suffix_starts_with_combining(self) -> bool {
642        debug_assert!(self.tag() == Tag::Contraction);
643        (self.0 & CONTRACT_NEXT_CCC) != 0
644    }
645    #[inline(always)]
646    pub(crate) fn at_least_one_suffix_contains_starter(self) -> bool {
647        debug_assert!(self.tag() == Tag::Contraction);
648        (self.0 & CONTRACT_HAS_STARTER) != 0
649    }
650    #[inline(always)]
651    pub(crate) fn at_least_one_suffix_ends_with_non_starter(self) -> bool {
652        debug_assert!(self.tag() == Tag::Contraction);
653        (self.0 & CONTRACT_TRAILING_CCC) != 0
654    }
655}
656
657impl Default for CollationElement32 {
658    fn default() -> Self {
659        CollationElement32(1) // NO_CE32
660    }
661}
662
663/// A collation element is a 64-bit value.
664///
665/// Bits 63..32 are the primary weight.
666/// Bits 31..16 are the secondary weight.
667/// Bits 15..14 are the case bits.
668/// Bits 13..8 and 5..0 are the (bitwise discontiguous) tertiary weight.
669/// Bits 7..6 the quaternary weight.
670#[derive(Copy, Clone, Debug, PartialEq)]
671pub(crate) struct CollationElement(u64);
672
673impl CollationElement {
674    #[inline(always)]
675    pub fn new(bits: u64) -> Self {
676        CollationElement(bits)
677    }
678
679    #[inline(always)]
680    pub fn new_from_primary(primary: u32) -> Self {
681        CollationElement((u64::from(primary) << 32) | COMMON_SEC_AND_TER_CE)
682    }
683
684    #[inline(always)]
685    pub fn new_from_secondary(secondary: u16) -> Self {
686        CollationElement((u64::from(secondary) << 16) | COMMON_TERTIARY_CE)
687    }
688
689    #[inline(always)]
690    pub fn new_implicit_from_char(c: char) -> Self {
691        // Collation::unassignedPrimaryFromCodePoint
692        // Create a gap before U+0000. Use c-1 for [first unassigned].
693        let mut c_with_offset = u32::from(c) + 1;
694        // Fourth byte: 18 values, every 14th byte value (gap of 13).
695        let mut primary: u32 = 2 + (c_with_offset % 18) * 14;
696        c_with_offset /= 18;
697        // Third byte: 254 values
698        primary |= (2 + (c_with_offset % 254)) << 8;
699        c_with_offset /= 254;
700        // Second byte: 251 values 04..FE excluding the primary compression bytes.
701        primary |= (4 + (c_with_offset % 251)) << 16;
702        // One lead byte covers all code points (c < 0x1182B4 = 1*251*254*18).
703        primary |= u32::from(UNASSIGNED_IMPLICIT_BYTE) << 24;
704        CollationElement::new_from_primary(primary)
705    }
706
707    #[inline(always)]
708    pub fn clone_with_non_primary_zeroed(self) -> Self {
709        CollationElement(self.0 & 0xFFFFFFFF00000000)
710    }
711
712    /// Get the primary weight
713    #[inline(always)]
714    pub fn primary(self) -> u32 {
715        (self.0 >> 32) as u32
716    }
717
718    /// Get the non-primary weights
719    #[inline(always)]
720    pub fn non_primary(self) -> NonPrimary {
721        NonPrimary::new(self.0 as u32)
722    }
723
724    /// Get the secondary weight
725    #[inline(always)]
726    pub fn secondary(self) -> u16 {
727        self.non_primary().secondary()
728    }
729    #[inline(always)]
730    pub fn quaternary(self) -> u32 {
731        self.non_primary().quaternary()
732    }
733    #[inline(always)]
734    pub fn tertiary_ignorable(self) -> bool {
735        self.non_primary().tertiary_ignorable()
736    }
737    #[inline(always)]
738    pub fn either_half_zero(self) -> bool {
739        self.primary() == 0 || (self.0 as u32) == 0
740    }
741
742    #[inline(always)]
743    pub const fn default() -> CollationElement {
744        CollationElement(NO_CE_VALUE) // NO_CE
745    }
746}
747
748impl Default for CollationElement {
749    #[inline(always)]
750    fn default() -> Self {
751        CollationElement(NO_CE_VALUE) // NO_CE
752    }
753}
754
755impl Default for &CollationElement {
756    #[inline(always)]
757    fn default() -> Self {
758        &CollationElement(NO_CE_VALUE) // NO_CE
759    }
760}
761
762/// The purpose of grouping the non-primary bits
763/// into a struct is to allow for a future optimization
764/// that specializes code over whether storage for primary
765/// weights is needed or not. (I.e. whether to specialize
766/// on `CollationElement` or `NonPrimary`.)
767#[derive(Copy, Clone, PartialEq, Debug)]
768pub(crate) struct NonPrimary(u32);
769
770impl NonPrimary {
771    /// Constructor
772    pub fn new(bits: u32) -> Self {
773        NonPrimary(bits)
774    }
775    /// Get the bits
776    pub fn bits(self) -> u32 {
777        self.0
778    }
779    /// Get the secondary weight
780    #[inline(always)]
781    pub fn secondary(self) -> u16 {
782        (self.0 >> 16) as u16
783    }
784    /// Get the case bits as the high two bits of a u16
785    #[inline(always)]
786    pub fn case(self) -> u16 {
787        (self.0 as u16) & CASE_MASK
788    }
789    /// Get the tertiary weight as u16 with the high
790    /// two bits of each half zeroed.
791    #[inline(always)]
792    pub fn tertiary(self) -> u16 {
793        (self.0 as u16) & TERTIARY_MASK
794    }
795    #[inline(always)]
796    pub fn tertiary_ignorable(self) -> bool {
797        (self.0 as u16) <= NO_CE_TERTIARY
798    }
799    /// Get the quaternary weight in the original
800    /// storage bit positions with the other bits
801    /// set to one.
802    #[inline(always)]
803    pub fn quaternary(self) -> u32 {
804        self.0 | !(QUATERNARY_MASK as u32)
805    }
806    /// Get any combination of tertiary, case, and quaternary
807    /// by mask.
808    #[inline(always)]
809    pub fn tertiary_case_quarternary(self, mask: u16) -> u16 {
810        debug_assert!((mask & CASE_MASK) == CASE_MASK || (mask & CASE_MASK) == 0);
811        debug_assert!((mask & TERTIARY_MASK) == TERTIARY_MASK || (mask & TERTIARY_MASK) == 0);
812        debug_assert!((mask & QUATERNARY_MASK) == QUATERNARY_MASK || (mask & QUATERNARY_MASK) == 0);
813        (self.0 as u16) & mask
814    }
815
816    #[inline(always)]
817    pub fn case_quaternary(self) -> u16 {
818        (self.0 as u16) & (CASE_MASK | QUATERNARY_MASK)
819    }
820
821    #[inline(always)]
822    pub fn ignorable(self) -> bool {
823        self.0 == 0
824    }
825}
826
827impl Default for NonPrimary {
828    #[inline(always)]
829    fn default() -> Self {
830        NonPrimary(0x01000100) // Low 32 bits of NO_CE
831    }
832}
833
834/// This struct makes the handling of the `upcoming` buffer
835/// easily so that trie lookups are done at most once. However,
836/// when `upcoming[0]` is an undecomposed starter, we don't
837/// need the ccc yet, and when lookahead has already done the
838/// trie lookups, we don't need `trie_value`, as it is implied
839/// by ccc.
840//
841// TODO(#2386): This struct carries redundant information, and
842// `upcoming` should be split into a buffer of `CharacterAndClass`
843//  and an `Option<CharacterAndTrieValue>`, but that refactoring
844// isn't 100% necessary, so focusing on data format stability
845// for 1.0.
846//
847// (Deliberately non-`Copy`, because `CharacterAndClass` is
848// non-`Copy`.)
849#[derive(Debug, Clone)]
850pub(crate) struct CharacterAndClassAndTrieValue {
851    c_and_c: CharacterAndClass,
852    pub trie_val: u32,
853}
854
855impl CharacterAndClassAndTrieValue {
856    pub fn new_with_non_decomposing_starter(c: char) -> Self {
857        CharacterAndClassAndTrieValue {
858            c_and_c: CharacterAndClass::new(c, CanonicalCombiningClass::NotReordered),
859            trie_val: 0,
860        }
861    }
862    pub fn new_with_non_zero_ccc(c: char, ccc: CanonicalCombiningClass) -> Self {
863        CharacterAndClassAndTrieValue {
864            c_and_c: CharacterAndClass::new(c, ccc),
865            trie_val: 0xD800 | u32::from(ccc.to_icu4c_value()),
866        }
867    }
868    pub fn new_with_non_special_decomposition_trie_val(c: char, trie_val: u32) -> Self {
869        debug_assert!(!trie_value_indicates_special_non_starter_decomposition(
870            trie_val
871        ));
872        CharacterAndClassAndTrieValue {
873            c_and_c: CharacterAndClass::new_with_trie_value(c, trie_val),
874            trie_val,
875        }
876    }
877    pub fn new_with_trie_val(c: char, trie_val: u32) -> Self {
878        if !trie_value_indicates_special_non_starter_decomposition(trie_val) {
879            CharacterAndClassAndTrieValue {
880                c_and_c: CharacterAndClass::new_with_trie_value(c, trie_val),
881                trie_val,
882            }
883        } else {
884            CharacterAndClassAndTrieValue {
885                c_and_c: CharacterAndClass::new(c, CanonicalCombiningClass::from_icu4c_value(0xFF)),
886                trie_val,
887            }
888        }
889    }
890
891    pub fn decomposition_starts_with_non_starter(&self) -> bool {
892        decomposition_starts_with_non_starter(self.trie_val)
893    }
894
895    pub fn character(&self) -> char {
896        self.c_and_c.character()
897    }
898
899    fn ccc(&self) -> CanonicalCombiningClass {
900        let ret = self.c_and_c.ccc();
901        debug_assert_ne!(ret, CanonicalCombiningClass::from_icu4c_value(0xFF));
902        ret
903    }
904}
905
906/// Pack a `char` and a `CanonicalCombiningClass` in
907/// 32 bits (the former in the lower 24 bits and the
908/// latter in the high 8 bits). The latter can be
909/// initialized to 0xFF upon creation, in which case
910/// it can be actually set later by calling
911/// `set_ccc_from_trie_if_not_already_set`. This is
912/// a micro optimization to avoid the Canonical
913/// Combining Class trie lookup when there is only
914/// one combining character in a sequence. This type
915/// is intentionally non-`Copy` to get compiler help
916/// in making sure that the class is set on the
917/// instance on which it is intended to be set
918/// and not on a temporary copy.
919///
920/// Note that 0xFF is won't be assigned to an actual
921/// canonical combining class per definition D104
922/// in The Unicode Standard.
923//
924// NOTE: The Pernosco debugger has special knowledge
925// of this struct. Please do not change the bit layout
926// or the crate-module-qualified name of this struct
927// without coordination.
928#[derive(Debug, Clone)]
929// Safety invariant: The low 24 bits are a valid char
930struct CharacterAndClass(u32);
931
932impl CharacterAndClass {
933    pub fn new(c: char, ccc: CanonicalCombiningClass) -> Self {
934        // Safety invariant upheld here: the first half is a valid char
935        // and the second half does not affect the low 24 bits
936        CharacterAndClass(u32::from(c) | (u32::from(ccc.to_icu4c_value()) << 24))
937    }
938    pub fn new_with_placeholder(c: char) -> Self {
939        // Safety invariant upheld here: the first half is a valid char
940        // and the second half does not affect the low 24 bits
941        CharacterAndClass(u32::from(c) | ((0xFF) << 24))
942    }
943    pub fn new_with_trie_value(c: char, trie_value: u32) -> Self {
944        Self::new(c, ccc_from_trie_value(trie_value))
945    }
946    pub fn character(&self) -> char {
947        // Safety: from the safety invariant, this extracts the low 24 bits
948        unsafe { char::from_u32_unchecked(self.0 & 0xFF_FFFF) }
949    }
950    pub fn ccc(&self) -> CanonicalCombiningClass {
951        // Safety invariant upheld here: The argument is outside of the low 24 bits,
952        // and \0 is a valid character
953        CanonicalCombiningClass::from_icu4c_value((self.0 >> 24) as u8)
954    }
955    pub fn character_and_ccc(&self) -> (char, CanonicalCombiningClass) {
956        (self.character(), self.ccc())
957    }
958    pub fn set_ccc_from_trie_if_not_already_set<'data, T: AbstractCodePointTrie<'data, u32>>(
959        &mut self,
960        trie: &T,
961    ) {
962        if self.0 >> 24 != 0xFF {
963            return;
964        }
965        let scalar = self.character();
966        // Safety invariant upheld here: The first half doesn't affect the lower 24 bits,
967        // and the second half was taken from the old `self` which had these invariants upheld already.
968        self.0 = ((ccc_from_trie_value(trie.scalar(scalar)).to_icu4c_value() as u32) << 24)
969            | u32::from(scalar);
970    }
971}
972
973/// Iterator that transforms an iterator over `char` into an iterator
974/// over `CollationElement` with a tailoring.
975/// Not a real Rust iterator: Instead of `None` uses `NO_CE` to indicate
976/// end of iteration to optimize comparison.
977///
978/// It is _extremely_ important for performance that `SmallVec`s not be
979/// moved. To facilitate move-avoidance, this struct has the following
980/// life cycle where `new` returns the struct in a state that is not
981/// yet valid for a `next` call until `init` is called:
982///
983/// 1. `new`.
984/// 2. Some number of calls to `iter_next_before_init` and
985///    `prepend_upcoming_before_init`.
986/// 3. `init`.
987/// 4. Some number of calls to `next`.
988pub(crate) struct CollationElements<'data, I, T>
989where
990    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
991    T: AbstractCodePointTrie<'data, u32>,
992{
993    /// See components/normalizer/trie-value-format.md for the trie wrapped in `iter`.
994    iter: I,
995    /// Already computed but not yet returned `CollationElement`s.
996    pending: SmallVec<[CollationElement; PENDING_CE_BUFFER_SIZE]>, // TODO(#2005): Figure out good length
997    /// The index of the next item to be returned from `pending`. The purpose
998    /// of this index is to avoid moving the rest of the items.
999    pending_pos: usize,
1000    /// The characters most previously seen (or never-matching placeholders)
1001    /// CLDR, as of 40, has two kinds of prefixes:
1002    /// Prefixes that contain a single starter
1003    /// Prefixes that contain a starter followed by either U+3099 or U+309A
1004    /// Last-pushed is at index 0 and previously-pushed at index 1
1005    prefix: [char; 2],
1006    /// `upcoming` holds the characters that have already been read from
1007    /// `iter` but haven't yet been mapped to `CollationElement`s.
1008    ///
1009    /// Typically, `upcoming` holds one character and corresponds semantically
1010    /// to `pending_unnormalized_starter` in `icu::normalizer::Decomposition`.
1011    /// This is why there isn't a move avoidance optimization similar to
1012    /// `pending_pos` above for this buffer. A complex decomposition, a
1013    /// Hangul syllable followed by a non-starter, or lookahead can cause
1014    /// `pending` to hold more than one `char`.
1015    ///
1016    /// Invariant: `upcoming` is allowed to become empty only after `iter`
1017    /// has been exhausted.
1018    ///
1019    /// Invariant: (Checked by `debug_assert!`) At the start of `next()` call,
1020    /// if `upcoming` isn't empty (with `iter` having been exhausted), the
1021    /// first `char` in `upcoming` must have its decomposition start with a
1022    /// starter.
1023    ///
1024    /// TODO: Reverse the order, since now `insert(0, x)` and `remove(0)`
1025    /// are used more often than `push()` and `pop()`.
1026    upcoming: SmallVec<[CharacterAndClassAndTrieValue; UPCOMING_CHARACTER_BUFFER_SIZE]>,
1027    /// The root collation data.
1028    root: &'data CollationData<'data>,
1029    /// Tailoring if applicable.
1030    tailoring: &'data CollationData<'data>,
1031    /// The `CollationElement32` mapping for the Hangul Jamo block.
1032    ///
1033    /// Note: in ICU4C the jamo table contains only modern jamo. Here, the jamo table contains the whole Unicode block.
1034    jamo: &'data [<u32 as AsULE>::ULE; JAMO_COUNT],
1035    /// The `CollationElement32` mapping for the Combining Diacritical Marks block.
1036    diacritics: &'data ZeroSlice<u16>,
1037    /// NFD complex decompositions on the BMP
1038    scalars16: &'data ZeroSlice<u16>,
1039    /// NFD complex decompositions on supplementary planes
1040    scalars32: &'data ZeroSlice<char>,
1041    /// If numeric mode is enabled, the 8 high bits of the numeric primary.
1042    /// `None` if disabled.
1043    numeric_primary: Option<u8>,
1044    /// Whether the Lithuanian combining dot above handling is enabled.
1045    lithuanian_dot_above: bool,
1046    /// Whether `upcoming` (except the last item) has been normalized already
1047    upcoming_normalized: bool,
1048    #[cfg(debug_assertions)]
1049    /// Whether `iter` has been exhausted
1050    iter_exhausted: bool,
1051    #[cfg(debug_assertions)]
1052    /// Whether `init` has been called
1053    initialized: bool,
1054    _phantom: PhantomData<T>,
1055}
1056
1057impl<'data, I, T> CollationElements<'data, I, T>
1058where
1059    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
1060    T: AbstractCodePointTrie<'data, u32> + 'data,
1061{
1062    #[expect(clippy::too_many_arguments)]
1063    pub fn new(
1064        delegate: I,
1065        root: &'data CollationData,
1066        tailoring: &'data CollationData,
1067        jamo: &'data [<u32 as AsULE>::ULE; JAMO_COUNT],
1068        diacritics: &'data ZeroSlice<u16>,
1069        tables: &'data DecompositionTables,
1070        numeric_primary: Option<u8>,
1071        lithuanian_dot_above: bool,
1072    ) -> Self {
1073        CollationElements::<I, T> {
1074            iter: delegate,
1075            pending: SmallVec::new(),
1076            pending_pos: 0,
1077            prefix: ['\u{FFFF}'; 2],
1078            upcoming: SmallVec::new(),
1079            root,
1080            tailoring,
1081            jamo,
1082            diacritics,
1083            scalars16: &tables.scalars16,
1084            scalars32: &tables.scalars24,
1085            numeric_primary,
1086            lithuanian_dot_above,
1087            upcoming_normalized: false,
1088            #[cfg(debug_assertions)]
1089            iter_exhausted: false,
1090            #[cfg(debug_assertions)]
1091            initialized: false,
1092            _phantom: PhantomData,
1093        }
1094    }
1095
1096    pub fn iter_next_before_init(&mut self) -> Option<CharacterAndClassAndTrieValue> {
1097        #[cfg(debug_assertions)]
1098        debug_assert!(!self.initialized);
1099        self.iter_next()
1100    }
1101
1102    pub fn prepend_upcoming_before_init(&mut self, c: CharacterAndClassAndTrieValue) {
1103        #[cfg(debug_assertions)]
1104        debug_assert!(!self.initialized);
1105        self.upcoming.insert(0, c);
1106    }
1107
1108    pub fn init(&mut self) {
1109        // TODO: Consider removing the invariant that this method upholds.
1110        #[cfg(debug_assertions)]
1111        {
1112            debug_assert!(!self.initialized);
1113            self.initialized = true;
1114        }
1115
1116        loop {
1117            // Ensure the last item is a starter (unless)
1118            // iter exhausted.
1119            if let Some(last) = self.upcoming.last() {
1120                if last.decomposition_starts_with_non_starter() {
1121                    // Not using `while let` to be able to set `iter_exhausted`
1122                    loop {
1123                        if let Some(ch) = self.iter_next() {
1124                            let starter = !ch.decomposition_starts_with_non_starter();
1125                            self.upcoming.push(ch);
1126                            if starter {
1127                                break;
1128                            }
1129                        } else {
1130                            #[cfg(debug_assertions)]
1131                            {
1132                                self.iter_exhausted = true;
1133                            }
1134                            break;
1135                        }
1136                    }
1137                }
1138                if let Some(first) = self.upcoming.first() {
1139                    if !first.decomposition_starts_with_non_starter() {
1140                        return;
1141                    }
1142                }
1143            } else {
1144                // Ensure that `upcoming` starts with a starter in the case where
1145                // we get here with an empty `upcoming` due to the identical prefix
1146                // code exiting right away, because the very first code units differ.
1147                if let Some(ch) = self.iter_next() {
1148                    let starter = !ch.decomposition_starts_with_non_starter();
1149                    self.upcoming.push(ch);
1150                    if starter {
1151                        return;
1152                    }
1153                    // Loop back to uphoad the invariant that `upcoming` ends with
1154                    // a character whose decomposition starts with a starter unless
1155                    // the iterator has been exhausted.
1156                    continue;
1157                } else {
1158                    #[cfg(debug_assertions)]
1159                    {
1160                        self.iter_exhausted = true;
1161                    }
1162                    return;
1163                }
1164            }
1165            break;
1166        }
1167
1168        // The case where upcoming does not start with a starter.
1169        // Ideally, we'd have something more specialized here that would extract
1170        // the code path that `self.next()` runs after dealing with the U+0000.
1171        self.upcoming.insert(
1172            0,
1173            CharacterAndClassAndTrieValue::new_with_non_decomposing_starter('\u{0}'),
1174        ); // Make sure the process always begins with a starter
1175        let _ = self.next(); // Remove the placeholder starter
1176    }
1177
1178    fn iter_next(&mut self) -> Option<CharacterAndClassAndTrieValue> {
1179        let (c, trie_val) = self.iter.next()?;
1180        Some(CharacterAndClassAndTrieValue::new_with_trie_val(
1181            c, trie_val,
1182        ))
1183    }
1184
1185    fn next_internal(&mut self) -> Option<CharacterAndClassAndTrieValue> {
1186        if self.upcoming.is_empty() {
1187            return None;
1188        }
1189        // TODO: something more efficient here.
1190        let ret = self.upcoming.remove(0);
1191        if self.upcoming.is_empty() {
1192            if let Some(c) = self.iter_next() {
1193                self.upcoming.push(c);
1194            } else {
1195                #[cfg(debug_assertions)]
1196                {
1197                    self.iter_exhausted = true;
1198                }
1199            }
1200        }
1201        Some(ret)
1202    }
1203
1204    fn maybe_gather_combining(&mut self) {
1205        let Some(first) = self.upcoming.first().cloned() else {
1206            return;
1207        };
1208        if !first.decomposition_starts_with_non_starter() {
1209            return;
1210        }
1211        if self.upcoming.len() == 1 {
1212            // We now have a single character that decomposes to start with
1213            // a non-starter. Decompose it and assign the real canonical combining class.
1214            self.upcoming.clear();
1215            // We just cleared `upcoming`, so we can no longer be in the exceptional
1216            // state tracked by `self.upcoming_normalized` being `false`.
1217            self.upcoming_normalized = true;
1218            self.push_decomposed_combining(first);
1219        } else {
1220            // We end up sorting more times than minimally necessary.
1221            self.ensure_upcoming_normalized();
1222            #[cfg(debug_assertions)]
1223            {
1224                if !self.iter_exhausted {
1225                    // `unwrap` OK, because len() must be > 1.
1226                    debug_assert!(!self
1227                        .upcoming
1228                        .last()
1229                        .unwrap()
1230                        .decomposition_starts_with_non_starter());
1231                }
1232            }
1233            return;
1234        }
1235        // Not using `while let` to be able to set `iter_exhausted`
1236        loop {
1237            if let Some(ch) = self.iter_next() {
1238                if ch.decomposition_starts_with_non_starter() {
1239                    self.push_decomposed_combining(ch);
1240                } else {
1241                    // Got a new starter
1242                    self.upcoming.push(ch);
1243                    break;
1244                }
1245            } else {
1246                #[cfg(debug_assertions)]
1247                {
1248                    self.iter_exhausted = true;
1249                }
1250                break;
1251            }
1252        }
1253    }
1254
1255    /// Ensures that `upcoming` is normalized to NFD, except:
1256    /// 1. When the last item is a starter, it isn't necessarily normalized.
1257    /// 2. Hangul syllable are unnormalized.
1258    fn ensure_upcoming_normalized(&mut self) {
1259        if self.upcoming_normalized {
1260            return;
1261        }
1262        self.upcoming_normalized = true;
1263        let without_trailing_starter = if let Some((last, head)) = self.upcoming.split_last() {
1264            if !last.decomposition_starts_with_non_starter() {
1265                if head.is_empty() {
1266                    // There is a single starter, which isn't required
1267                    // to be normalized.
1268                    return;
1269                } else {
1270                    head
1271                }
1272            } else {
1273                &self.upcoming[..]
1274            }
1275        } else {
1276            // Make the assertion conditional to make CI happy.
1277            #[cfg(debug_assertions)]
1278            debug_assert!(self.iter_exhausted);
1279            return;
1280        };
1281
1282        // It would be better to attempt to normalize in place, but let's do at
1283        // least this.
1284        if without_trailing_starter.iter().all(|c| {
1285            (c.trie_val
1286                & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER | HANGUL_SYLLABLE_MARKER))
1287                == 0
1288        }) {
1289            return;
1290        }
1291
1292        let mut unnormalized = core::mem::take(&mut self.upcoming);
1293        let last_index = unnormalized.len() - 1;
1294        let mut start_combining = 0;
1295        for (i, c) in unnormalized.drain(..).enumerate() {
1296            if c.decomposition_starts_with_non_starter() {
1297                self.push_decomposed_combining(c);
1298            } else if i == last_index {
1299                // Indices are in range by construction, so indexing is OK.
1300                #[expect(clippy::indexing_slicing)]
1301                self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1302                self.upcoming.push(c);
1303                return;
1304            } else {
1305                // Indices are in range by construction, so indexing is OK.
1306                #[expect(clippy::indexing_slicing)]
1307                self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1308                start_combining = self.push_decomposed_starter(c);
1309            }
1310        }
1311        // `init()` is responsible for collecting non-starters up to a
1312        // starter or iterator exhausting into `upcoming`, so if we
1313        // didn't take the early return in the `i == last_index` branch
1314        // above, we are in the iterator exhausted case.
1315
1316        // Make the assertion conditional to make CI happy.
1317        #[cfg(debug_assertions)]
1318        debug_assert!(self.iter_exhausted);
1319        // Indices are in range by construction, so indexing is OK.
1320        #[expect(clippy::indexing_slicing)]
1321        self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1322    }
1323
1324    fn push_decomposed_combining(&mut self, c: CharacterAndClassAndTrieValue) {
1325        if !trie_value_indicates_special_non_starter_decomposition(c.trie_val) {
1326            debug_assert!(trie_value_has_ccc(c.trie_val));
1327            self.upcoming.push(c);
1328            return;
1329        }
1330
1331        // The Tibetan special cases are starters that decompose into non-starters.
1332        match c.character() {
1333            '\u{0340}' => {
1334                // COMBINING GRAVE TONE MARK
1335                self.upcoming
1336                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1337                        '\u{0300}',
1338                        CanonicalCombiningClass::Above,
1339                    ));
1340            }
1341            '\u{0341}' => {
1342                // COMBINING ACUTE TONE MARK
1343                self.upcoming
1344                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1345                        '\u{0301}',
1346                        CanonicalCombiningClass::Above,
1347                    ));
1348            }
1349            '\u{0343}' => {
1350                // COMBINING GREEK KORONIS
1351                self.upcoming
1352                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1353                        '\u{0313}',
1354                        CanonicalCombiningClass::Above,
1355                    ));
1356            }
1357            '\u{0344}' => {
1358                // COMBINING GREEK DIALYTIKA TONOS
1359                self.upcoming
1360                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1361                        '\u{0308}',
1362                        CanonicalCombiningClass::Above,
1363                    ));
1364                self.upcoming
1365                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1366                        '\u{0301}',
1367                        CanonicalCombiningClass::Above,
1368                    ));
1369            }
1370            '\u{0F73}' => {
1371                // TIBETAN VOWEL SIGN II
1372                self.upcoming
1373                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1374                        '\u{0F71}',
1375                        CanonicalCombiningClass::CCC129,
1376                    ));
1377                self.upcoming
1378                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1379                        '\u{0F72}',
1380                        CanonicalCombiningClass::CCC130,
1381                    ));
1382            }
1383            '\u{0F75}' => {
1384                // TIBETAN VOWEL SIGN UU
1385                self.upcoming
1386                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1387                        '\u{0F71}',
1388                        CanonicalCombiningClass::CCC129,
1389                    ));
1390                self.upcoming
1391                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1392                        '\u{0F74}',
1393                        CanonicalCombiningClass::CCC132,
1394                    ));
1395            }
1396            '\u{0F81}' => {
1397                // TIBETAN VOWEL SIGN REVERSED II
1398                self.upcoming
1399                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1400                        '\u{0F71}',
1401                        CanonicalCombiningClass::CCC129,
1402                    ));
1403                self.upcoming
1404                    .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1405                        '\u{0F80}',
1406                        CanonicalCombiningClass::CCC130,
1407                    ));
1408            }
1409            _ => {
1410                // GIGO case
1411                debug_assert!(false);
1412            }
1413        }
1414    }
1415
1416    fn push_decomposed_starter(&mut self, c: CharacterAndClassAndTrieValue) -> usize {
1417        let mut search_start_combining = false;
1418        let old_len = self.upcoming.len();
1419        // Not inserting early returns below to keep the same structure
1420        // as in the ce32 mapping code.
1421
1422        // Hangul syllable check omitted, because it's fine not to decompose
1423        // Hangul syllables in lookahead, because Hangul isn't allowed to
1424        // participate in contractions, and the trie default is that a character
1425        // is its own decomposition.
1426
1427        // See components/normalizer/trie-value-format.md
1428        let decomposition = c.trie_val;
1429        if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1430            <= HANGUL_SYLLABLE_MARKER
1431        {
1432            // The character is its own decomposition (or Hangul syllable, in which case
1433            // we need to retain `trie_val`).
1434            debug_assert_eq!(
1435                ccc_from_trie_value(decomposition),
1436                CanonicalCombiningClass::NotReordered
1437            );
1438            self.upcoming.push(c);
1439        } else {
1440            let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1441            let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1442            if !high_zeros && !low_zeros {
1443                // Decomposition into two BMP characters: starter and non-starter
1444                let starter = char_from_u32(decomposition & 0x7FFF);
1445                let low_c = char_from_u32((decomposition >> 15) & 0x7FFF);
1446                self.upcoming
1447                    .push(CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(starter));
1448                let trie_value = self.iter.trie().bmp(low_c as u16);
1449                self.upcoming.push(
1450                    CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(
1451                        low_c, trie_value,
1452                    ),
1453                );
1454            } else if high_zeros {
1455                let singleton = decomposition as u16;
1456                debug_assert_ne!(
1457                    singleton, FDFA_MARKER,
1458                    "How come U+FDFA NFKD marker seen in NFD?"
1459                );
1460                if (singleton & 0xFF00) == 0xD800 {
1461                    // We're at the end of the stream, so we aren't dealing with the
1462                    // next undecomposed starter but are dealing with an
1463                    // already-decomposed non-starter. Just put it back.
1464                    self.upcoming.push(c);
1465                    // Make the assertion conditional to make CI happy.
1466                    #[cfg(debug_assertions)]
1467                    debug_assert!(self.iter_exhausted);
1468                } else {
1469                    // Decomposition into one BMP character
1470                    self.upcoming.push(
1471                        CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1472                            char_from_u16(singleton),
1473                        ),
1474                    );
1475                }
1476            } else {
1477                debug_assert!(low_zeros);
1478                // Only 12 of 14 bits used as of Unicode 16.
1479                let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1480                // Only 3 of 4 bits used as of Unicode 16.
1481                let len_bits = decomposition & 0b1111;
1482                let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1483                if offset < self.scalars16.len() {
1484                    let len = (len_bits + 2) as usize;
1485                    for u in unwrap_or_gigo(
1486                        self.scalars16.get_subslice(offset..offset + len),
1487                        SINGLE_REPLACEMENT_CHARACTER_U16, // single instead of empty for consistency with the other code path
1488                    )
1489                    .iter()
1490                    {
1491                        let ch = char_from_u16(u);
1492                        let trie_value = self.iter.trie().bmp(u);
1493                        self.upcoming
1494                            .push(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(ch, trie_value));
1495                    }
1496                } else {
1497                    let len = (len_bits + 1) as usize;
1498                    let offset32 = offset - self.scalars16.len();
1499                    for ch in unwrap_or_gigo(
1500                        self.scalars32.get_subslice(offset32..offset32 + len),
1501                        SINGLE_REPLACEMENT_CHARACTER_CHAR, // single instead of empty for consistency with the other code path
1502                    )
1503                    .iter()
1504                    {
1505                        let trie_value = self.iter.trie().scalar(ch);
1506                        self.upcoming
1507                            .push(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(ch, trie_value));
1508                    }
1509                }
1510                search_start_combining = !only_non_starters_in_trail;
1511            }
1512        }
1513        if search_start_combining {
1514            // The decomposition contains starters. As of Unicode 14,
1515            // There are two possible patterns:
1516            // BMP: starter, starter, non-starter
1517            // Plane 1: starter, starter.
1518            // However, for forward compatibility, support any combination
1519            // and search for the last starter.
1520            let mut i = self.upcoming.len() - 1;
1521            loop {
1522                if let Some(ch) = self.upcoming.get(i) {
1523                    if ch.decomposition_starts_with_non_starter() {
1524                        i -= 1;
1525                        continue;
1526                    }
1527                    break;
1528                }
1529                // GIGO case
1530                debug_assert!(false);
1531                // This will wrap to zero below
1532                i = usize::MAX;
1533                break;
1534            }
1535            i + 1
1536        } else {
1537            old_len + 1
1538        }
1539    }
1540
1541    // Decomposes `c`, pushes it to `self.upcoming` (unless the character is
1542    // a Hangul syllable; Hangul isn't allowed to participate in contractions),
1543    // gathers the following combining characters from `self.iter` and the following starter.
1544    // Sorts the combining characters and leaves the starter at the end
1545    // unnormalized. The trailing unnormalized starter doesn't get appended if
1546    // `self.iter` is exhausted.
1547    fn push_decomposed_and_gather_combining(&mut self, c: CharacterAndClassAndTrieValue) {
1548        let start_combining = self.push_decomposed_starter(c);
1549        // Not using `while let` to be able to set `iter_exhausted`
1550        loop {
1551            if let Some(ch) = self.iter_next() {
1552                if ch.decomposition_starts_with_non_starter() {
1553                    self.push_decomposed_combining(ch);
1554                } else {
1555                    // Got a new starter
1556                    // Indices are in range by construction, so indexing is OK.
1557                    #[expect(clippy::indexing_slicing)]
1558                    self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1559                    self.upcoming.push(ch);
1560                    return;
1561                }
1562            } else {
1563                #[cfg(debug_assertions)]
1564                {
1565                    self.iter_exhausted = true;
1566                }
1567                // Indices are in range by construction, so indexing is OK.
1568                #[expect(clippy::indexing_slicing)]
1569                self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1570                return;
1571            }
1572        }
1573    }
1574
1575    // Assumption: `pos` starts from zero and increases one by one.
1576    // Indexing is OK, because we check against `len()` and the `pos`
1577    // increases one by one by construction.
1578    #[expect(clippy::indexing_slicing)]
1579    fn look_ahead(&mut self, pos: usize) -> Option<CharacterAndClassAndTrieValue> {
1580        debug_assert!(self.upcoming_normalized);
1581        if pos + 1 == self.upcoming.len() {
1582            let c = self.upcoming.remove(pos);
1583            self.push_decomposed_and_gather_combining(c);
1584            Some(self.upcoming[pos].clone())
1585        } else if pos == self.upcoming.len() {
1586            if let Some(c) = self.iter_next() {
1587                debug_assert!(
1588                    false,
1589                    "The `upcoming` queue should be empty when iteration `pos` at the end"
1590                );
1591                self.push_decomposed_and_gather_combining(c);
1592                Some(self.upcoming[pos].clone())
1593            } else {
1594                #[cfg(debug_assertions)]
1595                {
1596                    self.iter_exhausted = true;
1597                }
1598                None
1599            }
1600        } else {
1601            Some(self.upcoming[pos].clone())
1602        }
1603    }
1604
1605    fn is_next_decomposition_starts_with_starter(&self) -> bool {
1606        if let Some(c_c_tv) = self.upcoming.first() {
1607            !c_c_tv.decomposition_starts_with_non_starter()
1608        } else {
1609            true
1610        }
1611    }
1612
1613    fn prepend_and_sort_non_starter_prefix_of_suffix(&mut self, c: CharacterAndClassAndTrieValue) {
1614        // Add one for the insertion afterwards.
1615        let end = 1 + {
1616            let mut iter = self.upcoming.iter().enumerate();
1617            loop {
1618                if let Some((i, ch)) = iter.next() {
1619                    if !ch.decomposition_starts_with_non_starter() {
1620                        break i;
1621                    }
1622                } else {
1623                    #[cfg(debug_assertions)]
1624                    {
1625                        self.iter_exhausted = true;
1626                    }
1627                    break self.upcoming.len();
1628                }
1629            }
1630        };
1631        let start = c.decomposition_starts_with_non_starter() as usize;
1632        self.upcoming.insert(0, c);
1633        // Indices in range by construction
1634        #[expect(clippy::indexing_slicing)]
1635        {
1636            let slice: &mut [CharacterAndClassAndTrieValue] = &mut self.upcoming[start..end];
1637            slice.sort_by_key(|cc| cc.ccc());
1638        };
1639    }
1640
1641    fn prefix_push(&mut self, c: char) {
1642        self.prefix[1] = self.prefix[0];
1643        self.prefix[0] = c;
1644    }
1645
1646    /// Micro optimization for doing a simpler write when
1647    /// we know the most recent character was a non-starter
1648    /// that is not a kana voicing mark.
1649    fn mark_prefix_unmatchable(&mut self) {
1650        self.prefix[0] = '\u{FFFF}';
1651    }
1652
1653    pub fn next(&mut self) -> CollationElement {
1654        #[cfg(debug_assertions)]
1655        debug_assert!(self.initialized);
1656        debug_assert!(self.is_next_decomposition_starts_with_starter());
1657        if let Some(&ret) = self.pending.get(self.pending_pos) {
1658            self.pending_pos += 1;
1659            if self.pending_pos == self.pending.len() {
1660                self.pending.clear();
1661                self.pending_pos = 0;
1662            }
1663            return ret;
1664        }
1665        debug_assert_eq!(self.pending_pos, 0);
1666        if let Some(c_c_tv) = self.next_internal() {
1667            let mut c = c_c_tv.character();
1668            let mut ce32;
1669            let mut data: &CollationData = self.tailoring;
1670            // TODO: Should this be a reusable buffer on the struct instead of
1671            // getting re-created on the stack every time?
1672            let mut combining_characters: SmallVec<
1673                [CharacterAndClass; COMBINING_CHARACTER_BUFFER_SIZE],
1674            > = SmallVec::new(); // TODO(#2005): Figure out good length
1675
1676            // Betting that fusing the NFD algorithm into this one at the
1677            // expense of the repetitiveness below, the common cases become
1678            // fast in a way that offsets the lack of the canonical closure.
1679            // The wall of code before the "Slow path" is an attempt to
1680            // optimize based on that bet.
1681
1682            // See components/normalizer/trie-value-format.md
1683            let decomposition = c_c_tv.trie_val;
1684            if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1685                // The character is its own decomposition
1686
1687                // TODO: This is a bad idea. Make sure the jamo are in the root trie and then
1688                // remove this special case.
1689                let jamo_index = (c as usize).wrapping_sub(HANGUL_L_BASE as usize);
1690                // Attribute belongs on an inner expression, but
1691                // https://github.com/rust-lang/rust/issues/15701
1692                #[expect(clippy::indexing_slicing)]
1693                if jamo_index >= self.jamo.len() {
1694                    // Note: It might seem like a good idea to reuse the CE32s
1695                    // from the identical prefix check here, but the logistics
1696                    // actually make everything slower.
1697                    ce32 = data.ce32_for_char(c);
1698                    if ce32 == FALLBACK_CE32 {
1699                        data = self.root;
1700                        ce32 = data.ce32_for_char(c);
1701                    }
1702                } else {
1703                    // The purpose of reading the CE32 from the jamo table instead
1704                    // of the trie even in this case is to make it unnecessary
1705                    // for all search collation tries to carry a copy of the Hangul
1706                    // part of the search root. Instead, all non-Korean tailorings
1707                    // can use a shared copy of the non-Korean search jamo table.
1708                    //
1709                    // TODO(#1941): This isn't actually true with the current jamo
1710                    // search expansions!
1711
1712                    // TODO(#1941): Instead of having different jamo CE32 table for
1713                    // "search" collations, we could instead decompose the archaic
1714                    // jamo to the modern approximation sequences here and then map
1715                    // those by looking up the modern jamo from the normal root.
1716
1717                    // We need to set data to root, because archaic jamo refer to
1718                    // the root.
1719                    data = self.root;
1720                    // Index in range by construction above. Not using `get` with
1721                    // `if let` in order to put the likely branch first.
1722                    ce32 = CollationElement32::new_from_ule(self.jamo[jamo_index]);
1723                }
1724                if self.is_next_decomposition_starts_with_starter() {
1725                    if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1726                        self.prefix_push(c);
1727                        return ce;
1728                    } else if ce32.tag() == Tag::Contraction
1729                        && ce32.every_suffix_starts_with_combining()
1730                    {
1731                        // Avoid falling onto the slow path e.g. that letters that
1732                        // may contract with a diacritic when we know that it won't
1733                        // contract with the next character.
1734                        let default = data.get_default(ce32.index());
1735                        if let Some(ce) = default.to_ce_simple_or_long_primary() {
1736                            self.prefix_push(c);
1737                            return ce;
1738                        }
1739                    }
1740                    // TODO(2003): Figure out if it would be an optimization to
1741                    // handle `Implicit` and `Offset` tags here.
1742                }
1743            } else {
1744                let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1745                let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1746                if !high_zeros && !low_zeros {
1747                    // Decomposition into two BMP characters: starter and non-starter
1748                    c = char_from_u32(decomposition & 0x7FFF);
1749                    ce32 = data.ce32_for_char(c);
1750                    if ce32 == FALLBACK_CE32 {
1751                        data = self.root;
1752                        ce32 = data.ce32_for_char(c);
1753                    }
1754                    let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
1755                    if self.is_next_decomposition_starts_with_starter() {
1756                        let diacritic_index =
1757                            (combining as usize).wrapping_sub(COMBINING_DIACRITICS_BASE);
1758                        if let Some(secondary) = self.diacritics.get(diacritic_index) {
1759                            debug_assert_ne!(combining, '\u{0344}', "Should never have COMBINING GREEK DIALYTIKA TONOS here, since it should have decomposed further.");
1760                            if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1761                                let ce_for_combining =
1762                                    CollationElement::new_from_secondary(secondary);
1763                                self.pending.push(ce_for_combining);
1764                                self.mark_prefix_unmatchable();
1765                                return ce;
1766                            }
1767                            if ce32.tag() == Tag::Contraction
1768                                && ce32.every_suffix_starts_with_combining()
1769                            {
1770                                let (default, mut trie) = data.get_default_and_trie(ce32.index());
1771                                match trie.next(combining) {
1772                                    TrieResult::NoMatch | TrieResult::NoValue => {
1773                                        if let Some(ce) = default.to_ce_simple_or_long_primary() {
1774                                            let ce_for_combining =
1775                                                CollationElement::new_from_secondary(secondary);
1776                                            self.pending.push(ce_for_combining);
1777                                            self.mark_prefix_unmatchable();
1778                                            return ce;
1779                                        }
1780                                    }
1781                                    TrieResult::Intermediate(trie_ce32) => {
1782                                        if !ce32.at_least_one_suffix_contains_starter() {
1783                                            if let Some(ce) =
1784                                                CollationElement32::new(trie_ce32 as u32)
1785                                                    .to_ce_simple_or_long_primary()
1786                                            {
1787                                                self.mark_prefix_unmatchable();
1788                                                return ce;
1789                                            }
1790                                        }
1791                                    }
1792                                    TrieResult::FinalValue(trie_ce32) => {
1793                                        if let Some(ce) = CollationElement32::new(trie_ce32 as u32)
1794                                            .to_ce_simple_or_long_primary()
1795                                        {
1796                                            self.mark_prefix_unmatchable();
1797                                            return ce;
1798                                        }
1799                                    }
1800                                }
1801                            }
1802                        }
1803                    }
1804                    combining_characters.push(CharacterAndClass::new_with_placeholder(combining));
1805                } else if high_zeros {
1806                    // Do the Hangul check on the character instead of trusting
1807                    // the trie value in order not to let GIGO cause unsafety.
1808                    let hangul_offset = u32::from(c).wrapping_sub(HANGUL_S_BASE); // SIndex in the spec
1809                    if hangul_offset < HANGUL_S_COUNT {
1810                        // Hangul syllable
1811                        // The math here comes from page 144 of Unicode 14.0
1812                        let l = hangul_offset / HANGUL_N_COUNT;
1813                        let v = (hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
1814                        let t = hangul_offset % HANGUL_T_COUNT;
1815
1816                        // No prefix matches on Hangul
1817                        self.mark_prefix_unmatchable();
1818                        // Indexing OK, because indices in range by construction
1819                        #[expect(clippy::indexing_slicing)]
1820                        if self.is_next_decomposition_starts_with_starter() {
1821                            // TODO(#1941): Assuming self-contained CE32s is OK for the root,
1822                            // but not currently OK for search collation, which at this time
1823                            // do not support tailored Hangul.
1824                            self.pending.push(
1825                                CollationElement32::new_from_ule(
1826                                    self.jamo[(HANGUL_V_BASE - HANGUL_L_BASE + v) as usize],
1827                                )
1828                                .to_ce_self_contained_or_gigo(),
1829                            );
1830                            if t != 0 {
1831                                self.pending.push(
1832                                    CollationElement32::new_from_ule(
1833                                        self.jamo[(HANGUL_T_BASE - HANGUL_L_BASE + t) as usize],
1834                                    )
1835                                    .to_ce_self_contained_or_gigo(),
1836                                );
1837                            }
1838                            return CollationElement32::new_from_ule(self.jamo[l as usize])
1839                                .to_ce_self_contained_or_gigo();
1840                        }
1841
1842                        // Uphold the invariant that the upcoming character is a starter (or end of stream)
1843                        // at the start of the next `next()` call. We uphold this invariant by leaving the
1844                        // last jamo unmapped to `CollationElement` in `pending` and instead prepend it to
1845                        // `upcoming`.
1846                        //
1847                        // Indexing OK, because indices in range by construction
1848                        #[expect(clippy::indexing_slicing)]
1849                        if t != 0 {
1850                            self.pending.push(
1851                                CollationElement32::new_from_ule(
1852                                    self.jamo[(HANGUL_V_BASE - HANGUL_L_BASE + v) as usize],
1853                                )
1854                                .to_ce_self_contained_or_gigo(),
1855                            );
1856                            self.upcoming.insert(
1857                                0,
1858                                // Safety: HANGUL_T_BASE is 0x11A7, t is < HANGUL_T_COUNT = 28, so this is definitely
1859                                // in range for a char (≤ 0xD800)
1860                                CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1861                                    unsafe { core::char::from_u32_unchecked(HANGUL_T_BASE + t) },
1862                                ),
1863                            );
1864                        } else {
1865                            self.upcoming.insert(
1866                                0,
1867                                // Safety: HANGUL_V_BASE is 0x1161, v is < HANGUL_N_COUNT = 588, so this is definitely
1868                                // in range for a char (≤ 0xD800)
1869                                CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1870                                    unsafe { core::char::from_u32_unchecked(HANGUL_V_BASE + v) },
1871                                ),
1872                            );
1873                        }
1874
1875                        // Indexing OK, because indices in range by construction
1876                        #[expect(clippy::indexing_slicing)]
1877                        return CollationElement32::new_from_ule(self.jamo[l as usize])
1878                            .to_ce_self_contained_or_gigo();
1879                    }
1880
1881                    let singleton = decomposition as u16;
1882                    debug_assert_ne!(
1883                        singleton, FDFA_MARKER,
1884                        "How come U+FDFA NFKD marker seen in NFD?"
1885                    );
1886                    // Decomposition into one BMP character
1887                    c = char_from_u16(singleton);
1888                    ce32 = data.ce32_for_char(c);
1889                    if ce32 == FALLBACK_CE32 {
1890                        data = self.root;
1891                        ce32 = data.ce32_for_char(c);
1892                    }
1893                    if self.is_next_decomposition_starts_with_starter() {
1894                        if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1895                            self.prefix_push(c);
1896                            return ce;
1897                        }
1898                    }
1899                } else {
1900                    debug_assert!(low_zeros);
1901                    // Only 12 of 14 bits used as of Unicode 16.
1902                    let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1903                    // Only 3 of 4 bits used as of Unicode 16.
1904                    let len_bits = decomposition & 0b1111;
1905                    let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1906                    if offset < self.scalars16.len() {
1907                        let len = (len_bits + 2) as usize;
1908                        let (starter, tail) = self
1909                            .scalars16
1910                            .get_subslice(offset..offset + len)
1911                            .and_then(ZeroSlice::split_first)
1912                            .map_or_else(
1913                                || {
1914                                    // GIGO case
1915                                    debug_assert!(false);
1916                                    (REPLACEMENT_CHARACTER, EMPTY_U16)
1917                                },
1918                                |(first, tail)| (char_from_u16(first), tail),
1919                            );
1920                        c = starter;
1921                        if only_non_starters_in_trail {
1922                            for u in tail.iter() {
1923                                let char_from_u = char_from_u16(u);
1924                                let trie_value = self.iter.trie().bmp(u);
1925                                let ccc = ccc_from_trie_value(trie_value);
1926                                combining_characters.push(CharacterAndClass::new(char_from_u, ccc));
1927                            }
1928                        } else {
1929                            let mut it = tail.iter();
1930                            while let Some(u) = it.next() {
1931                                let ch = char_from_u16(u);
1932                                let ccc = ccc_from_trie_value(self.iter.trie().bmp(u));
1933                                if ccc != CanonicalCombiningClass::NotReordered {
1934                                    // As of Unicode 14, this branch is never taken.
1935                                    // It exist for forward compatibility.
1936                                    combining_characters.push(CharacterAndClass::new(ch, ccc));
1937                                    continue;
1938                                }
1939
1940                                // At this point, we might have a single newly-read
1941                                // combining character in self.upcoming. In that case, we
1942                                // need to buffer up the upcoming combining characters, too,
1943                                // in order to make `prepend_and_sort_non_starter_prefix_of_suffix`
1944                                // sort the right characters.
1945                                self.maybe_gather_combining();
1946
1947                                while let Some(u) = it.next_back() {
1948                                    let tail_char = char_from_u16(u);
1949                                    let trie_value = self.iter.trie().bmp(u);
1950                                    self.prepend_and_sort_non_starter_prefix_of_suffix(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(tail_char, trie_value));
1951                                }
1952                                self.prepend_and_sort_non_starter_prefix_of_suffix(
1953                                    CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1954                                        ch,
1955                                    ),
1956                                );
1957                                break;
1958                            }
1959                        }
1960                    } else {
1961                        let len = (len_bits + 1) as usize;
1962                        let offset32 = offset - self.scalars16.len();
1963                        let (starter, tail) = self
1964                            .scalars32
1965                            .get_subslice(offset32..offset32 + len)
1966                            .and_then(|slice| slice.split_first())
1967                            .unwrap_or_else(|| {
1968                                // GIGO case
1969                                debug_assert!(false);
1970                                (REPLACEMENT_CHARACTER, EMPTY_CHAR)
1971                            });
1972
1973                        c = starter;
1974                        if only_non_starters_in_trail {
1975                            for ch in tail.iter() {
1976                                let trie_value = self.iter.trie().scalar(ch);
1977                                let ccc = ccc_from_trie_value(trie_value);
1978                                combining_characters.push(CharacterAndClass::new(ch, ccc));
1979                            }
1980                        } else {
1981                            let mut it = tail.iter();
1982                            while let Some(ch) = it.next() {
1983                                let ccc = ccc_from_trie_value(self.iter.trie().scalar(ch));
1984                                if ccc != CanonicalCombiningClass::NotReordered {
1985                                    // As of Unicode 14, this branch is never taken.
1986                                    // It exist for forward compatibility.
1987                                    combining_characters.push(CharacterAndClass::new(ch, ccc));
1988                                    continue;
1989                                }
1990                                // At this point, we might have a single newly-read
1991                                // combining character in self.upcoming. In that case, we
1992                                // need to buffer up the upcoming combining characters, too,
1993                                // in order to make `prepend_and_sort_non_starter_prefix_of_suffix`
1994                                // sort the right characters.
1995                                self.maybe_gather_combining();
1996
1997                                while let Some(tail_char) = it.next_back() {
1998                                    let trie_value = self.iter.trie().scalar(tail_char);
1999                                    self.prepend_and_sort_non_starter_prefix_of_suffix(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(tail_char, trie_value));
2000                                }
2001                                self.prepend_and_sort_non_starter_prefix_of_suffix(
2002                                    CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
2003                                        ch,
2004                                    ),
2005                                );
2006                                break;
2007                            }
2008                        }
2009                    }
2010                    ce32 = data.ce32_for_char(c);
2011                    if ce32 == FALLBACK_CE32 {
2012                        data = self.root;
2013                        ce32 = data.ce32_for_char(c);
2014                    }
2015                }
2016            }
2017            let mut may_have_contracted_starter = false;
2018            // Slow path
2019            self.collect_combining(&mut combining_characters);
2020            // Now:
2021            // c is the starter character
2022            // ce32 is the CollationElement32 for the starter
2023            // combining_characters contains all the combining characters before
2024            // the next starter sorted by combining class.
2025            let mut looked_ahead = 0;
2026            let mut drain_from_upcoming = 0;
2027            'outer: loop {
2028                'ce32loop: loop {
2029                    // TODO(#2002): Ensure that the CE32 flavors in this loop are checked in the optimal
2030                    // order given their frequency in real workloads.
2031                    if let Some(ce) = ce32.to_ce_self_contained() {
2032                        self.pending.push(ce);
2033                        break 'ce32loop;
2034                    } else {
2035                        match ce32.tag() {
2036                            Tag::Expansion32 => {
2037                                let ce32s = data.get_ce32s(ce32.index(), ce32.len());
2038                                for u in ce32s.iter() {
2039                                    self.pending.push(
2040                                        CollationElement32::new(u).to_ce_self_contained_or_gigo(),
2041                                    );
2042                                }
2043                                break 'ce32loop;
2044                            }
2045                            Tag::Expansion | Tag::ExpansionForReorderable => {
2046                                let ces = data.get_ces(ce32.index(), ce32.len());
2047                                for u in ces.iter() {
2048                                    self.pending.push(CollationElement::new(u));
2049                                }
2050                                break 'ce32loop;
2051                            }
2052                            Tag::Prefix => {
2053                                let (default, mut trie) = data.get_default_and_trie(ce32.index());
2054                                ce32 = default;
2055                                for &ch in self.prefix.iter() {
2056                                    match trie.next(ch) {
2057                                        TrieResult::NoValue => {}
2058                                        TrieResult::NoMatch => {
2059                                            continue 'ce32loop;
2060                                        }
2061                                        TrieResult::Intermediate(ce32_i) => {
2062                                            ce32 = CollationElement32::new(ce32_i as u32);
2063                                        }
2064                                        TrieResult::FinalValue(ce32_i) => {
2065                                            ce32 = CollationElement32::new(ce32_i as u32);
2066                                            continue 'ce32loop;
2067                                        }
2068                                    }
2069                                }
2070                                continue 'ce32loop;
2071                            }
2072                            Tag::Contraction => {
2073                                let every_suffix_starts_with_combining =
2074                                    ce32.every_suffix_starts_with_combining();
2075                                let at_least_one_suffix_contains_starter =
2076                                    ce32.at_least_one_suffix_contains_starter();
2077                                let at_least_one_suffix_ends_with_non_starter =
2078                                    ce32.at_least_one_suffix_ends_with_non_starter();
2079                                let (default, mut trie) = data.get_default_and_trie(ce32.index());
2080                                ce32 = default;
2081                                if every_suffix_starts_with_combining
2082                                    && combining_characters.is_empty()
2083                                {
2084                                    continue 'ce32loop;
2085                                }
2086                                let mut longest_matching_state = trie.clone();
2087                                let mut longest_matching_index = 0;
2088                                let mut attempt = 0;
2089                                let mut i = 0;
2090                                let mut most_recent_skipped_ccc =
2091                                    CanonicalCombiningClass::NotReordered;
2092                                // TODO(#2001): Pending removals will in practice be small numbers.
2093                                // What if we made the item smaller than usize?
2094                                let mut pending_removals: SmallVec<[usize; PENDING_REMOVALS_SIZE]> =
2095                                    SmallVec::new();
2096                                while let Some((character, ccc)) =
2097                                    combining_characters.get(i).map(|c| c.character_and_ccc())
2098                                {
2099                                    match (most_recent_skipped_ccc < ccc, trie.next(character)) {
2100                                        (true, TrieResult::Intermediate(ce32_i)) => {
2101                                            let _ = combining_characters.remove(i);
2102                                            while let Some(idx) = pending_removals.pop() {
2103                                                combining_characters.remove(idx);
2104                                                i -= 1; // Adjust for the shortening
2105                                            }
2106                                            attempt = 0;
2107                                            longest_matching_index = i;
2108                                            longest_matching_state = trie.clone();
2109                                            ce32 = CollationElement32::new(ce32_i as u32);
2110                                        }
2111                                        (true, TrieResult::FinalValue(ce32_i)) => {
2112                                            let _ = combining_characters.remove(i);
2113                                            while let Some(idx) = pending_removals.pop() {
2114                                                combining_characters.remove(idx);
2115                                            }
2116                                            ce32 = CollationElement32::new(ce32_i as u32);
2117                                            continue 'ce32loop;
2118                                        }
2119                                        (_, TrieResult::NoValue) => {
2120                                            pending_removals.push(i);
2121                                            i += 1;
2122                                        }
2123                                        _ => {
2124                                            pending_removals.clear();
2125                                            most_recent_skipped_ccc = ccc;
2126                                            attempt += 1;
2127                                            i = longest_matching_index + attempt;
2128                                            trie = longest_matching_state.clone();
2129                                        }
2130                                    }
2131                                }
2132                                if !(at_least_one_suffix_contains_starter
2133                                    && combining_characters.is_empty())
2134                                {
2135                                    continue 'ce32loop;
2136                                }
2137                                // Let's just set this flag here instead of trying to make
2138                                // it more granular and, therefore, more error-prone.
2139                                // After all, this flag is just about optimizing away one
2140                                // `CodePointInversionList` check in the common case.
2141                                may_have_contracted_starter = true;
2142                                debug_assert!(pending_removals.is_empty());
2143                                self.ensure_upcoming_normalized();
2144                                loop {
2145                                    let ahead = self.look_ahead(looked_ahead);
2146                                    looked_ahead += 1;
2147                                    if let Some(ch) = ahead {
2148                                        match trie.next(ch.character()) {
2149                                            TrieResult::NoValue => {}
2150                                            TrieResult::NoMatch => {
2151                                                if !at_least_one_suffix_ends_with_non_starter {
2152                                                    continue 'ce32loop;
2153                                                }
2154                                                if !ch.decomposition_starts_with_non_starter() {
2155                                                    continue 'ce32loop;
2156                                                }
2157                                                // The last-checked character is non-starter
2158                                                // and at least one contraction suffix ends
2159                                                // with a non-starter. Try a discontiguous
2160                                                // match.
2161                                                trie = longest_matching_state.clone();
2162                                                // For clarity, mint a new set of variables that
2163                                                // behave consistently with the
2164                                                // `combining_characters` case
2165                                                let mut longest_matching_index = 0;
2166                                                let mut attempt = 0;
2167                                                let mut i = 0;
2168                                                most_recent_skipped_ccc = ch.ccc();
2169                                                self.ensure_upcoming_normalized();
2170                                                loop {
2171                                                    let ahead = self.look_ahead(looked_ahead + i);
2172                                                    if let Some(ch) = ahead {
2173                                                        let ccc = ch.ccc();
2174                                                        if ccc
2175                                                            == CanonicalCombiningClass::NotReordered
2176                                                        {
2177                                                            // If we came here, we had an intervening non-matching
2178                                                            // non-starter, after which we cannot contract another
2179                                                            // starter anymore.
2180                                                            continue 'ce32loop;
2181                                                        }
2182                                                        match (
2183                                                            most_recent_skipped_ccc < ccc,
2184                                                            trie.next(ch.character()),
2185                                                        ) {
2186                                                            (
2187                                                                true,
2188                                                                TrieResult::Intermediate(ce32_i),
2189                                                            ) => {
2190                                                                let _ = self
2191                                                                    .upcoming
2192                                                                    .remove(looked_ahead + i);
2193                                                                while let Some(idx) =
2194                                                                    pending_removals.pop()
2195                                                                {
2196                                                                    self.upcoming
2197                                                                        .remove(looked_ahead + idx);
2198                                                                    i -= 1; // Adjust for the shortening
2199                                                                }
2200                                                                attempt = 0;
2201                                                                longest_matching_index = i;
2202                                                                longest_matching_state =
2203                                                                    trie.clone();
2204                                                                ce32 = CollationElement32::new(
2205                                                                    ce32_i as u32,
2206                                                                );
2207                                                            }
2208                                                            (
2209                                                                true,
2210                                                                TrieResult::FinalValue(ce32_i),
2211                                                            ) => {
2212                                                                let _ = self
2213                                                                    .upcoming
2214                                                                    .remove(looked_ahead + i);
2215                                                                while let Some(idx) =
2216                                                                    pending_removals.pop()
2217                                                                {
2218                                                                    self.upcoming
2219                                                                        .remove(looked_ahead + idx);
2220                                                                }
2221                                                                ce32 = CollationElement32::new(
2222                                                                    ce32_i as u32,
2223                                                                );
2224                                                                continue 'ce32loop;
2225                                                            }
2226                                                            (_, TrieResult::NoValue) => {
2227                                                                pending_removals.push(i);
2228                                                                i += 1;
2229                                                            }
2230                                                            _ => {
2231                                                                pending_removals.clear();
2232                                                                most_recent_skipped_ccc = ccc;
2233                                                                attempt += 1;
2234                                                                i = longest_matching_index
2235                                                                    + attempt;
2236                                                                trie =
2237                                                                    longest_matching_state.clone();
2238                                                            }
2239                                                        }
2240                                                    } else {
2241                                                        continue 'ce32loop;
2242                                                    }
2243                                                }
2244                                            }
2245                                            TrieResult::Intermediate(ce32_i) => {
2246                                                longest_matching_state = trie.clone();
2247                                                drain_from_upcoming = looked_ahead;
2248                                                ce32 = CollationElement32::new(ce32_i as u32);
2249                                            }
2250                                            TrieResult::FinalValue(ce32_i) => {
2251                                                drain_from_upcoming = looked_ahead;
2252                                                ce32 = CollationElement32::new(ce32_i as u32);
2253                                                continue 'ce32loop;
2254                                            }
2255                                        }
2256                                    } else {
2257                                        continue 'ce32loop;
2258                                    }
2259                                }
2260                                // Unreachable
2261                            }
2262                            Tag::Digit => {
2263                                if let Some(high_bits) = self.numeric_primary {
2264                                    let mut digits: SmallVec<[u8; DIGIT_BUFFER_SIZE]> =
2265                                        SmallVec::new(); // TODO(#2005): Figure out good length
2266                                    digits.push(ce32.digit());
2267                                    let numeric_primary = u32::from(high_bits) << 24;
2268                                    if combining_characters.is_empty() {
2269                                        // Numeric collation doesn't work with combining
2270                                        // characters applied to the digits.
2271                                        // It's unclear if reading from the tailoring first
2272                                        // is needed for practical purposes, since it doesn't
2273                                        // make much sense to tailor the numeric value of digits.
2274                                        // Performing the usual fallback pattern anyway just in
2275                                        // case.
2276                                        may_have_contracted_starter = true;
2277                                        self.ensure_upcoming_normalized();
2278                                        while let Some(upcoming) = self.look_ahead(looked_ahead) {
2279                                            looked_ahead += 1;
2280                                            ce32 =
2281                                                self.tailoring.ce32_for_char(upcoming.character());
2282                                            if ce32 == FALLBACK_CE32 {
2283                                                ce32 =
2284                                                    self.root.ce32_for_char(upcoming.character());
2285                                            }
2286                                            if ce32.tag_checked() != Some(Tag::Digit) {
2287                                                break;
2288                                            }
2289                                            drain_from_upcoming = looked_ahead;
2290                                            digits.push(ce32.digit());
2291                                        }
2292                                    }
2293                                    let mut remaining = digits.as_slice();
2294                                    while !remaining.is_empty() {
2295                                        // Skip leading zeros
2296
2297                                        // If this isn't our initial loop round and we've truncated
2298                                        // a chunk to 254 digits on a previous round, the eventual
2299                                        // comparison result can be wrong, but that replicates an
2300                                        // ICU4C bug. Let's fix both as a follow-up.
2301                                        loop {
2302                                            let Some((first, tail)) = remaining.split_first()
2303                                            else {
2304                                                // Keep one zero
2305                                                // If we get here, we must have skipped a zero, since
2306                                                // 1) the while loop condition above meant that we started
2307                                                //    with a non-empty slice AND
2308                                                // 2) this loop only skips zeros
2309                                                // Instead of trying to recover the same zero that we already
2310                                                // skipped, let's just fill in a static slice.
2311                                                remaining = &[0];
2312                                                break;
2313                                            };
2314                                            if *first != 0 {
2315                                                break;
2316                                            }
2317                                            remaining = tail;
2318                                        }
2319                                        // Numeric CEs are generated for segments of
2320                                        // up to 254 digits.
2321                                        let (head, tail) = remaining
2322                                            .split_at_checked(254)
2323                                            .unwrap_or((remaining, b""));
2324                                        remaining = tail;
2325                                        // From ICU4C CollationIterator::appendNumericSegmentCEs
2326                                        if head.len() <= 7 {
2327                                            let mut digit_iter = head.iter();
2328                                            // `unwrap` succeeds, because we always have at least one
2329                                            // digit to even start numeric processing.
2330                                            #[expect(clippy::unwrap_used)]
2331                                            let mut value = u32::from(*digit_iter.next().unwrap());
2332                                            for &digit in digit_iter {
2333                                                value *= 10;
2334                                                value += u32::from(digit);
2335                                            }
2336                                            // Primary weight second byte values:
2337                                            //     74 byte values   2.. 75 for small numbers in two-byte primary weights.
2338                                            //     40 byte values  76..115 for medium numbers in three-byte primary weights.
2339                                            //     16 byte values 116..131 for large numbers in four-byte primary weights.
2340                                            //    124 byte values 132..255 for very large numbers with 4..127 digit pairs.
2341                                            let mut first_byte = 2u32;
2342                                            let mut num_bytes = 74u32;
2343                                            if value < num_bytes {
2344                                                self.pending.push(
2345                                                    CollationElement::new_from_primary(
2346                                                        numeric_primary
2347                                                            | ((first_byte + value) << 16),
2348                                                    ),
2349                                                );
2350                                                continue;
2351                                            }
2352                                            value -= num_bytes;
2353                                            first_byte += num_bytes;
2354                                            num_bytes = 40;
2355                                            if value < num_bytes * 254 {
2356                                                // Three-byte primary for 74..10233=74+40*254-1, good for year numbers and more.
2357                                                self.pending.push(
2358                                                    CollationElement::new_from_primary(
2359                                                        numeric_primary
2360                                                            | ((first_byte + value / 254) << 16)
2361                                                            | ((2 + value % 254) << 8),
2362                                                    ),
2363                                                );
2364                                                continue;
2365                                            }
2366                                            value -= num_bytes * 254;
2367                                            first_byte += num_bytes;
2368                                            num_bytes = 16;
2369                                            if value < num_bytes * 254 * 254 {
2370                                                // Four-byte primary for 10234..1042489=10234+16*254*254-1.
2371                                                let mut primary =
2372                                                    numeric_primary | (2 + value % 254);
2373                                                value /= 254;
2374                                                primary |= (2 + value % 254) << 8;
2375                                                value /= 254;
2376                                                primary |= (first_byte + value % 254) << 16;
2377                                                self.pending.push(
2378                                                    CollationElement::new_from_primary(primary),
2379                                                );
2380                                                continue;
2381                                            }
2382                                            // original value > 1042489
2383                                        }
2384                                        debug_assert!(head.len() >= 7);
2385                                        // The second primary byte value 132..255 indicates the number of digit pairs (4..127),
2386                                        // then we generate primary bytes with those pairs.
2387                                        // Omit trailing 00 pairs.
2388                                        // Decrement the value for the last pair.
2389
2390                                        // Set the exponent. 4 pairs->132, 5 pairs->133, ..., 127 pairs->255.
2391                                        let mut len = head.len();
2392                                        let num_pairs = (len as u32).div_ceil(2); // as u32 OK, because capped to 254
2393                                        let mut primary =
2394                                            numeric_primary | ((132 - 4 + num_pairs) << 16);
2395                                        // Find the length without trailing 00 pairs.
2396                                        //
2397                                        // The indexing below is within bounds due to the following:
2398                                        //
2399                                        // * We skipped leading zeros.
2400                                        // * If `len == 2`: The loop condition is false, because
2401                                        //   `head[len - 2]` isn't a leading zero.
2402                                        // * If `len == 1`: The loop condition is false, because
2403                                        //   `head[len - 1]` isn't a leading zero, and `&&`
2404                                        //   short-circuits, so the `head[len - 2]` access doesn't
2405                                        //   occur.
2406                                        #[expect(clippy::indexing_slicing)]
2407                                        while head[len - 1] == 0 && head[len - 2] == 0 {
2408                                            len -= 2;
2409                                        }
2410                                        // Read the first pair
2411                                        // Index in bounds by construction above.
2412                                        #[expect(clippy::indexing_slicing)]
2413                                        let mut digit_iter = head[..len].iter();
2414                                        // `unwrap` succeeds by construction
2415                                        #[expect(clippy::unwrap_used)]
2416                                        let mut pair = if len & 1 == 1 {
2417                                            // Only "half a pair" if we have an odd number of digits.
2418                                            u32::from(*digit_iter.next().unwrap())
2419                                        } else {
2420                                            u32::from(*digit_iter.next().unwrap()) * 10
2421                                                + u32::from(*digit_iter.next().unwrap())
2422                                        };
2423                                        pair = 11 + 2 * pair;
2424                                        let mut shift = 8u32;
2425                                        while let (Some(&left), Some(&right)) =
2426                                            (digit_iter.next(), digit_iter.next())
2427                                        {
2428                                            if shift == 0 {
2429                                                primary |= pair;
2430                                                self.pending.push(
2431                                                    CollationElement::new_from_primary(primary),
2432                                                );
2433                                                primary = numeric_primary;
2434                                                shift = 16;
2435                                            } else {
2436                                                primary |= pair << shift;
2437                                                shift -= 8;
2438                                            }
2439                                            pair =
2440                                                11 + 2 * (u32::from(left) * 10 + u32::from(right));
2441                                        }
2442                                        primary |= (pair - 1) << shift;
2443                                        self.pending
2444                                            .push(CollationElement::new_from_primary(primary));
2445                                    }
2446                                    break 'ce32loop;
2447                                }
2448                                ce32 = data.get_ce32(ce32.index());
2449                                continue 'ce32loop;
2450                            }
2451                            Tag::Offset => {
2452                                self.pending.push(data.ce_from_offset_ce32(c, ce32));
2453                                break 'ce32loop;
2454                            }
2455                            Tag::Implicit => {
2456                                self.pending
2457                                    .push(CollationElement::new_implicit_from_char(c));
2458                                break 'ce32loop;
2459                            }
2460                            Tag::Fallback
2461                            | Tag::Reserved3
2462                            | Tag::LongPrimary
2463                            | Tag::LongPrimaryForReorderable
2464                            | Tag::LongSecondary
2465                            | Tag::BuilderData
2466                            | Tag::LatinExpansion
2467                            | Tag::Hangul => {
2468                                debug_assert!(false);
2469                                // GIGO case
2470                                self.pending.push(FFFD_CE);
2471                                break 'ce32loop;
2472                            }
2473                        }
2474                    }
2475                }
2476                self.prefix_push(c);
2477                'combining_outer: loop {
2478                    debug_assert!(drain_from_upcoming == 0 || combining_characters.is_empty());
2479                    let mut i = 0;
2480                    'combining: while let Some(ch) =
2481                        combining_characters.get(i).map(|c| c.character())
2482                    {
2483                        c = ch;
2484                        let diacritic_index = (c as usize).wrapping_sub(COMBINING_DIACRITICS_BASE);
2485                        if let Some(secondary) = self.diacritics.get(diacritic_index) {
2486                            // TODO(#2006): unlikely annotation
2487                            if c == '\u{0307}' && self.lithuanian_dot_above {
2488                                if let Some(next_c) =
2489                                    combining_characters.get(i + 1).map(|c| c.character())
2490                                {
2491                                    if next_c == '\u{0300}'
2492                                        || next_c == '\u{0301}'
2493                                        || next_c == '\u{0303}'
2494                                    {
2495                                        // Lithuanian contracts COMBINING DOT ABOVE with three other diacritics of the
2496                                        // same combining class such that the COMBINING DOT ABOVE is ignored for
2497                                        // collation. Since the combining class is the same, it's valid to simply
2498                                        // look at the next character in `combining_characters`.
2499                                        i += 1;
2500                                        continue 'combining;
2501                                    }
2502                                }
2503                            }
2504                            self.pending
2505                                .push(CollationElement::new_from_secondary(secondary));
2506                            self.mark_prefix_unmatchable();
2507                            i += 1;
2508                            continue 'combining;
2509                        }
2510                        // `c` is not a table-optimized diacritic.
2511                        // Not bothering to micro optimize away the move of the remaining
2512                        // part of `combining_characters`.
2513                        let _ = combining_characters.drain(..=i);
2514                        data = self.tailoring;
2515                        ce32 = data.ce32_for_char(c);
2516                        if ce32 == FALLBACK_CE32 {
2517                            data = self.root;
2518                            ce32 = data.ce32_for_char(c);
2519                        }
2520                        continue 'outer;
2521                    }
2522                    // Note: The borrow checker didn't like the iterator formulation
2523                    // for the loop below, because the `Drain` would have kept `self`
2524                    // mutable borrowed when trying to call `prefix_push`. To change
2525                    // this, `prefix` and `prefix_push` would need to be refactored
2526                    // into a struct.
2527                    i = 0;
2528                    while i < drain_from_upcoming {
2529                        // By construction, `drain_from_upcoming` doesn't exceed `upcoming.len()`
2530                        #[expect(clippy::indexing_slicing)]
2531                        let ch = self.upcoming[i].character();
2532                        self.prefix_push(ch);
2533                        i += 1;
2534                    }
2535                    // TODO(#2004): The above makes prefix out of sync when starter-contracting
2536                    // contractions use `pending_removals` instead of `drain_from_upcoming`.
2537                    // Do there exist prefixes that overlap contraction suffixes?
2538                    // At least as of CLDR 40, the two possible non-starters in prefixes,
2539                    // kana voicing marks, shouldn't be participating in Brahmic contractions.
2540                    let _ = self.upcoming.drain(..drain_from_upcoming);
2541                    if self.upcoming.is_empty() {
2542                        // Make the assertion conditional to make CI happy.
2543                        #[cfg(debug_assertions)]
2544                        debug_assert!(self.iter_exhausted || may_have_contracted_starter);
2545                        if let Some(c_c_tv) = self.iter_next() {
2546                            self.upcoming.push(c_c_tv);
2547                        } else {
2548                            #[cfg(debug_assertions)]
2549                            {
2550                                self.iter_exhausted = true;
2551                            }
2552                        }
2553                    }
2554                    if may_have_contracted_starter {
2555                        may_have_contracted_starter = false;
2556                        if !self.is_next_decomposition_starts_with_starter() {
2557                            // We need to loop back and process another round of
2558                            // non-starters in order to maintain the invariant of
2559                            // `upcoming` on the next call to `next()`.
2560                            drain_from_upcoming = 0;
2561                            looked_ahead = 0;
2562                            self.collect_combining(&mut combining_characters);
2563                            continue 'combining_outer;
2564                        }
2565                    }
2566                    // By construction, we have at least on pending CE by now.
2567                    #[expect(clippy::indexing_slicing)]
2568                    let ret = self.pending[0];
2569                    debug_assert_eq!(self.pending_pos, 0);
2570                    if self.pending.len() == 1 {
2571                        self.pending.clear();
2572                    } else {
2573                        self.pending_pos = 1;
2574                    }
2575                    return ret;
2576                }
2577            }
2578        } else {
2579            NO_CE
2580        }
2581    }
2582
2583    #[inline(always)]
2584    fn collect_combining(
2585        &mut self,
2586        combining_characters: &mut SmallVec<[CharacterAndClass; COMBINING_CHARACTER_BUFFER_SIZE]>,
2587    ) {
2588        while !self.is_next_decomposition_starts_with_starter() {
2589            // `unwrap` is OK, because `!self.is_next_decomposition_starts_with_starter()`
2590            // means the `unwrap()` must succeed.
2591            #[expect(clippy::unwrap_used)]
2592            let combining = self.next_internal().unwrap().c_and_c;
2593            let combining_c = combining.character();
2594            if !in_inclusive_range(combining_c, '\u{0340}', '\u{0F81}') {
2595                combining_characters.push(combining);
2596            } else {
2597                // The Tibetan special cases are starters that decompose into non-starters.
2598                match combining_c {
2599                    '\u{0340}' => {
2600                        // COMBINING GRAVE TONE MARK
2601                        combining_characters.push(CharacterAndClass::new(
2602                            '\u{0300}',
2603                            CanonicalCombiningClass::Above,
2604                        ));
2605                    }
2606                    '\u{0341}' => {
2607                        // COMBINING ACUTE TONE MARK
2608                        combining_characters.push(CharacterAndClass::new(
2609                            '\u{0301}',
2610                            CanonicalCombiningClass::Above,
2611                        ));
2612                    }
2613                    '\u{0343}' => {
2614                        // COMBINING GREEK KORONIS
2615                        combining_characters.push(CharacterAndClass::new(
2616                            '\u{0313}',
2617                            CanonicalCombiningClass::Above,
2618                        ));
2619                    }
2620                    '\u{0344}' => {
2621                        // COMBINING GREEK DIALYTIKA TONOS
2622                        combining_characters.push(CharacterAndClass::new(
2623                            '\u{0308}',
2624                            CanonicalCombiningClass::Above,
2625                        ));
2626                        combining_characters.push(CharacterAndClass::new(
2627                            '\u{0301}',
2628                            CanonicalCombiningClass::Above,
2629                        ));
2630                    }
2631                    '\u{0F73}' => {
2632                        // TIBETAN VOWEL SIGN II
2633                        combining_characters.push(CharacterAndClass::new(
2634                            '\u{0F71}',
2635                            CanonicalCombiningClass::CCC129,
2636                        ));
2637                        combining_characters.push(CharacterAndClass::new(
2638                            '\u{0F72}',
2639                            CanonicalCombiningClass::CCC130,
2640                        ));
2641                    }
2642                    '\u{0F75}' => {
2643                        // TIBETAN VOWEL SIGN UU
2644                        combining_characters.push(CharacterAndClass::new(
2645                            '\u{0F71}',
2646                            CanonicalCombiningClass::CCC129,
2647                        ));
2648                        combining_characters.push(CharacterAndClass::new(
2649                            '\u{0F74}',
2650                            CanonicalCombiningClass::CCC132,
2651                        ));
2652                    }
2653                    '\u{0F81}' => {
2654                        // TIBETAN VOWEL SIGN REVERSED II
2655                        combining_characters.push(CharacterAndClass::new(
2656                            '\u{0F71}',
2657                            CanonicalCombiningClass::CCC129,
2658                        ));
2659                        combining_characters.push(CharacterAndClass::new(
2660                            '\u{0F80}',
2661                            CanonicalCombiningClass::CCC130,
2662                        ));
2663                    }
2664                    _ => {
2665                        combining_characters.push(combining);
2666                    }
2667                };
2668            }
2669        }
2670        if combining_characters.len() > 1 {
2671            // This optimizes away the class lookup when len() == 1.
2672            // Unclear if this micro optimization is worthwhile.
2673            // In any case, we store the CanonicalCombiningClass in order to
2674            // avoid having to look it up again when deciding whether to proceed
2675            // with a discontiguous match. As a side effect, it also means that
2676            // duplicate lookups aren't needed if the sort below happens to compare
2677            // an item more than once.
2678            combining_characters
2679                .iter_mut()
2680                .for_each(|cc| cc.set_ccc_from_trie_if_not_already_set(self.iter.trie()));
2681            combining_characters.sort_by_key(|cc| cc.ccc());
2682        }
2683    }
2684}