• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 6815798908

09 Nov 2023 05:17PM UTC coverage: 72.607% (-2.4%) from 75.01%
6815798908

push

github

web-flow
Implement `Any/BufferProvider` for some smart pointers (#4255)

Allows storing them as a `Box<dyn Any/BufferProvider>` without using a
wrapper type that implements the trait.

44281 of 60987 relevant lines covered (72.61%)

201375.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.92
/components/collections/src/codepointinvlist/cpinvlist.rs
1
// This file is part of ICU4X. For terms of use, please see the file
2✔
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
#[cfg(feature = "serde")]
6
use alloc::format;
7
#[cfg(feature = "serde")]
8
use alloc::string::String;
9
use alloc::vec::Vec;
10
use core::{char, ops::RangeBounds, ops::RangeInclusive};
11
use yoke::Yokeable;
12
use zerofrom::ZeroFrom;
13
use zerovec::{ule::AsULE, zerovec, ZeroVec};
14

15
use super::CodePointInversionListError;
16
use crate::codepointinvlist::utils::{deconstruct_range, is_valid_zv};
17

18
/// Represents the end code point of the Basic Multilingual Plane range, starting from code point 0, inclusive
19
const BMP_MAX: u32 = 0xFFFF;
20

21
/// Represents the inversion list for a set of all code points in the Basic Multilingual Plane.
22
const BMP_INV_LIST_VEC: ZeroVec<u32> =
23
    zerovec!(u32; <u32 as AsULE>::ULE::from_unsigned; [0x0, BMP_MAX + 1]);
24

25
/// Represents the inversion list for all of the code points in the Unicode range.
26
const ALL_VEC: ZeroVec<u32> =
27
    zerovec!(u32; <u32 as AsULE>::ULE::from_unsigned; [0x0, (char::MAX as u32) + 1]);
28

29
/// A membership wrapper for [`CodePointInversionList`].
30
///
31
/// Provides exposure to membership functions and constructors from serialized `CodePointSet`s (sets of code points)
32
/// and predefined ranges.
33
#[zerovec::make_varule(CodePointInversionListULE)]
493,772✔
34
#[zerovec::skip_derive(Ord)]
35
#[zerovec::derive(Debug)]
36
#[derive(Debug, Eq, PartialEq, Clone, Yokeable, ZeroFrom)]
23,855✔
37
pub struct CodePointInversionList<'data> {
38
    // If we wanted to use an array to keep the memory on the stack, there is an unsafe nightly feature
39
    // https://doc.rust-lang.org/nightly/core/array/trait.FixedSizeArray.html
40
    // Allows for traits of fixed size arrays
41

42
    // Implements an [inversion list.](https://en.wikipedia.org/wiki/Inversion_list)
43
    inv_list: ZeroVec<'data, u32>,
11,083✔
44
    size: u32,
11,083✔
45
}
46

47
#[cfg(feature = "serde")]
48
impl<'de: 'a, 'a> serde::Deserialize<'de> for CodePointInversionList<'a> {
49
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6✔
50
    where
51
        D: serde::Deserializer<'de>,
52
    {
53
        use serde::de::Error;
54

55
        let parsed_inv_list = if deserializer.is_human_readable() {
11✔
56
            #[derive(serde::Deserialize)]
5✔
57
            #[serde(untagged)]
58
            pub enum De<'data> {
59
                // TODO(#2856): Remove in ICU4X 2.0
60
                #[serde(borrow)]
61
                OldStyle(ZeroVec<'data, u32>),
2✔
62
                #[serde(borrow)]
63
                NewStyle(Vec<alloc::borrow::Cow<'data, str>>),
2✔
64
            }
65

66
            match De::<'de>::deserialize(deserializer)? {
5✔
67
                De::OldStyle(parsed_inv_list) => parsed_inv_list,
2✔
68
                De::NewStyle(parsed_strings) => {
2✔
69
                    let mut inv_list =
70
                        ZeroVec::new_owned(Vec::with_capacity(parsed_strings.len() * 2));
2✔
71
                    for range in parsed_strings {
5✔
72
                        fn internal(range: &str) -> Option<(u32, u32)> {
3✔
73
                            let (start, range) = UnicodeCodePoint::parse(range)?;
3✔
74
                            if range.is_empty() {
3✔
75
                                return Some((start.0, start.0));
×
76
                            }
77
                            let (hyphen, range) = UnicodeCodePoint::parse(range)?;
3✔
78
                            if hyphen.0 != '-' as u32 {
3✔
79
                                return None;
×
80
                            }
81
                            let (end, range) = UnicodeCodePoint::parse(range)?;
3✔
82
                            range.is_empty().then_some((start.0, end.0))
3✔
83
                        }
3✔
84
                        let (start, end) = internal(&range).ok_or_else(|| Error::custom(format!(
3✔
85
                            "Cannot deserialize invalid inversion list for CodePointInversionList: {range:?}"
86
                        )))?;
×
87
                        inv_list.with_mut(|v| {
6✔
88
                            v.push(start.to_unaligned());
3✔
89
                            v.push((end + 1).to_unaligned());
3✔
90
                        });
3✔
91
                    }
3✔
92
                    inv_list
2✔
93
                }
2✔
94
            }
95
        } else {
×
96
            ZeroVec::<u32>::deserialize(deserializer)?
1✔
97
        };
×
98
        CodePointInversionList::try_from_inversion_list(parsed_inv_list).map_err(|e| {
6✔
99
            Error::custom(format!(
1✔
100
                "Cannot deserialize invalid inversion list for CodePointInversionList: {e:?}"
101
            ))
102
        })
1✔
103
    }
6✔
104
}
105

106
#[cfg(feature = "databake")]
107
impl databake::Bake for CodePointInversionList<'_> {
108
    fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
1✔
109
        env.insert("icu_collections");
1✔
110
        let inv_list = self.inv_list.bake(env);
1✔
111
        let size = self.size.bake(env);
1✔
112
        // Safe because our parts are safe.
113
        databake::quote! { unsafe {
1✔
114
            #[allow(unused_unsafe)]
115
            icu_collections::codepointinvlist::CodePointInversionList::from_parts_unchecked(#inv_list, #size)
116
        }}
117
    }
1✔
118
}
119

120
#[cfg(feature = "serde")]
121
#[derive(Debug, Copy, Clone)]
×
122
struct UnicodeCodePoint(u32);
×
123

