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
5use core::iter::FromIterator;
6
7use alloc::boxed::Box;
8use alloc::vec;
9use alloc::vec::Vec;
10use core::ops::{Deref, DerefMut};
11use litemap::store::*;
12
13/// Internal: A vector that supports no-allocation, constant values if length 0 or 1.
14/// Using ZeroOne(Option<T>) saves 8 bytes in ShortSlice via niche optimization.
15#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub(crate) enum ShortSlice<T> {
17 ZeroOne(Option<T>),
18 Multi(Box<[T]>),
19}
20
21impl<T> ShortSlice<T> {
22 #[inline]
23 pub const fn new() -> Self {
24 Self::ZeroOne(None)
25 }
26
27 #[inline]
28 pub const fn new_single(item: T) -> Self {
29 Self::ZeroOne(Some(item))
30 }
31
32 pub fn push(&mut self, item: T) {
33 *self = match core::mem::replace(self, Self::ZeroOne(None)) {
34 ShortSlice::ZeroOne(None) => ShortSlice::ZeroOne(Some(item)),
35 ShortSlice::ZeroOne(Some(prev_item)) => {
36 ShortSlice::Multi(vec![prev_item, item].into_boxed_slice())
37 }
38 ShortSlice::Multi(items) => {
39 let mut items = items.into_vec();
40 items.push(item);
41 ShortSlice::Multi(items.into_boxed_slice())
42 }
43 };
44 }
45
46 #[inline]
47 pub const fn single(&self) -> Option<&T> {
48 match self {
49 ShortSlice::ZeroOne(Some(v)) => Some(v),
50 _ => None,
51 }
52 }
53
54 #[inline]
55 pub fn len(&self) -> usize {
56 match self {
57 ShortSlice::ZeroOne(None) => 0,
58 ShortSlice::ZeroOne(_) => 1,
59 ShortSlice::Multi(ref v) => v.len(),
60 }
61 }
62
63 pub fn insert(&mut self, index: usize, elt: T) {
64 assert!(
65 index <= self.len(),
66 "insertion index (is {}) should be <= len (is {})",
67 index,
68 self.len()
69 );
70
71 *self = match core::mem::replace(self, ShortSlice::ZeroOne(None)) {
72 ShortSlice::ZeroOne(None) => ShortSlice::ZeroOne(Some(elt)),
73 ShortSlice::ZeroOne(Some(item)) => {
74 let items = if index == 0 {
75 vec![elt, item].into_boxed_slice()
76 } else {
77 vec![item, elt].into_boxed_slice()
78 };
79 ShortSlice::Multi(items)
80 }
81 ShortSlice::Multi(items) => {
82 let mut items = items.into_vec();
83 items.insert(index, elt);
84 ShortSlice::Multi(items.into_boxed_slice())
85 }
86 }
87 }
88
89 pub fn remove(&mut self, index: usize) -> T {
90 assert!(
91 index < self.len(),
92 "removal index (is {}) should be < len (is {})",
93 index,
94 self.len()
95 );
96
97 let (replaced, removed_item) = match core::mem::replace(self, ShortSlice::ZeroOne(None)) {
98 ShortSlice::ZeroOne(None) => unreachable!(),
99 ShortSlice::ZeroOne(Some(v)) => (ShortSlice::ZeroOne(None), v),
100 ShortSlice::Multi(v) => {
101 let mut v = v.into_vec();
102 let removed_item = v.remove(index);
103 match v.len() {
104 #[allow(clippy::unwrap_used)]
105 // we know that the vec has exactly one element left
106 1 => (ShortSlice::ZeroOne(Some(v.pop().unwrap())), removed_item),
107 // v has at least 2 elements, create a Multi variant
108 _ => (ShortSlice::Multi(v.into_boxed_slice()), removed_item),
109 }
110 }
111 };
112 *self = replaced;
113 removed_item
114 }
115
116 #[inline]
117 pub fn clear(&mut self) {
118 let _ = core::mem::replace(self, ShortSlice::ZeroOne(None));
119 }
120
121 pub fn retain<F>(&mut self, mut f: F)
122 where
123 F: FnMut(&T) -> bool,
124 {
125 *self = match core::mem::take(self) {
126 Self::ZeroOne(Some(one)) if f(&one) => Self::ZeroOne(Some(one)),
127 Self::ZeroOne(_) => Self::ZeroOne(None),
128 Self::Multi(slice) => {
129 let mut vec = slice.into_vec();
130 vec.retain(f);
131 Self::from(vec)
132 }
133 };
134 }
135}
136
137impl<T> Deref for ShortSlice<T> {
138 type Target = [T];
139
140 fn deref(&self) -> &Self::Target {
141 match self {
142 ShortSlice::ZeroOne(None) => &[],
143 ShortSlice::ZeroOne(Some(v: &T)) => core::slice::from_ref(v),
144 ShortSlice::Multi(v: &Box<[T]>) => v,
145 }
146 }
147}
148
149impl<T> DerefMut for ShortSlice<T> {
150 fn deref_mut(&mut self) -> &mut Self::Target {
151 match self {
152 ShortSlice::ZeroOne(None) => &mut [],
153 ShortSlice::ZeroOne(Some(v: &mut T)) => core::slice::from_mut(v),
154 ShortSlice::Multi(v: &mut Box<[T]>) => v,
155 }
156 }
157}
158
159impl<T> From<Vec<T>> for ShortSlice<T> {
160 fn from(v: Vec<T>) -> Self {
161 match v.len() {
162 0 => ShortSlice::ZeroOne(None),
163 #[allow(clippy::unwrap_used)] // we know that the vec is not empty
164 1 => ShortSlice::ZeroOne(Some(v.into_iter().next().unwrap())),
165 _ => ShortSlice::Multi(v.into_boxed_slice()),
166 }
167 }
168}
169
170impl<T> Default for ShortSlice<T> {
171 fn default() -> Self {
172 ShortSlice::ZeroOne(None)
173 }
174}
175
176impl<T> FromIterator<T> for ShortSlice<T> {
177 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
178 let mut iter: ::IntoIter = iter.into_iter();
179 match (iter.next(), iter.next()) {
180 (Some(first: T), Some(second: T)) => {
181 // Size hint behaviour same as `Vec::extend` + 2
182 let mut vec: Vec = Vec::with_capacity(iter.size_hint().0.saturating_add(3));
183 vec.push(first);
184 vec.push(second);
185 vec.extend(iter);
186 Self::Multi(vec.into_boxed_slice())
187 }
188 (first: Option, _) => Self::ZeroOne(first),
189 }
190 }
191}
192
193impl<K, V> StoreConstEmpty<K, V> for ShortSlice<(K, V)> {
194 const EMPTY: ShortSlice<(K, V)> = ShortSlice::ZeroOne(None);
195}
196
197impl<K, V> Store<K, V> for ShortSlice<(K, V)> {
198 #[inline]
199 fn lm_len(&self) -> usize {
200 self.len()
201 }
202
203 #[inline]
204 fn lm_is_empty(&self) -> bool {
205 matches!(self, ShortSlice::ZeroOne(None))
206 }
207
208 #[inline]
209 fn lm_get(&self, index: usize) -> Option<(&K, &V)> {
210 self.get(index).map(|elt| (&elt.0, &elt.1))
211 }
212
213 #[inline]
214 fn lm_last(&self) -> Option<(&K, &V)> {
215 match self {
216 ShortSlice::ZeroOne(v) => v.as_ref(),
217 ShortSlice::Multi(v) => v.last(),
218 }
219 .map(|elt| (&elt.0, &elt.1))
220 }
221
222 #[inline]
223 fn lm_binary_search_by<F>(&self, mut cmp: F) -> Result<usize, usize>
224 where
225 F: FnMut(&K) -> core::cmp::Ordering,
226 {
227 self.binary_search_by(|(k, _)| cmp(k))
228 }
229}
230
231impl<K: Ord, V> StoreFromIterable<K, V> for ShortSlice<(K, V)> {
232 fn lm_sort_from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
233 let v: Vec<(K, V)> = Vec::lm_sort_from_iter(iter);
234 v.into()
235 }
236}
237
238impl<K, V> StoreMut<K, V> for ShortSlice<(K, V)> {
239 fn lm_with_capacity(_capacity: usize) -> Self {
240 ShortSlice::ZeroOne(None)
241 }
242
243 fn lm_reserve(&mut self, _additional: usize) {}
244
245 fn lm_get_mut(&mut self, index: usize) -> Option<(&K, &mut V)> {
246 self.get_mut(index).map(|elt| (&elt.0, &mut elt.1))
247 }
248
249 fn lm_push(&mut self, key: K, value: V) {
250 self.push((key, value))
251 }
252
253 fn lm_insert(&mut self, index: usize, key: K, value: V) {
254 self.insert(index, (key, value))
255 }
256
257 fn lm_remove(&mut self, index: usize) -> (K, V) {
258 self.remove(index)
259 }
260
261 fn lm_clear(&mut self) {
262 self.clear();
263 }
264
265 fn lm_retain<F>(&mut self, mut predicate: F)
266 where
267 F: FnMut(&K, &V) -> bool,
268 {
269 self.retain(|(k, v)| predicate(k, v))
270 }
271}
272
273impl<'a, K: 'a, V: 'a> StoreIterable<'a, K, V> for ShortSlice<(K, V)> {
274 type KeyValueIter =
275 core::iter::Map<core::slice::Iter<'a, (K, V)>, for<'r> fn(&'r (K, V)) -> (&'r K, &'r V)>;
276
277 fn lm_iter(&'a self) -> Self::KeyValueIter {
278 self.iter().map(|elt: &(K, V)| (&elt.0, &elt.1))
279 }
280}
281
282impl<K, V> StoreFromIterator<K, V> for ShortSlice<(K, V)> {}
283
284#[test]
285fn test_short_slice_impl() {
286 litemap::testing::check_store::<ShortSlice<(u32, u64)>>();
287}
288
289macro_rules! impl_tinystr_subtag {
290 (
291 $(#[$doc:meta])*
292 $name:ident,
293 $($path:ident)::+,
294 $macro_name:ident,
295 $legacy_macro_name:ident,
296 $len_start:literal..=$len_end:literal,
297 $tinystr_ident:ident,
298 $validate:expr,
299 $normalize:expr,
300 $is_normalized:expr,
301 $error:ident,
302 [$good_example:literal $(,$more_good_examples:literal)*],
303 [$bad_example:literal $(, $more_bad_examples:literal)*],
304 ) => {
305 #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Copy)]
306 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
307 #[repr(transparent)]
308 $(#[$doc])*
309 pub struct $name(tinystr::TinyAsciiStr<$len_end>);
310
311 impl $name {
312 /// A constructor which takes a UTF-8 slice, parses it and
313 #[doc = concat!("produces a well-formed [`", stringify!($name), "`].")]
314 ///
315 /// # Examples
316 ///
317 /// ```
318 #[doc = concat!("use icu_locid::", stringify!($($path::)+), stringify!($name), ";")]
319 ///
320 #[doc = concat!("assert!(", stringify!($name), "::try_from_bytes(b", stringify!($good_example), ").is_ok());")]
321 #[doc = concat!("assert!(", stringify!($name), "::try_from_bytes(b", stringify!($bad_example), ").is_err());")]
322 /// ```
323 pub const fn try_from_bytes(v: &[u8]) -> Result<Self, crate::parser::errors::ParserError> {
324 Self::try_from_bytes_manual_slice(v, 0, v.len())
325 }
326
327 /// Equivalent to [`try_from_bytes(bytes[start..end])`](Self::try_from_bytes),
328 /// but callable in a `const` context (which range indexing is not).
329 pub const fn try_from_bytes_manual_slice(
330 v: &[u8],
331 start: usize,
332 end: usize,
333 ) -> Result<Self, crate::parser::errors::ParserError> {
334 let slen = end - start;
335
336 #[allow(clippy::double_comparisons)] // if len_start == len_end
337 if slen < $len_start || slen > $len_end {
338 return Err(crate::parser::errors::ParserError::$error);
339 }
340
341 match tinystr::TinyAsciiStr::from_bytes_manual_slice(v, start, end) {
342 Ok($tinystr_ident) if $validate => Ok(Self($normalize)),
343 _ => Err(crate::parser::errors::ParserError::$error),
344 }
345 }
346
347 #[doc = concat!("Safely creates a [`", stringify!($name), "`] from its raw format")]
348 /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_bytes`],
349 /// this constructor only takes normalized values.
350 pub const fn try_from_raw(
351 v: [u8; $len_end],
352 ) -> Result<Self, crate::parser::errors::ParserError> {
353 if let Ok($tinystr_ident) = tinystr::TinyAsciiStr::<$len_end>::try_from_raw(v) {
354 if $tinystr_ident.len() >= $len_start && $is_normalized {
355 Ok(Self($tinystr_ident))
356 } else {
357 Err(crate::parser::errors::ParserError::$error)
358 }
359 } else {
360 Err(crate::parser::errors::ParserError::$error)
361 }
362 }
363
364 #[doc = concat!("Unsafely creates a [`", stringify!($name), "`] from its raw format")]
365 /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_bytes`],
366 /// this constructor only takes normalized values.
367 ///
368 /// # Safety
369 ///
370 /// This function is safe iff [`Self::try_from_raw`] returns an `Ok`. This is the case
371 /// for inputs that are correctly normalized.
372 pub const unsafe fn from_raw_unchecked(v: [u8; $len_end]) -> Self {
373 Self(tinystr::TinyAsciiStr::from_bytes_unchecked(v))
374 }
375
376 /// Deconstructs into a raw format to be consumed by
377 /// [`from_raw_unchecked`](Self::from_raw_unchecked()) or
378 /// [`try_from_raw`](Self::try_from_raw()).
379 pub const fn into_raw(self) -> [u8; $len_end] {
380 *self.0.all_bytes()
381 }
382
383 #[inline]
384 /// A helper function for displaying as a `&str`.
385 pub const fn as_str(&self) -> &str {
386 self.0.as_str()
387 }
388
389 #[doc(hidden)]
390 pub const fn into_tinystr(&self) -> tinystr::TinyAsciiStr<$len_end> {
391 self.0
392 }
393
394 /// Compare with BCP-47 bytes.
395 ///
396 /// The return value is equivalent to what would happen if you first converted
397 /// `self` to a BCP-47 string and then performed a byte comparison.
398 ///
399 /// This function is case-sensitive and results in a *total order*, so it is appropriate for
400 /// binary search. The only argument producing [`Ordering::Equal`](core::cmp::Ordering::Equal)
401 /// is `self.as_str().as_bytes()`.
402 #[inline]
403 pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
404 self.as_str().as_bytes().cmp(other)
405 }
406
407 /// Compare with a potentially unnormalized BCP-47 string.
408 ///
409 /// The return value is equivalent to what would happen if you first parsed the
410 /// BCP-47 string and then performed a structural comparison.
411 ///
412 #[inline]
413 pub fn normalizing_eq(self, other: &str) -> bool {
414 self.as_str().eq_ignore_ascii_case(other)
415 }
416 }
417
418 impl core::str::FromStr for $name {
419 type Err = crate::parser::errors::ParserError;
420
421 fn from_str(source: &str) -> Result<Self, Self::Err> {
422 Self::try_from_bytes(source.as_bytes())
423 }
424 }
425
426 impl<'l> From<&'l $name> for &'l str {
427 fn from(input: &'l $name) -> Self {
428 input.as_str()
429 }
430 }
431
432 impl From<$name> for tinystr::TinyAsciiStr<$len_end> {
433 fn from(input: $name) -> Self {
434 input.into_tinystr()
435 }
436 }
437
438 impl writeable::Writeable for $name {
439 #[inline]
440 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
441 sink.write_str(self.as_str())
442 }
443 #[inline]
444 fn writeable_length_hint(&self) -> writeable::LengthHint {
445 writeable::LengthHint::exact(self.0.len())
446 }
447 #[inline]
448 fn write_to_string(&self) -> alloc::borrow::Cow<str> {
449 alloc::borrow::Cow::Borrowed(self.0.as_str())
450 }
451 }
452
453 writeable::impl_display_with_writeable!($name);
454
455 #[doc = concat!("A macro allowing for compile-time construction of valid [`", stringify!($name), "`] subtags.")]
456 ///
457 /// # Examples
458 ///
459 /// Parsing errors don't have to be handled at runtime:
460 /// ```
461 /// assert_eq!(
462 #[doc = concat!(" icu_locid::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($good_example) ,"),")]
463 #[doc = concat!(" ", stringify!($good_example), ".parse::<icu_locid::", $(stringify!($path), "::",)+ stringify!($name), ">().unwrap()")]
464 /// );
465 /// ```
466 ///
467 /// Invalid input is a compile failure:
468 /// ```compile_fail,E0080
469 #[doc = concat!("icu_locid::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($bad_example) ,");")]
470 /// ```
471 ///
472 #[doc = concat!("[`", stringify!($name), "`]: crate::", $(stringify!($path), "::",)+ stringify!($name))]
473 #[macro_export]
474 #[doc(hidden)]
475 macro_rules! $legacy_macro_name {
476 ($string:literal) => {{
477 use $crate::$($path ::)+ $name;
478 const R: $name =
479 match $name::try_from_bytes($string.as_bytes()) {
480 Ok(r) => r,
481 #[allow(clippy::panic)] // const context
482 _ => panic!(concat!("Invalid ", $(stringify!($path), "::",)+ stringify!($name), ": ", $string)),
483 };
484 R
485 }};
486 }
487 #[doc(inline)]
488 pub use $legacy_macro_name as $macro_name;
489
490 #[cfg(feature = "databake")]
491 impl databake::Bake for $name {
492 fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
493 env.insert("icu_locid");
494 let string = self.as_str();
495 databake::quote! { icu_locid::$($path::)+ $macro_name!(#string) }
496 }
497 }
498
499 #[test]
500 fn test_construction() {
501 let maybe = $name::try_from_bytes($good_example.as_bytes());
502 assert!(maybe.is_ok());
503 assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
504 assert_eq!(maybe.unwrap().as_str(), $good_example);
505 $(
506 let maybe = $name::try_from_bytes($more_good_examples.as_bytes());
507 assert!(maybe.is_ok());
508 assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
509 assert_eq!(maybe.unwrap().as_str(), $more_good_examples);
510 )*
511 assert!($name::try_from_bytes($bad_example.as_bytes()).is_err());
512 $(
513 assert!($name::try_from_bytes($more_bad_examples.as_bytes()).is_err());
514 )*
515 }
516
517 #[test]
518 fn test_writeable() {
519 writeable::assert_writeable_eq!(&$good_example.parse::<$name>().unwrap(), $good_example);
520 $(
521 writeable::assert_writeable_eq!($more_good_examples.parse::<$name>().unwrap(), $more_good_examples);
522 )*
523 }
524
525 #[cfg(feature = "serde")]
526 impl<'de> serde::Deserialize<'de> for $name {
527 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
528 where
529 D: serde::de::Deserializer<'de>,
530 {
531 struct Visitor;
532
533 impl<'de> serde::de::Visitor<'de> for Visitor {
534 type Value = $name;
535
536 fn expecting(
537 &self,
538 formatter: &mut core::fmt::Formatter<'_>,
539 ) -> core::fmt::Result {
540 write!(formatter, "a valid BCP-47 {}", stringify!($name))
541 }
542
543 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
544 s.parse().map_err(serde::de::Error::custom)
545 }
546 }
547
548 if deserializer.is_human_readable() {
549 deserializer.deserialize_string(Visitor)
550 } else {
551 Self::try_from_raw(serde::de::Deserialize::deserialize(deserializer)?)
552 .map_err(serde::de::Error::custom)
553 }
554 }
555 }
556
557 // Safety checklist for ULE:
558 //
559 // 1. Must not include any uninitialized or padding bytes (true since transparent over a ULE).
560 // 2. Must have an alignment of 1 byte (true since transparent over a ULE).
561 // 3. ULE::validate_byte_slice() checks that the given byte slice represents a valid slice.
562 // 4. ULE::validate_byte_slice() checks that the given byte slice has a valid length.
563 // 5. All other methods must be left with their default impl.
564 // 6. Byte equality is semantic equality.
565 #[cfg(feature = "zerovec")]
566 unsafe impl zerovec::ule::ULE for $name {
567 fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> {
568 let it = bytes.chunks_exact(core::mem::size_of::<Self>());
569 if !it.remainder().is_empty() {
570 return Err(zerovec::ZeroVecError::length::<Self>(bytes.len()));
571 }
572 for v in it {
573 // The following can be removed once `array_chunks` is stabilized.
574 let mut a = [0; core::mem::size_of::<Self>()];
575 a.copy_from_slice(v);
576 if Self::try_from_raw(a).is_err() {
577 return Err(zerovec::ZeroVecError::parse::<Self>());
578 }
579 }
580 Ok(())
581 }
582 }
583
584 #[cfg(feature = "zerovec")]
585 impl zerovec::ule::AsULE for $name {
586 type ULE = Self;
587 fn to_unaligned(self) -> Self::ULE {
588 self
589 }
590 fn from_unaligned(unaligned: Self::ULE) -> Self {
591 unaligned
592 }
593 }
594
595 #[cfg(feature = "zerovec")]
596 impl<'a> zerovec::maps::ZeroMapKV<'a> for $name {
597 type Container = zerovec::ZeroVec<'a, $name>;
598 type Slice = zerovec::ZeroSlice<$name>;
599 type GetType = $name;
600 type OwnedType = $name;
601 }
602 };
603}
604
605macro_rules! impl_writeable_for_each_subtag_str_no_test {
606 ($type:tt $(, $self:ident, $borrow_cond:expr => $borrow:expr)?) => {
607 impl writeable::Writeable for $type {
608 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
609 let mut initial = true;
610 self.for_each_subtag_str(&mut |subtag| {
611 if initial {
612 initial = false;
613 } else {
614 sink.write_char('-')?;
615 }
616 sink.write_str(subtag)
617 })
618 }
619
620 #[inline]
621 fn writeable_length_hint(&self) -> writeable::LengthHint {
622 let mut result = writeable::LengthHint::exact(0);
623 let mut initial = true;
624 self.for_each_subtag_str::<core::convert::Infallible, _>(&mut |subtag| {
625 if initial {
626 initial = false;
627 } else {
628 result += 1;
629 }
630 result += subtag.len();
631 Ok(())
632 })
633 .expect("infallible");
634 result
635 }
636
637 $(
638 fn write_to_string(&self) -> alloc::borrow::Cow<str> {
639 #[allow(clippy::unwrap_used)] // impl_writeable_for_subtag_list's $borrow uses unwrap
640 let $self = self;
641 if $borrow_cond {
642 $borrow
643 } else {
644 let mut output = alloc::string::String::with_capacity(self.writeable_length_hint().capacity());
645 let _ = self.write_to(&mut output);
646 alloc::borrow::Cow::Owned(output)
647 }
648 }
649 )?
650 }
651
652 writeable::impl_display_with_writeable!($type);
653 };
654}
655
656macro_rules! impl_writeable_for_subtag_list {
657 ($type:tt, $sample1:literal, $sample2:literal) => {
658 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()));
659
660 #[test]
661 fn test_writeable() {
662 writeable::assert_writeable_eq!(&$type::default(), "");
663 writeable::assert_writeable_eq!(
664 &$type::from_short_slice_unchecked(alloc::vec![$sample1.parse().unwrap()].into()),
665 $sample1,
666 );
667 writeable::assert_writeable_eq!(
668 &$type::from_short_slice_unchecked(vec![
669 $sample1.parse().unwrap(),
670 $sample2.parse().unwrap()
671 ].into()),
672 core::concat!($sample1, "-", $sample2),
673 );
674 }
675 };
676}
677
678macro_rules! impl_writeable_for_key_value {
679 ($type:tt, $key1:literal, $value1:literal, $key2:literal, $expected2:literal) => {
680 impl_writeable_for_each_subtag_str_no_test!($type);
681
682 #[test]
683 fn test_writeable() {
684 writeable::assert_writeable_eq!(&$type::default(), "");
685 writeable::assert_writeable_eq!(
686 &$type::from_tuple_vec(vec![($key1.parse().unwrap(), $value1.parse().unwrap())]),
687 core::concat!($key1, "-", $value1),
688 );
689 writeable::assert_writeable_eq!(
690 &$type::from_tuple_vec(vec![
691 ($key1.parse().unwrap(), $value1.parse().unwrap()),
692 ($key2.parse().unwrap(), "true".parse().unwrap())
693 ]),
694 core::concat!($key1, "-", $value1, "-", $expected2),
695 );
696 }
697 };
698}
699