• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 macro_rules! impl_tinystr_subtag {
6     (
7         $(#[$doc:meta])*
8         $name:ident,
9         $($path:ident)::+,
10         $macro_name:ident,
11         $internal_macro_name:ident,
12         $len_start:literal..=$len_end:literal,
13         $tinystr_ident:ident,
14         $validate:expr,
15         $normalize:expr,
16         $is_normalized:expr,
17         $error:ident,
18         [$good_example:literal $(,$more_good_examples:literal)*],
19         [$bad_example:literal $(, $more_bad_examples:literal)*],
20     ) => {
21         #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Copy)]
22         #[cfg_attr(feature = "serde", derive(serde::Serialize))]
23         #[repr(transparent)]
24         $(#[$doc])*
25         pub struct $name(tinystr::TinyAsciiStr<$len_end>);
26 
27         impl $name {
28             /// A constructor which takes a str slice, parses it and
29             #[doc = concat!("produces a well-formed [`", stringify!($name), "`].")]
30             ///
31             /// # Examples
32             ///
33             /// ```
34             #[doc = concat!("use icu_locale_core::", stringify!($($path::)+), stringify!($name), ";")]
35             ///
36             #[doc = concat!("assert!(", stringify!($name), "::try_from_str(", stringify!($good_example), ").is_ok());")]
37             #[doc = concat!("assert!(", stringify!($name), "::try_from_str(", stringify!($bad_example), ").is_err());")]
38             /// ```
39             #[inline]
40             pub const fn try_from_str(s: &str) -> Result<Self, crate::parser::errors::ParseError> {
41                 Self::try_from_utf8(s.as_bytes())
42             }
43 
44             /// See [`Self::try_from_str`]
45             pub const fn try_from_utf8(
46                 code_units: &[u8],
47             ) -> Result<Self, crate::parser::errors::ParseError> {
48                 #[allow(clippy::double_comparisons)] // if code_units.len() === 0
49                 if code_units.len() < $len_start || code_units.len() > $len_end {
50                     return Err(crate::parser::errors::ParseError::$error);
51                 }
52 
53                 match tinystr::TinyAsciiStr::try_from_utf8(code_units) {
54                     Ok($tinystr_ident) if $validate => Ok(Self($normalize)),
55                     _ => Err(crate::parser::errors::ParseError::$error),
56                 }
57             }
58 
59             #[doc = concat!("Safely creates a [`", stringify!($name), "`] from its raw format")]
60             /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
61             /// this constructor only takes normalized values.
62             pub const fn try_from_raw(
63                 raw: [u8; $len_end],
64             ) -> Result<Self, crate::parser::errors::ParseError> {
65                 if let Ok($tinystr_ident) = tinystr::TinyAsciiStr::<$len_end>::try_from_raw(raw) {
66                     if $tinystr_ident.len() >= $len_start && $is_normalized {
67                         Ok(Self($tinystr_ident))
68                     } else {
69                         Err(crate::parser::errors::ParseError::$error)
70                     }
71                 } else {
72                     Err(crate::parser::errors::ParseError::$error)
73                 }
74             }
75 
76             #[doc = concat!("Unsafely creates a [`", stringify!($name), "`] from its raw format")]
77             /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
78             /// this constructor only takes normalized values.
79             ///
80             /// # Safety
81             ///
82             /// This function is safe iff [`Self::try_from_raw`] returns an `Ok`. This is the case
83             /// for inputs that are correctly normalized.
84             pub const unsafe fn from_raw_unchecked(v: [u8; $len_end]) -> Self {
85                 Self(tinystr::TinyAsciiStr::from_utf8_unchecked(v))
86             }
87 
88             /// Deconstructs into a raw format to be consumed by
89             /// [`from_raw_unchecked`](Self::from_raw_unchecked()) or
90             /// [`try_from_raw`](Self::try_from_raw()).
91             pub const fn into_raw(self) -> [u8; $len_end] {
92                 *self.0.all_bytes()
93             }
94 
95             #[inline]
96             /// A helper function for displaying as a `&str`.
97             pub const fn as_str(&self) -> &str {
98                 self.0.as_str()
99             }
100 
101             #[doc(hidden)]
102             pub const fn to_tinystr(&self) -> tinystr::TinyAsciiStr<$len_end> {
103                 self.0
104             }
105 
106             /// Compare with BCP-47 bytes.
107             ///
108             /// The return value is equivalent to what would happen if you first converted
109             /// `self` to a BCP-47 string and then performed a byte comparison.
110             ///
111             /// This function is case-sensitive and results in a *total order*, so it is appropriate for
112             /// binary search. The only argument producing [`Ordering::Equal`](core::cmp::Ordering::Equal)
113             /// is `self.as_str().as_bytes()`.
114             #[inline]
115             pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
116                 self.as_str().as_bytes().cmp(other)
117             }
118 
119             /// Compare with a potentially unnormalized BCP-47 string.
120             ///
121             /// The return value is equivalent to what would happen if you first parsed the
122             /// BCP-47 string and then performed a structural comparison.
123             ///
124             #[inline]
125             pub fn normalizing_eq(self, other: &str) -> bool {
126                 self.as_str().eq_ignore_ascii_case(other)
127             }
128         }
129 
130         impl core::str::FromStr for $name {
131             type Err = crate::parser::errors::ParseError;
132 
133             #[inline]
134             fn from_str(s: &str) -> Result<Self, Self::Err> {
135                 Self::try_from_str(s)
136             }
137         }
138 
139         impl<'l> From<&'l $name> for &'l str {
140             fn from(input: &'l $name) -> Self {
141                 input.as_str()
142             }
143         }
144 
145         impl From<$name> for tinystr::TinyAsciiStr<$len_end> {
146             fn from(input: $name) -> Self {
147                 input.to_tinystr()
148             }
149         }
150 
151         impl writeable::Writeable for $name {
152             #[inline]
153             fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
154                 sink.write_str(self.as_str())
155             }
156             #[inline]
157             fn writeable_length_hint(&self) -> writeable::LengthHint {
158                 writeable::LengthHint::exact(self.0.len())
159             }
160             #[inline]
161             #[cfg(feature = "alloc")]
162             fn write_to_string(&self) -> alloc::borrow::Cow<str> {
163                 alloc::borrow::Cow::Borrowed(self.0.as_str())
164             }
165         }
166 
167         writeable::impl_display_with_writeable!($name);
168 
169         #[doc = concat!("A macro allowing for compile-time construction of valid [`", stringify!($name), "`] subtags.")]
170         ///
171         /// # Examples
172         ///
173         /// Parsing errors don't have to be handled at runtime:
174         /// ```
175         /// assert_eq!(
176         #[doc = concat!("  icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($good_example) ,"),")]
177         #[doc = concat!("  ", stringify!($good_example), ".parse::<icu_locale_core::", $(stringify!($path), "::",)+ stringify!($name), ">().unwrap()")]
178         /// );
179         /// ```
180         ///
181         /// Invalid input is a compile failure:
182         /// ```compile_fail,E0080
183         #[doc = concat!("icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($bad_example) ,");")]
184         /// ```
185         ///
186         #[doc = concat!("[`", stringify!($name), "`]: crate::", $(stringify!($path), "::",)+ stringify!($name))]
187         #[macro_export]
188         #[doc(hidden)] // macro
189         macro_rules! $internal_macro_name {
190             ($string:literal) => {{
191                 use $crate::$($path ::)+ $name;
192                 const R: $name =
193                     match $name::try_from_utf8($string.as_bytes()) {
194                         Ok(r) => r,
195                         #[allow(clippy::panic)] // const context
196                         _ => panic!(concat!("Invalid ", $(stringify!($path), "::",)+ stringify!($name), ": ", $string)),
197                     };
198                 R
199             }};
200         }
201         #[doc(inline)]
202         pub use $internal_macro_name as $macro_name;
203 
204         #[cfg(feature = "databake")]
205         impl databake::Bake for $name {
206             fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
207                 env.insert("icu_locale_core");
208                 let string = self.as_str();
209                 databake::quote! { icu_locale_core::$($path::)+ $macro_name!(#string) }
210             }
211         }
212 
213         #[cfg(feature = "databake")]
214         impl databake::BakeSize for $name {
215             fn borrows_size(&self) -> usize {
216                 0
217             }
218         }
219 
220         #[test]
221         fn test_construction() {
222             let maybe = $name::try_from_utf8($good_example.as_bytes());
223             assert!(maybe.is_ok());
224             assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
225             assert_eq!(maybe.unwrap().as_str(), $good_example);
226             $(
227                 let maybe = $name::try_from_utf8($more_good_examples.as_bytes());
228                 assert!(maybe.is_ok());
229                 assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
230                 assert_eq!(maybe.unwrap().as_str(), $more_good_examples);
231             )*
232             assert!($name::try_from_utf8($bad_example.as_bytes()).is_err());
233             $(
234                 assert!($name::try_from_utf8($more_bad_examples.as_bytes()).is_err());
235             )*
236         }
237 
238         #[test]
239         fn test_writeable() {
240             writeable::assert_writeable_eq!(&$good_example.parse::<$name>().unwrap(), $good_example);
241             $(
242                 writeable::assert_writeable_eq!($more_good_examples.parse::<$name>().unwrap(), $more_good_examples);
243             )*
244         }
245 
246         #[cfg(feature = "serde")]
247         impl<'de> serde::Deserialize<'de> for $name {
248             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
249             where
250                 D: serde::de::Deserializer<'de>,
251             {
252                 struct Visitor;
253 
254                 impl<'de> serde::de::Visitor<'de> for Visitor {
255                     type Value = $name;
256 
257                     fn expecting(
258                         &self,
259                         formatter: &mut core::fmt::Formatter<'_>,
260                     ) -> core::fmt::Result {
261                         write!(formatter, "a valid BCP-47 {}", stringify!($name))
262                     }
263 
264                     fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
265                         s.parse().map_err(serde::de::Error::custom)
266                     }
267                 }
268 
269                 if deserializer.is_human_readable() {
270                     deserializer.deserialize_string(Visitor)
271                 } else {
272                     Self::try_from_raw(serde::de::Deserialize::deserialize(deserializer)?)
273                         .map_err(serde::de::Error::custom)
274                 }
275             }
276         }
277 
278         // Safety checklist for ULE:
279         //
280         // 1. Must not include any uninitialized or padding bytes (true since transparent over a ULE).
281         // 2. Must have an alignment of 1 byte (true since transparent over a ULE).
282         // 3. ULE::validate_bytes() checks that the given byte slice represents a valid slice.
283         // 4. ULE::validate_bytes() checks that the given byte slice has a valid length.
284         // 5. All other methods must be left with their default impl.
285         // 6. Byte equality is semantic equality.
286         #[cfg(feature = "zerovec")]
287         unsafe impl zerovec::ule::ULE for $name {
288             fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
289                 let it = bytes.chunks_exact(core::mem::size_of::<Self>());
290                 if !it.remainder().is_empty() {
291                     return Err(zerovec::ule::UleError::length::<Self>(bytes.len()));
292                 }
293                 for v in it {
294                     // The following can be removed once `array_chunks` is stabilized.
295                     let mut a = [0; core::mem::size_of::<Self>()];
296                     a.copy_from_slice(v);
297                     if Self::try_from_raw(a).is_err() {
298                         return Err(zerovec::ule::UleError::parse::<Self>());
299                     }
300                 }
301                 Ok(())
302             }
303         }
304 
305         #[cfg(feature = "zerovec")]
306         impl zerovec::ule::NicheBytes<$len_end> for $name {
307             const NICHE_BIT_PATTERN: [u8; $len_end] = <tinystr::TinyAsciiStr<$len_end>>::NICHE_BIT_PATTERN;
308         }
309 
310         #[cfg(feature = "zerovec")]
311         impl zerovec::ule::AsULE for $name {
312             type ULE = Self;
313             fn to_unaligned(self) -> Self::ULE {
314                 self
315             }
316             fn from_unaligned(unaligned: Self::ULE) -> Self {
317                 unaligned
318             }
319         }
320 
321         #[cfg(feature = "zerovec")]
322         impl<'a> zerovec::maps::ZeroMapKV<'a> for $name {
323             type Container = zerovec::ZeroVec<'a, $name>;
324             type Slice = zerovec::ZeroSlice<$name>;
325             type GetType = $name;
326             type OwnedType = $name;
327         }
328     };
329 }
330 
331 #[macro_export]
332 #[doc(hidden)]
333 macro_rules! impl_writeable_for_each_subtag_str_no_test {
334     ($type:tt $(, $self:ident, $borrow_cond:expr => $borrow:expr)?) => {
335         impl writeable::Writeable for $type {
336             fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
337                 let mut initial = true;
338                 self.for_each_subtag_str(&mut |subtag| {
339                     if initial {
340                         initial = false;
341                     } else {
342                         sink.write_char('-')?;
343                     }
344                     sink.write_str(subtag)
345                 })
346             }
347 
348             #[inline]
349             fn writeable_length_hint(&self) -> writeable::LengthHint {
350                 let mut result = writeable::LengthHint::exact(0);
351                 let mut initial = true;
352                 self.for_each_subtag_str::<core::convert::Infallible, _>(&mut |subtag| {
353                     if initial {
354                         initial = false;
355                     } else {
356                         result += 1;
357                     }
358                     result += subtag.len();
359                     Ok(())
360                 })
361                 .expect("infallible");
362                 result
363             }
364 
365             $(
366                 #[cfg(feature = "alloc")]
367                 fn write_to_string(&self) -> alloc::borrow::Cow<str> {
368                     #[allow(clippy::unwrap_used)] // impl_writeable_for_subtag_list's $borrow uses unwrap
369                     let $self = self;
370                     if $borrow_cond {
371                         $borrow
372                     } else {
373                         let mut output = alloc::string::String::with_capacity(self.writeable_length_hint().capacity());
374                         let _ = self.write_to(&mut output);
375                         alloc::borrow::Cow::Owned(output)
376                     }
377                 }
378             )?
379         }
380 
381         writeable::impl_display_with_writeable!($type);
382     };
383 }
384 
385 macro_rules! impl_writeable_for_subtag_list {
386     ($type:tt, $sample1:literal, $sample2:literal) => {
387         impl_writeable_for_each_subtag_str_no_test!($type, selff, selff.0.len() == 1 => alloc::borrow::Cow::Borrowed(selff.0.get(0).unwrap().as_str()));
388 
389         #[test]
390         fn test_writeable() {
391             writeable::assert_writeable_eq!(&$type::default(), "");
392             writeable::assert_writeable_eq!(
393                 &$type::from_vec_unchecked(alloc::vec![$sample1.parse().unwrap()]),
394                 $sample1,
395             );
396             writeable::assert_writeable_eq!(
397                 &$type::from_vec_unchecked(vec![
398                     $sample1.parse().unwrap(),
399                     $sample2.parse().unwrap()
400                 ]),
401                 core::concat!($sample1, "-", $sample2),
402             );
403         }
404     };
405 }
406 
407 macro_rules! impl_writeable_for_key_value {
408     ($type:tt, $key1:literal, $value1:literal, $key2:literal, $expected2:literal) => {
409         impl_writeable_for_each_subtag_str_no_test!($type);
410 
411         #[test]
412         fn test_writeable() {
413             writeable::assert_writeable_eq!(&$type::default(), "");
414             writeable::assert_writeable_eq!(
415                 &$type::from_tuple_vec(vec![($key1.parse().unwrap(), $value1.parse().unwrap())]),
416                 core::concat!($key1, "-", $value1),
417             );
418             writeable::assert_writeable_eq!(
419                 &$type::from_tuple_vec(vec![
420                     ($key1.parse().unwrap(), $value1.parse().unwrap()),
421                     ($key2.parse().unwrap(), "true".parse().unwrap())
422                 ]),
423                 core::concat!($key1, "-", $value1, "-", $expected2),
424             );
425         }
426     };
427 }
428