124
#[cfg(feature = "serde")]
125
impl UnicodeCodePoint {
126
    fn from_u32(cp: u32) -> Result<Self, String> {
6✔
127
        if cp <= char::MAX as u32 {
6✔
128
            Ok(Self(cp))
6✔
129
        } else {
130
            Err(format!("Not a Unicode code point {}", cp))
×
131
        }
132
    }
6✔
133

134
    fn parse(value: &str) -> Option<(Self, &str)> {
9✔
135
        Some(if let Some(hex) = value.strip_prefix("U+") {
18✔
136
            let (escape, remainder) = (hex.get(..4)?, hex.get(4..)?);
2✔
137
            (Self(u32::from_str_radix(escape, 16).ok()?), remainder)
2✔
138
        } else {
139
            let c = value.chars().next()?;
7✔
140
            (Self(c as u32), value.get(c.len_utf8()..)?)
7✔
141
        })
142
    }
9✔
143
}
144

145
#[cfg(feature = "serde")]
146
impl core::fmt::Display for UnicodeCodePoint {
147
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
6✔
148
        match self.0 {
149
            s @ 0xD800..=0xDFFF => write!(f, "U+{s:X}"),
6✔
150
            // SAFETY: c <= char::MAX by construction, and not a surrogate
151
            c => write!(f, "{}", unsafe { char::from_u32_unchecked(c) }),
4✔
152
        }
153
    }
6✔
154
}
155

156
#[cfg(feature = "serde")]
157
impl<'data> serde::Serialize for CodePointInversionList<'data> {
158
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
9✔
159
    where
160
        S: serde::Serializer,
161
    {
162
        if serializer.is_human_readable() {
11✔
163
            use serde::ser::Error;
164
            use serde::ser::SerializeSeq;
165
            let mut seq = serializer.serialize_seq(Some(self.inv_list.len() / 2))?;
2✔
166
            for range in self.iter_ranges() {
8✔
167
                let start = UnicodeCodePoint::from_u32(*range.start()).map_err(S::Error::custom)?;
3✔
168
                if range.start() == range.end() {
3✔
169
                    seq.serialize_element(&format!("{start}"))?;
×
170
                } else {
171
                    let end = UnicodeCodePoint::from_u32(*range.end()).map_err(S::Error::custom)?;
3✔
172
                    seq.serialize_element(&format!("{start}-{end}",))?;
3✔
173
                }
174
            }
175
            seq.end()
2✔
176
        } else {
2✔
177
            // Note: serde(flatten) currently does not promote a struct field of type Vec
178
            // to replace the struct when serializing. The error message from the default
179
            // serialization is: "can only flatten structs and maps (got a sequence)".
180
            self.inv_list.serialize(serializer)
1✔
181
        }
182
    }
3✔
183
}
184

185
impl<'data> CodePointInversionList<'data> {
186
    /// Returns a new [`CodePointInversionList`] from an [inversion list](https://en.wikipedia.org/wiki/Inversion_list)
187
    /// represented as a [`ZeroVec`]`<`[`u32`]`>` of code points.
188
    ///
189
    /// The inversion list must be of even length, sorted ascending non-overlapping,
190
    /// and within the bounds of `0x0 -> 0x10FFFF` inclusive, and end points being exclusive.
191
    ///
192
    /// # Examples
193
    ///
194
    /// ```
195
    /// use icu_collections::codepointinvlist::CodePointInversionList;
196
    /// use icu_collections::codepointinvlist::CodePointInversionListError;
197
    /// use zerovec::ZeroVec;
198
    /// let valid = [0x0, 0x10000];
199
    /// let inv_list: ZeroVec<u32> = ZeroVec::from_slice_or_alloc(&valid);
200
    /// let result = CodePointInversionList::try_from_inversion_list(inv_list);
201
    /// assert!(matches!(result, CodePointInversionList));
202
    ///
203
    /// let invalid: Vec<u32> = vec![0x0, 0x80, 0x3];
204
    /// let inv_list: ZeroVec<u32> = ZeroVec::from_slice_or_alloc(&invalid);
205
    /// let result = CodePointInversionList::try_from_inversion_list(inv_list);
206
    /// assert!(matches!(
207
    ///     result,
208
    ///     Err(CodePointInversionListError::InvalidSet(_))
209
    /// ));
210
    /// if let Err(CodePointInversionListError::InvalidSet(actual)) = result {
211
    ///     assert_eq!(&invalid, &actual);
212
    /// }
213
    /// ```
214
    pub fn try_from_inversion_list(
1,039✔
215
        inv_list: ZeroVec<'data, u32>,
216
    ) -> Result<Self, CodePointInversionListError> {
217
        #[allow(clippy::indexing_slicing)] // chunks
218
        if is_valid_zv(&inv_list) {
2,072✔
219
            let size = inv_list
1,025✔
220
                .as_ule_slice()
221
                .chunks(2)
222
                .map(|end_points| {
180,900✔
223
                    <u32 as AsULE>::from_unaligned(end_points[1])
180,900✔
224
                        - <u32 as AsULE>::from_unaligned(end_points[0])
180,900✔
225
                })
180,900✔
226
                .sum::<u32>();
227
            Ok(Self { inv_list, size })
1,029✔
228
        } else {
229
            Err(CodePointInversionListError::InvalidSet(inv_list.to_vec()))
4✔
230
        }
231
    }
1,033✔
232

233
    #[doc(hidden)] // databake internal
234
    pub const unsafe fn from_parts_unchecked(inv_list: ZeroVec<'data, u32>, size: u32) -> Self {
1✔
235
        Self { inv_list, size }
1✔
236
    }
1✔
237

238
    /// Returns a new [`CodePointInversionList`] by borrowing an [inversion list](https://en.wikipedia.org/wiki/Inversion_list)
239
    /// represented as a slice of [`u32`] code points.
240
    ///
241
    /// The inversion list must be of even length, sorted ascending non-overlapping,
242
    /// and within the bounds of `0x0 -> 0x10FFFF` inclusive, and end points being exclusive.
243
    ///
244
    /// Note: The slice may be cloned on certain platforms; for more information, see [`ZeroVec::from_slice_or_alloc`].
245
    ///
246
    /// # Examples
247
    ///
248
    /// ```
249
    /// use icu_collections::codepointinvlist::CodePointInversionList;
250
    /// use icu_collections::codepointinvlist::CodePointInversionListError;
251
    /// use zerovec::ZeroVec;
252
    /// let valid = [0x0, 0x10000];
253
    /// let result = CodePointInversionList::try_from_inversion_list_slice(&valid);
254
    /// assert!(matches!(result, CodePointInversionList));
255
    ///
256
    /// let invalid: Vec<u32> = vec![0x0, 0x80, 0x3];
257
    /// let result =
258
    ///     CodePointInversionList::try_from_inversion_list_slice(&invalid);
259
    /// assert!(matches!(
260
    ///     result,
261
    ///     Err(CodePointInversionListError::InvalidSet(_))
262
    /// ));
263
    /// if let Err(CodePointInversionListError::InvalidSet(actual)) = result {
264
    ///     assert_eq!(&invalid, &actual);
265
    /// }
266
    /// ```
267
    pub fn try_from_inversion_list_slice(
52✔
268
        inv_list: &'data [u32],
269
    ) -> Result<Self, CodePointInversionListError> {
270
        let inv_list_zv: ZeroVec<u32> = ZeroVec::from_slice_or_alloc(inv_list);
52✔
271
        CodePointInversionList::try_from_inversion_list(inv_list_zv)
52✔
272
    }
52✔
273

274
    /// Returns a new, fully-owned [`CodePointInversionList`] by cloning an [inversion list](https://en.wikipedia.org/wiki/Inversion_list)
275
    /// represented as a slice of [`u32`] code points.
276
    ///
277
    /// The inversion list must be of even length, sorted ascending non-overlapping,
278
    /// and within the bounds of `0x0 -> 0x10FFFF` inclusive, and end points being exclusive.
279
    ///
280
    /// # Examples
281
    ///
282
    /// ```
283
    /// use icu_collections::codepointinvlist::CodePointInversionList;
284
    /// use std::vec::Vec;
285
    /// use zerovec::ZeroVec;
286
    ///
287
    /// let bmp_list = &[0x0, 0x10000];
288
    /// let smp_list = &[0x10000, 0x20000];
289
    /// let sip_list = &[0x20000, 0x30000];
290
    ///
291
    /// let lists: Vec<CodePointInversionList> =
292
    ///     [&bmp_list[..], smp_list, sip_list]
293
    ///         .into_iter()
294
    ///         .map(|l| {
295
    ///             CodePointInversionList::try_clone_from_inversion_list_slice(l)
296
    ///                 .unwrap()
297
    ///         })
298
    ///         .collect();
299
    ///
300
    /// let bmp = &lists[0];
301
    /// assert!(bmp.contains32(0xFFFF));
302
    /// assert!(!bmp.contains32(0x10000));
303
    ///
304
    /// assert!(!lists.iter().any(|set| set.contains32(0x40000)));
305
    /// ```
306
    pub fn try_clone_from_inversion_list_slice(
11✔
307
        inv_list: &[u32],
308
    ) -> Result<Self, CodePointInversionListError> {
309
        let inv_list_zv: ZeroVec<u32> = ZeroVec::alloc_from_slice(inv_list);
11✔
310
        CodePointInversionList::try_from_inversion_list(inv_list_zv)
11✔
311
    }
11✔
312

313
    /// Attempts to convert this list into a fully-owned one. No-op if already fully owned
314
    pub fn into_owned(self) -> CodePointInversionList<'static> {
×
315
        CodePointInversionList {
×
316
            inv_list: self.inv_list.into_owned(),
×
317
            size: self.size,
×
318
        }
319
    }
