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