Skip to main content

icu_normalizer/
lib.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// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
6#![cfg_attr(not(any(test, doc)), no_std)]
7#![cfg_attr(
8    not(test),
9    deny(
10        clippy::indexing_slicing,
11        clippy::unwrap_used,
12        clippy::expect_used,
13        clippy::panic,
14    )
15)]
16#![warn(missing_docs)]
17
18//! Normalizing text into Unicode Normalization Forms.
19//!
20//! This module is published as its own crate ([`icu_normalizer`](https://docs.rs/icu_normalizer/latest/icu_normalizer/))
21//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
22//!
23//! # Functionality
24//!
25//! The top level of the crate provides normalization of input into the four normalization forms defined in [UAX #15: Unicode
26//! Normalization Forms](https://www.unicode.org/reports/tr15/): NFC, NFD, NFKC, and NFKD.
27//!
28//! Three kinds of contiguous inputs are supported: known-well-formed UTF-8 (`&str`), potentially-not-well-formed UTF-8,
29//! and potentially-not-well-formed UTF-16. Additionally, an iterator over `char` can be wrapped in a normalizing iterator.
30//!
31//! The `uts46` module provides the combination of mapping and normalization operations for [UTS #46: Unicode IDNA
32//! Compatibility Processing](https://www.unicode.org/reports/tr46/). This functionality is not meant to be used by
33//! applications directly. Instead, it is meant as a building block for a full implementation of UTS #46, such as the
34//! [`idna`](https://docs.rs/idna/latest/idna/) crate.
35//!
36//! The `properties` module provides the non-recursive canonical decomposition operation on a per `char` basis and
37//! the canonical compositon operation given two `char`s. It also provides access to the Canonical Combining Class
38//! property. These operations are primarily meant for [HarfBuzz](https://harfbuzz.github.io/), the types
39//! [`CanonicalComposition`](properties::CanonicalComposition), [`CanonicalDecomposition`](properties::CanonicalDecomposition),
40//! and [`CanonicalCombiningClassMap`](properties::CanonicalCombiningClassMap) implement the [`harfbuzz_traits`] if
41//! the `harfbuzz_traits` Cargo feature is enabled.
42//!
43//! Notably, this normalizer does _not_ provide the normalization “quick check” that can result in “maybe” in
44//! addition to “yes” and “no”. The normalization checks provided by this crate always give a definitive
45//! non-“maybe” answer.
46//!
47//! # Examples
48//!
49//! ```
50//! let nfc = icu_normalizer::ComposingNormalizerBorrowed::new_nfc();
51//! assert_eq!(nfc.normalize("a\u{0308}"), "ä");
52//! assert!(nfc.is_normalized("ä"));
53//!
54//! let nfd = icu_normalizer::DecomposingNormalizerBorrowed::new_nfd();
55//! assert_eq!(nfd.normalize("ä"), "a\u{0308}");
56//! assert!(!nfd.is_normalized("ä"));
57//! ```
58
59extern crate alloc;
60
61#[cfg(feature = "serde")]
62type Trie<'trie> = CodePointTrie<'trie, u32>;
63
64#[cfg(not(feature = "serde"))]
65type Trie<'trie> = FastCodePointTrie<'trie, u32>;
66
67type CombiningBuffer = SmallVec<[CharacterAndClass; 2]>;
68
69type CompositionTrie<'trie> = FastCodePointTrie<'trie, u16>;
70
71// We don't depend on icu_properties to minimize deps, but we want to be able
72// to ensure we're using the right CCC values
73macro_rules! ccc {
74    ($name:ident, $num:expr) => {
75        const {
76            #[cfg(feature = "icu_properties")]
77            if icu_properties::props::CanonicalCombiningClass::$name.to_icu4c_value() != $num {
78                panic!("icu_normalizer has incorrect ccc values")
79            }
80            CanonicalCombiningClass::from_icu4c_value($num)
81        }
82    };
83}
84
85#[cfg(feature = "harfbuzz_traits")]
86mod harfbuzz;
87#[cfg(feature = "latin1")]
88pub mod latin1;
89pub mod properties;
90pub mod provider;
91pub mod uts46;
92
93#[cfg(feature = "serde")]
94use crate::provider::CanonicalCompositions;
95use crate::provider::CanonicalCompositionsNew;
96use crate::provider::DecompositionData;
97use crate::provider::NormalizerNfdDataV1;
98use crate::provider::NormalizerNfkdDataV1;
99use crate::provider::NormalizerUts46DataV1;
100use alloc::borrow::Cow;
101use alloc::string::String;
102use core::char::REPLACEMENT_CHARACTER;
103use core::marker::PhantomData;
104#[cfg(feature = "serde")]
105use icu_collections::char16trie::Char16Trie;
106#[cfg(feature = "serde")]
107use icu_collections::char16trie::Char16TrieIterator;
108#[cfg(feature = "serde")]
109use icu_collections::char16trie::TrieResult;
110use icu_collections::codepointtrie::AbstractCodePointTrie;
111use icu_collections::codepointtrie::CharIterWithTrie;
112use icu_collections::codepointtrie::CharsWithTrieDefaultForAsciiEx;
113use icu_collections::codepointtrie::CodePointTrie;
114use icu_collections::codepointtrie::FastCodePointTrie;
115use icu_collections::codepointtrie::WithTrie;
116#[cfg(feature = "icu_properties")]
117use icu_properties::props::CanonicalCombiningClass;
118use icu_provider::prelude::*;
119use provider::DecompositionTables;
120#[cfg(feature = "serde")]
121use provider::NormalizerNfcV1;
122use provider::NormalizerNfcV2;
123use provider::NormalizerNfdTablesV1;
124use provider::NormalizerNfkdTablesV1;
125use smallvec::SmallVec;
126#[cfg(feature = "utf16_iter")]
127use utf16_iter::Utf16CharsWithTrieEx;
128#[cfg(feature = "utf8_iter")]
129use utf8_iter::Utf8CharsEx;
130#[cfg(feature = "utf8_iter")]
131use utf8_iter::Utf8CharsWithTrieDefaultForAsciiEx;
132use zerovec::{zeroslice, ZeroSlice};
133
134// The optimizations in the area where `likely` is used
135// are extremely brittle. `likely` is useful in the typed-trie
136// case on the UTF-16 fast path, but in order not to disturb
137// the untyped-trie case on the UTF-16 fast path, make the
138// annotations no-ops in the untyped-trie case.
139
140// `cold_path` and `likely` come from
141// https://github.com/rust-lang/hashbrown/commit/64bd7db1d1b148594edfde112cdb6d6260e2cfc3 .
142// See https://github.com/rust-lang/hashbrown/commit/64bd7db1d1b148594edfde112cdb6d6260e2cfc3#commitcomment-164768806
143// for permission to relicense under Unicode-3.0.
144
145#[cfg(not(feature = "serde"))]
146#[inline(always)]
147#[cold]
148fn cold_path() {}
149
150#[cfg(not(feature = "serde"))]
151#[inline(always)]
152pub(crate) fn likely(b: bool) -> bool {
153    if b {
154        true
155    } else {
156        cold_path();
157        false
158    }
159}
160
161#[cfg(not(feature = "serde"))]
162#[inline(always)]
163pub(crate) fn unlikely(b: bool) -> bool {
164    if b {
165        cold_path();
166        true
167    } else {
168        false
169    }
170}
171
172// End import from https://github.com/rust-lang/hashbrown/commit/64bd7db1d1b148594edfde112cdb6d6260e2cfc3 .
173
174/// No-op for typed trie case.
175#[cfg(feature = "serde")]
176#[inline(always)]
177fn likely(b: bool) -> bool {
178    b
179}
180
181/// No-op for typed trie case.
182#[cfg(feature = "serde")]
183#[inline(always)]
184fn unlikely(b: bool) -> bool {
185    b
186}
187
188/// This type exists as a shim for icu_properties CanonicalCombiningClass when the crate is disabled
189/// It should not be exposed to users.
190#[cfg(not(feature = "icu_properties"))]
191#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
192struct CanonicalCombiningClass(pub(crate) u8);
193
194#[cfg(not(feature = "icu_properties"))]
195impl CanonicalCombiningClass {
196    const fn from_icu4c_value(v: u8) -> Self {
197        Self(v)
198    }
199    const fn to_icu4c_value(self) -> u8 {
200        self.0
201    }
202}
203
204const CCC_NOT_REORDERED: CanonicalCombiningClass = ccc!(NotReordered, 0);
205const CCC_ABOVE: CanonicalCombiningClass = ccc!(Above, 230);
206
207/// Treatment of the ignorable marker (0xFFFFFFFF) in data.
208#[derive(Debug, PartialEq, Eq)]
209enum IgnorableBehavior {
210    /// 0xFFFFFFFF in data is not supported.
211    Unsupported,
212    /// Ignorables are ignored.
213    Ignored,
214    /// Ignorables are treated as singleton decompositions
215    /// to the REPLACEMENT CHARACTER.
216    ReplacementCharacter,
217}
218
219pub(crate) trait IteratorPolicy {
220    const IGNORABLE_BEHAVIOR: IgnorableBehavior;
221}
222
223#[derive(Debug)]
224struct Uax15Policy;
225
226impl IteratorPolicy for Uax15Policy {
227    const IGNORABLE_BEHAVIOR: IgnorableBehavior = IgnorableBehavior::Unsupported;
228}
229
230/// Marker for UTS 46 ignorables.
231///
232/// See trie-value-format.md
233const IGNORABLE_MARKER: u32 = 0xFFFFFFFF;
234
235/// Marker that the decomposition does not round trip via NFC.
236///
237/// See trie-value-format.md
238const NON_ROUND_TRIP_MARKER: u32 = 1 << 30;
239
240/// Marker that the first character of the decomposition
241/// can combine backwards.
242///
243/// See trie-value-format.md
244const BACKWARD_COMBINING_MARKER: u32 = 1 << 31;
245
246/// Mask for the bits have to be zero for this to be a BMP
247/// singleton decomposition, or value baked into the surrogate
248/// range.
249///
250/// See trie-value-format.md
251const HIGH_ZEROS_MASK: u32 = 0x3FFF0000;
252
253/// Mask for the bits have to be zero for this to be a complex
254/// decomposition.
255///
256/// See trie-value-format.md
257const LOW_ZEROS_MASK: u32 = 0xFFE0;
258
259/// Checks if a trie value carries a (non-zero) canonical
260/// combining class.
261///
262/// See trie-value-format.md
263fn trie_value_has_ccc(trie_value: u32) -> bool {
264    (trie_value & 0x3FFFFE00) == 0xD800
265}
266
267/// Checks if the trie signifies a special non-starter decomposition.
268///
269/// See trie-value-format.md
270fn trie_value_indicates_special_non_starter_decomposition(trie_value: u32) -> bool {
271    (trie_value & 0x3FFFFF00) == 0xD900
272}
273
274/// Checks if the trie signifies a non-decomposing non-starter.
275///
276/// See trie-value-format.md
277fn trie_value_indicates_non_decomposing_non_starter(trie_value: u32) -> bool {
278    (trie_value & 0x3FFFFF00) == 0xD800
279}
280
281/// Checks if a trie value signifies a character whose decomposition
282/// starts with a non-starter.
283///
284/// See trie-value-format.md
285fn decomposition_starts_with_non_starter(trie_value: u32) -> bool {
286    trie_value_has_ccc(trie_value)
287}
288
289/// Extracts a canonical combining class (possibly zero) from a trie value.
290///
291/// See trie-value-format.md
292fn ccc_from_trie_value(trie_value: u32) -> CanonicalCombiningClass {
293    if trie_value_has_ccc(trie_value) {
294        CanonicalCombiningClass::from_icu4c_value(trie_value as u8)
295    } else {
296        CCC_NOT_REORDERED
297    }
298}
299
300/// The tail (everything after the first character) of the NFKD form U+FDFA
301/// as 16-bit units.
302static FDFA_NFKD: [u16; 17] = [
303    0x644, 0x649, 0x20, 0x627, 0x644, 0x644, 0x647, 0x20, 0x639, 0x644, 0x64A, 0x647, 0x20, 0x648,
304    0x633, 0x644, 0x645,
305];
306
307/// Marker value for U+FDFA in NFKD. (Unified with Hangul syllable marker,
308/// but they differ by `NON_ROUND_TRIP_MARKER`.)
309///
310/// See trie-value-format.md
311const FDFA_MARKER: u16 = 1;
312
313// These constants originate from page 143 of Unicode 14.0
314/// Syllable base
315const HANGUL_S_BASE: u32 = 0xAC00;
316/// Lead jamo base
317const HANGUL_L_BASE: u32 = 0x1100;
318/// Vowel jamo base
319const HANGUL_V_BASE: u32 = 0x1161;
320/// Trail jamo base (deliberately off by one to account for the absence of a trail)
321const HANGUL_T_BASE: u32 = 0x11A7;
322/// Lead jamo count
323const HANGUL_L_COUNT: u32 = 19;
324/// Vowel jamo count
325const HANGUL_V_COUNT: u32 = 21;
326/// Trail jamo count (deliberately off by one to account for the absence of a trail)
327const HANGUL_T_COUNT: u32 = 28;
328/// Vowel jamo count times trail jamo count
329const HANGUL_N_COUNT: u32 = 588;
330/// Syllable count
331const HANGUL_S_COUNT: u32 = 11172;
332/// One past the conjoining jamo block
333#[cfg(feature = "serde")]
334const HANGUL_JAMO_LIMIT: u32 = 0x1200;
335/// Trie value base corresponding for L
336const HANGUL_L_TRIE_VAL_BASE: u16 = 0xD6A7;
337
338/// If `opt` is `Some`, unwrap it. If `None`, panic if debug assertions
339/// are enabled and return `default` if debug assertions are not enabled.
340///
341/// Use this only if the only reason why `opt` could be `None` is bogus
342/// data from the provider.
343#[inline(always)]
344fn unwrap_or_gigo<T>(opt: Option<T>, default: T) -> T {
345    if let Some(val) = opt {
346        val
347    } else {
348        // GIGO case
349        debug_assert!(false);
350        default
351    }
352}
353
354/// Convert a `u32` _obtained from data provider data_ to `char`.
355#[inline(always)]
356fn char_from_u32(u: u32) -> char {
357    unwrap_or_gigo(core::char::from_u32(u), REPLACEMENT_CHARACTER)
358}
359
360/// Convert a `u16` _obtained from data provider data_ to `char`.
361#[inline(always)]
362fn char_from_u16(u: u16) -> char {
363    char_from_u32(u32::from(u))
364}
365
366const EMPTY_U16: &ZeroSlice<u16> = zeroslice![];
367
368const EMPTY_CHAR: &ZeroSlice<char> = zeroslice![];
369
370#[inline(always)]
371fn in_inclusive_range(c: char, start: char, end: char) -> bool {
372    u32::from(c).wrapping_sub(u32::from(start)) <= (u32::from(end) - u32::from(start))
373}
374
375#[inline(always)]
376#[cfg(feature = "utf16_iter")]
377fn in_inclusive_range16(u: u16, start: u16, end: u16) -> bool {
378    u.wrapping_sub(start) <= (end - start)
379}
380
381#[derive(Debug)]
382pub(crate) enum CanonicalCompositionsPayload {
383    Current(DataPayload<NormalizerNfcV2>),
384    #[cfg(feature = "serde")]
385    Legacy(DataPayload<NormalizerNfcV1>),
386}
387
388impl<'data> CanonicalCompositionsPayload {
389    pub(crate) fn as_borrowed(&'data self) -> CanonicalCompositionsBorrowed<'data> {
390        match self {
391            CanonicalCompositionsPayload::Current(data_payload) => {
392                CanonicalCompositionsBorrowed::Current(data_payload.get())
393            }
394            #[cfg(feature = "serde")]
395            CanonicalCompositionsPayload::Legacy(data_payload) => {
396                CanonicalCompositionsBorrowed::Legacy(data_payload.get())
397            }
398        }
399    }
400}
401
402#[derive(Debug, Copy, Clone)]
403pub(crate) enum CanonicalCompositionsBorrowed<'data> {
404    Current(&'data CanonicalCompositionsNew<'data>),
405    #[cfg(feature = "serde")]
406    Legacy(&'data CanonicalCompositions<'data>),
407}
408
409impl CanonicalCompositionsBorrowed<'static> {
410    pub(crate) const fn static_to_owned(self) -> CanonicalCompositionsPayload {
411        match self {
412            CanonicalCompositionsBorrowed::Current(s) => {
413                CanonicalCompositionsPayload::Current(DataPayload::from_static_ref(s))
414            }
415            #[cfg(feature = "serde")]
416            CanonicalCompositionsBorrowed::Legacy(s) => {
417                CanonicalCompositionsPayload::Legacy(DataPayload::from_static_ref(s))
418            }
419        }
420    }
421}
422
423impl<'data> CanonicalCompositionsBorrowed<'data> {
424    pub(crate) fn as_ref(&'data self) -> CanonicalCompositionsRef<'data> {
425        match self {
426            CanonicalCompositionsBorrowed::Current(s) => CanonicalCompositionsRef::Current(
427                <&CompositionTrie<'data>>::try_from(&s.trie)
428                    .unwrap_or_else(|_| unreachable!("Incompatible data")),
429                &s.linear16,
430                &s.linear24,
431            ),
432            #[cfg(feature = "serde")]
433            CanonicalCompositionsBorrowed::Legacy(s) => {
434                CanonicalCompositionsRef::Legacy(s.canonical_compositions.clone())
435            }
436        }
437    }
438}
439
440#[derive(Debug)]
441pub(crate) enum CanonicalCompositionsRef<'data> {
442    Current(
443        &'data CompositionTrie<'data>,
444        &'data ZeroSlice<(u16, u16)>,
445        &'data ZeroSlice<(char, char)>,
446    ),
447    #[cfg(feature = "serde")]
448    Legacy(Char16Trie<'data>),
449}
450
451impl<'data> CanonicalCompositionsRef<'data> {
452    /// Performs canonical composition (including Hangul) on a pair of
453    /// characters or returns `None` if these characters don't compose.
454    /// Composition exclusions are taken into account.
455    ///
456    /// TODO: Have the caller retain more state and have this function return
457    /// more information that is useful for retaining information between
458    /// attempts to compose in a sequence of such attempts:
459    ///
460    /// * We can return the linear search slice when we search through it but don't find anything.
461    /// * We can know that no further matches are possible.
462    /// * We can know that the starter was a special ASCII vowel.
463    /// * We can know that we just formed a Hangul LV syllable.
464    pub(crate) fn compose(&self, starter: char, second: char) -> Option<char> {
465        match self {
466            CanonicalCompositionsRef::Current(trie, linear16, linear24) => {
467                // According to Compiler Explorer, the `match` optimizes to a bitfield lookup.
468                // Don't bother optimizing manually without inspecting the generated assembly.
469                let (primary, secondary) = match starter {
470                    'a' | 'e' | 'i' | 'o' | 'u' | 'A' | 'E' | 'I' | 'O' | 'U' => {
471                        // This special case balances out the max length of entries
472                        // in `linear` so that no entry exceeds 10 items as of Unicode 17.
473                        (second, starter)
474                    }
475                    _ => (starter, second),
476                };
477                let packed = trie.scalar(primary);
478                let len = usize::from(packed & 0b1111);
479                let index = usize::from(packed >> 4);
480                if let Some(slice16) = linear16.get_subslice(index..index + len) {
481                    let secondary32 = u32::from(secondary);
482                    for (candidate, composed) in slice16.iter() {
483                        if u32::from(candidate) == secondary32 {
484                            return Some(char_from_u16(composed));
485                        }
486                    }
487                    return None;
488                }
489
490                if packed < 0b1000_0000_0000_0000 {
491                    debug_assert_eq!(packed, 0b0111_1111_1111_1111);
492                    return None;
493                }
494
495                // Mask off the bit that was the most-significant bit in `u16` before we
496                // shifted right by 4.
497                let index = index & 0b1_11111_11111; // 11 bits set
498                if let Some(slice24) = linear24.get_subslice(index..index + len) {
499                    for (candidate, composed) in slice24.iter() {
500                        if candidate == secondary {
501                            return Some(composed);
502                        }
503                    }
504                    return None;
505                }
506
507                // Handle Hangul L after non-BMP, because HarfBuzz isn't actually supposed
508                // to exercise this case and in the normalizer itself, we come here only
509                // in NFKC in the case of enclosed Hangul.
510                if packed >= HANGUL_L_TRIE_VAL_BASE {
511                    // If the debug asserts fail, we have a GIGO case.
512                    debug_assert!(u32::from(primary).wrapping_sub(HANGUL_L_BASE) < HANGUL_L_COUNT);
513                    debug_assert_eq!(
514                        u32::from(packed - HANGUL_L_TRIE_VAL_BASE),
515                        u32::from(primary).wrapping_sub(HANGUL_L_BASE) * HANGUL_N_COUNT
516                    );
517
518                    let v = u32::from(second).wrapping_sub(HANGUL_V_BASE);
519                    if v < HANGUL_V_COUNT {
520                        // Acconding to Compiler Explorer, multiplication by `HANGUL_T_COUNT`
521                        // optimizes to not actually using a multiplication instruction.
522                        let lv = u32::from(packed - HANGUL_L_TRIE_VAL_BASE) + v * HANGUL_T_COUNT;
523                        // SAFETY: Safe, because the inputs are known to be in range. Notably
524                        // packed cannot have been above 0xFFFF, since it came from `u16`.
525                        // That is, this must be in scalar value range. However, the result
526                        // can still be GIGO if the trie value does not contain the right value
527                        // within its possible range, in which case either of the above debug
528                        // assertions should fail.
529                        return Some(unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lv) });
530                    }
531                    return None;
532                }
533
534                // `starter` is Hangul LV unless GIGO. If the debug asserts fail, we have a GIGO case.
535                debug_assert!(u32::from(primary).wrapping_sub(HANGUL_S_BASE) < HANGUL_S_COUNT);
536                debug_assert_eq!(
537                    u32::from(primary).wrapping_sub(HANGUL_S_BASE) % HANGUL_T_COUNT,
538                    0
539                );
540                if in_inclusive_range(secondary, '\u{11A8}', '\u{11C2}') {
541                    let lvt = u32::from(primary) + (u32::from(secondary) - HANGUL_T_BASE);
542                    if lvt < 0xD800 {
543                        // SAFETY: Immediately above we checked that `c32` is below the surrogate
544                        // range. (Not using `char::from_u32` itself as a micro optimization.)
545                        // This is only a check about the safe `char` range. The result could
546                        // still be GIGO, wich which case either of the above debug assertions
547                        // should fail.
548                        return Some(unsafe { char::from_u32_unchecked(lvt) });
549                    } else {
550                        // GIGO
551                        // Asserting `false`, although either of the above two debug assertions
552                        // should already have caught this case.
553                        debug_assert!(false);
554                    }
555                }
556                None
557            }
558            #[cfg(feature = "serde")]
559            CanonicalCompositionsRef::Legacy(char16_trie) => {
560                Self::compose_legacy(char16_trie.iter(), starter, second)
561            }
562        }
563    }
564
565    /// Performs canonical composition (including Hangul) on a pair of
566    /// characters on the assumption that the second one is a starter
567    /// or returns `None` if these characters don't compose.
568    /// Composition exclusions are taken into account.
569    ///
570    /// The returned boolean can be true only if `char` a Hangul LV syllable.
571    ///
572    /// The argument `starter_is_lv` must be set either to false or to the value
573    /// that this method previously returned alongside `starter`.
574    pub(crate) fn compose_starter(
575        &self,
576        starter: char,
577        second: char,
578        starter_is_lv: bool,
579    ) -> Option<(char, bool)> {
580        if starter_is_lv {
581            debug_assert!(u32::from(starter).wrapping_sub(HANGUL_S_BASE) < HANGUL_S_COUNT);
582            debug_assert_eq!(
583                u32::from(starter).wrapping_sub(HANGUL_S_BASE) % HANGUL_T_COUNT,
584                0
585            );
586            if in_inclusive_range(second, '\u{11A8}', '\u{11C2}') {
587                // We take the perf hit of checking the returned character for range
588                // even though we could omit the check if we trusted 100% that the
589                // other code has no mistakes regarding the stated required semantics
590                // of `starter_is_lv`.
591                return Some((
592                    char_from_u32(u32::from(starter) + (u32::from(second) - HANGUL_T_BASE)),
593                    false,
594                ));
595            }
596            return None;
597        }
598        match self {
599            CanonicalCompositionsRef::Current(trie, linear16, linear24) => {
600                // We assume that future versions of Unicode won't introduce starters
601                // that would compose with ASCII vowels.
602                let primary = starter;
603                let secondary = second;
604                let packed = trie.scalar(primary);
605
606                if packed >= HANGUL_L_TRIE_VAL_BASE {
607                    // If the debug asserts fail, we have a GIGO case.
608                    debug_assert!(u32::from(primary).wrapping_sub(HANGUL_L_BASE) < HANGUL_L_COUNT);
609                    debug_assert_eq!(
610                        u32::from(packed - HANGUL_L_TRIE_VAL_BASE),
611                        u32::from(primary).wrapping_sub(HANGUL_L_BASE) * HANGUL_N_COUNT
612                    );
613
614                    let v = u32::from(second).wrapping_sub(HANGUL_V_BASE);
615                    if v < HANGUL_V_COUNT {
616                        // Acconding to Compiler Explorer, multiplication by `HANGUL_T_COUNT`
617                        // optimizes to not actually using a multiplication instruction.
618                        let lv = u32::from(packed - HANGUL_L_TRIE_VAL_BASE) + v * HANGUL_T_COUNT;
619                        // SAFETY: Safe, because the inputs are known to be in range. Notably
620                        // packed cannot have been above 0xFFFF, since it came from `u16`.
621                        // That is, this must be in scalar value range. However, the result
622                        // can still be GIGO if the trie value does not contain the right value
623                        // within its possible range, in which case either of the above debug
624                        // assertions should fail.
625                        return Some((
626                            unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lv) },
627                            true,
628                        ));
629                    }
630                    return None;
631                }
632
633                // Putting the Hangul case above, because in NFD Hangul the above case
634                // happens with each syllable whereas with the languages for which the
635                // case immediately below is relevant, the case occurs only once in a
636                // while.
637
638                let len = usize::from(packed & 0b1111);
639                let index = usize::from(packed >> 4);
640                if let Some(slice16) = linear16.get_subslice(index..index + len) {
641                    let secondary32 = u32::from(secondary);
642                    for (candidate, composed) in slice16.iter() {
643                        if u32::from(candidate) == secondary32 {
644                            return Some((char_from_u16(composed), false));
645                        }
646                    }
647                    return None;
648                }
649
650                if packed < 0b1000_0000_0000_0000 {
651                    debug_assert_eq!(packed, 0b0111_1111_1111_1111);
652                    return None;
653                }
654                // Mask off the bit that was the most-significant bit in `u16` before we
655                // shifted right by 4.
656                let index = index & 0b1_11111_11111; // 11 bits set
657                if let Some(slice24) = linear24.get_subslice(index..index + len) {
658                    for (candidate, composed) in slice24.iter() {
659                        if candidate == secondary {
660                            return Some((composed, false));
661                        }
662                    }
663                    return None;
664                }
665                // `starter` is Hangul LV unless GIGO. If the debug asserts fail, we have a GIGO case.
666                debug_assert!(u32::from(primary).wrapping_sub(HANGUL_S_BASE) < HANGUL_S_COUNT);
667                debug_assert_eq!(
668                    u32::from(primary).wrapping_sub(HANGUL_S_BASE) % HANGUL_T_COUNT,
669                    0
670                );
671                if in_inclusive_range(secondary, '\u{11A8}', '\u{11C2}') {
672                    let lvt = u32::from(primary) + (u32::from(secondary) - HANGUL_T_BASE);
673                    if lvt < 0xD800 {
674                        // SAFETY: Immediately above we checked that `c32` is below the surrogate
675                        // range. (Not using `char::from_u32` itself as a micro optimization.)
676                        // This is only a check about the safe `char` range. The result could
677                        // still be GIGO, wich which case either of the above debug assertions
678                        // should fail.
679                        return Some((unsafe { char::from_u32_unchecked(lvt) }, false));
680                    } else {
681                        // GIGO
682                        // Asserting `false`, although either of the above two debug assertions
683                        // should already have caught this case.
684                        debug_assert!(false);
685                    }
686                }
687                None
688            }
689            #[cfg(feature = "serde")]
690            CanonicalCompositionsRef::Legacy(char16_trie) => {
691                Self::compose_legacy(char16_trie.iter(), starter, second).map(|c| (c, false))
692            }
693        }
694    }
695
696    #[cfg(feature = "serde")]
697    #[cold]
698    #[inline(never)]
699    fn compose_legacy(mut iter: Char16TrieIterator, starter: char, second: char) -> Option<char> {
700        let v = u32::from(second).wrapping_sub(HANGUL_V_BASE);
701        if v >= HANGUL_JAMO_LIMIT - HANGUL_V_BASE {
702            // To make the trie smaller, the pairs are stored second character first.
703            // Given how this method is used in ways where it's known that `second`
704            // is or isn't a starter. We could potentially split the trie into two
705            // tries depending on whether `second` is a starter.
706            match iter.next(second) {
707                TrieResult::NoMatch => None,
708                TrieResult::NoValue => match iter.next(starter) {
709                    TrieResult::NoMatch => None,
710                    TrieResult::FinalValue(i) => {
711                        if let Some(c) = char::from_u32(i as u32) {
712                            Some(c)
713                        } else {
714                            // GIGO case
715                            debug_assert!(false);
716                            None
717                        }
718                    }
719                    TrieResult::NoValue | TrieResult::Intermediate(_) => {
720                        // GIGO case
721                        debug_assert!(false);
722                        None
723                    }
724                },
725                TrieResult::FinalValue(_) | TrieResult::Intermediate(_) => {
726                    // GIGO case
727                    debug_assert!(false);
728                    None
729                }
730            }
731        } else {
732            if v < HANGUL_V_COUNT {
733                let l = u32::from(starter).wrapping_sub(HANGUL_L_BASE);
734                if l < HANGUL_L_COUNT {
735                    let lv = l * HANGUL_N_COUNT + v * HANGUL_T_COUNT;
736                    // Safe, because the inputs are known to be in range.
737                    return Some(unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lv) });
738                }
739                return None;
740            }
741            if in_inclusive_range(second, '\u{11A8}', '\u{11C2}') {
742                let lv = u32::from(starter).wrapping_sub(HANGUL_S_BASE);
743                if lv < HANGUL_S_COUNT && lv % HANGUL_T_COUNT == 0 {
744                    let lvt = lv + (u32::from(second) - HANGUL_T_BASE);
745                    // Safe, because the inputs are known to be in range.
746                    return Some(unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lvt) });
747                }
748            }
749            None
750        }
751    }
752}
753
754/// See trie-value-format.md
755#[inline(always)]
756fn starter_and_decomposes_to_self_impl(trie_val: u32) -> bool {
757    // The REPLACEMENT CHARACTER has `NON_ROUND_TRIP_MARKER` set,
758    // and this function needs to ignore that.
759    (trie_val & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0
760}
761
762/// See trie-value-format.md
763#[inline(always)]
764#[cfg(feature = "utf8_iter")]
765pub fn starter_and_decomposes_to_self_except_replacement(trie_val: u32) -> bool {
766    // This intentionally leaves `NON_ROUND_TRIP_MARKER` in the value
767    // to be compared with zero. U+FFFD has that flag set despite really
768    // being being round-tripping in order to make UTF-8 errors
769    // ineligible for passthrough.
770    (trie_val & !BACKWARD_COMBINING_MARKER) == 0
771}
772
773/// See trie-value-format.md
774#[inline(always)]
775fn potential_passthrough_and_cannot_combine_backwards(trie_val: u32) -> bool {
776    (trie_val & (NON_ROUND_TRIP_MARKER | BACKWARD_COMBINING_MARKER)) == 0
777}
778
779/// Struct for holding together a character and the value
780/// looked up for it from the NFD trie in a more explicit
781/// way than an anonymous pair.
782/// Also holds a flag about the supplementary-trie provenance.
783#[derive(Debug, PartialEq, Eq)]
784struct CharacterAndTrieValue {
785    character: char,
786    /// See trie-value-format.md
787    trie_val: u32,
788}
789
790impl CharacterAndTrieValue {
791    #[inline(always)]
792    pub fn new(c: char, trie_value: u32) -> Self {
793        CharacterAndTrieValue {
794            character: c,
795            trie_val: trie_value,
796        }
797    }
798
799    #[inline(always)]
800    pub fn starter_and_decomposes_to_self(&self) -> bool {
801        starter_and_decomposes_to_self_impl(self.trie_val)
802    }
803
804    /// See trie-value-format.md
805    #[inline(always)]
806    pub fn can_combine_backwards(&self) -> bool {
807        (self.trie_val & BACKWARD_COMBINING_MARKER) != 0
808    }
809    /// See trie-value-format.md
810    #[inline(always)]
811    pub fn potential_passthrough(&self) -> bool {
812        (self.trie_val & NON_ROUND_TRIP_MARKER) == 0
813    }
814}
815
816/// Pack a `char` and a `CanonicalCombiningClass` in
817/// 32 bits (the former in the lower 24 bits and the
818/// latter in the high 8 bits). The latter can be
819/// initialized to 0xFF upon creation, in which case
820/// it can be actually set later by calling
821/// `set_ccc_from_trie_if_not_already_set`. This is
822/// a micro optimization to avoid the Canonical
823/// Combining Class trie lookup when there is only
824/// one combining character in a sequence. This type
825/// is intentionally non-`Copy` to get compiler help
826/// in making sure that the class is set on the
827/// instance on which it is intended to be set
828/// and not on a temporary copy.
829///
830/// Note that 0xFF is won't be assigned to an actual
831/// canonical combining class per definition D104
832/// in The Unicode Standard.
833//
834// NOTE: The Pernosco debugger has special knowledge
835// of this struct. Please do not change the bit layout
836// or the crate-module-qualified name of this struct
837// without coordination.
838#[derive(Debug)]
839struct CharacterAndClass(u32);
840
841impl<'data> CharacterAndClass {
842    pub fn new(c: char, ccc: CanonicalCombiningClass) -> Self {
843        CharacterAndClass(u32::from(c) | (u32::from(ccc.to_icu4c_value()) << 24))
844    }
845    pub fn new_with_placeholder(c: char) -> Self {
846        CharacterAndClass(u32::from(c) | ((0xFF) << 24))
847    }
848    pub fn new_with_trie_value(c_tv: CharacterAndTrieValue) -> Self {
849        Self::new(c_tv.character, ccc_from_trie_value(c_tv.trie_val))
850    }
851    pub fn new_starter(c: char) -> Self {
852        CharacterAndClass(u32::from(c))
853    }
854    /// This method must exist for Pernosco to apply its special rendering.
855    /// Also, this must not be dead code!
856    pub fn character(&self) -> char {
857        // SAFETY: Safe, because the low 24 bits came from a `char`
858        // originally.
859        unsafe { char::from_u32_unchecked(self.0 & 0xFFFFFF) }
860    }
861    /// This method must exist for Pernosco to apply its special rendering.
862    pub fn ccc(&self) -> CanonicalCombiningClass {
863        CanonicalCombiningClass::from_icu4c_value((self.0 >> 24) as u8)
864    }
865
866    pub fn character_and_ccc(&self) -> (char, CanonicalCombiningClass) {
867        (self.character(), self.ccc())
868    }
869    pub fn set_ccc_from_trie_if_not_already_set<T: AbstractCodePointTrie<'data, u32>>(
870        &mut self,
871        trie: &'data T,
872    ) {
873        if self.0 >> 24 != 0xFF {
874            return;
875        }
876        let scalar = self.0 & 0xFFFFFF;
877        // SAFETY: Safe, because the low 24 bits came from a `char`
878        // originally.
879        self.0 = ((ccc_from_trie_value(trie.scalar(unsafe { char::from_u32_unchecked(scalar) }))
880            .to_icu4c_value() as u32)
881            << 24)
882            | scalar;
883    }
884}
885
886/// An iterator adaptor that turns an `Iterator` over `char` into
887/// a lazily-decomposed `char` sequence.
888#[derive(Debug)]
889pub struct Decomposition<'data, I>
890where
891    I: Iterator<Item = char>,
892{
893    inner: DecompositionInner<
894        'data,
895        CharIterWithTrie<'data, Trie<'data>, u32, I>,
896        Trie<'data>,
897        Uax15Policy,
898    >,
899}
900
901impl<'data, I> Decomposition<'data, I>
902where
903    I: Iterator<Item = char>,
904{
905    /// Constructs a decomposing iterator adapter from a delegate
906    /// iterator and references to the necessary data, without
907    /// supplementary data.
908    ///
909    /// Use `DecomposingNormalizer::normalize_iter()` instead unless
910    /// there's a good reason to use this constructor directly.
911    ///
912    /// Public but hidden in order to be able to use this from the
913    /// collator.
914    #[doc(hidden)] // used in older versions of collator
915    #[deprecated = "Use `new_decomposition()` instead"]
916    pub fn new(
917        delegate: I,
918        decompositions: &'data DecompositionData,
919        tables: &'data DecompositionTables,
920    ) -> Self {
921        let mut ret = Self {
922            inner: DecompositionInner::new_with_supplements(
923                CharIterWithTrie::new(
924                    delegate,
925                    #[allow(clippy::useless_conversion)]
926                    <&Trie<'data>>::try_from(&decompositions.trie)
927                        .unwrap_or_else(|_| unreachable!("Incompatible data")),
928                ),
929                tables,
930                None,
931            ),
932        };
933        let _ = ret.next();
934        ret
935    }
936}
937
938impl<I> Iterator for Decomposition<'_, I>
939where
940    I: Iterator<Item = char>,
941{
942    type Item = char;
943
944    #[inline]
945    fn next(&mut self) -> Option<char> {
946        self.inner.next()
947    }
948}
949
950/// The iterator first yields an extra U+FFFD and then
951/// the sequence actually corresponding to the input.
952#[doc(hidden)] // used in collator
953#[inline(always)]
954pub fn new_decomposition<'data, I, T>(
955    delegate: I,
956    tables: &'data DecompositionTables,
957) -> impl Iterator<Item = char> + 'data
958where
959    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
960    T: AbstractCodePointTrie<'data, u32> + 'data,
961{
962    DecompositionInner::<'data, I, T, Uax15Policy>::new_with_supplements(delegate, tables, None)
963}
964
965#[derive(Debug)]
966struct DecompositionInner<'data, I, T, P>
967where
968    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
969    T: AbstractCodePointTrie<'data, u32>,
970    P: IteratorPolicy,
971{
972    // See trie-value-format.md for the trie wrapped in `delegate`
973    delegate: I,
974    buffer: CombiningBuffer,
975    /// The index of the next item to be read from `buffer`.
976    /// The purpose if this index is to avoid having to move
977    /// the rest upon every read.
978    buffer_pos: usize,
979    // At the start of `next()` if not `None`, this is a pending unnormalized
980    // starter. When `Decomposition` appears alone, this is never a non-starter.
981    // However, when `Decomposition` appears inside a `Composition`, this
982    // may become a non-starter before `decomposing_next()` is called.
983    pending: Option<CharacterAndTrieValue>, // None at end of stream
984    scalars16: &'data ZeroSlice<u16>,
985    scalars24: &'data ZeroSlice<char>,
986    supplementary_scalars16: &'data ZeroSlice<u16>,
987    supplementary_scalars24: &'data ZeroSlice<char>,
988    _phantom_p: PhantomData<P>,
989    _phantom_t: PhantomData<T>,
990}
991
992impl<'data, I, T, P> DecompositionInner<'data, I, T, P>
993where
994    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
995    T: AbstractCodePointTrie<'data, u32> + 'data,
996    P: IteratorPolicy,
997{
998    /// Constructs a decomposing iterator adapter from a delegate
999    /// iterator and references to the necessary data, including
1000    /// supplementary data.
1001    ///
1002    /// The iterator first yields a U+0000 and only then the sequence
1003    /// corresponding to the input. Unfortunately, due to the way
1004    /// stack placement of structs work in Rust, the caller is responsible
1005    /// for dealing with the initial U+0000. Alternatively, callers in this
1006    /// crate file can (and should) call `init()`.
1007    #[inline(always)]
1008    fn new_with_supplements(
1009        delegate: I,
1010        tables: &'data DecompositionTables,
1011        supplementary_tables: Option<&'data DecompositionTables>,
1012    ) -> Self {
1013        DecompositionInner::<I, T, P> {
1014            delegate,
1015            buffer: SmallVec::new(), // Normalized
1016            buffer_pos: 0,
1017            // Initialize with a placeholder starter in case
1018            // the real stream starts with a non-starter.
1019            pending: Some(CharacterAndTrieValue::new('\u{0}', 0)),
1020            scalars16: &tables.scalars16,
1021            scalars24: &tables.scalars24,
1022            supplementary_scalars16: if let Some(supplementary) = supplementary_tables {
1023                &supplementary.scalars16
1024            } else {
1025                EMPTY_U16
1026            },
1027            supplementary_scalars24: if let Some(supplementary) = supplementary_tables {
1028                &supplementary.scalars24
1029            } else {
1030                EMPTY_CHAR
1031            },
1032            _phantom_p: PhantomData,
1033            _phantom_t: PhantomData,
1034        }
1035    }
1036
1037    /// Simplified alternative to calling `next()` and discarding the value after constructing this struct.
1038    fn init(&mut self) {
1039        self.pending = None;
1040        self.gather_and_sort_combining(0);
1041    }
1042
1043    fn push_decomposition16(
1044        &mut self,
1045        offset: usize,
1046        len: usize,
1047        only_non_starters_in_trail: bool,
1048        slice16: &ZeroSlice<u16>,
1049    ) -> (char, usize) {
1050        let (starter, tail) = slice16
1051            .get_subslice(offset..offset + len)
1052            .and_then(|slice| slice.split_first())
1053            .map_or_else(
1054                || {
1055                    // GIGO case
1056                    debug_assert!(false);
1057                    (REPLACEMENT_CHARACTER, EMPTY_U16)
1058                },
1059                |(first, trail)| (char_from_u16(first), trail),
1060            );
1061        if only_non_starters_in_trail {
1062            // All the rest are combining
1063            self.buffer.extend(
1064                tail.iter()
1065                    .map(|u| CharacterAndClass::new_with_placeholder(char_from_u16(u))),
1066            );
1067            (starter, 0)
1068        } else {
1069            let mut i = 0;
1070            let mut combining_start = 0;
1071            for u in tail.iter() {
1072                let ch = char_from_u16(u);
1073                let trie_value = self.delegate.trie().scalar(ch);
1074                self.buffer.push(CharacterAndClass::new_with_trie_value(
1075                    CharacterAndTrieValue::new(ch, trie_value),
1076                ));
1077                i += 1;
1078                // Half-width kana and iota subscript don't occur in the tails
1079                // of these multicharacter decompositions.
1080                if !decomposition_starts_with_non_starter(trie_value) {
1081                    combining_start = i;
1082                }
1083            }
1084            (starter, combining_start)
1085        }
1086    }
1087
1088    fn push_decomposition32(
1089        &mut self,
1090        offset: usize,
1091        len: usize,
1092        only_non_starters_in_trail: bool,
1093        slice32: &ZeroSlice<char>,
1094    ) -> (char, usize) {
1095        let (starter, tail) = slice32
1096            .get_subslice(offset..offset + len)
1097            .and_then(|slice| slice.split_first())
1098            .unwrap_or_else(|| {
1099                // GIGO case
1100                debug_assert!(false);
1101                (REPLACEMENT_CHARACTER, EMPTY_CHAR)
1102            });
1103        if only_non_starters_in_trail {
1104            // All the rest are combining
1105            self.buffer
1106                .extend(tail.iter().map(CharacterAndClass::new_with_placeholder));
1107            (starter, 0)
1108        } else {
1109            let mut i = 0;
1110            let mut combining_start = 0;
1111            for ch in tail.iter() {
1112                let trie_value = self.delegate.trie().scalar(ch);
1113                self.buffer.push(CharacterAndClass::new_with_trie_value(
1114                    CharacterAndTrieValue::new(ch, trie_value),
1115                ));
1116                i += 1;
1117                // Half-width kana and iota subscript don't occur in the tails
1118                // of these multicharacter decompositions.
1119                if !decomposition_starts_with_non_starter(trie_value) {
1120                    combining_start = i;
1121                }
1122            }
1123            (starter, combining_start)
1124        }
1125    }
1126
1127    fn delegate_next_no_pending(&mut self) -> Option<CharacterAndTrieValue> {
1128        debug_assert!(self.pending.is_none());
1129        loop {
1130            let (c, trie_val) = self.delegate.next()?;
1131
1132            if trie_val == IGNORABLE_MARKER {
1133                match P::IGNORABLE_BEHAVIOR {
1134                    IgnorableBehavior::Unsupported => {
1135                        debug_assert!(false);
1136                    }
1137                    IgnorableBehavior::ReplacementCharacter => {
1138                        return Some(CharacterAndTrieValue::new(
1139                            c,
1140                            u32::from(REPLACEMENT_CHARACTER) | NON_ROUND_TRIP_MARKER,
1141                        ));
1142                    }
1143                    IgnorableBehavior::Ignored => {
1144                        // Else ignore this character by reading the next one from the delegate.
1145                        continue;
1146                    }
1147                }
1148            }
1149            return Some(CharacterAndTrieValue::new(c, trie_val));
1150        }
1151    }
1152
1153    fn delegate_next(&mut self) -> Option<CharacterAndTrieValue> {
1154        if let Some(pending) = self.pending.take() {
1155            // Only happens as part of `Composition` and as part of
1156            // the contiguous-buffer methods of `DecomposingNormalizer`.
1157            // I.e. does not happen as part of standalone iterator
1158            // usage of `Decomposition`.
1159            Some(pending)
1160        } else {
1161            self.delegate_next_no_pending()
1162        }
1163    }
1164
1165    fn decomposing_next(&mut self, c_and_trie_val: CharacterAndTrieValue) -> char {
1166        let (starter, combining_start) = {
1167            let c = c_and_trie_val.character;
1168            // See trie-value-format.md
1169            let decomposition = c_and_trie_val.trie_val;
1170            // The REPLACEMENT CHARACTER has `NON_ROUND_TRIP_MARKER` set,
1171            // and that flag needs to be ignored here.
1172            if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1173                // The character is its own decomposition
1174                (c, 0)
1175            } else {
1176                let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1177                let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1178                if !high_zeros && !low_zeros {
1179                    // Decomposition into two BMP characters: starter and non-starter
1180                    let starter = char_from_u32(decomposition & 0x7FFF);
1181                    let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
1182                    self.buffer
1183                        .push(CharacterAndClass::new_with_placeholder(combining));
1184                    (starter, 0)
1185                } else if high_zeros {
1186                    // Do the check by looking at `c` instead of looking at a marker
1187                    // in `singleton` below, because if we looked at the trie value,
1188                    // we'd still have to check that `c` is in the Hangul syllable
1189                    // range in order for the subsequent interpretations as `char`
1190                    // to be safe.
1191                    // Alternatively, `FDFA_MARKER` and the Hangul marker could
1192                    // be unified. That would add a branch for Hangul and remove
1193                    // a branch from singleton decompositions. It seems more
1194                    // important to favor Hangul syllables than singleton
1195                    // decompositions.
1196                    // Note that it would be valid to hoist this Hangul check
1197                    // one or even two steps earlier in this check hierarchy.
1198                    // Right now, it's assumed the kind of decompositions into
1199                    // BMP starter and non-starter, which occur in many languages,
1200                    // should be checked before Hangul syllables, which are about
1201                    // one language specifically. Hopefully, we get some
1202                    // instruction-level parallelism out of the disjointness of
1203                    // operations on `c` and `decomposition`.
1204                    let hangul_offset = u32::from(c).wrapping_sub(HANGUL_S_BASE); // SIndex in the spec
1205                    if hangul_offset < HANGUL_S_COUNT {
1206                        debug_assert_eq!(decomposition, 1);
1207                        // Hangul syllable
1208                        // The math here comes from page 144 of Unicode 14.0
1209                        let l = hangul_offset / HANGUL_N_COUNT;
1210                        let v = (hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
1211                        let t = hangul_offset % HANGUL_T_COUNT;
1212
1213                        // The unsafe blocks here are OK, because the values stay
1214                        // within the Hangul jamo block and, therefore, the scalar
1215                        // value range by construction.
1216                        self.buffer.push(CharacterAndClass::new_starter(unsafe {
1217                            core::char::from_u32_unchecked(HANGUL_V_BASE + v)
1218                        }));
1219                        let first = unsafe { core::char::from_u32_unchecked(HANGUL_L_BASE + l) };
1220                        if t != 0 {
1221                            self.buffer.push(CharacterAndClass::new_starter(unsafe {
1222                                core::char::from_u32_unchecked(HANGUL_T_BASE + t)
1223                            }));
1224                            (first, 2)
1225                        } else {
1226                            (first, 1)
1227                        }
1228                    } else {
1229                        let singleton = decomposition as u16;
1230                        if singleton != FDFA_MARKER {
1231                            // Decomposition into one BMP character
1232                            let starter = char_from_u16(singleton);
1233                            (starter, 0)
1234                        } else {
1235                            // Special case for the NFKD form of U+FDFA.
1236                            self.buffer.extend(FDFA_NFKD.map(|u| {
1237                                // SAFETY: `FDFA_NFKD` is known not to contain
1238                                // surrogates.
1239                                CharacterAndClass::new_starter(unsafe {
1240                                    core::char::from_u32_unchecked(u32::from(u))
1241                                })
1242                            }));
1243                            ('\u{0635}', 17)
1244                        }
1245                    }
1246                } else {
1247                    debug_assert!(low_zeros);
1248                    // Only 12 of 14 bits used as of Unicode 16.
1249                    let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1250                    // Only 3 of 4 bits used as of Unicode 16.
1251                    let len_bits = decomposition & 0b1111;
1252                    let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1253                    if offset < self.scalars16.len() {
1254                        self.push_decomposition16(
1255                            offset,
1256                            (len_bits + 2) as usize,
1257                            only_non_starters_in_trail,
1258                            self.scalars16,
1259                        )
1260                    } else if offset < self.scalars16.len() + self.scalars24.len() {
1261                        self.push_decomposition32(
1262                            offset - self.scalars16.len(),
1263                            (len_bits + 1) as usize,
1264                            only_non_starters_in_trail,
1265                            self.scalars24,
1266                        )
1267                    } else if offset
1268                        < self.scalars16.len()
1269                            + self.scalars24.len()
1270                            + self.supplementary_scalars16.len()
1271                    {
1272                        self.push_decomposition16(
1273                            offset - (self.scalars16.len() + self.scalars24.len()),
1274                            (len_bits + 2) as usize,
1275                            only_non_starters_in_trail,
1276                            self.supplementary_scalars16,
1277                        )
1278                    } else {
1279                        self.push_decomposition32(
1280                            offset
1281                                - (self.scalars16.len()
1282                                    + self.scalars24.len()
1283                                    + self.supplementary_scalars16.len()),
1284                            (len_bits + 1) as usize,
1285                            only_non_starters_in_trail,
1286                            self.supplementary_scalars24,
1287                        )
1288                    }
1289                }
1290            }
1291        };
1292        // Either we're inside `Composition` or `self.pending.is_none()`.
1293
1294        self.gather_and_sort_combining(combining_start);
1295        starter
1296    }
1297
1298    // This function exists as a borrow check helper.
1299    #[inline(always)]
1300    fn sort_slice_by_ccc(slice: &mut [CharacterAndClass], trie: &'data T) {
1301        // We don't look up the canonical combining class for starters
1302        // of for single combining characters between starters. When
1303        // there's more than one combining character between starters,
1304        // we look up the canonical combining class for each character
1305        // exactly once.
1306        if slice.len() < 2 {
1307            return;
1308        }
1309        slice
1310            .iter_mut()
1311            .for_each(|cc| cc.set_ccc_from_trie_if_not_already_set(trie));
1312        slice.sort_by_key(|cc| cc.ccc());
1313    }
1314
1315    #[cold]
1316    #[inline(never)]
1317    fn push_special_decomposition(buffer: &mut CombiningBuffer, c: char) {
1318        // The Tibetan special cases are starters that decompose into non-starters.
1319        let mapped = match c {
1320            '\u{0340}' => {
1321                // COMBINING GRAVE TONE MARK
1322                CharacterAndClass::new('\u{0300}', CCC_ABOVE)
1323            }
1324            '\u{0341}' => {
1325                // COMBINING ACUTE TONE MARK
1326                CharacterAndClass::new('\u{0301}', CCC_ABOVE)
1327            }
1328            '\u{0343}' => {
1329                // COMBINING GREEK KORONIS
1330                CharacterAndClass::new('\u{0313}', CCC_ABOVE)
1331            }
1332            '\u{0344}' => {
1333                // COMBINING GREEK DIALYTIKA TONOS
1334                buffer.push(CharacterAndClass::new('\u{0308}', CCC_ABOVE));
1335                CharacterAndClass::new('\u{0301}', CCC_ABOVE)
1336            }
1337            '\u{0F73}' => {
1338                // TIBETAN VOWEL SIGN II
1339                buffer.push(CharacterAndClass::new('\u{0F71}', ccc!(CCC129, 129)));
1340                CharacterAndClass::new('\u{0F72}', ccc!(CCC130, 130))
1341            }
1342            '\u{0F75}' => {
1343                // TIBETAN VOWEL SIGN UU
1344                buffer.push(CharacterAndClass::new('\u{0F71}', ccc!(CCC129, 129)));
1345                CharacterAndClass::new('\u{0F74}', ccc!(CCC132, 132))
1346            }
1347            '\u{0F81}' => {
1348                // TIBETAN VOWEL SIGN REVERSED II
1349                buffer.push(CharacterAndClass::new('\u{0F71}', ccc!(CCC129, 129)));
1350                CharacterAndClass::new('\u{0F80}', ccc!(CCC130, 130))
1351            }
1352            '\u{FF9E}' => {
1353                // HALFWIDTH KATAKANA VOICED SOUND MARK
1354                CharacterAndClass::new('\u{3099}', ccc!(KanaVoicing, 8))
1355            }
1356            '\u{FF9F}' => {
1357                // HALFWIDTH KATAKANA VOICED SOUND MARK
1358                CharacterAndClass::new('\u{309A}', ccc!(KanaVoicing, 8))
1359            }
1360            _ => {
1361                // GIGO case
1362                debug_assert!(false);
1363                CharacterAndClass::new_with_placeholder(REPLACEMENT_CHARACTER)
1364            }
1365        };
1366        buffer.push(mapped);
1367    }
1368
1369    fn gather_and_sort_combining(&mut self, combining_start: usize) {
1370        // Not a `for` loop to avoid holding a mutable reference to `self` across
1371        // the loop body.
1372        while let Some(ch_and_trie_val) = self.delegate_next() {
1373            if !trie_value_has_ccc(ch_and_trie_val.trie_val) {
1374                self.pending = Some(ch_and_trie_val);
1375                break;
1376            } else if !trie_value_indicates_special_non_starter_decomposition(
1377                ch_and_trie_val.trie_val,
1378            ) {
1379                self.buffer
1380                    .push(CharacterAndClass::new_with_trie_value(ch_and_trie_val));
1381            } else {
1382                Self::push_special_decomposition(&mut self.buffer, ch_and_trie_val.character);
1383            }
1384        }
1385        // Slicing succeeds by construction; we've always ensured that `combining_start`
1386        // is in permissible range.
1387        #[expect(clippy::indexing_slicing)]
1388        Self::sort_slice_by_ccc(&mut self.buffer[combining_start..], self.delegate.trie());
1389    }
1390}
1391
1392impl<'data, I, T, P> Iterator for DecompositionInner<'data, I, T, P>
1393where
1394    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
1395    T: AbstractCodePointTrie<'data, u32> + 'data,
1396    P: IteratorPolicy,
1397{
1398    type Item = char;
1399
1400    #[inline]
1401    fn next(&mut self) -> Option<char> {
1402        if let Some(ret) = self.buffer.get(self.buffer_pos).map(|c| c.character()) {
1403            self.buffer_pos += 1;
1404            if self.buffer_pos == self.buffer.len() {
1405                self.buffer.clear();
1406                self.buffer_pos = 0;
1407            }
1408            return Some(ret);
1409        }
1410        debug_assert_eq!(self.buffer_pos, 0);
1411        let c_and_trie_val = self.pending.take()?;
1412        Some(self.decomposing_next(c_and_trie_val))
1413    }
1414}
1415
1416/// An iterator adaptor that turns an `Iterator` over `char` into
1417/// a lazily-decomposed and then canonically composed `char` sequence.
1418#[derive(Debug)]
1419pub struct Composition<'data, I>
1420where
1421    I: Iterator<Item = char>,
1422{
1423    inner: CompositionInner<
1424        'data,
1425        CharIterWithTrie<'data, Trie<'data>, u32, I>,
1426        Trie<'data>,
1427        Uax15Policy,
1428    >,
1429}
1430
1431impl<I> Iterator for Composition<'_, I>
1432where
1433    I: Iterator<Item = char>,
1434{
1435    type Item = char;
1436
1437    #[inline]
1438    fn next(&mut self) -> Option<char> {
1439        self.inner.next()
1440    }
1441}
1442
1443#[derive(Debug)]
1444struct CompositionInner<'data, I, T, P>
1445where
1446    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
1447    T: AbstractCodePointTrie<'data, u32>,
1448    P: IteratorPolicy,
1449{
1450    /// The decomposing part of the normalizer than operates before
1451    /// the canonical composition is performed on its output.
1452    decomposition: DecompositionInner<'data, I, T, P>,
1453    /// Non-Hangul canonical composition data.
1454    canonical_compositions: CanonicalCompositionsRef<'data>,
1455    /// To make `next()` yield in cases where there's a non-composing
1456    /// starter in the decomposition buffer, we put it here to let it
1457    /// wait for the next `next()` call (or a jump forward within the
1458    /// `next()` call).
1459    unprocessed_starter: Option<char>,
1460}
1461
1462impl<'data, I, T, P> CompositionInner<'data, I, T, P>
1463where
1464    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
1465    T: AbstractCodePointTrie<'data, u32>,
1466    P: IteratorPolicy,
1467{
1468    #[inline(always)]
1469    fn new(
1470        decomposition: DecompositionInner<'data, I, T, P>,
1471        canonical_compositions: CanonicalCompositionsRef<'data>,
1472    ) -> Self {
1473        Self {
1474            decomposition,
1475            canonical_compositions,
1476            unprocessed_starter: None,
1477        }
1478    }
1479
1480    /// Performs canonical composition (including Hangul) on a pair of
1481    /// characters or returns `None` if these characters don't compose.
1482    /// Composition exclusions are taken into account.
1483    #[inline(always)]
1484    pub(crate) fn compose(&self, starter: char, second: char) -> Option<char> {
1485        self.canonical_compositions.compose(starter, second)
1486    }
1487
1488    /// Performs canonical composition (including Hangul) on a pair of
1489    /// characters on the assumption that the second one is a starter
1490    /// or returns `None` if these characters don't compose.
1491    /// Composition exclusions are taken into account.
1492    ///
1493    /// The returned boolean can be true only if `char` a Hangul LV syllable.
1494    ///
1495    /// The argument `starter_is_lv` must be set either to false or to the value
1496    /// that this method previously returned alongside `starter`.
1497    #[inline(always)]
1498    pub(crate) fn compose_starter(
1499        &self,
1500        starter: char,
1501        second: char,
1502        starter_is_lv: bool,
1503    ) -> Option<(char, bool)> {
1504        self.canonical_compositions
1505            .compose_starter(starter, second, starter_is_lv)
1506    }
1507}
1508
1509impl<'data, I, T, P> Iterator for CompositionInner<'data, I, T, P>
1510where
1511    I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
1512    T: AbstractCodePointTrie<'data, u32> + 'data,
1513    P: IteratorPolicy,
1514{
1515    type Item = char;
1516
1517    #[inline]
1518    fn next(&mut self) -> Option<char> {
1519        let mut undecomposed_starter = CharacterAndTrieValue::new('\u{0}', 0); // The compiler can't figure out that this gets overwritten before use.
1520        if self.unprocessed_starter.is_none() {
1521            // The loop is only broken out of as goto forward
1522            #[expect(clippy::never_loop)]
1523            loop {
1524                if let Some((character, ccc)) = self
1525                    .decomposition
1526                    .buffer
1527                    .get(self.decomposition.buffer_pos)
1528                    .map(|c| c.character_and_ccc())
1529                {
1530                    self.decomposition.buffer_pos += 1;
1531                    if self.decomposition.buffer_pos == self.decomposition.buffer.len() {
1532                        self.decomposition.buffer.clear();
1533                        self.decomposition.buffer_pos = 0;
1534                    }
1535                    if ccc == CCC_NOT_REORDERED {
1536                        // Previous decomposition contains a starter. This must
1537                        // now become the `unprocessed_starter` for it to have
1538                        // a chance to compose with the upcoming characters.
1539                        //
1540                        // E.g. parenthesized Hangul in NFKC comes through here,
1541                        // but suitable composition exclusion could exercise this
1542                        // in NFC.
1543                        self.unprocessed_starter = Some(character);
1544                        break; // We already have a starter, so skip taking one from `pending`.
1545                    }
1546                    return Some(character);
1547                }
1548                debug_assert_eq!(self.decomposition.buffer_pos, 0);
1549                undecomposed_starter = self.decomposition.pending.take()?;
1550                if undecomposed_starter.potential_passthrough() {
1551                    // TODO(#2385): In the NFC case (moot for NFKC and UTS46), if the upcoming
1552                    // character is not below `decomposition_passthrough_bound` but is
1553                    // below `composition_passthrough_bound`, we read from the trie
1554                    // unnecessarily.
1555                    if let Some(upcoming) = self.decomposition.delegate_next_no_pending() {
1556                        let cannot_combine_backwards = !upcoming.can_combine_backwards();
1557                        self.decomposition.pending = Some(upcoming);
1558                        if cannot_combine_backwards {
1559                            // Fast-track succeeded!
1560                            return Some(undecomposed_starter.character);
1561                        }
1562                    } else {
1563                        // End of stream
1564                        return Some(undecomposed_starter.character);
1565                    }
1566                }
1567                break; // Not actually looping
1568            }
1569        }
1570        let mut starter = '\u{0}'; // The compiler can't figure out this gets overwritten before use.
1571                                   // It would be fancier to bundle `starter` and `starter_is_lv` into an encapsulating
1572                                   // struct, but that would result in lots of useless assignments to `starter_is_lv`.
1573                                   // Using `debug_assert!(!starter_is_lv);` a lot instead.
1574        let mut starter_is_lv = false;
1575
1576        // The point of having this boolean is to have only one call site to
1577        // `self.decomposition.decomposing_next`, which is hopefully beneficial for
1578        // code size under inlining.
1579        let mut attempt_composition = false;
1580        loop {
1581            if let Some(unprocessed) = self.unprocessed_starter.take() {
1582                debug_assert_eq!(undecomposed_starter, CharacterAndTrieValue::new('\u{0}', 0));
1583                debug_assert_eq!(starter, '\u{0}');
1584                debug_assert!(!starter_is_lv);
1585                starter = unprocessed;
1586            } else {
1587                debug_assert_eq!(self.decomposition.buffer_pos, 0);
1588                let next_starter = self.decomposition.decomposing_next(undecomposed_starter);
1589                if !attempt_composition {
1590                    debug_assert!(!starter_is_lv);
1591                    starter = next_starter;
1592                } else if let Some((composed, is_lv)) =
1593                    self.compose_starter(starter, next_starter, starter_is_lv)
1594                {
1595                    // Normal non-enclosed Hangul is composed here.
1596                    starter_is_lv = is_lv;
1597                    starter = composed;
1598                } else {
1599                    // This is our yield point. We'll pick this up above in the
1600                    // next call to `next()`.
1601                    self.unprocessed_starter = Some(next_starter);
1602                    return Some(starter);
1603                }
1604            }
1605            // We first loop by index to avoid moving the contents of `buffer`, but
1606            // if there's a discontiguous match, we'll start modifying `buffer` instead.
1607            loop {
1608                let (character, ccc) = if let Some((character, ccc)) = self
1609                    .decomposition
1610                    .buffer
1611                    .get(self.decomposition.buffer_pos)
1612                    .map(|c| c.character_and_ccc())
1613                {
1614                    (character, ccc)
1615                } else {
1616                    self.decomposition.buffer.clear();
1617                    self.decomposition.buffer_pos = 0;
1618                    break;
1619                };
1620                starter_is_lv = false;
1621                // In NFKC, enclosed Hangul is recomposed here.
1622                if let Some(composed) = self.compose(starter, character) {
1623                    debug_assert!(!starter_is_lv);
1624                    starter = composed;
1625                    self.decomposition.buffer_pos += 1;
1626                    continue;
1627                }
1628                let mut most_recent_skipped_ccc = ccc;
1629                {
1630                    let _ = self
1631                        .decomposition
1632                        .buffer
1633                        .drain(0..self.decomposition.buffer_pos);
1634                }
1635                self.decomposition.buffer_pos = 0;
1636                if most_recent_skipped_ccc == CCC_NOT_REORDERED {
1637                    // We failed to compose a starter. Discontiguous match not allowed.
1638                    // We leave the starter in `buffer` for `next()` to find.
1639                    return Some(starter);
1640                }
1641                // TODO: Make use of `compose` having figured out that no other matches are
1642                // possible, either.
1643                let mut i = 1; // We have skipped one non-starter.
1644                while let Some((character, ccc)) = self
1645                    .decomposition
1646                    .buffer
1647                    .get(i)
1648                    .map(|c| c.character_and_ccc())
1649                {
1650                    if ccc == CCC_NOT_REORDERED {
1651                        // Discontiguous match not allowed.
1652                        return Some(starter);
1653                    }
1654                    debug_assert!(ccc >= most_recent_skipped_ccc);
1655                    if ccc != most_recent_skipped_ccc {
1656                        // `character` is a non-starter, so we could use a variant of
1657                        // `compose` that omits all the Hangul cases.
1658                        // TODO: Make use of above `compose` having already done the trie lookup,
1659                        // so the linear slice could be reused here.
1660                        if let Some(composed) = self.compose(starter, character) {
1661                            self.decomposition.buffer.remove(i);
1662                            debug_assert!(!starter_is_lv);
1663                            starter = composed;
1664                            continue;
1665                        }
1666                    }
1667                    most_recent_skipped_ccc = ccc;
1668                    i += 1;
1669                }
1670                break;
1671            }
1672
1673            debug_assert_eq!(self.decomposition.buffer_pos, 0);
1674
1675            if !self.decomposition.buffer.is_empty() {
1676                return Some(starter);
1677            }
1678            // Now we need to check if composition with an upcoming starter is possible.
1679            #[expect(clippy::unwrap_used)]
1680            if self.decomposition.pending.is_some() {
1681                // We know that `pending_starter` decomposes to start with a starter.
1682                // Otherwise, it would have been moved to `self.decomposition.buffer`
1683                // by `self.decomposing_next()`. We do this set lookup here in order
1684                // to get an opportunity to go back to the fast track.
1685                // Note that this check has to happen _after_ checking that `pending`
1686                // holds a character, because this flag isn't defined to be meaningful
1687                // when `pending` isn't holding a character.
1688                let pending = self.decomposition.pending.as_ref().unwrap();
1689                if !pending.can_combine_backwards() {
1690                    // Won't combine backwards anyway.
1691                    return Some(starter);
1692                }
1693                // Consume what we peeked. `unwrap` OK, because we checked `is_some()`
1694                // above.
1695                undecomposed_starter = self.decomposition.pending.take().unwrap();
1696                // The following line is OK, because we're about to loop back
1697                // to `self.decomposition.decomposing_next(c);`, which will
1698                // restore the between-`next()`-calls invariant of `pending`
1699                // before this function returns.
1700                attempt_composition = true;
1701                continue;
1702            }
1703            // End of input
1704            return Some(starter);
1705        }
1706    }
1707}
1708
1709macro_rules! composing_normalize_to {
1710    ($(#[$meta:meta])*,
1711     $normalize_to:ident,
1712     $write:path,
1713     $slice:ty,
1714     $prolog:block,
1715     $always_valid_utf:literal,
1716     $as_slice:ident,
1717     $fast:block,
1718     $text:ident,
1719     $sink:ident,
1720     $composition:ident,
1721     $undecomposed_starter:ident,
1722     $pending_slice:ident,
1723     $len_utf:ident,
1724     $self:ident,
1725     $chars_with_trie:ident,
1726    ) => {
1727        $(#[$meta])*
1728        pub fn $normalize_to<W: $write + ?Sized>(
1729            &$self,
1730            $text: $slice,
1731            $sink: &mut W,
1732        ) -> core::fmt::Result {
1733            $prolog
1734            let mut $composition = $self.normalize_iter_private::<_, Trie, Uax15Policy>($text.$chars_with_trie($self.trie()));
1735            let _ = $composition.decomposition.init(); // Discard the U+0000.
1736
1737            for cc in $composition.decomposition.buffer.drain(..) {
1738                $sink.write_char(cc.character())?;
1739            }
1740
1741            'outer: loop {
1742                debug_assert_eq!($composition.decomposition.buffer_pos, 0);
1743                let mut $undecomposed_starter =
1744                    if let Some(pending) = $composition.decomposition.pending.take() {
1745                        pending
1746                    } else {
1747                        return Ok(());
1748                    };
1749                if $undecomposed_starter.potential_passthrough()
1750                {
1751                    // We don't know if a `REPLACEMENT_CHARACTER` occurred in the slice or
1752                    // was returned in response to an error by the iterator. Assume the
1753                    // latter for correctness even though it pessimizes the former.
1754                    if $always_valid_utf || $undecomposed_starter.character != REPLACEMENT_CHARACTER {
1755                        let $pending_slice = &$text[$text.len() - $composition.decomposition.delegate.$as_slice().len() - $undecomposed_starter.character.$len_utf()..];
1756                        // The `$fast` block must either:
1757                        // 1. Return due to reaching EOF
1758                        // 2. Leave a starter with its trie value in `$undecomposed_starter`
1759                        //    and, if there is still more input, leave the next character
1760                        //    and its trie value in `$composition.decomposition.pending`.
1761                        $fast
1762                    }
1763                }
1764                // Fast track above, full algorithm below
1765                let mut starter = $composition
1766                    .decomposition
1767                    .decomposing_next($undecomposed_starter);
1768                'bufferloop: loop {
1769                    // We first loop by index to avoid moving the contents of `buffer`, but
1770                    // if there's a discontiguous match, we'll start modifying `buffer` instead.
1771                    loop {
1772                        let (character, ccc) = if let Some((character, ccc)) = $composition
1773                            .decomposition
1774                            .buffer
1775                            .get($composition.decomposition.buffer_pos)
1776                            .map(|c| c.character_and_ccc())
1777                        {
1778                            (character, ccc)
1779                        } else {
1780                            $composition.decomposition.buffer.clear();
1781                            $composition.decomposition.buffer_pos = 0;
1782                            break;
1783                        };
1784                        // In NFKC, enclosed Hangul get recomposed here.
1785                        // Furthermore, in NFC if input has lv followed by t, lv gets
1786                        // decomposed above and recomposed here.
1787                        if let Some(composed) = $composition.compose(starter, character) {
1788                            starter = composed;
1789                            $composition.decomposition.buffer_pos += 1;
1790                            continue;
1791                        }
1792                        let mut most_recent_skipped_ccc = ccc;
1793                        if most_recent_skipped_ccc == CCC_NOT_REORDERED {
1794                            // We failed to compose a starter. Discontiguous match not allowed.
1795                            // Write the current `starter` we've been composing, make the unmatched
1796                            // starter in the buffer the new `starter` (we know it's been decomposed)
1797                            // and process the rest of the buffer with that as the starter.
1798                            $sink.write_char(starter)?;
1799                            starter = character;
1800                            $composition.decomposition.buffer_pos += 1;
1801                            continue 'bufferloop;
1802                        } else {
1803                            {
1804                                let _ = $composition
1805                                    .decomposition
1806                                    .buffer
1807                                    .drain(0..$composition.decomposition.buffer_pos);
1808                            }
1809                            $composition.decomposition.buffer_pos = 0;
1810                        }
1811                        let mut i = 1; // We have skipped one non-starter.
1812                        while let Some((character, ccc)) = $composition
1813                            .decomposition
1814                            .buffer
1815                            .get(i)
1816                            .map(|c| c.character_and_ccc())
1817                        {
1818                            if ccc == CCC_NOT_REORDERED {
1819                                // Discontiguous match not allowed.
1820                                $sink.write_char(starter)?;
1821                                for cc in $composition.decomposition.buffer.drain(..i) {
1822                                    $sink.write_char(cc.character())?;
1823                                }
1824                                starter = character;
1825                                {
1826                                    let removed = $composition.decomposition.buffer.remove(0);
1827                                    debug_assert_eq!(starter, removed.character());
1828                                }
1829                                debug_assert_eq!($composition.decomposition.buffer_pos, 0);
1830                                continue 'bufferloop;
1831                            }
1832                            debug_assert!(ccc >= most_recent_skipped_ccc);
1833                            if ccc != most_recent_skipped_ccc {
1834                                // `character` is a non-starter, so we could use a variant of
1835                                // `compose` that omits all the Hangul cases.
1836                                if let Some(composed) =
1837                                    $composition.compose(starter, character)
1838                                {
1839                                    $composition.decomposition.buffer.remove(i);
1840                                    starter = composed;
1841                                    continue;
1842                                }
1843                            }
1844                            most_recent_skipped_ccc = ccc;
1845                            i += 1;
1846                        }
1847                        break;
1848                    }
1849                    debug_assert_eq!($composition.decomposition.buffer_pos, 0);
1850
1851                    if !$composition.decomposition.buffer.is_empty() {
1852                        $sink.write_char(starter)?;
1853                        for cc in $composition.decomposition.buffer.drain(..) {
1854                            $sink.write_char(cc.character())?;
1855                        }
1856                        // We had non-empty buffer, so can't compose with upcoming.
1857                        continue 'outer;
1858                    }
1859                    // We can loop back in case we compose a Hangul LV. Looping back
1860                    // makes this code much simpler than trying to have a special
1861                    // case that advances the underlying iterator in the branch that
1862                    // now says `continue;` below.
1863                    let mut starter_is_lv = false;
1864                    loop {
1865                        // Now we need to check if composition with an upcoming starter is possible.
1866                        if $composition.decomposition.pending.is_some() {
1867                            // We know that `pending_starter` decomposes to start with a starter.
1868                            // Otherwise, it would have been moved to `composition.decomposition.buffer`
1869                            // by `composition.decomposing_next()`. We do this set lookup here in order
1870                            // to get an opportunity to go back to the fast track.
1871                            // Note that this check has to happen _after_ checking that `pending`
1872                            // holds a character, because this flag isn't defined to be meaningful
1873                            // when `pending` isn't holding a character.
1874                            let pending = $composition.decomposition.pending.as_ref().unwrap();
1875                            if !pending.can_combine_backwards()
1876                            {
1877                                // Won't combine backwards anyway.
1878                                $sink.write_char(starter)?;
1879                                continue 'outer;
1880                            }
1881                            let pending_starter = $composition.decomposition.pending.take().unwrap();
1882                            let decomposed = $composition.decomposition.decomposing_next(pending_starter);
1883                            // Normal non-enclosed Hangul is composed here. The case where we have LV and T,
1884                            // but LV was not composed here previously is possible.
1885                            if let Some((composed, is_lv)) = $composition.compose_starter(starter, decomposed, starter_is_lv) {
1886                                starter = composed;
1887                                if is_lv && $composition.decomposition.buffer.is_empty() {
1888                                    starter_is_lv = true;
1889                                    // TODO: Put a Hangul fast-path that deals with conjoining jamo and ASCII
1890                                    // in a manner specialized for the UTF (i.e. not doing surrogate checks,
1891                                    // since surrogates are neither conjoining jamo nor ASCII) here.
1892                                    // https://github.com/unicode-org/icu4x/issues/7516
1893                                    continue;
1894                                }
1895                            } else {
1896                                $sink.write_char(starter)?;
1897                                starter = decomposed;
1898                            }
1899                            continue 'bufferloop;
1900                        }
1901                        break;
1902                    }
1903                    // End of input
1904                    $sink.write_char(starter)?;
1905                    return Ok(());
1906                } // 'bufferloop
1907            }
1908        }
1909    };
1910}
1911
1912macro_rules! decomposing_normalize_to {
1913    ($(#[$meta:meta])*,
1914     $normalize_to:ident,
1915     $write:path,
1916     $slice:ty,
1917     $prolog:block,
1918     $as_slice:ident,
1919     $fast:block,
1920     $text:ident,
1921     $sink:ident,
1922     $decomposition:ident,
1923     $undecomposed_starter:ident,
1924     $pending_slice:ident,
1925     $outer:lifetime, // loop labels use lifetime tokens
1926     $self:ident,
1927     $chars_with_trie:ident,
1928    ) => {
1929        $(#[$meta])*
1930        pub fn $normalize_to<W: $write + ?Sized>(
1931            &$self,
1932            $text: $slice,
1933            $sink: &mut W,
1934        ) -> core::fmt::Result {
1935            $prolog
1936
1937            let mut $decomposition = $self.normalize_iter_private::<_, Trie, Uax15Policy>($text.$chars_with_trie($self.trie()));
1938            let _ = $decomposition.init(); // Discard the U+0000.
1939
1940            $outer: loop {
1941                for cc in $decomposition.buffer.drain(..) {
1942                    $sink.write_char(cc.character())?;
1943                }
1944                debug_assert_eq!($decomposition.buffer_pos, 0);
1945                let mut $undecomposed_starter = if let Some(pending) = $decomposition.pending.take() {
1946                    pending
1947                } else {
1948                    return Ok(());
1949                };
1950                loop {
1951                    if $undecomposed_starter.starter_and_decomposes_to_self() {
1952                        // Don't bother including `undecomposed_starter` in a contiguous buffer
1953                        // write: Just write it right away:
1954                        $sink.write_char($undecomposed_starter.character)?;
1955
1956                        let $pending_slice = $decomposition.delegate.$as_slice();
1957                        $fast
1958                    }
1959                    debug_assert!($decomposition.pending.is_none());
1960                    let c_and_trie_val_unless_at_end = if let Some((upcoming, trie_val)) = $decomposition.delegate.next() {
1961                        if likely(!decomposition_starts_with_non_starter(trie_val)) {
1962                            Some(CharacterAndTrieValue::new(upcoming, trie_val))
1963                        } else {
1964                            $decomposition.pending = Some(CharacterAndTrieValue::new(upcoming, trie_val));
1965                            break;
1966                        }
1967                    } else {
1968                        None
1969                    };
1970                    // The upcoming character cannot sort into the tail of this decomposition,
1971                    // so, for performance, let's write decomposition directly here without
1972                    // going via `$decomposition.buffer`. This wall of (edited) copypaste is
1973                    // crucial for performance competitiveness with ICU4C.
1974
1975                    // Start edited copypaste from `decomposing_next`
1976
1977                    let c = $undecomposed_starter.character;
1978                    // See trie-value-format.md
1979                    let decomposition = $undecomposed_starter.trie_val;
1980                    // The REPLACEMENT CHARACTER has `NON_ROUND_TRIP_MARKER` set,
1981                    // and that flag needs to be ignored here.
1982                    if unlikely((decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0) {
1983                        // The character is its own decomposition
1984                        $sink.write_char(c)?;
1985                    } else {
1986                        let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1987                        let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1988                        if !high_zeros && !low_zeros {
1989                            // Decomposition into two BMP characters: starter and non-starter
1990                            let starter = char_from_u32(decomposition & 0x7FFF);
1991                            let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
1992                            $sink.write_char(starter)?;
1993                            $sink.write_char(combining)?;
1994                        } else if high_zeros {
1995                            // Do the check by looking at `c` instead of looking at a marker
1996                            // in `singleton` below, because if we looked at the trie value,
1997                            // we'd still have to check that `c` is in the Hangul syllable
1998                            // range in order for the subsequent interpretations as `char`
1999                            // to be safe.
2000                            // Alternatively, `FDFA_MARKER` and the Hangul marker could
2001                            // be unified. That would add a branch for Hangul and remove
2002                            // a branch from singleton decompositions. It seems more
2003                            // important to favor Hangul syllables than singleton
2004                            // decompositions.
2005                            // Note that it would be valid to hoist this Hangul check
2006                            // one or even two steps earlier in this check hierarchy.
2007                            // Right now, it's assumed the kind of decompositions into
2008                            // BMP starter and non-starter, which occur in many languages,
2009                            // should be checked before Hangul syllables, which are about
2010                            // one language specifically. Hopefully, we get some
2011                            // instruction-level parallelism out of the disjointness of
2012                            // operations on `c` and `decomposition`.
2013                            let hangul_offset = u32::from(c).wrapping_sub(HANGUL_S_BASE); // SIndex in the spec
2014                            if hangul_offset < HANGUL_S_COUNT {
2015                                debug_assert_eq!(decomposition, 1);
2016                                // Hangul syllable
2017                                // The math here comes from page 144 of Unicode 14.0
2018                                let l = hangul_offset / HANGUL_N_COUNT;
2019                                let v = (hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
2020                                let t = hangul_offset % HANGUL_T_COUNT;
2021
2022                                // The unsafe blocks here are OK, because the values stay
2023                                // within the Hangul jamo block and, therefore, the scalar
2024                                // value range by construction.
2025                                $sink.write_char(unsafe { core::char::from_u32_unchecked(HANGUL_L_BASE + l) })?;
2026                                $sink.write_char(unsafe {
2027                                    core::char::from_u32_unchecked(HANGUL_V_BASE + v)
2028                                })?;
2029                                if t != 0 {
2030                                    $sink.write_char(unsafe {
2031                                        core::char::from_u32_unchecked(HANGUL_T_BASE + t)
2032                                    })?;
2033                                }
2034                            } else {
2035                                let singleton = decomposition as u16;
2036                                if singleton != FDFA_MARKER {
2037                                    // Decomposition into one BMP character
2038                                    let starter = char_from_u16(singleton);
2039                                    $sink.write_char(starter)?;
2040                                } else {
2041                                    // Special case for the NFKD form of U+FDFA.
2042                                    $sink.write_char('\u{0635}')?;
2043                                    for u in FDFA_NFKD {
2044                                        // SAFETY: `FDFA_NFKD` is known not to contain
2045                                        // surrogates.
2046                                        $sink.write_char(unsafe { core::char::from_u32_unchecked(u32::from(u)) })?;
2047                                    }
2048                                }
2049                            }
2050                        } else {
2051                            debug_assert!(low_zeros);
2052                            // Only 12 of 14 bits used as of Unicode 16.
2053                            let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
2054                            // Only 3 of 4 bits used as of Unicode 16.
2055                            let len_bits = decomposition & 0b1111;
2056                            if let Some(subslice) = $decomposition.scalars16.get_subslice(offset..offset+((len_bits + 2) as usize)) {
2057                                for u in subslice.iter() {
2058                                    $sink.write_char(char_from_u16(u))?;
2059                                }
2060                            } else {
2061                                let offset = offset - $decomposition.scalars16.len();
2062                                if let Some(subslice) = $decomposition.scalars24.get_subslice(offset..offset+((len_bits + 1) as usize)) {
2063                                    for c in subslice.iter() {
2064                                        $sink.write_char(c)?;
2065                                    }
2066                                } else {
2067                                    let offset = offset - $decomposition.scalars24.len();
2068                                    if let Some(subslice) = $decomposition.supplementary_scalars16.get_subslice(offset..offset+((len_bits + 2) as usize)) {
2069                                        for u in subslice.iter() {
2070                                            $sink.write_char(char_from_u16(u))?;
2071                                        }
2072                                    } else {
2073                                        let offset = offset - $decomposition.supplementary_scalars16.len();
2074                                        if let Some(subslice) = $decomposition.supplementary_scalars24.get_subslice(offset..offset+((len_bits + 1) as usize)) {
2075                                            for c in subslice.iter() {
2076                                                $sink.write_char(c)?;
2077                                            }
2078                                        } else {
2079                                            // GIGO case
2080                                            debug_assert!(false);
2081                                        }
2082                                    }
2083                                }
2084                            }
2085                        }
2086                    }
2087
2088                    // End edited copypaste from `decomposing_next`
2089
2090                    if let Some(c_and_trie_val) = c_and_trie_val_unless_at_end {
2091                        $undecomposed_starter = c_and_trie_val;
2092                        continue;
2093                    }
2094                    return Ok(());
2095                }
2096                let starter = $decomposition.decomposing_next($undecomposed_starter);
2097                $sink.write_char(starter)?;
2098            }
2099        }
2100    };
2101}
2102
2103macro_rules! normalizer_methods {
2104    () => {
2105        /// Normalize a string slice into a `Cow<'a, str>`.
2106        pub fn normalize<'a>(&self, text: &'a str) -> Cow<'a, str> {
2107            let (head, tail) = self.split_normalized(text);
2108            if tail.is_empty() {
2109                return Cow::Borrowed(head);
2110            }
2111            let mut ret = String::new();
2112            ret.reserve(text.len());
2113            ret.push_str(head);
2114            let _ = self.normalize_to(tail, &mut ret);
2115            Cow::Owned(ret)
2116        }
2117
2118        /// Split a string slice into maximum normalized prefix and unnormalized suffix
2119        /// such that the concatenation of the prefix and the normalization of the suffix
2120        /// is the normalization of the whole input.
2121        pub fn split_normalized<'a>(&self, text: &'a str) -> (&'a str, &'a str) {
2122            let up_to = self.is_normalized_up_to(text);
2123            text.split_at_checked(up_to).unwrap_or_else(|| {
2124                // Internal bug, not even GIGO, never supposed to happen
2125                debug_assert!(false);
2126                ("", text)
2127            })
2128        }
2129
2130        /// Return the index a string slice is normalized up to.
2131        fn is_normalized_up_to(&self, text: &str) -> usize {
2132            let mut sink = IsNormalizedSinkStr::new(text);
2133            let _ = self.normalize_to(text, &mut sink);
2134            text.len() - sink.remaining_len()
2135        }
2136
2137        /// Check whether a string slice is normalized.
2138        pub fn is_normalized(&self, text: &str) -> bool {
2139            self.is_normalized_up_to(text) == text.len()
2140        }
2141
2142        /// Normalize a slice of potentially-invalid UTF-16 into a `Cow<'a, [u16]>`.
2143        ///
2144        /// Unpaired surrogates are mapped to the REPLACEMENT CHARACTER
2145        /// before normalizing.
2146        ///
2147        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2148        #[cfg(feature = "utf16_iter")]
2149        pub fn normalize_utf16<'a>(&self, text: &'a [u16]) -> Cow<'a, [u16]> {
2150            let (head, tail) = self.split_normalized_utf16(text);
2151            if tail.is_empty() {
2152                return Cow::Borrowed(head);
2153            }
2154            let mut ret = alloc::vec::Vec::with_capacity(text.len());
2155            ret.extend_from_slice(head);
2156            let _ = self.normalize_utf16_to(tail, &mut ret);
2157            Cow::Owned(ret)
2158        }
2159
2160        /// Split a slice of potentially-invalid UTF-16 into maximum normalized (and valid)
2161        /// prefix and unnormalized suffix such that the concatenation of the prefix and the
2162        /// normalization of the suffix is the normalization of the whole input.
2163        ///
2164        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2165        #[cfg(feature = "utf16_iter")]
2166        pub fn split_normalized_utf16<'a>(&self, text: &'a [u16]) -> (&'a [u16], &'a [u16]) {
2167            let up_to = self.is_normalized_utf16_up_to(text);
2168            text.split_at_checked(up_to).unwrap_or_else(|| {
2169                // Internal bug, not even GIGO, never supposed to happen
2170                debug_assert!(false);
2171                (&[], text)
2172            })
2173        }
2174
2175        /// Return the index a slice of potentially-invalid UTF-16 is normalized up to.
2176        ///
2177        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2178        #[cfg(feature = "utf16_iter")]
2179        fn is_normalized_utf16_up_to(&self, text: &[u16]) -> usize {
2180            let mut sink = IsNormalizedSinkUtf16::new(text);
2181            let _ = self.normalize_utf16_to(text, &mut sink);
2182            text.len() - sink.remaining_len()
2183        }
2184
2185        /// Checks whether a slice of potentially-invalid UTF-16 is normalized.
2186        ///
2187        /// Unpaired surrogates are treated as the REPLACEMENT CHARACTER.
2188        ///
2189        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2190        #[cfg(feature = "utf16_iter")]
2191        pub fn is_normalized_utf16(&self, text: &[u16]) -> bool {
2192            self.is_normalized_utf16_up_to(text) == text.len()
2193        }
2194
2195        /// Normalize a slice of potentially-invalid UTF-8 into a `Cow<'a, str>`.
2196        ///
2197        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
2198        /// according to the WHATWG Encoding Standard.
2199        ///
2200        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
2201        #[cfg(feature = "utf8_iter")]
2202        pub fn normalize_utf8<'a>(&self, text: &'a [u8]) -> Cow<'a, str> {
2203            let (head, tail) = self.split_normalized_utf8(text);
2204            if tail.is_empty() {
2205                return Cow::Borrowed(head);
2206            }
2207            let mut ret = String::new();
2208            ret.reserve(text.len());
2209            ret.push_str(head);
2210            let _ = self.normalize_utf8_to(tail, &mut ret);
2211            Cow::Owned(ret)
2212        }
2213
2214        /// Split a slice of potentially-invalid UTF-8 into maximum normalized (and valid)
2215        /// prefix and unnormalized suffix such that the concatenation of the prefix and the
2216        /// normalization of the suffix is the normalization of the whole input.
2217        ///
2218        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
2219        #[cfg(feature = "utf8_iter")]
2220        pub fn split_normalized_utf8<'a>(&self, text: &'a [u8]) -> (&'a str, &'a [u8]) {
2221            let up_to = self.is_normalized_utf8_up_to(text);
2222            let (head, tail) = text.split_at_checked(up_to).unwrap_or_else(|| {
2223                // Internal bug, not even GIGO, never supposed to happen
2224                debug_assert!(false);
2225                (&[], text)
2226            });
2227            // SAFETY: The normalization check also checks for
2228            // UTF-8 well-formedness.
2229            (unsafe { core::str::from_utf8_unchecked(head) }, tail)
2230        }
2231
2232        /// Return the index a slice of potentially-invalid UTF-8 is normalized up to
2233        ///
2234        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
2235        #[cfg(feature = "utf8_iter")]
2236        fn is_normalized_utf8_up_to(&self, text: &[u8]) -> usize {
2237            let mut sink = IsNormalizedSinkUtf8::new(text);
2238            let _ = self.normalize_utf8_to(text, &mut sink);
2239            text.len() - sink.remaining_len()
2240        }
2241
2242        /// Check if a slice of potentially-invalid UTF-8 is normalized.
2243        ///
2244        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
2245        /// according to the WHATWG Encoding Standard before checking.
2246        ///
2247        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
2248        #[cfg(feature = "utf8_iter")]
2249        pub fn is_normalized_utf8(&self, text: &[u8]) -> bool {
2250            self.is_normalized_utf8_up_to(text) == text.len()
2251        }
2252    };
2253}
2254
2255/// Borrowed version of a normalizer for performing decomposing normalization.
2256#[derive(Debug)]
2257pub struct DecomposingNormalizerBorrowed<'a> {
2258    decompositions: &'a DecompositionData<'a>,
2259    tables: &'a DecompositionTables<'a>,
2260    supplementary_tables: Option<&'a DecompositionTables<'a>>,
2261    decomposition_passthrough_bound: u8, // never above 0xC0
2262    composition_passthrough_bound: u16,  // never above 0x0300
2263}
2264
2265impl DecomposingNormalizerBorrowed<'static> {
2266    /// Cheaply converts a [`DecomposingNormalizerBorrowed<'static>`] into a [`DecomposingNormalizer`].
2267    ///
2268    /// Note: Due to branching and indirection, using [`DecomposingNormalizer`] might inhibit some
2269    /// compile-time optimizations that are possible with [`DecomposingNormalizerBorrowed`].
2270    pub const fn static_to_owned(self) -> DecomposingNormalizer {
2271        DecomposingNormalizer {
2272            decompositions: DataPayload::from_static_ref(self.decompositions),
2273            tables: DataPayload::from_static_ref(self.tables),
2274            supplementary_tables: if let Some(s) = self.supplementary_tables {
2275                // `map` not available in const context
2276                Some(DataPayload::from_static_ref(s))
2277            } else {
2278                None
2279            },
2280            decomposition_passthrough_bound: self.decomposition_passthrough_bound,
2281            composition_passthrough_bound: self.composition_passthrough_bound,
2282        }
2283    }
2284
2285    /// NFD constructor using compiled data.
2286    ///
2287    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2288    ///
2289    /// [📚 Help choosing a constructor](icu_provider::constructors)
2290    #[cfg(feature = "compiled_data")]
2291    pub const fn new_nfd() -> Self {
2292        const _: () = assert!(
2293            provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
2294                .scalars16
2295                .const_len()
2296                + provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
2297                    .scalars24
2298                    .const_len()
2299                <= 0xFFF,
2300            "future extension"
2301        );
2302
2303        DecomposingNormalizerBorrowed {
2304            decompositions: provider::Baked::SINGLETON_NORMALIZER_NFD_DATA_V1,
2305            tables: provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
2306            supplementary_tables: None,
2307            decomposition_passthrough_bound: 0xC0,
2308            composition_passthrough_bound: 0x0300,
2309        }
2310    }
2311
2312    /// NFKD constructor using compiled data.
2313    ///
2314    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2315    ///
2316    /// [📚 Help choosing a constructor](icu_provider::constructors)
2317    #[cfg(feature = "compiled_data")]
2318    pub const fn new_nfkd() -> Self {
2319        const _: () = assert!(
2320            provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
2321                .scalars16
2322                .const_len()
2323                + provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
2324                    .scalars24
2325                    .const_len()
2326                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
2327                    .scalars16
2328                    .const_len()
2329                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
2330                    .scalars24
2331                    .const_len()
2332                <= 0xFFF,
2333            "future extension"
2334        );
2335
2336        // TODO: Perhaps hard-code these?
2337
2338        const _: () = assert!(
2339            provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap <= 0x0300,
2340            "invalid"
2341        );
2342
2343        const _: () = assert!(
2344            provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap >= 0x80,
2345            "invalid"
2346        );
2347
2348        let decomposition_capped =
2349            if provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap < 0xC0 {
2350                provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap
2351            } else {
2352                0xC0
2353            };
2354        let composition_capped =
2355            if provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap < 0x0300 {
2356                provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap
2357            } else {
2358                0x0300
2359            };
2360
2361        DecomposingNormalizerBorrowed {
2362            decompositions: provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1,
2363            tables: provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
2364            supplementary_tables: Some(provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1),
2365            decomposition_passthrough_bound: decomposition_capped as u8,
2366            composition_passthrough_bound: composition_capped,
2367        }
2368    }
2369
2370    #[cfg(feature = "compiled_data")]
2371    pub(crate) const fn new_uts46_decomposed() -> Self {
2372        const _: () = assert!(
2373            provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
2374                .scalars16
2375                .const_len()
2376                + provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
2377                    .scalars24
2378                    .const_len()
2379                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
2380                    .scalars16
2381                    .const_len()
2382                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
2383                    .scalars24
2384                    .const_len()
2385                <= 0xFFF,
2386            "future extension"
2387        );
2388
2389        const _: () = assert!(
2390            provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap <= 0x0300,
2391            "invalid"
2392        );
2393
2394        // Is less than 0x80!
2395
2396        let decomposition_capped =
2397            if provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap < 0xC0 {
2398                provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap
2399            } else {
2400                0xC0
2401            };
2402        let composition_capped =
2403            if provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap < 0x0300 {
2404                provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap
2405            } else {
2406                0x0300
2407            };
2408
2409        DecomposingNormalizerBorrowed {
2410            decompositions: provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1,
2411            tables: provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
2412            supplementary_tables: Some(provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1),
2413            decomposition_passthrough_bound: decomposition_capped as u8,
2414            composition_passthrough_bound: composition_capped,
2415        }
2416    }
2417}
2418
2419impl<'data> DecomposingNormalizerBorrowed<'data> {
2420    /// NFD constructor using already-loaded data.
2421    ///
2422    /// This constructor is intended for use by collations.
2423    ///
2424    /// [📚 Help choosing a constructor](icu_provider::constructors)
2425    #[doc(hidden)]
2426    pub fn new_with_data(
2427        decompositions: &'data DecompositionData<'data>,
2428        tables: &'data DecompositionTables<'data>,
2429    ) -> Self {
2430        Self {
2431            decompositions,
2432            tables,
2433            supplementary_tables: None,
2434            decomposition_passthrough_bound: 0xC0,
2435            composition_passthrough_bound: 0x0300,
2436        }
2437    }
2438
2439    /// Wraps a delegate iterator into a decomposing iterator
2440    /// adapter by using the data already held by this normalizer.
2441    #[inline]
2442    pub fn normalize_iter<I: Iterator<Item = char>>(&self, iter: I) -> Decomposition<'data, I> {
2443        let mut ret = Decomposition {
2444            inner: self.normalize_iter_private(CharIterWithTrie::new(iter, self.trie())),
2445        };
2446        ret.inner.init(); // Discard the U+0000.
2447        ret
2448    }
2449
2450    /// There's an extra U+FFFD at the start. The caller must deal with it.
2451    #[inline(always)]
2452    fn normalize_iter_private<
2453        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
2454        T: AbstractCodePointTrie<'data, u32> + 'data,
2455        P: IteratorPolicy,
2456    >(
2457        &self,
2458        iter: I,
2459    ) -> DecompositionInner<'data, I, T, P> {
2460        DecompositionInner::new_with_supplements(iter, self.tables, self.supplementary_tables)
2461    }
2462
2463    fn trie<T: AbstractCodePointTrie<'data, u32>>(&self) -> &'data T
2464    where
2465        &'data T: TryFrom<&'data CodePointTrie<'data, u32>>,
2466    {
2467        <&T>::try_from(&self.decompositions.trie)
2468            .unwrap_or_else(|_| unreachable!("Incompatible data"))
2469    }
2470
2471    normalizer_methods!();
2472
2473    decomposing_normalize_to!(
2474        /// Normalize a string slice into a `Write` sink.
2475        ,
2476        normalize_to,
2477        core::fmt::Write,
2478        &str,
2479        {
2480        },
2481        as_str,
2482        {
2483            'fast: loop {
2484                if let Some((mut upcoming, mut trie_val)) = decomposition.delegate.next() {
2485                    if starter_and_decomposes_to_self_impl(trie_val) {
2486                        continue 'fast;
2487                    }
2488
2489                    // Try to handle a single  combining mark followed by a starter in a way
2490                    // that avoids `decomposition.buffer`.
2491
2492                    if likely(trie_value_indicates_non_decomposing_non_starter(trie_val)) {
2493                        // This loop is only broken out of as goto forward.
2494                        #[expect(clippy::never_loop)]
2495                        loop {
2496                            if let Some((after_mark, after_mark_trie_value)) = decomposition.delegate.next() {
2497                                if likely(starter_and_decomposes_to_self_impl(after_mark_trie_value)) {
2498                                    continue 'fast;
2499                                }
2500                                if likely(!decomposition_starts_with_non_starter(after_mark_trie_value)) {
2501                                    // We have a decomposing starter.
2502                                    upcoming = after_mark;
2503                                    trie_val = after_mark_trie_value;
2504                                    break;
2505                                }
2506                                // We have another combining mark.
2507                                // We put the first combining mark, which we know doesn't decompose,
2508                                // directly into the buffer. We put the second one, which might decompose,
2509                                // into `decomposition.pending` for `gather_and_sort_combining` to deal
2510                                // with.
2511
2512                                let consumed_so_far_slice = &pending_slice[..pending_slice.len()
2513                                    - decomposition.delegate.as_str().len()
2514                                    - upcoming.len_utf8()
2515                                    - after_mark.len_utf8()];
2516                                sink.write_str(consumed_so_far_slice)?;
2517
2518                                debug_assert!(decomposition.buffer.is_empty());
2519
2520                                // Narrowing `trie_value` to `u8` is OK, because we already checked
2521                                // `decomposition_starts_with_non_starter`.
2522                                debug_assert!(trie_value_has_ccc(trie_val));
2523                                decomposition.buffer.push(CharacterAndClass::new(upcoming, CanonicalCombiningClass::from_icu4c_value(trie_val as u8)));
2524
2525                                decomposition.pending = Some(CharacterAndTrieValue::new(after_mark, after_mark_trie_value));
2526                                decomposition.gather_and_sort_combining(0);
2527                                continue 'outer;
2528                            }
2529                            // End of stream
2530                            sink.write_str(pending_slice)?;
2531                            return Ok(());
2532                        }
2533                    }
2534                    // End skipping over single combining mark
2535
2536                    let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_val);
2537                    let consumed_so_far_slice = &pending_slice[..pending_slice.len()
2538                        - decomposition.delegate.as_str().len()
2539                        - upcoming.len_utf8()];
2540                    sink.write_str(consumed_so_far_slice)?;
2541
2542                    // Now let's figure out if we got a starter or a non-starter.
2543                    if decomposition_starts_with_non_starter(
2544                        trie_val,
2545                    ) {
2546                        // Let this trie value to be reprocessed in case it is
2547                        // one of the rare decomposing ones.
2548                        decomposition.pending = Some(upcoming_with_trie_value);
2549                        decomposition.gather_and_sort_combining(0);
2550                        continue 'outer;
2551                    }
2552                    undecomposed_starter = upcoming_with_trie_value;
2553                    debug_assert!(decomposition.pending.is_none());
2554                    break 'fast;
2555                }
2556                // End of stream
2557                sink.write_str(pending_slice)?;
2558                return Ok(());
2559            }
2560        },
2561        text,
2562        sink,
2563        decomposition,
2564        undecomposed_starter,
2565        pending_slice,
2566        'outer,
2567        self,
2568        chars_with_trie_default_for_ascii,
2569    );
2570
2571    decomposing_normalize_to!(
2572        /// Normalize a slice of potentially-invalid UTF-8 into a `Write` sink.
2573        ///
2574        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
2575        /// according to the WHATWG Encoding Standard.
2576        ///
2577        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
2578        #[cfg(feature = "utf8_iter")]
2579        ,
2580        normalize_utf8_to,
2581        core::fmt::Write,
2582        &[u8],
2583        {
2584        },
2585        as_slice,
2586        {
2587            'fast: loop {
2588                if let Some((mut upcoming, mut trie_val)) = decomposition.delegate.next() {
2589                    if starter_and_decomposes_to_self_except_replacement(trie_val) {
2590                        // Note: The trie value of the REPLACEMENT CHARACTER is
2591                        // intentionally formatted to fail the
2592                        // `starter_and_decomposes_to_self` test even though it
2593                        // really is a starter that decomposes to self. This
2594                        // Allows moving the branch on REPLACEMENT CHARACTER
2595                        // below this `continue`.
2596                        continue 'fast;
2597                    }
2598
2599                    // Try to handle a single  combining mark followed by a starter in a way
2600                    // that avoids `decomposition.buffer`.
2601
2602                    if likely(trie_value_indicates_non_decomposing_non_starter(trie_val)) {
2603                        // This loop is only broken out of as goto forward.
2604                        #[expect(clippy::never_loop)]
2605                        loop {
2606                            if let Some((after_mark, after_mark_trie_value)) = decomposition.delegate.next() {
2607                                if likely(starter_and_decomposes_to_self_except_replacement(after_mark_trie_value)) {
2608                                    continue 'fast;
2609                                }
2610                                if likely(!decomposition_starts_with_non_starter(after_mark_trie_value)) {
2611                                    // We have a decomposing starter.
2612                                    upcoming = after_mark;
2613                                    trie_val = after_mark_trie_value;
2614                                    break;
2615                                }
2616                                // We have another combining mark.
2617                                // We put the first combining mark, which we know doesn't decompose,
2618                                // directly into the buffer. We put the second one, which might decompose,
2619                                // into `decomposition.pending` for `gather_and_sort_combining` to deal
2620                                // with.
2621
2622                                // `len_utf8` is OK, since knowing that we have two combining marks
2623                                // means that neither is U+FFFD, so we didn't have a UTF-8 error.
2624                                debug_assert_ne!(upcoming, '\u{FFFD}');
2625                                debug_assert_ne!(after_mark, '\u{FFFD}');
2626                                #[expect(clippy::indexing_slicing)]
2627                                let consumed_so_far_slice = &pending_slice[..pending_slice.len()
2628                                    - decomposition.delegate.as_slice().len()
2629                                    - upcoming.len_utf8()
2630                                    - after_mark.len_utf8()];
2631                                sink.write_str(unsafe { core::str::from_utf8_unchecked(consumed_so_far_slice) } )?;
2632
2633                                debug_assert!(decomposition.buffer.is_empty());
2634
2635                                // Narrowing `trie_value` to `u8` is OK, because we already checked
2636                                // `decomposition_starts_with_non_starter`.
2637                                debug_assert!(trie_value_has_ccc(trie_val));
2638                                decomposition.buffer.push(CharacterAndClass::new(upcoming, CanonicalCombiningClass::from_icu4c_value(trie_val as u8)));
2639
2640                                decomposition.pending = Some(CharacterAndTrieValue::new(after_mark, after_mark_trie_value));
2641                                decomposition.gather_and_sort_combining(0);
2642                                continue 'outer;
2643                            }
2644                            // End of stream
2645                            sink.write_str(unsafe { core::str::from_utf8_unchecked(pending_slice) } )?;
2646                            return Ok(());
2647                        }
2648                    }
2649                    // End skipping over single combining mark
2650
2651                    let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_val);
2652                    if unlikely(upcoming == REPLACEMENT_CHARACTER) {
2653                        // We might have an error, so fall out of the fast path.
2654
2655                        // Since the U+FFFD might signify an error, we can't
2656                        // assume `upcoming.len_utf8()` for the backoff length.
2657                        #[expect(clippy::indexing_slicing)]
2658                        let mut consumed_so_far = pending_slice[..pending_slice.len() - decomposition.delegate.as_slice().len()].chars();
2659                        let back = consumed_so_far.next_back();
2660                        debug_assert_eq!(back, Some(REPLACEMENT_CHARACTER));
2661                        let consumed_so_far_slice = consumed_so_far.as_slice();
2662                        sink.write_str(unsafe { core::str::from_utf8_unchecked(consumed_so_far_slice) } )?;
2663
2664                        // We could call `gather_and_sort_combining` here and
2665                        // `continue 'outer`, but this should be better for code
2666                        // size.
2667                        undecomposed_starter = upcoming_with_trie_value;
2668                        debug_assert!(decomposition.pending.is_none());
2669                        break 'fast;
2670                    }
2671
2672                    #[expect(clippy::indexing_slicing)]
2673                    let consumed_so_far_slice = &pending_slice[..pending_slice.len()
2674                        - decomposition.delegate.as_slice().len()
2675                        - upcoming.len_utf8()];
2676                    sink.write_str(unsafe { core::str::from_utf8_unchecked(consumed_so_far_slice) } )?;
2677
2678                    // Now let's figure out if we got a starter or a non-starter.
2679                    if decomposition_starts_with_non_starter(
2680                        upcoming_with_trie_value.trie_val,
2681                    ) {
2682                        // Let this trie value to be reprocessed in case it is
2683                        // one of the rare decomposing ones.
2684                        decomposition.pending = Some(upcoming_with_trie_value);
2685                        decomposition.gather_and_sort_combining(0);
2686                        continue 'outer;
2687                    }
2688                    undecomposed_starter = upcoming_with_trie_value;
2689                    debug_assert!(decomposition.pending.is_none());
2690                    break 'fast;
2691                }
2692                // End of stream
2693                sink.write_str(unsafe { core::str::from_utf8_unchecked(pending_slice) } )?;
2694                return Ok(());
2695            }
2696        },
2697        text,
2698        sink,
2699        decomposition,
2700        undecomposed_starter,
2701        pending_slice,
2702        'outer,
2703        self,
2704        chars_with_trie_default_for_ascii,
2705    );
2706
2707    decomposing_normalize_to!(
2708        /// Normalize a slice of potentially-invalid UTF-16 into a `Write16` sink.
2709        ///
2710        /// Unpaired surrogates are mapped to the REPLACEMENT CHARACTER
2711        /// before normalizing.
2712        ///
2713        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2714        #[cfg(feature = "utf16_iter")]
2715        ,
2716        normalize_utf16_to,
2717        write16::Write16,
2718        &[u16],
2719        {
2720            sink.size_hint(text.len())?;
2721        },
2722        as_slice,
2723        {
2724            // This loop is only broken out of as goto forward and only as release-build recovery from
2725            // detecting an internal bug without panic. (In debug builds, internal bugs panic instead.)
2726            #[expect(clippy::never_loop)]
2727            'fastwrap: loop {
2728                // Commented out `code_unit_iter` and used `ptr` and `end` to
2729                // work around https://github.com/rust-lang/rust/issues/144684 .
2730                //
2731                // let mut code_unit_iter = decomposition.delegate.as_slice().iter();
2732                let delegate_as_slice = decomposition.delegate.as_slice();
2733                let mut ptr: *const u16 = delegate_as_slice.as_ptr();
2734                // SAFETY: materializing a pointer immediately past the end of an
2735                // allocation is OK.
2736                let end: *const u16 = unsafe { ptr.add(delegate_as_slice.len()) };
2737                let decomposition_passthrough_bound = u16::from(self.decomposition_passthrough_bound);
2738                'fast: loop {
2739                    // if let Some(&upcoming_code_unit) = code_unit_iter.next() {
2740                    if likely(ptr != end) {
2741                        // SAFETY: We just checked that `ptr` has not reached `end`.
2742                        // `ptr` always advances by one, and we always have a check
2743                        // per advancement.
2744                        let upcoming_code_unit = unsafe { *ptr };
2745                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2746                        // by one points to the same allocation or to immediately
2747                        // after, which is OK.
2748                        ptr = unsafe { ptr.add(1) };
2749
2750                        // The performance of what logically is supposed to be this
2751                        // branch is _incredibly_ brittle and what LLVM ends up doing
2752                        // that affects the performance of what's logically about this
2753                        // decision can swing to double/halve the throughput for Basic
2754                        // Latin in ways that are completely unintuitive. Basically _any_
2755                        // change to _any_ code that participates in how LLVM sees the
2756                        // code around here can make the perf fall over. In seems that
2757                        // manually annotating this branch as likely has worse effects
2758                        // on non-Basic-Latin input that the case where LLVM just happens to
2759                        // do the right thing.
2760                        //
2761                        // What happens with this branch may depend on what sink type
2762                        // this code is monomorphized over.
2763                        //
2764                        // What a terrible sink of developer time!
2765                        if upcoming_code_unit < decomposition_passthrough_bound {
2766                            continue 'fast;
2767                        }
2768                        // We might be doing a trie lookup by surrogate. Surrogates get
2769                        // a decomposition to U+FFFD.
2770                        let mut trie_value = decomposition.delegate.trie().bmp(upcoming_code_unit);
2771                        if likely(starter_and_decomposes_to_self_impl(trie_value)) {
2772                            continue 'fast;
2773                        }
2774
2775                        let mut upcoming32 = u32::from(upcoming_code_unit);
2776
2777                        // We might now be looking at a surrogate.
2778                        // The loop is only broken out of as goto forward
2779                        #[expect(clippy::never_loop)]
2780                        'surrogateloop: loop {
2781
2782                            // Try to handle a single BMP combining mark followed by a starter in a way
2783                            // that avoids `decomposition.buffer`. Crucial for perf competitiveness with ICU4C.
2784
2785                            if likely(trie_value_indicates_non_decomposing_non_starter(trie_value)) {
2786                                if likely(ptr != end) {
2787                                    // SAFETY: We just checked that `ptr` has not reached `end`.
2788                                    // `ptr` always advances by one, and we always have a check
2789                                    // per advancement.
2790                                    let after_mark_code_unit = unsafe { *ptr };
2791                                    // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2792                                    // by one points to the same allocation or to immediately
2793                                    // after, which is OK.
2794                                    ptr = unsafe { ptr.add(1) };
2795                                    let after_mark_trie_value = decomposition.delegate.trie().bmp(after_mark_code_unit);
2796                                    if likely(starter_and_decomposes_to_self_impl(after_mark_trie_value)) {
2797                                        continue 'fast;
2798                                    }
2799                                    if unlikely(in_inclusive_range16(after_mark_code_unit, 0xD800, 0xDFFF)) {
2800                                        // We have a surrogate. Too complicated to deal with, because
2801                                        // it might be the first half of a combining mark.
2802                                        // Pretend we didn't see it.
2803
2804                                        // SAFETY: We just incremented `ptr`, so decrementing it
2805                                        // has to stay within the allocation.
2806                                        ptr = unsafe { ptr.sub(1) };
2807                                        break 'surrogateloop;
2808                                    }
2809                                    if likely(!decomposition_starts_with_non_starter(after_mark_trie_value)) {
2810                                        // We have a decomposing starter.
2811
2812                                        // No need to sync `upcoming_code_unit`, since nothing reads it below.
2813                                        upcoming32 = u32::from(after_mark_code_unit);
2814                                        trie_value = after_mark_trie_value;
2815                                        break 'surrogateloop;
2816                                    }
2817                                    // We have another combining mark.
2818                                    // We put the first combining mark, which we know doesn't decompose,
2819                                    // directly into the buffer. We put the second one, which might decompose,
2820                                    // into `decomposition.pending` for `gather_and_sort_combining` to deal
2821                                    // with.
2822
2823                                    let Some(consumed_so_far_slice) = pending_slice.get(..pending_slice.len() -
2824                                        // code_unit_iter.as_slice().len()
2825                                        // SAFETY: `ptr` and `end` have been derived from the same allocation
2826                                        // and `ptr` is never greater than `end`.
2827                                        unsafe { end.offset_from(ptr) as usize }
2828                                        - 2) else {
2829                                        // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
2830                                        debug_assert!(false);
2831                                        // Throw away the results of the fast path.
2832                                        break 'fastwrap;
2833                                    };
2834                                    sink.write_slice(consumed_so_far_slice)?;
2835
2836                                    // Our belief that `upcoming32` is not a surrogate is based on trie data,
2837                                    // which might be GIGO.
2838                                    let upcoming = char_from_u32(upcoming32);
2839
2840                                    debug_assert!(decomposition.buffer.is_empty());
2841
2842                                    // Narrowing `trie_value` to `u8` is OK, because we already checked
2843                                    // `decomposition_starts_with_non_starter`.
2844                                    debug_assert!(trie_value_has_ccc(trie_value));
2845                                    decomposition.buffer.push(CharacterAndClass::new(upcoming, CanonicalCombiningClass::from_icu4c_value(trie_value as u8)));
2846
2847                                    // Sync with main iterator
2848                                    // SAFETY: `ptr` and `end` have been derived from the same allocation
2849                                    // and `ptr` is never greater than `end`.
2850                                    decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars_with_trie(decomposition.delegate.trie());
2851                                    // Let this trie value to be reprocessed in case it is
2852                                    // one of the rare decomposing ones.
2853                                    // SAFETY: We checked above that we don't have surrogate.
2854                                    let after_mark_char = unsafe { char::from_u32_unchecked(u32::from(after_mark_code_unit))};
2855                                    decomposition.pending = Some(CharacterAndTrieValue::new(after_mark_char, after_mark_trie_value));
2856                                    decomposition.gather_and_sort_combining(0);
2857                                    continue 'outer;
2858                                }
2859                                // End of stream
2860                                sink.write_slice(pending_slice)?;
2861                                return Ok(());
2862                            }
2863
2864                            // End skipping over single combining mark
2865
2866                            // LLVM's optimizations are incredibly brittle for the code _above_,
2867                            // and using `likely` _below_ without using it _above_ helps!
2868                            // What a massive sink of developer time!
2869                            // Seriously, the effect of these annotations is massively
2870                            // unintuitive. Measure everything!
2871                            // Notably, the `if likely(...)` formulation optimizes differently
2872                            // than just putting `cold_path()` on the `else` path!
2873                            let surrogate_base = upcoming32.wrapping_sub(0xD800);
2874                            if likely(surrogate_base > (0xDFFF - 0xD800)) {
2875                                // Not surrogate
2876                                break 'surrogateloop;
2877                            }
2878                            if likely(surrogate_base <= (0xDBFF - 0xD800)) {
2879                                // let iter_backup = code_unit_iter.clone();
2880                                // if let Some(&low) = code_unit_iter.next() {
2881                                if likely(ptr != end) {
2882                                    // SAFETY: We just checked that `ptr` has not reached `end`.
2883                                    // `ptr` always advances by one, and we always have a check
2884                                    // per advancement.
2885                                    let low = unsafe { *ptr };
2886                                    if likely(in_inclusive_range16(low, 0xDC00, 0xDFFF)) {
2887                                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2888                                        // by one points to the same allocation or to immediately
2889                                        // after, which is OK.
2890                                        ptr = unsafe { ptr.add(1) };
2891
2892                                        upcoming32 = (upcoming32 << 10) + u32::from(low)
2893                                            - (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
2894                                        // Successfully-paired surrogate. Read from the trie again.
2895                                        trie_value = {
2896                                            // Semantically, this bit of conditional compilation makes no sense.
2897                                            // The purpose is to keep LLVM seeing the untyped trie case the way
2898                                            // it did before so as not to regress the performance of the untyped
2899                                            // case due to unintuitive optimizer effects. If you care about the
2900                                            // perf of the untyped trie case and have better ideas, please try
2901                                            // something better.
2902                                            #[cfg(feature = "serde")]
2903                                            {decomposition.delegate.trie().code_point(upcoming32)}
2904                                            #[cfg(not(feature = "serde"))]
2905                                            {decomposition.delegate.trie().supplementary(upcoming32)}
2906                                        };
2907                                        if likely(starter_and_decomposes_to_self_impl(trie_value)) {
2908                                            continue 'fast;
2909                                        }
2910                                        break 'surrogateloop;
2911                                    // } else {
2912                                    //     code_unit_iter = iter_backup;
2913                                    }
2914                                }
2915                            }
2916                            // unpaired surrogate
2917                            upcoming32 = 0xFFFD; // Safe value for `char::from_u32_unchecked` and matches later potential error check.
2918                            // trie_value already holds a decomposition to U+FFFD.
2919                            break 'surrogateloop;
2920                        }
2921
2922                        let upcoming = unsafe { char::from_u32_unchecked(upcoming32) };
2923                        let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_value);
2924
2925
2926                        let Some(consumed_so_far_slice) = pending_slice.get(..pending_slice.len() -
2927                            // code_unit_iter.as_slice().len()
2928                            // SAFETY: `ptr` and `end` have been derived from the same allocation
2929                            // and `ptr` is never greater than `end`.
2930                            unsafe { end.offset_from(ptr) as usize }
2931                            - upcoming.len_utf16()) else {
2932                            // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
2933                            debug_assert!(false);
2934                            // Throw away the results of the fast path.
2935                            break 'fastwrap;
2936                        };
2937                        sink.write_slice(consumed_so_far_slice)?;
2938
2939                        if decomposition_starts_with_non_starter(
2940                            upcoming_with_trie_value.trie_val,
2941                        ) {
2942                            // Sync with main iterator
2943                            // decomposition.delegate = code_unit_iter.as_slice().chars();
2944                            // SAFETY: `ptr` and `end` have been derived from the same allocation
2945                            // and `ptr` is never greater than `end`.
2946                            decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars_with_trie(decomposition.delegate.trie());
2947                            // Let this trie value to be reprocessed in case it is
2948                            // one of the rare decomposing ones.
2949                            decomposition.pending = Some(upcoming_with_trie_value);
2950                            decomposition.gather_and_sort_combining(0);
2951                            continue 'outer;
2952                        }
2953                        undecomposed_starter = upcoming_with_trie_value;
2954                        debug_assert!(decomposition.pending.is_none());
2955                        break 'fast;
2956                    }
2957                    // End of stream
2958                    sink.write_slice(pending_slice)?;
2959                    return Ok(());
2960                }
2961                // Sync the main iterator
2962                // decomposition.delegate = code_unit_iter.as_slice().chars();
2963                // SAFETY: `ptr` and `end` have been derived from the same allocation
2964                // and `ptr` is never greater than `end`.
2965                decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars_with_trie(decomposition.delegate.trie());
2966                break 'fastwrap;
2967            }
2968        },
2969        text,
2970        sink,
2971        decomposition,
2972        undecomposed_starter,
2973        pending_slice,
2974        'outer,
2975        self,
2976        chars_with_trie,
2977    );
2978}
2979
2980/// A normalizer for performing decomposing normalization.
2981#[derive(Debug)]
2982pub struct DecomposingNormalizer {
2983    decompositions: DataPayload<NormalizerNfdDataV1>,
2984    tables: DataPayload<NormalizerNfdTablesV1>,
2985    supplementary_tables: Option<DataPayload<NormalizerNfkdTablesV1>>,
2986    decomposition_passthrough_bound: u8, // never above 0xC0
2987    composition_passthrough_bound: u16,  // never above 0x0300
2988}
2989
2990impl DecomposingNormalizer {
2991    /// Constructs a borrowed version of this type for more efficient querying.
2992    pub fn as_borrowed(&self) -> DecomposingNormalizerBorrowed<'_> {
2993        DecomposingNormalizerBorrowed {
2994            decompositions: self.decompositions.get(),
2995            tables: self.tables.get(),
2996            supplementary_tables: self.supplementary_tables.as_ref().map(|s| s.get()),
2997            decomposition_passthrough_bound: self.decomposition_passthrough_bound,
2998            composition_passthrough_bound: self.composition_passthrough_bound,
2999        }
3000    }
3001
3002    /// NFD constructor using compiled data.
3003    ///
3004    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
3005    ///
3006    /// [📚 Help choosing a constructor](icu_provider::constructors)
3007    #[cfg(feature = "compiled_data")]
3008    pub const fn new_nfd() -> DecomposingNormalizerBorrowed<'static> {
3009        DecomposingNormalizerBorrowed::new_nfd()
3010    }
3011
3012    icu_provider::gen_buffer_data_constructors!(
3013        () -> error: DataError,
3014        functions: [
3015            new_nfd: skip,
3016            try_new_nfd_with_buffer_provider,
3017            try_new_nfd_unstable,
3018            Self,
3019        ]
3020    );
3021
3022    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfd)]
3023    pub fn try_new_nfd_unstable<D>(provider: &D) -> Result<Self, DataError>
3024    where
3025        D: DataProvider<NormalizerNfdDataV1> + DataProvider<NormalizerNfdTablesV1> + ?Sized,
3026    {
3027        let decompositions: DataPayload<NormalizerNfdDataV1> =
3028            provider.load(Default::default())?.payload;
3029        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
3030
3031        if tables.get().scalars16.len() + tables.get().scalars24.len() > 0xFFF {
3032            // The data is from a future where there exists a normalization flavor whose
3033            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
3034            // of space. If a good use case from such a decomposition flavor arises, we can
3035            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
3036            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
3037            // since for now the masks are hard-coded, error out.
3038            return Err(
3039                DataError::custom("future extension").with_marker(NormalizerNfdTablesV1::INFO)
3040            );
3041        }
3042
3043        let cap = decompositions.get().passthrough_cap;
3044        if cap > 0x0300 {
3045            return Err(DataError::custom("invalid").with_marker(NormalizerNfdDataV1::INFO));
3046        }
3047        if cap < 0x80 {
3048            return Err(DataError::custom("invalid").with_marker(NormalizerNfdDataV1::INFO));
3049        }
3050        let decomposition_capped = cap.min(0xC0);
3051        let composition_capped = cap.min(0x0300);
3052
3053        Ok(DecomposingNormalizer {
3054            decompositions,
3055            tables,
3056            supplementary_tables: None,
3057            decomposition_passthrough_bound: decomposition_capped as u8,
3058            composition_passthrough_bound: composition_capped,
3059        })
3060    }
3061
3062    icu_provider::gen_buffer_data_constructors!(
3063        () -> error: DataError,
3064        functions: [
3065            new_nfkd: skip,
3066            try_new_nfkd_with_buffer_provider,
3067            try_new_nfkd_unstable,
3068            Self,
3069        ]
3070    );
3071
3072    /// NFKD constructor using compiled data.
3073    ///
3074    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
3075    ///
3076    /// [📚 Help choosing a constructor](icu_provider::constructors)
3077    #[cfg(feature = "compiled_data")]
3078    pub const fn new_nfkd() -> DecomposingNormalizerBorrowed<'static> {
3079        DecomposingNormalizerBorrowed::new_nfkd()
3080    }
3081
3082    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfkd)]
3083    pub fn try_new_nfkd_unstable<D>(provider: &D) -> Result<Self, DataError>
3084    where
3085        D: DataProvider<NormalizerNfkdDataV1>
3086            + DataProvider<NormalizerNfdTablesV1>
3087            + DataProvider<NormalizerNfkdTablesV1>
3088            + ?Sized,
3089    {
3090        let decompositions: DataPayload<NormalizerNfkdDataV1> =
3091            provider.load(Default::default())?.payload;
3092        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
3093        let supplementary_tables: DataPayload<NormalizerNfkdTablesV1> =
3094            provider.load(Default::default())?.payload;
3095
3096        if tables.get().scalars16.len()
3097            + tables.get().scalars24.len()
3098            + supplementary_tables.get().scalars16.len()
3099            + supplementary_tables.get().scalars24.len()
3100            > 0xFFF
3101        {
3102            // The data is from a future where there exists a normalization flavor whose
3103            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
3104            // of space. If a good use case from such a decomposition flavor arises, we can
3105            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
3106            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
3107            // since for now the masks are hard-coded, error out.
3108            return Err(
3109                DataError::custom("future extension").with_marker(NormalizerNfdTablesV1::INFO)
3110            );
3111        }
3112
3113        let cap = decompositions.get().passthrough_cap;
3114        if cap > 0x0300 {
3115            return Err(DataError::custom("invalid").with_marker(NormalizerNfkdDataV1::INFO));
3116        }
3117        if cap < 0x80 {
3118            return Err(DataError::custom("invalid").with_marker(NormalizerNfdDataV1::INFO));
3119        }
3120        let decomposition_capped = cap.min(0xC0);
3121        let composition_capped = cap.min(0x0300);
3122
3123        Ok(DecomposingNormalizer {
3124            decompositions: decompositions.cast(),
3125            tables,
3126            supplementary_tables: Some(supplementary_tables),
3127            decomposition_passthrough_bound: decomposition_capped as u8,
3128            composition_passthrough_bound: composition_capped,
3129        })
3130    }
3131
3132    /// UTS 46 decomposed constructor (testing only)
3133    ///
3134    /// This is a special building block normalization for IDNA. It is the decomposed counterpart of
3135    /// ICU4C's UTS 46 normalization with two exceptions: characters that UTS 46 disallows and
3136    /// ICU4C maps to U+FFFD and characters that UTS 46 maps to the empty string normalize as in
3137    /// NFD in this normalization. In both cases, the previous UTS 46 processing before using
3138    /// normalization is expected to deal with these characters. Making the disallowed characters
3139    /// behave like this is beneficial to data size, and this normalizer implementation cannot
3140    /// deal with a character normalizing to the empty string, which doesn't happen in NFD or
3141    /// NFKD as of Unicode 14.
3142    ///
3143    /// Warning: In this normalization, U+0345 COMBINING GREEK YPOGEGRAMMENI exhibits a behavior
3144    /// that no character in Unicode exhibits in NFD, NFKD, NFC, or NFKC: Case folding turns
3145    /// U+0345 from a reordered character into a non-reordered character before reordering happens.
3146    /// Therefore, the output of this normalization may differ for different inputs that are
3147    /// canonically equivalent with each other if they differ by how U+0345 is ordered relative
3148    /// to other reorderable characters.
3149    pub(crate) fn try_new_uts46_decomposed_unstable<D>(provider: &D) -> Result<Self, DataError>
3150    where
3151        D: DataProvider<NormalizerUts46DataV1>
3152            + DataProvider<NormalizerNfdTablesV1>
3153            + DataProvider<NormalizerNfkdTablesV1>
3154            // UTS 46 tables merged into CompatibilityDecompositionTablesV1
3155            + ?Sized,
3156    {
3157        let decompositions: DataPayload<NormalizerUts46DataV1> =
3158            provider.load(Default::default())?.payload;
3159        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
3160        let supplementary_tables: DataPayload<NormalizerNfkdTablesV1> =
3161            provider.load(Default::default())?.payload;
3162
3163        if tables.get().scalars16.len()
3164            + tables.get().scalars24.len()
3165            + supplementary_tables.get().scalars16.len()
3166            + supplementary_tables.get().scalars24.len()
3167            > 0xFFF
3168        {
3169            // The data is from a future where there exists a normalization flavor whose
3170            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
3171            // of space. If a good use case from such a decomposition flavor arises, we can
3172            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
3173            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
3174            // since for now the masks are hard-coded, error out.
3175            return Err(
3176                DataError::custom("future extension").with_marker(NormalizerNfdTablesV1::INFO)
3177            );
3178        }
3179
3180        let cap = decompositions.get().passthrough_cap;
3181        if cap > 0x0300 {
3182            return Err(DataError::custom("invalid").with_marker(NormalizerUts46DataV1::INFO));
3183        }
3184        // Can be below 0x80!
3185        let decomposition_capped = cap.min(0xC0);
3186        let composition_capped = cap.min(0x0300);
3187
3188        Ok(DecomposingNormalizer {
3189            decompositions: decompositions.cast(),
3190            tables,
3191            supplementary_tables: Some(supplementary_tables),
3192            decomposition_passthrough_bound: decomposition_capped as u8,
3193            composition_passthrough_bound: composition_capped,
3194        })
3195    }
3196}
3197
3198/// Borrowed version of a normalizer for performing composing normalization.
3199#[derive(Debug)]
3200pub struct ComposingNormalizerBorrowed<'a> {
3201    decomposing_normalizer: DecomposingNormalizerBorrowed<'a>,
3202    canonical_compositions: CanonicalCompositionsBorrowed<'a>,
3203}
3204
3205impl ComposingNormalizerBorrowed<'static> {
3206    /// Cheaply converts a [`ComposingNormalizerBorrowed<'static>`] into a [`ComposingNormalizer`].
3207    ///
3208    /// Note: Due to branching and indirection, using [`ComposingNormalizer`] might inhibit some
3209    /// compile-time optimizations that are possible with [`ComposingNormalizerBorrowed`].
3210    pub const fn static_to_owned(self) -> ComposingNormalizer {
3211        ComposingNormalizer {
3212            decomposing_normalizer: self.decomposing_normalizer.static_to_owned(),
3213            canonical_compositions: self.canonical_compositions.static_to_owned(),
3214        }
3215    }
3216
3217    /// NFC constructor using compiled data.
3218    ///
3219    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
3220    ///
3221    /// [📚 Help choosing a constructor](icu_provider::constructors)
3222    #[cfg(feature = "compiled_data")]
3223    pub const fn new_nfc() -> Self {
3224        ComposingNormalizerBorrowed {
3225            decomposing_normalizer: DecomposingNormalizerBorrowed::new_nfd(),
3226            canonical_compositions: CanonicalCompositionsBorrowed::Current(
3227                provider::Baked::SINGLETON_NORMALIZER_NFC_V2,
3228            ),
3229        }
3230    }
3231
3232    /// NFKC constructor using compiled data.
3233    ///
3234    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
3235    ///
3236    /// [📚 Help choosing a constructor](icu_provider::constructors)
3237    #[cfg(feature = "compiled_data")]
3238    pub const fn new_nfkc() -> Self {
3239        ComposingNormalizerBorrowed {
3240            decomposing_normalizer: DecomposingNormalizerBorrowed::new_nfkd(),
3241            canonical_compositions: CanonicalCompositionsBorrowed::Current(
3242                provider::Baked::SINGLETON_NORMALIZER_NFC_V2,
3243            ),
3244        }
3245    }
3246
3247    /// This is a special building block normalization for IDNA that implements parts of the Map
3248    /// step and the following Normalize step.
3249    ///
3250    /// Warning: In this normalization, U+0345 COMBINING GREEK YPOGEGRAMMENI exhibits a behavior
3251    /// that no character in Unicode exhibits in NFD, NFKD, NFC, or NFKC: Case folding turns
3252    /// U+0345 from a reordered character into a non-reordered character before reordering happens.
3253    /// Therefore, the output of this normalization may differ for different inputs that are
3254    /// canonically equivalents with each other if they differ by how U+0345 is ordered relative
3255    /// to other reorderable characters.
3256    #[cfg(feature = "compiled_data")]
3257    pub(crate) const fn new_uts46() -> Self {
3258        ComposingNormalizerBorrowed {
3259            decomposing_normalizer: DecomposingNormalizerBorrowed::new_uts46_decomposed(),
3260            canonical_compositions: CanonicalCompositionsBorrowed::Current(
3261                provider::Baked::SINGLETON_NORMALIZER_NFC_V2,
3262            ),
3263        }
3264    }
3265}
3266
3267impl<'data> ComposingNormalizerBorrowed<'data> {
3268    /// Wraps a delegate iterator into a composing iterator
3269    /// adapter by using the data already held by this normalizer.
3270    #[inline]
3271    pub fn normalize_iter<I: Iterator<Item = char>>(&'data self, iter: I) -> Composition<'data, I> {
3272        let mut ret = Composition {
3273            inner: self.normalize_iter_private(CharIterWithTrie::new(iter, self.trie())),
3274        };
3275        ret.inner.decomposition.init(); // Discard the U+0000.
3276        ret
3277    }
3278
3279    /// There's an extra U+FFFD at the start. The caller must deal with it.
3280    #[inline(always)]
3281    fn normalize_iter_private<
3282        I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
3283        T: AbstractCodePointTrie<'data, u32> + 'data,
3284        P: IteratorPolicy,
3285    >(
3286        &'data self,
3287        iter: I,
3288    ) -> CompositionInner<'data, I, T, P> {
3289        CompositionInner::new(
3290            DecompositionInner::new_with_supplements(
3291                iter,
3292                self.decomposing_normalizer.tables,
3293                self.decomposing_normalizer.supplementary_tables,
3294            ),
3295            self.canonical_compositions.as_ref(),
3296        )
3297    }
3298
3299    fn trie<T: AbstractCodePointTrie<'data, u32>>(&self) -> &'data T
3300    where
3301        &'data T: TryFrom<&'data CodePointTrie<'data, u32>>,
3302    {
3303        self.decomposing_normalizer.trie()
3304    }
3305
3306    normalizer_methods!();
3307
3308    composing_normalize_to!(
3309        /// Normalize a string slice into a `Write` sink.
3310        ,
3311        normalize_to,
3312        core::fmt::Write,
3313        &str,
3314        {},
3315        true,
3316        as_str,
3317        {
3318            let composition_passthrough_byte_bound = if self.decomposing_normalizer.composition_passthrough_bound == 0x300 {
3319                0xCCu8
3320            } else {
3321                // We can make this fancy if a normalization other than NFC where looking at
3322                // non-ASCII lead bytes is worthwhile is ever introduced.
3323                self.decomposing_normalizer.composition_passthrough_bound.min(0x80) as u8
3324            };
3325            // Attributes have to be on blocks, so hoisting all the way here.
3326            let mut code_unit_iter = composition.decomposition.delegate.as_str().as_bytes().iter();
3327            'fast: loop {
3328                if let Some(b) = code_unit_iter.next() {
3329                    let upcoming_byte = *b;
3330                    if upcoming_byte < composition_passthrough_byte_bound {
3331                        // Fast-track succeeded!
3332                        continue 'fast;
3333                    }
3334                    // Begin manual inlining from `CharsWithTrie`
3335
3336                    // SAFETY: Since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, we may assume that we
3337                    // have a valid lead byte. We can assume that the lead byte won't be ASCII, because `composition_passthrough_byte_bound`
3338                    // is never less than 0x80. Not need to check for other cases.
3339                    let (upcoming, trie_val) = if upcoming_byte < 0xE0 {
3340                        // Two-byte sequence.
3341                        // SAFETY, since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, we may assume the
3342                        // presence of a trail byte.
3343                        let trail = *unsafe { code_unit_iter.next().unwrap_unchecked() };
3344                        let high_five = u32::from(upcoming_byte & 0b11_111);
3345                        let low_six = u32::from(trail & 0b111_111);
3346                        // SAFETY: By construction, `high_five` and `low_six` conform
3347                        // to the invariant of `utf8_two_byte`.
3348                        let v = unsafe { composition.decomposition.delegate.trie().utf8_two_byte(high_five, low_six) };
3349                        // SAFETY: Since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, `lead` must be a
3350                        // valid (not overlong) two-byte lead and `trail` must be a valid
3351                        // trail. Therefore, the following shift and OR stays in the
3352                        // scalar value range.
3353                        let c = unsafe { char::from_u32_unchecked((high_five << 6) | low_six) };
3354                        (c, v)
3355                    } else if upcoming_byte < 0xF0 {
3356                        // Three-byte sequence.
3357                        // SAFETY, since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, we may assume the
3358                        // presence of two trail bytes.
3359                        let second = *unsafe { code_unit_iter.next().unwrap_unchecked() };
3360                        let third = *unsafe { code_unit_iter.next().unwrap_unchecked() };
3361                        let high_ten = (u32::from(upcoming_byte & 0b1111) << 6) | u32::from(second & 0b111_111);
3362                        let low_six = u32::from(third & 0b111_111);
3363                        // SAFETY: By construction, `high_ten` and `low_six` conform
3364                        // to the invariant of `utf8_three_byte`.
3365                        let v = unsafe { composition.decomposition.delegate.trie().utf8_three_byte(high_ten, low_six) };
3366                        // SAFETY: Since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, `lead` must be a
3367                        // valid (not overlong) three-byte lead and `second` and `third`
3368                        // must be valid trails. Therefore, the following shift and OR
3369                        // stays in the scalar value range.
3370                        let c = unsafe { char::from_u32_unchecked((high_ten << 6) | low_six) };
3371                        (c, v)
3372                    } else {
3373                        // Four-byte sequence
3374                        // SAFETY, since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, we may assume the
3375                        // presence of three trail bytes.
3376                        let second = *unsafe { code_unit_iter.next().unwrap_unchecked() };
3377                        let third = *unsafe { code_unit_iter.next().unwrap_unchecked() };
3378                        let fourth = *unsafe { code_unit_iter.next().unwrap_unchecked() };
3379                        // SAFETY: Since `code_unit_iter` came from `str` and we always advance by a full UTF-8 sequence, `lead` must be a
3380                        // valid (not overlong or out-of-range) four-byte lead and `second`,
3381                        // `third`, and `fourth` must be valid trails. Therefore, the
3382                        // following shift and OR stays in the scalar value range.
3383                        let c = unsafe {
3384                            char::from_u32_unchecked(
3385                                (u32::from(upcoming_byte & 0b111) << 18)
3386                                    | (u32::from(second & 0b111_111) << 12)
3387                                    | (u32::from(third & 0b111_111) << 6)
3388                                    | u32::from(fourth & 0b111_111),
3389                            )
3390                        };
3391                        (c, composition.decomposition.delegate.trie().supplementary(c as u32))
3392                    };
3393
3394                    // End manual inlining from `CharsWithTrie`
3395                    if potential_passthrough_and_cannot_combine_backwards(trie_val) {
3396                        continue 'fast;
3397                    }
3398                    // SAFETY: We've advanced `code_unit_iter` to a UTF-8 boundary.
3399                    composition.decomposition.delegate = unsafe { core::str::from_utf8_unchecked(code_unit_iter.as_slice())}.chars_with_trie_default_for_ascii(composition.decomposition.delegate.trie());
3400                    let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_val);
3401                    // We need to fall off the fast path.
3402                    composition.decomposition.pending = Some(upcoming_with_trie_value);
3403
3404                    // slicing and unwrap OK, because we've just evidently read enough previously.
3405                    let mut consumed_so_far = pending_slice[..pending_slice.len() - composition.decomposition.delegate.as_str().len() - upcoming.len_utf8()].chars_with_trie_default_for_ascii(composition.decomposition.delegate.trie());
3406                    // Whether we could do something better than `next_back()` below is
3407                    // https://github.com/unicode-org/icu4x/issues/7525
3408                    // `unwrap` OK, because we've previously manage to read the previous character
3409                    #[expect(clippy::unwrap_used)]
3410                    let (undecomposed, undecomposed_trie_val) = consumed_so_far.next_back().unwrap();
3411                    undecomposed_starter = CharacterAndTrieValue::new(undecomposed, undecomposed_trie_val);
3412                    let consumed_so_far_slice = consumed_so_far.as_str();
3413                    sink.write_str(consumed_so_far_slice)?;
3414                    break 'fast;
3415                }
3416                // End of stream
3417                sink.write_str(pending_slice)?;
3418                return Ok(());
3419            }
3420        },
3421        text,
3422        sink,
3423        composition,
3424        undecomposed_starter,
3425        pending_slice,
3426        len_utf8,
3427        self,
3428        chars_with_trie_default_for_ascii,
3429    );
3430
3431    composing_normalize_to!(
3432        /// Normalize a slice of potentially-invalid UTF-8 into a `Write` sink.
3433        ///
3434        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
3435        /// according to the WHATWG Encoding Standard.
3436        ///
3437        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
3438        #[cfg(feature = "utf8_iter")]
3439        ,
3440        normalize_utf8_to,
3441        core::fmt::Write,
3442        &[u8],
3443        {},
3444        false,
3445        as_slice,
3446        {
3447            'fast: loop {
3448                if let Some((upcoming, trie_val)) = composition.decomposition.delegate.next() {
3449                    if potential_passthrough_and_cannot_combine_backwards(trie_val) {
3450                        // Note: The trie value of the REPLACEMENT CHARACTER is
3451                        // intentionally formatted to fail the
3452                        // `potential_passthrough_and_cannot_combine_backwards`
3453                        // test even though it really is a starter that decomposes
3454                        // to self and cannot combine backwards. This
3455                        // Allows moving the branch on REPLACEMENT CHARACTER
3456                        // below this `continue`.
3457                        continue 'fast;
3458                    }
3459                    // We need to fall off the fast path.
3460
3461                    let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_val);
3462                    if unlikely(upcoming == REPLACEMENT_CHARACTER) {
3463                        // Can't tell if this is an error or a literal U+FFFD in
3464                        // the input. Assuming the former to be sure.
3465
3466                        // Since the U+FFFD might signify an error, we can't
3467                        // assume `upcoming.len_utf8()` for the backoff length.
3468                        #[expect(clippy::indexing_slicing)]
3469                        let mut consumed_so_far = pending_slice[..pending_slice.len() - composition.decomposition.delegate.as_slice().len()].chars();
3470                        let back = consumed_so_far.next_back();
3471                        debug_assert_eq!(back, Some(REPLACEMENT_CHARACTER));
3472                        let consumed_so_far_slice = consumed_so_far.as_slice();
3473                        sink.write_str(unsafe { core::str::from_utf8_unchecked(consumed_so_far_slice) })?;
3474                        undecomposed_starter = CharacterAndTrieValue::new(REPLACEMENT_CHARACTER, 0);
3475                        composition.decomposition.pending = None;
3476                        break 'fast;
3477                    }
3478
3479                    composition.decomposition.pending = Some(upcoming_with_trie_value);
3480                    // slicing and unwrap OK, because we've just evidently read enough previously.
3481                    // `unwrap` OK, because we've previously manage to read the previous character
3482                    #[expect(clippy::indexing_slicing)]
3483                    let mut consumed_so_far = pending_slice[..pending_slice.len() - composition.decomposition.delegate.as_slice().len() - upcoming.len_utf8()].chars_with_trie_default_for_ascii(composition.decomposition.delegate.trie());
3484                    #[expect(clippy::unwrap_used)]
3485                    {
3486                        // Whether we could do something better than `next_back()` below is
3487                        // https://github.com/unicode-org/icu4x/issues/7525
3488                        let (undecomposed, undecomposed_trie_val) = consumed_so_far.next_back().unwrap();
3489                        undecomposed_starter = CharacterAndTrieValue::new(undecomposed, undecomposed_trie_val);
3490                    }
3491                    let consumed_so_far_slice = consumed_so_far.as_slice();
3492                    sink.write_str(unsafe { core::str::from_utf8_unchecked(consumed_so_far_slice)})?;
3493                    break 'fast;
3494                }
3495                // End of stream
3496                sink.write_str(unsafe { core::str::from_utf8_unchecked(pending_slice) })?;
3497                return Ok(());
3498            }
3499        },
3500        text,
3501        sink,
3502        composition,
3503        undecomposed_starter,
3504        pending_slice,
3505        len_utf8,
3506        self,
3507        chars_with_trie_default_for_ascii,
3508    );
3509
3510    composing_normalize_to!(
3511        /// Normalize a slice of potentially-invalid UTF-16 into a `Write16` sink.
3512        ///
3513        /// Unpaired surrogates are mapped to the REPLACEMENT CHARACTER
3514        /// before normalizing.
3515        ///
3516        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
3517        #[cfg(feature = "utf16_iter")]
3518        ,
3519        normalize_utf16_to,
3520        write16::Write16,
3521        &[u16],
3522        {
3523            sink.size_hint(text.len())?;
3524        },
3525        false,
3526        as_slice,
3527        {
3528            // This loop is only broken out of as goto forward and only as release-build recovery from
3529            // detecting an internal bug without panic. (In debug builds, internal bugs panic instead.)
3530            #[expect(clippy::never_loop)]
3531            'fastwrap: loop {
3532                // Commented out `code_unit_iter` and used `ptr` and `end` to
3533                // work around https://github.com/rust-lang/rust/issues/144684 .
3534                //
3535                // let mut code_unit_iter = composition.decomposition.delegate.as_slice().iter();
3536                let delegate_as_slice = composition.decomposition.delegate.as_slice();
3537                let mut ptr: *const u16 = delegate_as_slice.as_ptr();
3538                // SAFETY: materializing a pointer immediately past the end of an
3539                // allocation is OK.
3540                let end: *const u16 = unsafe { ptr.add(delegate_as_slice.len()) };
3541                let composition_passthrough_bound = self.decomposing_normalizer.composition_passthrough_bound;
3542                'fast: loop {
3543                    // Only broken out of as goto forward
3544                    'end: loop {
3545                        // if let Some(&upcoming_code_unit) = code_unit_iter.next() {
3546                        if likely(ptr != end) {
3547                            // SAFETY: We just checked that `ptr` has not reached `end`.
3548                            // `ptr` always advances by one, and we always have a check
3549                            // per advancement.
3550                            let mut upcoming_code_unit = unsafe { *ptr };
3551                            // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
3552                            // by one points to the same allocation or to immediately
3553                            // after, which is OK.
3554                            ptr = unsafe { ptr.add(1) };
3555
3556                            if likely(upcoming_code_unit < composition_passthrough_bound) {
3557                                // No need for surrogate or U+FFFD check, because
3558                                // `composition_passthrough_bound` cannot be higher than
3559                                // U+0300.
3560                                // Fast-track succeeded!
3561                                continue 'fast;
3562                            }
3563                            if unlikely(in_inclusive_range16(upcoming_code_unit, 0x2013, 0x2022)) && upcoming_code_unit != 0x2017 {
3564                                // Don't allow dashes and smart quotes to fall off the trie-bypass
3565                                // path.
3566                                // Fast-track succeeded!
3567                                continue 'fast;
3568                            }
3569                            // This is intentionally bimodal so that if we exit the above trie-bypass path,
3570                            // we stay on the trie-reading path until we've processed a non-BMP character
3571                            // (likely emoji) or to the end of this passthrough run. This makes NFC faster
3572                            // than ICU4C for most real-world content. The result is not optimal for NFKC
3573                            // Latin, but let's take the NFC non-Latin win.
3574                            let mut trie_value;
3575                            let mut upcoming32; // May be surrogate
3576                            loop {
3577                                // We might be doing a trie lookup by surrogate. Surrogates get
3578                                // a decomposition to U+FFFD.
3579                                trie_value = composition.decomposition.delegate.trie().bmp(upcoming_code_unit);
3580                                if likely(potential_passthrough_and_cannot_combine_backwards(trie_value)) {
3581                                    // Can't combine backwards, hence a plain (non-backwards-combining)
3582                                    // starter albeit past `composition_passthrough_bound`
3583
3584                                    // Fast-track succeeded!
3585                                    // Instead of going back to `'fast`, we stay here to skip the branch
3586                                    // for `composition_passthrough_bound`.
3587                                    if likely(ptr != end) {
3588                                        // SAFETY: We just checked that `ptr` has not reached `end`.
3589                                        // `ptr` always advances by one, and we always have a check
3590                                        // per advancement.
3591                                        upcoming_code_unit = unsafe { *ptr };
3592                                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
3593                                        // by one points to the same allocation or to immediately
3594                                        // after, which is OK.
3595                                        ptr = unsafe { ptr.add(1) };
3596                                        continue;
3597                                    }
3598                                    break 'end;
3599                                }
3600                                upcoming32 = u32::from(upcoming_code_unit);
3601                                break;
3602                            }
3603
3604                            // We might now be looking at a surrogate.
3605                            // The loop is only broken out of as goto forward
3606                            #[expect(clippy::never_loop)]
3607                            'surrogateloop: loop {
3608                                // The `likely` annotations _below_ exist to make the code _above_
3609                                // go faster!
3610                                let surrogate_base = upcoming32.wrapping_sub(0xD800);
3611                                if likely(surrogate_base > (0xDFFF - 0xD800)) {
3612                                    // Not surrogate
3613                                    break 'surrogateloop;
3614                                }
3615                                if likely(surrogate_base <= (0xDBFF - 0xD800)) {
3616                                    // let iter_backup = code_unit_iter.clone();
3617                                    // if let Some(&low) = code_unit_iter.next() {
3618                                    if likely(ptr != end) {
3619                                        // SAFETY: We just checked that `ptr` has not reached `end`.
3620                                        // `ptr` always advances by one, and we always have a check
3621                                        // per advancement.
3622                                        let low = unsafe { *ptr };
3623                                        if likely(in_inclusive_range16(low, 0xDC00, 0xDFFF)) {
3624                                            // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
3625                                            // by one points to the same allocation or to immediately
3626                                            // after, which is OK.
3627                                            ptr = unsafe { ptr.add(1) };
3628
3629                                            upcoming32 = (upcoming32 << 10) + u32::from(low)
3630                                                - (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
3631                                            // Successfully-paired surrogate. Read from the trie again.
3632                                            trie_value = {
3633                                                // Semantically, this bit of conditional compilation makes no sense.
3634                                                // The purpose is to keep LLVM seeing the untyped trie case the way
3635                                                // it did before so as not to regress the performance of the untyped
3636                                                // case due to unintuitive optimizer effects. If you care about the
3637                                                // perf of the untyped trie case and have better ideas, please try
3638                                                // something better.
3639                                                #[cfg(feature = "serde")]
3640                                                {composition.decomposition.delegate.trie().code_point(upcoming32)}
3641                                                #[cfg(not(feature = "serde"))]
3642                                                {composition.decomposition.delegate.trie().supplementary(upcoming32)}
3643                                            };
3644                                            if likely(potential_passthrough_and_cannot_combine_backwards(trie_value)) {
3645                                                // Fast-track succeeded!
3646                                                continue 'fast;
3647                                            }
3648                                            break 'surrogateloop;
3649                                        // } else {
3650                                        //     code_unit_iter = iter_backup;
3651                                        }
3652                                    }
3653                                }
3654                                // unpaired surrogate
3655                                upcoming32 = 0xFFFD; // Safe value for `char::from_u32_unchecked` and matches later potential error check.
3656                                // trie_value already holds a decomposition to U+FFFD.
3657                                debug_assert_eq!(trie_value, NON_ROUND_TRIP_MARKER | BACKWARD_COMBINING_MARKER | 0xFFFD);
3658                                break 'surrogateloop;
3659                            }
3660
3661                            // SAFETY: upcoming32 can no longer be a surrogate.
3662                            let upcoming = unsafe { char::from_u32_unchecked(upcoming32) };
3663                            let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_value);
3664                            // We need to fall off the fast path.
3665                            composition.decomposition.pending = Some(upcoming_with_trie_value);
3666                            let Some(consumed_so_far_slice) = pending_slice.get(..pending_slice.len() -
3667                                // code_unit_iter.as_slice().len()
3668                                // SAFETY: `ptr` and `end` have been derived from the same allocation
3669                                // and `ptr` is never greater than `end`.
3670                                unsafe { end.offset_from(ptr) as usize }
3671                                - upcoming.len_utf16()) else {
3672                                // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
3673                                debug_assert!(false);
3674                                // Throw away the results of the fast path.
3675                                break 'fastwrap;
3676                            };
3677                            let mut consumed_so_far = consumed_so_far_slice.chars_with_trie(composition.decomposition.delegate.trie());
3678                            // Whether we could do something better than `next_back()` below is
3679                            // https://github.com/unicode-org/icu4x/issues/7525
3680                            let Some((c_from_back, trie_val_from_back)) = consumed_so_far.next_back() else {
3681                                // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
3682                                debug_assert!(false);
3683                                // Throw away the results of the fast path.
3684                                break 'fastwrap;
3685                            };
3686                            // TODO: If the previous character was below the passthrough bound,
3687                            // we really need to read from the trie. Otherwise, we could maintain
3688                            // the most-recent trie value. Need to measure what's more expensive:
3689                            // Remembering the trie value on each iteration or re-reading the
3690                            // last one after the fast-track run.
3691                            undecomposed_starter = CharacterAndTrieValue::new(c_from_back, trie_val_from_back);
3692                            sink.write_slice(consumed_so_far.as_slice())?;
3693                            break 'fast;
3694                        }
3695                        break;
3696                    }
3697                    // End of stream
3698                    sink.write_slice(pending_slice)?;
3699                    return Ok(());
3700                }
3701                // Sync the main iterator
3702                // composition.decomposition.delegate = code_unit_iter.as_slice().chars();
3703                // SAFETY: `ptr` and `end` have been derive from the same allocation
3704                // and `ptr` is never greater than `end`.
3705                composition.decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars_with_trie(composition.decomposition.delegate.trie());
3706                break 'fastwrap;
3707            }
3708        },
3709        text,
3710        sink,
3711        composition,
3712        undecomposed_starter,
3713        pending_slice,
3714        len_utf16,
3715        self,
3716        chars_with_trie,
3717    );
3718}
3719
3720/// A normalizer for performing composing normalization.
3721#[derive(Debug)]
3722pub struct ComposingNormalizer {
3723    decomposing_normalizer: DecomposingNormalizer,
3724    canonical_compositions: CanonicalCompositionsPayload,
3725}
3726
3727impl ComposingNormalizer {
3728    /// Constructs a borrowed version of this type for more efficient querying.
3729    pub fn as_borrowed(&self) -> ComposingNormalizerBorrowed<'_> {
3730        ComposingNormalizerBorrowed {
3731            decomposing_normalizer: self.decomposing_normalizer.as_borrowed(),
3732            canonical_compositions: self.canonical_compositions.as_borrowed(),
3733        }
3734    }
3735
3736    /// NFC constructor using compiled data.
3737    ///
3738    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
3739    ///
3740    /// [📚 Help choosing a constructor](icu_provider::constructors)
3741    #[cfg(feature = "compiled_data")]
3742    pub const fn new_nfc() -> ComposingNormalizerBorrowed<'static> {
3743        ComposingNormalizerBorrowed::new_nfc()
3744    }
3745
3746    icu_provider::gen_buffer_data_constructors!(
3747        () -> error: DataError,
3748        functions: [
3749            new_nfc: skip,
3750            try_new_nfc_with_buffer_provider,
3751            try_new_nfc_unstable,
3752            Self,
3753        ]
3754    );
3755
3756    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfc)]
3757    pub fn try_new_nfc_unstable<D>(provider: &D) -> Result<Self, DataError>
3758    where
3759        D: DataProvider<NormalizerNfdDataV1>
3760            + DataProvider<NormalizerNfdTablesV1>
3761            + DataProvider<NormalizerNfcV2>
3762            + ?Sized,
3763    {
3764        let decomposing_normalizer = DecomposingNormalizer::try_new_nfd_unstable(provider)?;
3765
3766        let canonical_compositions: DataPayload<NormalizerNfcV2> =
3767            provider.load(Default::default())?.payload;
3768
3769        Ok(ComposingNormalizer {
3770            decomposing_normalizer,
3771            canonical_compositions: CanonicalCompositionsPayload::Current(canonical_compositions),
3772        })
3773    }
3774
3775    /// NFKC constructor using compiled data.
3776    ///
3777    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
3778    ///
3779    /// [📚 Help choosing a constructor](icu_provider::constructors)
3780    #[cfg(feature = "compiled_data")]
3781    pub const fn new_nfkc() -> ComposingNormalizerBorrowed<'static> {
3782        ComposingNormalizerBorrowed::new_nfkc()
3783    }
3784
3785    icu_provider::gen_buffer_data_constructors!(
3786        () -> error: DataError,
3787        functions: [
3788            new_nfkc: skip,
3789            try_new_nfkc_with_buffer_provider,
3790            try_new_nfkc_unstable,
3791            Self,
3792        ]
3793    );
3794
3795    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfkc)]
3796    pub fn try_new_nfkc_unstable<D>(provider: &D) -> Result<Self, DataError>
3797    where
3798        D: DataProvider<NormalizerNfkdDataV1>
3799            + DataProvider<NormalizerNfdTablesV1>
3800            + DataProvider<NormalizerNfkdTablesV1>
3801            + DataProvider<NormalizerNfcV2>
3802            + ?Sized,
3803    {
3804        let decomposing_normalizer = DecomposingNormalizer::try_new_nfkd_unstable(provider)?;
3805
3806        let canonical_compositions: DataPayload<NormalizerNfcV2> =
3807            provider.load(Default::default())?.payload;
3808
3809        Ok(ComposingNormalizer {
3810            decomposing_normalizer,
3811            canonical_compositions: CanonicalCompositionsPayload::Current(canonical_compositions),
3812        })
3813    }
3814
3815    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_uts46)]
3816    pub(crate) fn try_new_uts46_unstable<D>(provider: &D) -> Result<Self, DataError>
3817    where
3818        D: DataProvider<NormalizerUts46DataV1>
3819            + DataProvider<NormalizerNfdTablesV1>
3820            + DataProvider<NormalizerNfkdTablesV1>
3821            // UTS 46 tables merged into CompatibilityDecompositionTablesV1
3822            + DataProvider<NormalizerNfcV2>
3823            + ?Sized,
3824    {
3825        let decomposing_normalizer =
3826            DecomposingNormalizer::try_new_uts46_decomposed_unstable(provider)?;
3827
3828        let canonical_compositions: DataPayload<NormalizerNfcV2> =
3829            provider.load(Default::default())?.payload;
3830
3831        Ok(ComposingNormalizer {
3832            decomposing_normalizer,
3833            canonical_compositions: CanonicalCompositionsPayload::Current(canonical_compositions),
3834        })
3835    }
3836}
3837
3838#[cfg(feature = "utf16_iter")]
3839struct IsNormalizedSinkUtf16<'a> {
3840    expect: &'a [u16],
3841}
3842
3843#[cfg(feature = "utf16_iter")]
3844impl<'a> IsNormalizedSinkUtf16<'a> {
3845    pub fn new(slice: &'a [u16]) -> Self {
3846        IsNormalizedSinkUtf16 { expect: slice }
3847    }
3848    pub fn remaining_len(&self) -> usize {
3849        self.expect.len()
3850    }
3851}
3852
3853#[cfg(feature = "utf16_iter")]
3854impl write16::Write16 for IsNormalizedSinkUtf16<'_> {
3855    fn write_slice(&mut self, s: &[u16]) -> core::fmt::Result {
3856        // We know that if we get a slice, it's a pass-through,
3857        // so we can compare addresses. Indexing is OK, because
3858        // an indexing failure would be a code bug rather than
3859        // an input or data issue.
3860        #[expect(clippy::indexing_slicing)]
3861        if core::ptr::eq(s.as_ptr(), self.expect.as_ptr()) {
3862            self.expect = &self.expect[s.len()..];
3863            Ok(())
3864        } else {
3865            Err(core::fmt::Error {})
3866        }
3867    }
3868
3869    fn write_char(&mut self, c: char) -> core::fmt::Result {
3870        let mut iter = utf16_iter::ErrorReportingUtf16Chars::new(self.expect);
3871        if iter.next() == Some(Ok(c)) {
3872            self.expect = iter.as_slice();
3873            Ok(())
3874        } else {
3875            Err(core::fmt::Error {})
3876        }
3877    }
3878}
3879
3880#[cfg(feature = "utf8_iter")]
3881struct IsNormalizedSinkUtf8<'a> {
3882    expect: &'a [u8],
3883}
3884
3885#[cfg(feature = "utf8_iter")]
3886impl<'a> IsNormalizedSinkUtf8<'a> {
3887    pub fn new(slice: &'a [u8]) -> Self {
3888        IsNormalizedSinkUtf8 { expect: slice }
3889    }
3890    pub fn remaining_len(&self) -> usize {
3891        self.expect.len()
3892    }
3893}
3894
3895#[cfg(feature = "utf8_iter")]
3896impl core::fmt::Write for IsNormalizedSinkUtf8<'_> {
3897    fn write_str(&mut self, s: &str) -> core::fmt::Result {
3898        // We know that if we get a slice, it's a pass-through,
3899        // so we can compare addresses. Indexing is OK, because
3900        // an indexing failure would be a code bug rather than
3901        // an input or data issue.
3902        #[expect(clippy::indexing_slicing)]
3903        if core::ptr::eq(s.as_ptr(), self.expect.as_ptr()) {
3904            self.expect = &self.expect[s.len()..];
3905            Ok(())
3906        } else {
3907            Err(core::fmt::Error {})
3908        }
3909    }
3910
3911    fn write_char(&mut self, c: char) -> core::fmt::Result {
3912        let mut iter = utf8_iter::ErrorReportingUtf8Chars::new(self.expect);
3913        if iter.next() == Some(Ok(c)) {
3914            self.expect = iter.as_slice();
3915            Ok(())
3916        } else {
3917            Err(core::fmt::Error {})
3918        }
3919    }
3920}
3921
3922struct IsNormalizedSinkStr<'a> {
3923    expect: &'a str,
3924}
3925
3926impl<'a> IsNormalizedSinkStr<'a> {
3927    pub fn new(slice: &'a str) -> Self {
3928        IsNormalizedSinkStr { expect: slice }
3929    }
3930    pub fn remaining_len(&self) -> usize {
3931        self.expect.len()
3932    }
3933}
3934
3935impl core::fmt::Write for IsNormalizedSinkStr<'_> {
3936    fn write_str(&mut self, s: &str) -> core::fmt::Result {
3937        // We know that if we get a slice, it's a pass-through,
3938        // so we can compare addresses. Indexing is OK, because
3939        // an indexing failure would be a code bug rather than
3940        // an input or data issue.
3941        if core::ptr::eq(s.as_ptr(), self.expect.as_ptr()) {
3942            self.expect = &self.expect[s.len()..];
3943            Ok(())
3944        } else {
3945            Err(core::fmt::Error {})
3946        }
3947    }
3948
3949    fn write_char(&mut self, c: char) -> core::fmt::Result {
3950        let mut iter = self.expect.chars();
3951        if iter.next() == Some(c) {
3952            self.expect = iter.as_str();
3953            Ok(())
3954        } else {
3955            Err(core::fmt::Error {})
3956        }
3957    }
3958}