×
320

321
    /// Returns an owned inversion list representing the current [`CodePointInversionList`]
322
    pub fn get_inversion_list_vec(&self) -> Vec<u32> {
18✔
323
        let result: Vec<u32> = self.as_inversion_list().to_vec(); // Only crate public, to not leak impl
18✔
324
        result
325
    }
18✔
326

327
    /// Returns [`CodePointInversionList`] spanning entire Unicode range
328
    ///
329
    /// The range spans from `0x0 -> 0x10FFFF` inclusive.
330
    ///  
331
    /// # Examples
332
    ///
333
    /// ```
334
    /// use icu_collections::codepointinvlist::CodePointInversionList;
335
    /// use zerovec::ZeroVec;
336
    ///
337
    /// let expected = [0x0, (char::MAX as u32) + 1];
338
    /// assert_eq!(
339
    ///     CodePointInversionList::all().get_inversion_list_vec(),
340
    ///     expected
341
    /// );
342
    /// assert_eq!(
343
    ///     CodePointInversionList::all().size(),
344
    ///     (expected[1] - expected[0]) as usize
345
    /// );
346
    /// ```
347
    pub fn all() -> Self {
10,702✔
348
        Self {
10,702✔
349
            inv_list: ALL_VEC,
10,702✔
350
            size: (char::MAX as u32) + 1,
351
        }
352
    }
10,702✔
353

354
    /// Returns [`CodePointInversionList`] spanning BMP range
355
    ///
356
    /// The range spans from `0x0 -> 0xFFFF` inclusive.
357
    ///
358
    /// # Examples
359
    ///
360
    /// ```
361
    /// use icu_collections::codepointinvlist::CodePointInversionList;
362
    /// use zerovec::ZeroVec;
363
    ///
364
    /// const BMP_MAX: u32 = 0xFFFF;
365
    ///
366
    /// let expected = [0x0, BMP_MAX + 1];
367
    /// assert_eq!(
368
    ///     CodePointInversionList::bmp().get_inversion_list_vec(),
369
    ///     expected
370
    /// );
371
    /// assert_eq!(
372
    ///     CodePointInversionList::bmp().size(),
373
    ///     (expected[1] - expected[0]) as usize
374
    /// );
375
    /// ```
376
    pub fn bmp() -> Self {
3✔
377
        Self {
3✔
378
            inv_list: BMP_INV_LIST_VEC,
3✔
379
            size: BMP_MAX + 1,
380
        }
381
    }
3✔
382

383
    /// Returns the inversion list as a slice
384
    ///
385
    /// Public only to the crate, not exposed to public
386
    pub(crate) fn as_inversion_list(&self) -> &ZeroVec<u32> {
306✔
387
        &self.inv_list
388
    }
306✔
389

390
    /// Yields an [`Iterator`] going through the character set in the [`CodePointInversionList`]
391
    ///
392
    /// # Examples
393
    ///
394
    /// ```
395
    /// use icu_collections::codepointinvlist::CodePointInversionList;
396
    /// let example_list = [0x41, 0x44, 0x45, 0x46];
397
    /// let example =
398
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
399
    ///         .unwrap();
400
    /// let mut ex_iter_chars = example.iter_chars();
401
    /// assert_eq!(Some('A'), ex_iter_chars.next());
402
    /// assert_eq!(Some('B'), ex_iter_chars.next());
403
    /// assert_eq!(Some('C'), ex_iter_chars.next());
404
    /// assert_eq!(Some('E'), ex_iter_chars.next());
405
    /// assert_eq!(None, ex_iter_chars.next());
406
    /// ```
407
    pub fn iter_chars(&self) -> impl Iterator<Item = char> + '_ {
15✔
408
        #[allow(clippy::indexing_slicing)] // chunks
409
        self.inv_list
15✔
410
            .as_ule_slice()
411
            .chunks(2)
412
            .flat_map(|pair| (AsULE::from_unaligned(pair[0])..AsULE::from_unaligned(pair[1])))
6✔
413
            .filter_map(char::from_u32)
414
    }
15✔
415

416
    /// Yields an [`Iterator`] returning the ranges of the code points that are
417
    /// included in the [`CodePointInversionList`]
418
    ///
419
    /// Ranges are returned as [`RangeInclusive`], which is inclusive of its
420
    /// `end` bound value. An end-inclusive behavior matches the ICU4C/J
421
    /// behavior of ranges, ex: `CodePointInversionList::contains(UChar32 start, UChar32 end)`.
422
    ///
423
    /// # Example
424
    ///
425
    /// ```
426
    /// use icu_collections::codepointinvlist::CodePointInversionList;
427
    /// let example_list = [0x41, 0x44, 0x45, 0x46];
428
    /// let example =
429
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
430
    ///         .unwrap();
431
    /// let mut example_iter_ranges = example.iter_ranges();
432
    /// assert_eq!(Some(0x41..=0x43), example_iter_ranges.next());
433
    /// assert_eq!(Some(0x45..=0x45), example_iter_ranges.next());
434
    /// assert_eq!(None, example_iter_ranges.next());
435
    /// ```
436
    pub fn iter_ranges(&self) -> impl ExactSizeIterator<Item = RangeInclusive<u32>> + '_ {
248✔
437
        #[allow(clippy::indexing_slicing)] // chunks
438
        self.inv_list.as_ule_slice().chunks(2).map(|pair| {
21,650✔
439
            let range_start: u32 = AsULE::from_unaligned(pair[0]);
21,402✔
440
            let range_limit: u32 = AsULE::from_unaligned(pair[1]);
21,402✔
441
            RangeInclusive::new(range_start, range_limit - 1)
21,402✔
442
        })
21,402✔
443
    }
248✔
444

445
    /// Yields an [`Iterator`] returning the ranges of the code points that are
446
    /// *not* included in the [`CodePointInversionList`]
447
    ///
448
    /// Ranges are returned as [`RangeInclusive`], which is inclusive of its
449
    /// `end` bound value. An end-inclusive behavior matches the ICU4C/J
450
    /// behavior of ranges, ex: `CodePointInversionList::contains(UChar32 start, UChar32 end)`.
451
    ///
452
    /// # Example
453
    ///
454
    /// ```
455
    /// use icu_collections::codepointinvlist::CodePointInversionList;
456
    /// let example_list = [0x41, 0x44, 0x45, 0x46];
457
    /// let example =
458
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
459
    ///         .unwrap();
460
    /// let mut example_iter_ranges = example.iter_ranges_complemented();
461
    /// assert_eq!(Some(0..=0x40), example_iter_ranges.next());
462
    /// assert_eq!(Some(0x44..=0x44), example_iter_ranges.next());
463
    /// assert_eq!(Some(0x46..=char::MAX as u32), example_iter_ranges.next());
464
    /// assert_eq!(None, example_iter_ranges.next());
465
    /// ```
466
    pub fn iter_ranges_complemented(&self) -> impl Iterator<Item = RangeInclusive<u32>> + '_ {
66✔
467
        let inv_ule = self.inv_list.as_ule_slice();
66✔
468
        let middle = inv_ule.get(1..inv_ule.len() - 1).unwrap_or(&[]);
66✔
469
        let beginning = if let Some(first) = self.inv_list.first() {
66✔
470
            if first == 0 {
66✔
471
                None
5✔
472
            } else {
473
                Some(0..=first - 1)
61✔
474
            }
475
        } else {
476
            None
×
477
        };
478
        let end = if let Some(last) = self.inv_list.last() {
66✔
479
            if last == char::MAX as u32 {
66✔
480
                None
×
481
            } else {
482
                Some(last..=char::MAX as u32)
66✔
483
            }
484
        } else {
485
            None
×
486
        };
487
        #[allow(clippy::indexing_slicing)] // chunks
488
        let chunks = middle.chunks(2).map(|pair| {
17,190✔
489
            let range_start: u32 = AsULE::from_unaligned(pair[0]);
17,124✔
490
            let range_limit: u32 = AsULE::from_unaligned(pair[1]);
17,124✔
491
            RangeInclusive::new(range_start, range_limit - 1)
17,124✔
492
        });
17,124✔
493
        beginning.into_iter().chain(chunks).chain(end)
66✔
494
    }
66✔
495

496
    /// Returns the number of ranges contained in this [`CodePointInversionList`]
497
    pub fn get_range_count(&self) -> usize {
1✔
498
        self.inv_list.len() / 2
1✔
499
    }
1✔
500

501
    /// Returns a specific range contained in this [`CodePointInversionList`] by index.
502
    /// Intended for use in FFI.
503
    pub fn get_nth_range(&self, idx: usize) -> Option<RangeInclusive<u32>> {
4✔
504
        let start_idx = idx * 2;
4✔
505
        let end_idx = start_idx + 1;
4✔
506
        let start = self.inv_list.get(start_idx)?;
4✔
507
        let end = self.inv_list.get(end_idx)?;
3✔
508
        Some(RangeInclusive::new(start, end - 1))
3✔
509
    }
4✔
510

511
    /// Returns the number of elements of the [`CodePointInversionList`]
512
    pub fn size(&self) -> usize {
306✔
513
        if self.is_empty() {
306✔
514
            return 0;
57✔
515
        }
516
        self.size as usize
249✔
517
    }
306✔
518

519
    /// Returns whether or not the [`CodePointInversionList`] is empty
520
    pub fn is_empty(&self) -> bool {
310✔
521
        self.inv_list.is_empty()
310✔
522
    }
310✔
523

524
    /// Wrapper for contains
525
    ///
526
    /// Returns an [`Option`] as to whether or not it is possible for the query to be contained.
527
    /// The value in the [`Option`] is the start index of the range that contains the query.
528
    fn contains_query(&self, query: u32) -> Option<usize> {
1,709,413✔
529
        match self.inv_list.binary_search(&query) {
1,709,413✔
530
            Ok(pos) => {
12,987✔
531
                if pos % 2 == 0 {
12,987✔
532
                    Some(pos)
8,173✔
533
                } else {
534
                    None
4,814✔
535
                }
536
            }
537
            Err(pos) => {
1,696,426✔
538
                if pos % 2 != 0 && pos < self.inv_list.len() {
1,696,426✔
539
                    Some(pos - 1)
42,478✔
540
                } else {
541
                    None
1,653,948✔
542
                }
543
            }
544
        }
545
    }
1,709,413✔
546

547
    /// Checks to see the query is in the [`CodePointInversionList`]
548
    ///
549
    /// Runs a binary search in `O(log(n))` where `n` is the number of start and end points
550
    /// in the set using [`core`] implementation
551
    ///
552
    /// # Examples
553
    ///
554
    /// ```
555
    /// use icu_collections::codepointinvlist::CodePointInversionList;
556
    /// let example_list = [0x41, 0x43, 0x44, 0x45];
557
    /// let example =
558
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
559
    ///         .unwrap();
560
    /// assert!(example.contains('A'));
561
    /// assert!(!example.contains('C'));
562
    /// ```
563
    pub fn contains(&self, query: char) -> bool {
355,144✔
564
        self.contains_query(query as u32).is_some()
355,144✔
565
    }
355,144✔
566

567
    /// Checks to see the unsigned int is in the [`CodePointInversionList::all()`](CodePointInversionList::all())
568
    ///
569
    /// Note: Even though [`u32`] and [`prim@char`] in Rust are non-negative 4-byte
570
    /// values, there is an important difference. A [`u32`] can take values up to
571
    /// a very large integer value, while a [`prim@char`] in Rust is defined to be in
572
    /// the range from 0 to the maximum valid Unicode Scalar Value.
573
    ///
574
    /// Runs a binary search in `O(log(n))` where `n` is the number of start and end points
575
    /// in the set using [`core`] implementation
576
    ///
577
    /// # Examples
578
    ///
579
    /// ```
580
    /// use icu_collections::codepointinvlist::CodePointInversionList;
581
    /// let example_list = [0x41, 0x43, 0x44, 0x45];
582
    /// let example =
583
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
584
    ///         .unwrap();
585
    /// assert!(example.contains32(0x41));
586
    /// assert!(!example.contains32(0x43));
587
    /// ```
588
    pub fn contains32(&self, query: u32) -> bool {
1,354,516✔
589
        self.contains_query(query).is_some()
1,354,516✔
590
    }
1,354,516✔
591

592
    /// Checks to see if the range is in the [`CodePointInversionList`]
593
    ///
594
    /// Runs a binary search in `O(log(n))` where `n` is the number of start and end points
595
    /// in the set using [`Vec`] implementation. Only runs the search once on the `start`
596
    /// parameter, while the `end` parameter is checked in a single `O(1)` step.
597
    ///
598
    /// # Examples
599
    ///
600
    /// ```
601
    /// use icu_collections::codepointinvlist::CodePointInversionList;
602
    /// let example_list = [0x41, 0x43, 0x44, 0x45];
603
    /// let example =
604
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
605
    ///         .unwrap();
606
    /// assert!(example.contains_range(&('A'..'C')));
607
    /// assert!(example.contains_range(&('A'..='B')));
608
    /// assert!(!example.contains_range(&('A'..='C')));
609
    /// ```
610
    ///
611
    /// Surrogate points (`0xD800 -> 0xDFFF`) will return [`false`] if the Range contains them but the
612
    /// [`CodePointInversionList`] does not.
613
    ///
614
    /// Note: when comparing to ICU4C/J, keep in mind that `Range`s in Rust are
615
    /// constructed inclusive of start boundary and exclusive of end boundary.
616
    /// The ICU4C/J `CodePointInversionList::contains(UChar32 start, UChar32 end)` method
617
    /// differs by including the end boundary.
618
    ///
619
    /// # Examples
620
    ///
621
    /// ```
622
    /// use icu_collections::codepointinvlist::CodePointInversionList;
623
    /// use std::char;
624
    /// let check =
625
    ///     char::from_u32(0xD7FE).unwrap()..char::from_u32(0xE001).unwrap();
626
    /// let example_list = [0xD7FE, 0xD7FF, 0xE000, 0xE001];
627
    /// let example =
628
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
629
    ///         .unwrap();
630
    /// assert!(!example.contains_range(&(check)));
631
    /// ```
632
    pub fn contains_range(&self, range: &impl RangeBounds<char>) -> bool {
9✔
633
        let (from, till) = deconstruct_range(range);
9✔
634
        if from >= till {
9✔
635
            return false;
2✔
636
        }
637
        match self.contains_query(from) {
7✔
638
            Some(pos) => {
4✔
639
                if let Some(x) = self.inv_list.get(pos + 1) {
4✔
640
                    (till) <= x
4✔
641
                } else {
642
                    debug_assert!(
×
643
                        false,
644
                        "Inversion list query should not return out of bounds index"
645
                    );
646
                    false
647
                }
648
            }
649
            None => false,
3✔
650
        }
651
    }
9✔
652

653
    /// Check if the calling [`CodePointInversionList`] contains all the characters of the given [`CodePointInversionList`]
654
    ///
655
    /// # Examples
656
    ///
657
    /// ```
658
    /// use icu_collections::codepointinvlist::CodePointInversionList;
659
    /// let example_list = [0x41, 0x46, 0x55, 0x5B]; // A - E, U - Z
660
    /// let example =
661
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
662
    ///         .unwrap();
663
    /// let a_to_d =
664
    ///     CodePointInversionList::try_from_inversion_list_slice(&[0x41, 0x45])
665
    ///         .unwrap();
666
    /// let f_to_t =
667
    ///     CodePointInversionList::try_from_inversion_list_slice(&[0x46, 0x55])
668
    ///         .unwrap();
669
    /// let r_to_x =
670
    ///     CodePointInversionList::try_from_inversion_list_slice(&[0x52, 0x58])
671
    ///         .unwrap();
672
    /// assert!(example.contains_set(&a_to_d)); // contains all
673
    /// assert!(!example.contains_set(&f_to_t)); // contains none
674
    /// assert!(!example.contains_set(&r_to_x)); // contains some
675
    /// ```
676
    pub fn contains_set(&self, set: &Self) -> bool {
5✔
677
        if set.size() > self.size() {
5✔
678
            return false;
2✔
679
        }
680

681
        let mut set_ranges = set.iter_ranges();
3✔
682
        let mut check_elem = set_ranges.next();
3✔
683

684
        let ranges = self.iter_ranges();
3✔
685
        for range in ranges {
10✔
686
            match check_elem {
8✔
687
                Some(ref check_range) => {
7✔
688
                    if check_range.start() >= range.start()
7✔
689
                        && check_range.end() <= &(range.end() + 1)
6✔
690
                    {
691
                        check_elem = set_ranges.next();
5✔
692
                    }
693
                }
694
                _ => break,
695
            }
696
        }
697
        check_elem.is_none()
3✔
698
    }
5✔
699

700
    /// Returns the end of the initial substring where the characters are either contained/not contained
701
    /// in the set.
702
    ///
703
    /// # Examples
704
    ///
705
    /// ```
706
    /// use icu_collections::codepointinvlist::CodePointInversionList;
707
    /// let example_list = [0x41, 0x44]; // {A, B, C}
708
    /// let example =
709
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
710
    ///         .unwrap();
711
    /// assert_eq!(example.span("CABXYZ", true), 3);
712
    /// assert_eq!(example.span("XYZC", false), 3);
713
    /// assert_eq!(example.span("XYZ", true), 0);
714
    /// assert_eq!(example.span("ABC", false), 0);
715
    /// ```
716
    pub fn span(&self, span_str: &str, contained: bool) -> usize {
8✔
717
        span_str
16✔
718
            .chars()
719
            .take_while(|&x| self.contains(x) == contained)
30✔
720
            .count()
721
    }
8✔
722

723
    /// Returns the start of the trailing substring (starting from end of string) where the characters are
724
    /// either contained/not contained in the set. Returns the length of the string if no valid return.
725
    ///
726
    /// # Examples
727
    ///
728
    /// ```
729
    /// use icu_collections::codepointinvlist::CodePointInversionList;
730
    /// let example_list = [0x41, 0x44]; // {A, B, C}
731
    /// let example =
732
    ///     CodePointInversionList::try_from_inversion_list_slice(&example_list)
733
    ///         .unwrap();
734
    /// assert_eq!(example.span_back("XYZCAB", true), 3);
735
    /// assert_eq!(example.span_back("ABCXYZ", true), 6);
736
    /// assert_eq!(example.span_back("CABXYZ", false), 3);
737
    /// ```
738
    pub fn span_back(&self, span_str: &str, contained: bool) -> usize {
7✔
739
        span_str.len()
14✔
740
            - span_str
14✔
741
                .chars()
742
                .rev()
743
                .take_while(|&x| self.contains(x) == contained)
27✔
744
                .count()
745
    }
7✔
746
}
747

748
#[cfg(test)]
749
mod tests {
750
    use super::{CodePointInversionList, CodePointInversionListError};
751
    use std::{char, vec::Vec};
752
    use zerovec::ZeroVec;
753

754
    #[test]
755
    fn test_codepointinversionlist_try_from_vec() {
2✔
756
        let ex = vec![0x2, 0x3, 0x4, 0x5];
1✔
757
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
758
        assert_eq!(ex, check.get_inversion_list_vec());
1✔
759
        assert_eq!(2, check.size());
1✔
760
    }
2✔
761

762
    #[test]
763
    fn test_codepointinversionlist_try_from_vec_error() {
2✔
764
        let check = vec![0x1, 0x1, 0x2, 0x3, 0x4];
1✔
765
        let inv_list = ZeroVec::from_slice_or_alloc(&check);
1✔
766
        let set = CodePointInversionList::try_from_inversion_list(inv_list);
1✔
767
        assert!(matches!(
1✔
768
            set,
1✔
769
            Err(CodePointInversionListError::InvalidSet(_))
770
        ));
771
        if let Err(CodePointInversionListError::InvalidSet(actual)) = set {
1✔
772
            assert_eq!(&check, &actual);
1✔
773
        }
1✔
774
    }
2✔
775

776
    // CodePointInversionList membership functions
777
    #[test]
778
    fn test_codepointinversionlist_contains_query() {
2✔
779
        let ex = vec![0x41, 0x46, 0x4B, 0x55];
1✔
780
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
781
        assert!(check.contains_query(0x40).is_none());
1✔
782
        assert_eq!(check.contains_query(0x41).unwrap(), 0);
1✔
783
        assert_eq!(check.contains_query(0x44).unwrap(), 0);
1✔
784
        assert!(check.contains_query(0x46).is_none());
1✔
785
        assert_eq!(check.contains_query(0x4C).unwrap(), 2);
1✔
786
        assert!(check.contains_query(0x56).is_none());
1✔
787
    }
2✔
788

789
    #[test]
790
    fn test_codepointinversionlist_contains() {
2✔
791
        let ex = vec![0x2, 0x5, 0xA, 0xF];
1✔
792
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
793
        assert!(check.contains(0x2 as char));
1✔
794
        assert!(check.contains(0x4 as char));
1✔
795
        assert!(check.contains(0xA as char));
1✔
796
        assert!(check.contains(0xE as char));
1✔
797
    }
2✔
798

799
    #[test]
800
    fn test_codepointinversionlist_contains_false() {
2✔
801
        let ex = vec![0x2, 0x5, 0xA, 0xF];
1✔
802
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
803
        assert!(!check.contains(0x1 as char));
1✔
804
        assert!(!check.contains(0x5 as char));
1✔
805
        assert!(!check.contains(0x9 as char));
1✔
806
        assert!(!check.contains(0xF as char));
1✔
807
        assert!(!check.contains(0x10 as char));
1✔
808
    }
2✔
809

810
    #[test]
811
    fn test_codepointinversionlist_contains_range() {
2✔
812
        let ex = vec![0x41, 0x46, 0x4B, 0x55];
1✔
813
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
814
        assert!(check.contains_range(&('A'..='E'))); // 65 - 69
1✔
815
        assert!(check.contains_range(&('C'..'D'))); // 67 - 67
1✔
816
        assert!(check.contains_range(&('L'..'P'))); // 76 - 80
1✔
817
        assert!(!check.contains_range(&('L'..='U'))); // 76 - 85
1✔
818
    }
2✔
819

820
    #[test]
821
    fn test_codepointinversionlist_contains_range_false() {
2✔
822
        let ex = vec![0x41, 0x46, 0x4B, 0x55];
1✔
823
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
824
        assert!(!check.contains_range(&('!'..'A'))); // 33 - 65
1✔
825
        assert!(!check.contains_range(&('F'..'K'))); // 70 - 74
1✔
826
        assert!(!check.contains_range(&('U'..))); // 85 - ..
1✔
827
    }
2✔
828

829
    #[test]
830
    fn test_codepointinversionlist_contains_range_invalid() {
2✔
831
        let check = CodePointInversionList::all();
1✔
832
        assert!(!check.contains_range(&('A'..'!'))); // 65 - 33
1✔
833
        assert!(!check.contains_range(&('A'..'A'))); // 65 - 65
1✔
834
    }
2✔
835

836
    #[test]
837
    fn test_codepointinversionlist_contains_set_u() {
2✔
838
        let ex = vec![0xA, 0x14, 0x28, 0x32, 0x46, 0x50, 0x64, 0x6E];
1✔
839
        let u = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
840
        let inside = vec![0xF, 0x14, 0x2C, 0x31, 0x46, 0x50, 0x64, 0x6D];
1✔
841
        let s = CodePointInversionList::try_from_inversion_list_slice(&inside).unwrap();
1✔
842
        assert!(u.contains_set(&s));
1✔
843
    }
2✔
844

845
    #[test]
846
    fn test_codepointinversionlist_contains_set_u_false() {
2✔
847
        let ex = vec![0xA, 0x14, 0x28, 0x32, 0x46, 0x50, 0x64, 0x78];
1✔
848
        let u = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
849
        let outside = vec![0x0, 0xA, 0x16, 0x2C, 0x32, 0x46, 0x4F, 0x51, 0x6D, 0x6F];
1✔
850
        let s = CodePointInversionList::try_from_inversion_list_slice(&outside).unwrap();
1✔
851
        assert!(!u.contains_set(&s));
1✔
852
    }
2✔
853

854
    #[test]
855
    fn test_codepointinversionlist_size() {
2✔
856
        let ex = vec![0x2, 0x5, 0xA, 0xF];
1✔
857
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
858
        assert_eq!(8, check.size());
1✔
859
        let check = CodePointInversionList::all();
1✔
860
        let expected = (char::MAX as u32) + 1;
1✔
861
        assert_eq!(expected as usize, check.size());
1✔
862
        let inv_list_vec: Vec<u32> = vec![];
1✔
863
        let check = CodePointInversionList {
1✔
864
            inv_list: ZeroVec::from_slice_or_alloc(&inv_list_vec),
1✔
865
            size: 0,
866
        };
867
        assert_eq!(check.size(), 0);
1✔
868
    }
2✔
869

870
    #[test]
871
    fn test_codepointinversionlist_is_empty() {
2✔
872
        let inv_list_vec: Vec<u32> = vec![];
1✔
873
        let check = CodePointInversionList {
1✔
874
            inv_list: ZeroVec::from_slice_or_alloc(&inv_list_vec),
1✔
875
            size: 0,
876
        };
877
        assert!(check.is_empty());
1✔
878
    }
2✔
879

880
    #[test]
881
    fn test_codepointinversionlist_is_not_empty() {
2✔
882
        let check = CodePointInversionList::all();
1✔
883
        assert!(!check.is_empty());
1✔
884
    }
2✔
885

886
    #[test]
887
    fn test_codepointinversionlist_iter_chars() {
2✔
888
        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
1✔
889
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
890
        let mut iter = check.iter_chars();
1✔
891
        assert_eq!(Some('A'), iter.next());
1✔
892
        assert_eq!(Some('B'), iter.next());
1✔
893
        assert_eq!(Some('C'), iter.next());
1✔
894
        assert_eq!(Some('E'), iter.next());
1✔
895
        assert_eq!(None, iter.next());
1✔
896
    }
2✔
897

898
    #[test]
899
    fn test_codepointinversionlist_iter_ranges() {
2✔
900
        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
1✔
901
        let set = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
902
        let mut ranges = set.iter_ranges();
1✔
903
        assert_eq!(Some(0x41..=0x43), ranges.next());
1✔
904
        assert_eq!(Some(0x45..=0x45), ranges.next());
1✔
905
        assert_eq!(Some(0xD800..=0xD800), ranges.next());
1✔
906
        assert_eq!(None, ranges.next());
1✔
907
    }
2✔
908

909
    #[test]
910
    fn test_codepointinversionlist_iter_ranges_exactsizeiter_trait() {
2✔
911
        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
1✔
912
        let set = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
913
        let ranges = set.iter_ranges();
1✔
914
        assert_eq!(3, ranges.len());
1✔
915
    }
2✔
916

917
    #[test]
918
    fn test_codepointinversionlist_range_count() {
2✔
919
        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
1✔
920
        let set = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
921
        assert_eq!(3, set.get_range_count());
1✔
922
    }
2✔
923

924
    #[test]
925
    fn test_codepointinversionlist_get_nth_range() {
2✔
926
        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
1✔
927
        let set = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
928
        assert_eq!(Some(0x41..=0x43), set.get_nth_range(0));
1✔
929
        assert_eq!(Some(0x45..=0x45), set.get_nth_range(1));
1✔
930
        assert_eq!(Some(0xD800..=0xD800), set.get_nth_range(2));
1✔
931
        assert_eq!(None, set.get_nth_range(3));
1✔
932
    }
2✔
933

934
    // Range<char> cannot represent the upper bound (non-inclusive) for
935
    // char::MAX, whereas Range<u32> can.
936
    #[test]
937
    fn test_codepointinversionlist_iter_ranges_with_max_code_point() {
2✔
938
        let ex = vec![0x80, (char::MAX as u32) + 1];
1✔
939
        let set = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
940
        let mut ranges = set.iter_ranges();
1✔
941
        assert_eq!(Some(0x80..=(char::MAX as u32)), ranges.next());
1✔
942
        assert_eq!(None, ranges.next());
1✔
943
    }
2✔
944

945
    #[test]
946
    fn test_codepointinversionlist_span_contains() {
2✔
947
        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
1✔
948
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
949
        assert_eq!(check.span("ABCDE", true), 3);
1✔
950
        assert_eq!(check.span("E", true), 0);
1✔
951
    }
2✔
952

953
    #[test]
954
    fn test_codepointinversionlist_span_does_not_contain() {
2✔
955
        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
1✔
956
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
957
        assert_eq!(check.span("DEF", false), 2);
1✔
958
        assert_eq!(check.span("KLMA", false), 3);
1✔
959
    }
2✔
960

961
    #[test]
962
    fn test_codepointinversionlist_span_back_contains() {
2✔
963
        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
1✔
964
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
965
        assert_eq!(check.span_back("XYZABFH", true), 3);
1✔
966
        assert_eq!(check.span_back("ABCXYZ", true), 6);
1✔
967
    }
2✔
968

969
    #[test]
970
    fn test_codepointinversionlist_span_back_does_not_contain() {
2✔
971
        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
1✔
972
        let check = CodePointInversionList::try_from_inversion_list_slice(&ex).unwrap();
1✔
973
        assert_eq!(check.span_back("ABCXYZ", false), 3);
1✔
974
        assert_eq!(check.span_back("XYZABC", false), 6);
1✔
975
    }
2✔
976

977
    #[test]
978
    fn test_uniset_to_inv_list() {
2✔
979
        let inv_list = [
1✔
980
            0x9, 0xE, 0x20, 0x21, 0x85, 0x86, 0xA0, 0xA1, 0x1626, 0x1627, 0x2000, 0x2003, 0x2028,
981
            0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001,
982
        ];
983
        let s: CodePointInversionList =
984
            CodePointInversionList::try_from_inversion_list_slice(&inv_list).unwrap();
1✔
985
        let round_trip_inv_list = s.get_inversion_list_vec();
1✔
986
        assert_eq!(round_trip_inv_list, inv_list);
1✔
987
    }
2✔
988

989
    #[test]
990
    fn test_serde_serialize() {
2✔
991
        let inv_list = [0x41, 0x46, 0x4B, 0x55];
1✔
992
        let uniset = CodePointInversionList::try_from_inversion_list_slice(&inv_list).unwrap();
1✔
993
        let json_str = serde_json::to_string(&uniset).unwrap();
1✔
994
        assert_eq!(json_str, r#"["A-E","K-T"]"#);
1✔
995
    }
2✔
996

997
    #[test]
998
    fn test_serde_serialize_surrogates() {
2✔
999
        let inv_list = [0xDFAB, 0xDFFF];
1✔
1000
        let uniset = CodePointInversionList::try_from_inversion_list_slice(&inv_list).unwrap();
1✔
1001
        let json_str = serde_json::to_string(&uniset).unwrap();
1✔
1002
        assert_eq!(json_str, r#"["U+DFAB-U+DFFE"]"#);
1✔
1003
    }
2✔
1004

1005
    #[test]
1006
    fn test_serde_deserialize() {
2✔
1007
        let inv_list_str = r#"["A-E","K-T"]"#;
1✔
1008
        let exp_inv_list = [0x41, 0x46, 0x4B, 0x55];
1✔
1009
        let exp_uniset =
1010
            CodePointInversionList::try_from_inversion_list_slice(&exp_inv_list).unwrap();
1✔
1011
        let act_uniset: CodePointInversionList = serde_json::from_str(inv_list_str).unwrap();
1✔
1012
        assert_eq!(act_uniset, exp_uniset);
1✔
1013
    }
2✔
1014

1015
    #[test]
1016
    fn test_serde_deserialize_surrogates() {
2✔
1017
        let inv_list_str = r#"["U+DFAB-U+DFFE"]"#;
1✔
1018
        let exp_inv_list = [0xDFAB, 0xDFFF];
1✔
1019
        let exp_uniset =
1020
            CodePointInversionList::try_from_inversion_list_slice(&exp_inv_list).unwrap();
1✔
1021
        let act_uniset: CodePointInversionList = serde_json::from_str(inv_list_str).unwrap();
1✔
1022
        assert_eq!(act_uniset, exp_uniset);
1✔
1023
    }
2✔
1024

1025
    #[test]
1026
    fn test_serde_deserialize_legacy() {
2✔
1027
        let inv_list_str = "[65,70,75,85]";
1✔
1028
        let exp_inv_list = [0x41, 0x46, 0x4B, 0x55];
1✔
1029
        let exp_uniset =
1030
            CodePointInversionList::try_from_inversion_list_slice(&exp_inv_list).unwrap();
1✔
1031
        let act_uniset: CodePointInversionList = serde_json::from_str(inv_list_str).unwrap();
1✔
1032
        assert_eq!(act_uniset, exp_uniset);
1✔
1033
    }
2✔
1034

1035
    #[test]
1036
    fn test_serde_deserialize_invalid() {
2✔
1037
        assert!(serde_json::from_str::<CodePointInversionList>("[65,70,98775,85]").is_err());
1✔
1038
        assert!(serde_json::from_str::<CodePointInversionList>("[65,70,U+FFFFFFFFFF,85]").is_err());
1✔
1039
    }
2✔
1040

1041
    #[test]
1042
    fn test_serde_with_postcard_roundtrip() -> Result<(), postcard::Error> {
2✔
1043
        let set = CodePointInversionList::bmp();
1✔
1044
        let set_serialized: Vec<u8> = postcard::to_allocvec(&set).unwrap();
1✔
1045
        let set_deserialized: CodePointInversionList =
1046
            postcard::from_bytes::<CodePointInversionList>(&set_serialized)?;
1✔
1047

1048
        assert_eq!(&set, &set_deserialized);
1✔
1049
        assert!(!set_deserialized.inv_list.is_owned());
1✔
1050

1051
        Ok(())
1✔
1052
    }
2✔
1053

1054
    #[test]
1055
    fn databake() {
2✔
1056
        databake::test_bake!(
2✔
1057
            CodePointInversionList<'static>,
1058
            const: unsafe {
1059
                #[allow(unused_unsafe)]
1060
                crate::codepointinvlist::CodePointInversionList::from_parts_unchecked(
1✔
1061
                    unsafe {
1062
                        zerovec::ZeroVec::from_bytes_unchecked(
1✔
1063
                            b"0\0\0\0:\0\0\0A\0\0\0G\0\0\0a\0\0\0g\0\0\0"
1064
                        )
1065
                    },
1066
                    22u32,
1067
                )
1068
            },
1069
            icu_collections,
1070
            [zerovec],
1071
        );
1072
    }
2✔
1073
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc