From d043e459505ee186c1101ae634aa7b7ebeec9ae8 Mon Sep 17 00:00:00 2001 From: Niels Saurer Date: Thu, 25 May 2023 13:56:55 +0200 Subject: [PATCH 1/4] add macro --- utils/zerovec/src/zerovec/mod.rs | 81 ++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/utils/zerovec/src/zerovec/mod.rs b/utils/zerovec/src/zerovec/mod.rs index 848ee0ffd5d..9f1c439cd4f 100644 --- a/utils/zerovec/src/zerovec/mod.rs +++ b/utils/zerovec/src/zerovec/mod.rs @@ -919,6 +919,87 @@ impl FromIterator for ZeroVec<'_, T> { } } +/// Convenience wrapper for [`ZeroSlice::from_ule_slice`]. The value will be created at compile-time. +/// +/// # Arguments +/// +/// * `$aligned` - The type of an element in its canonical, aligned form, e.g., `char`. +/// * `$array_fn` - A const function that converts an array of `$aligned` elements into an array +/// of their unaligned equivalents, e.g., +/// `const fn from_array(arr: [char; N]) -> [::ULE; N]`. +/// * `$x` - The elements that the `ZeroSlice` will hold. +/// +/// # Examples +/// +/// Using array-conversion functions provided by this crate: +/// +/// ``` +/// use zerovec::{ZeroSlice, zeroslice, ule::AsULE}; +/// +/// const SIGNATURE: &ZeroSlice = zeroslice![char; ::ULE::from_array; 'b', 'y', 'e', '✌']; +/// const EMPTY: &ZeroSlice = zeroslice![]; +/// let empty: &ZeroSlice = zeroslice![]; +/// let nums = zeroslice![u32; ::ULE::from_array; 1, 2, 3, 4, 5]; +/// assert_eq!(nums.last().unwrap(), 5); +/// ``` +/// +/// Using a custom array-conversion function: +/// +/// ``` +/// use zerovec::{ZeroSlice, zeroslice}; +/// +/// mod conversion { +/// use zerovec::ule::RawBytesULE; +/// pub(super) const fn i16_array_to_be_array(arr: [i16; N]) -> [RawBytesULE<2>; N] { +/// let mut result = [RawBytesULE([0; 2]); N]; +/// let mut i = 0; +/// while i < N { +/// result[i] = RawBytesULE(arr[i].to_be_bytes()); +/// i += 1; +/// } +/// result +/// } +/// } +/// +/// const NUMBERS: &ZeroSlice = zeroslice![i16; conversion::i16_array_to_be_array; 1, -2, 3, -4, 5]; +/// ``` +#[macro_export] +macro_rules! zeroslice { + () => ( + $crate::ZeroSlice::new_empty() + ); + ($aligned:ty; $array_fn:expr; $($x:expr),+ $(,)?) => ( + $crate::ZeroSlice::<$aligned>::from_ule_slice( + {const X: &[<$aligned as $crate::ule::AsULE>::ULE] = &$array_fn([$($x),+]); X} + ) + ); +} + +/// Creates a borrowed `ZeroVec`. Convenience wrapper for `zeroslice![...].as_zerovec()`. +/// +/// See [`zeroslice!`](crate::zeroslice) for more information. +/// +/// # Examples +/// +/// ``` +/// use zerovec::{ZeroVec, zerovec, ule::AsULE}; +/// +/// const SIGNATURE: ZeroVec = zerovec![char; ::ULE::from_array; 'a', 'y', 'e', '✌']; +/// assert!(!SIGNATURE.is_owned()); +/// +/// const EMPTY: ZeroVec = zerovec![]; +/// assert!(!EMPTY.is_owned()); +/// ``` +#[macro_export] +macro_rules! zerovec { + () => ( + $crate::ZeroVec::new() + ); + ($aligned:ty; $array_fn:expr; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![$aligned; $array_fn; $($x),+].as_zerovec() + ); +} + #[cfg(test)] mod tests { use super::*; From 6469df6ef2b5f1293a5fb4dbf85a13b34c7d1206 Mon Sep 17 00:00:00 2001 From: Niels Saurer Date: Thu, 25 May 2023 15:17:08 +0200 Subject: [PATCH 2/4] make zeroslice macros magic --- utils/zerovec/derive/src/make_ule.rs | 36 +++++++++++++++++++++++++++ utils/zerovec/src/ule/chars.rs | 21 +++++++++++++++- utils/zerovec/src/ule/constconvert.rs | 17 +++++++++++++ utils/zerovec/src/ule/macros.rs | 22 ++++++++++++++++ utils/zerovec/src/ule/mod.rs | 32 ++++++++++++++++++++++++ utils/zerovec/src/ule/plain.rs | 15 +++++++++++ utils/zerovec/src/zerovec/mod.rs | 21 ++++++++++++---- 7 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 utils/zerovec/src/ule/constconvert.rs diff --git a/utils/zerovec/derive/src/make_ule.rs b/utils/zerovec/derive/src/make_ule.rs index f87fadb482d..c8dc69c347d 100644 --- a/utils/zerovec/derive/src/make_ule.rs +++ b/utils/zerovec/derive/src/make_ule.rs @@ -159,6 +159,15 @@ fn make_ule_enum_impl( let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}"); + let aligned_to_unaligned_doc = format!("Converts a [`{name}`] to a [`{ule_name}`]. This is equivalent to calling [`AsULE::to_unaligned`]."); + + // avoids multiple-import issues + // TODO: is it okay to add this macro to the make_ule user's scope or is there a way around it? + let ule_array_macro_alias: TokenStream2 = + format!("__impl_const_as_ule_array_{}_{}", name, ule_name) + .parse() + .unwrap(); + // Safety (based on the safety checklist on the ULE trait): // 1. ULE type does not include any uninitialized or padding bytes. // (achieved by `#[repr(transparent)]` on a type that satisfies this invariant @@ -177,6 +186,26 @@ fn make_ule_enum_impl( #[doc = #doc] #vis struct #ule_name(u8); + use zerovec::impl_const_as_ule_array as #ule_array_macro_alias; + + impl #ule_name { + #[doc = #aligned_to_unaligned_doc] + pub const fn aligned_to_unaligned(a: #name) -> #ule_name { + // safety: the enum is repr(u8) and can be cast to a u8 + unsafe { + ::core::mem::transmute(a) + } + } + + + #ule_array_macro_alias!( + #name, + #ule_name, + // safety: the enum is repr(u8), can be cast to a u8 and contains at least one variant + unsafe { ::core::mem::transmute(0u8) } + ); + } + unsafe impl zerovec::ule::ULE for #ule_name { #[inline] fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> { @@ -208,6 +237,11 @@ fn make_ule_enum_impl( } } + impl zerovec::ule::ConstAsULE for #name { + // The unique canonical relationship is #name <=> #ule_name + type ConstConvert = #ule_name; + } + impl #name { /// Attempt to construct the value from its corresponding integer, /// returning None if not possible @@ -334,6 +368,8 @@ fn make_ule_struct_impl( quote!() }; + // TODO: implement ConstAsULE for struct types + quote!( #asule_impl diff --git a/utils/zerovec/src/ule/chars.rs b/utils/zerovec/src/ule/chars.rs index cc6d49e0c57..61693cfcf18 100644 --- a/utils/zerovec/src/ule/chars.rs +++ b/utils/zerovec/src/ule/chars.rs @@ -6,6 +6,7 @@ //! ULE implementation for the `char` type. use super::*; +use crate::impl_const_as_ule_array; use crate::impl_ule_from_array; use core::cmp::Ordering; use core::convert::TryFrom; @@ -53,6 +54,19 @@ impl CharULE { } impl_ule_from_array!(char, CharULE, Self([0; 3])); + + /// Converts a [`char`] to a [`CharULE`]. This is equivalent to calling + /// [`AsULE::to_unaligned()`] + /// + /// See the type-level documentation for [`CharULE`] for more information. + #[allow(dead_code)] + #[inline] + pub const fn aligned_to_unaligned(c: char) -> CharULE { + let [u0, u1, u2, _u3] = (c as u32).to_le_bytes(); + CharULE([u0, u1, u2]) + } + + impl_const_as_ule_array!(char, CharULE, CharULE([0; 3])); } // Safety (based on the safety checklist on the ULE trait): @@ -87,7 +101,7 @@ impl AsULE for char { #[inline] fn to_unaligned(self) -> Self::ULE { - CharULE::from_aligned(self) + ::ConstConvert::aligned_to_unaligned(self) } #[inline] @@ -104,6 +118,11 @@ impl AsULE for char { } } +impl ConstAsULE for char { + // The unique canonical relationship is char <=> CharULE + type ConstConvert = CharULE; +} + impl PartialOrd for CharULE { fn partial_cmp(&self, other: &Self) -> Option { char::from_unaligned(*self).partial_cmp(&char::from_unaligned(*other)) diff --git a/utils/zerovec/src/ule/constconvert.rs b/utils/zerovec/src/ule/constconvert.rs new file mode 100644 index 00000000000..6befefb9ada --- /dev/null +++ b/utils/zerovec/src/ule/constconvert.rs @@ -0,0 +1,17 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use crate::ule::AsULE; +use core::marker::PhantomData; + +// Type is only explicitly used internally for `ConstAsULE`. Due to the way public traits work, +// it must be public, however. +#[doc(hidden)] +#[allow(dead_code, missing_debug_implementations)] +pub struct ConstConvert +where + T: AsULE, +{ + _phantom: PhantomData<(T, U)>, +} diff --git a/utils/zerovec/src/ule/macros.rs b/utils/zerovec/src/ule/macros.rs index 955b1eb2e41..8dc6af90460 100644 --- a/utils/zerovec/src/ule/macros.rs +++ b/utils/zerovec/src/ule/macros.rs @@ -27,3 +27,25 @@ macro_rules! impl_ule_from_array { impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned); }; } + +#[macro_export] +macro_rules! impl_const_as_ule_array { + ($aligned:ty, $unaligned:ty, $default:expr, $single:path) => { + #[doc = concat!("Convert an array of `", stringify!($aligned), "` to an array of `", stringify!($unaligned), "`.")] + #[allow(dead_code)] + pub const fn aligned_to_unaligned_array(arr: [$aligned; N]) -> [$unaligned; N] { + let mut result = [$default; N]; + let mut i = 0; + // Won't panic because i < N and arr has length N + #[allow(clippy::indexing_slicing)] + while i < N { + result[i] = $single(arr[i]); + i += 1; + } + result + } + }; + ($aligned:ty, $unaligned:ty, $default:expr) => { + $crate::impl_const_as_ule_array!($aligned, $unaligned, $default, Self::aligned_to_unaligned); + }; +} diff --git a/utils/zerovec/src/ule/mod.rs b/utils/zerovec/src/ule/mod.rs index ef148f8bb14..9c26d4fd0f2 100644 --- a/utils/zerovec/src/ule/mod.rs +++ b/utils/zerovec/src/ule/mod.rs @@ -11,6 +11,7 @@ //! See [the design doc](https://github.com/unicode-org/icu4x/blob/main/utils/zerovec/design_doc.md) for details on how these traits //! works under the hood. mod chars; +mod constconvert; #[cfg(doc)] pub mod custom; mod encode; @@ -239,6 +240,37 @@ where } } +/// A trait for any type that can be const-mapped to its ULE type. It is particularly +/// useful in combination with [`zeroslice!`](crate::zeroslice). +/// +/// This trait serves only as a redirection to a type that contains the required associated const +/// functions for conversion. This is currently necessary due to the lack of const traits in Rust. +/// +/// # Implementation +/// +/// Using the [`#[make_ule]`](crate::make_ule) will implement this trait for you and you do not +/// have to read further. +/// +/// If you implement `ConstAsULE` manually for your type `T`, the associated type `ConstConvert` +/// must have the following associated `const` functions: +/// * `ConstConvert::aligned_to_unaligned(T) -> T::ULE` +/// * `ConstConvert::aligned_to_unaligned_array([T; N]) -> [T::ULE; N]` +/// +/// For `T::ULE` types that only have one canonical aligned type, `T`, the associated type +/// `ConstConvert` can safely be set to `T::ULE` and the required functions can be implemented +/// there. +/// +/// Issues arise when `T::ULE` has multiple or no canonical aligned types, as is the case with e.g. +/// `RawBytesULE<4>` and `i32`/`u32`. In this case, `ConstConvert` must be set to a different type +/// for each `impl ConstAsULE for T`. This crate does that by providing associated functions on a +/// custom type for each pair of `T` and `T::ULE`, namely +/// `crate::ule::constconvert::ConstConvert`. Unfortunately, you will not be able +/// to reuse this type for your own `ConstAsULE` implementations, since you cannot provide +/// implementations for types outside of your crate. +pub trait ConstAsULE: AsULE { + type ConstConvert; +} + /// Variable-width, byte-aligned data that can be cast to and from a little-endian byte slice. /// /// If you need to implement this trait, consider using [`#[make_varule]`](crate::make_varule) or diff --git a/utils/zerovec/src/ule/plain.rs b/utils/zerovec/src/ule/plain.rs index dc8e4510e0a..7c23078a25c 100644 --- a/utils/zerovec/src/ule/plain.rs +++ b/utils/zerovec/src/ule/plain.rs @@ -6,7 +6,9 @@ //! ULE implementation for Plain Old Data types, including all sized integers. use super::*; +use crate::impl_const_as_ule_array; use crate::impl_ule_from_array; +use crate::ule::constconvert::ConstConvert; use crate::ZeroSlice; use core::num::{NonZeroI8, NonZeroU8}; @@ -132,6 +134,19 @@ macro_rules! impl_byte_slice_type { // EqULE is true because $type and RawBytesULE<$size> // have the same byte sequence on little-endian unsafe impl EqULE for $type {} + + impl ConstConvert<$type, RawBytesULE<$size>> { + #[allow(dead_code)] + pub const fn aligned_to_unaligned(value: $type) -> RawBytesULE<$size> { + RawBytesULE(value.to_le_bytes()) + } + + impl_const_as_ule_array!($type, RawBytesULE<$size>, RawBytesULE([0; $size])); + } + + impl ConstAsULE for $type { + type ConstConvert = ConstConvert<$type, RawBytesULE<$size>>; + } }; } diff --git a/utils/zerovec/src/zerovec/mod.rs b/utils/zerovec/src/zerovec/mod.rs index 9f1c439cd4f..d8bf184c2c0 100644 --- a/utils/zerovec/src/zerovec/mod.rs +++ b/utils/zerovec/src/zerovec/mod.rs @@ -929,18 +929,23 @@ impl FromIterator for ZeroVec<'_, T> { /// `const fn from_array(arr: [char; N]) -> [::ULE; N]`. /// * `$x` - The elements that the `ZeroSlice` will hold. /// +/// The `$array_fn` argument is optional if the `$aligned` type correctly implements [`ConstAsULE`]. +/// This is the case for types created with [`#[make_ule]`](crate::make_ule). +/// /// # Examples /// /// Using array-conversion functions provided by this crate: /// /// ``` -/// use zerovec::{ZeroSlice, zeroslice, ule::AsULE}; +/// use zerovec::{ZeroSlice, zeroslice}; /// -/// const SIGNATURE: &ZeroSlice = zeroslice![char; ::ULE::from_array; 'b', 'y', 'e', '✌']; +/// const SIGNATURE: &ZeroSlice = zeroslice![char; 'b', 'y', 'e', '✌']; /// const EMPTY: &ZeroSlice = zeroslice![]; /// let empty: &ZeroSlice = zeroslice![]; -/// let nums = zeroslice![u32; ::ULE::from_array; 1, 2, 3, 4, 5]; -/// assert_eq!(nums.last().unwrap(), 5); +/// let nums_signed = zeroslice![i32; -1, 2, 3, 4, 5]; +/// let nums_unsigned = zeroslice![u32; u32::MAX, 2, 3, 4, 5]; +/// assert_eq!(nums_signed.last().unwrap(), 5); +/// assert_eq!(nums_signed.as_ule_slice(), nums_unsigned.as_ule_slice()); /// ``` /// /// Using a custom array-conversion function: @@ -973,6 +978,9 @@ macro_rules! zeroslice { {const X: &[<$aligned as $crate::ule::AsULE>::ULE] = &$array_fn([$($x),+]); X} ) ); + ($aligned:ty; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![$aligned; <$aligned as $crate::ule::ConstAsULE>::ConstConvert::aligned_to_unaligned_array; $($x),+] + ); } /// Creates a borrowed `ZeroVec`. Convenience wrapper for `zeroslice![...].as_zerovec()`. @@ -984,7 +992,7 @@ macro_rules! zeroslice { /// ``` /// use zerovec::{ZeroVec, zerovec, ule::AsULE}; /// -/// const SIGNATURE: ZeroVec = zerovec![char; ::ULE::from_array; 'a', 'y', 'e', '✌']; +/// const SIGNATURE: ZeroVec = zerovec![char; 'a', 'y', 'e', '✌']; /// assert!(!SIGNATURE.is_owned()); /// /// const EMPTY: ZeroVec = zerovec![]; @@ -995,6 +1003,9 @@ macro_rules! zerovec { () => ( $crate::ZeroVec::new() ); + ($aligned:ty; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![$aligned; $($x),+].as_zerovec() + ); ($aligned:ty; $array_fn:expr; $($x:expr),+ $(,)?) => ( $crate::zeroslice![$aligned; $array_fn; $($x),+].as_zerovec() ); From 5b9b0c6a2421084764ea9310c1d93e51c4be74b5 Mon Sep 17 00:00:00 2001 From: Niels Saurer Date: Fri, 26 May 2023 15:59:52 +0000 Subject: [PATCH 3/4] wip-option-2.i --- components/properties/src/expandme.rs | 10 +++++ components/properties/src/lib.rs | 2 +- utils/zerovec/derive/src/make_ule.rs | 42 ++++++++++--------- utils/zerovec/src/ule/chars.rs | 21 +--------- utils/zerovec/src/ule/constconvert.rs | 17 -------- utils/zerovec/src/ule/macros.rs | 6 +-- utils/zerovec/src/ule/mod.rs | 1 - utils/zerovec/src/ule/plain.rs | 49 +++++++++++++--------- utils/zerovec/src/ule/unvalidated.rs | 17 ++++++++ utils/zerovec/src/zerovec/mod.rs | 59 +++++++++++++++++++++++++-- 10 files changed, 140 insertions(+), 84 deletions(-) create mode 100644 components/properties/src/expandme.rs delete mode 100644 utils/zerovec/src/ule/constconvert.rs diff --git a/components/properties/src/expandme.rs b/components/properties/src/expandme.rs new file mode 100644 index 00000000000..ae9bc037f12 --- /dev/null +++ b/components/properties/src/expandme.rs @@ -0,0 +1,10 @@ +// expand me by cd'ing into properties, then `cargo expand expandme` + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[allow(clippy::exhaustive_structs)] +#[zerovec::make_ule(AlignedULE)] +pub struct Aligned { + field_one: u8, + field_two: char, + field_three: i32, +} \ No newline at end of file diff --git a/components/properties/src/lib.rs b/components/properties/src/lib.rs index f7fd9092a1a..fa670705e5b 100644 --- a/components/properties/src/lib.rs +++ b/components/properties/src/lib.rs @@ -90,7 +90,7 @@ pub mod maps; // module. Please do not change the crate-module-qualified // name of that struct without coordination. mod props; - +mod expandme; pub mod bidi_data; pub mod exemplar_chars; pub mod provider; diff --git a/utils/zerovec/derive/src/make_ule.rs b/utils/zerovec/derive/src/make_ule.rs index c8dc69c347d..b0887ba5df1 100644 --- a/utils/zerovec/derive/src/make_ule.rs +++ b/utils/zerovec/derive/src/make_ule.rs @@ -159,14 +159,7 @@ fn make_ule_enum_impl( let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}"); - let aligned_to_unaligned_doc = format!("Converts a [`{name}`] to a [`{ule_name}`]. This is equivalent to calling [`AsULE::to_unaligned`]."); - - // avoids multiple-import issues - // TODO: is it okay to add this macro to the make_ule user's scope or is there a way around it? - let ule_array_macro_alias: TokenStream2 = - format!("__impl_const_as_ule_array_{}_{}", name, ule_name) - .parse() - .unwrap(); + let from_aligned_doc = format!("Converts a [`{name}`] to a [`{ule_name}`]. This is equivalent to calling [`AsULE::to_unaligned`]."); // Safety (based on the safety checklist on the ULE trait): // 1. ULE type does not include any uninitialized or padding bytes. @@ -186,19 +179,16 @@ fn make_ule_enum_impl( #[doc = #doc] #vis struct #ule_name(u8); - use zerovec::impl_const_as_ule_array as #ule_array_macro_alias; - impl #ule_name { - #[doc = #aligned_to_unaligned_doc] - pub const fn aligned_to_unaligned(a: #name) -> #ule_name { + #[doc = #from_aligned_doc] + pub const fn from_aligned(a: #name) -> #ule_name { // safety: the enum is repr(u8) and can be cast to a u8 unsafe { ::core::mem::transmute(a) } } - - #ule_array_macro_alias!( + zerovec::impl_ule_from_array!( #name, #ule_name, // safety: the enum is repr(u8), can be cast to a u8 and contains at least one variant @@ -237,11 +227,6 @@ fn make_ule_enum_impl( } } - impl zerovec::ule::ConstAsULE for #name { - // The unique canonical relationship is #name <=> #ule_name - type ConstConvert = #ule_name; - } - impl #name { /// Attempt to construct the value from its corresponding integer, /// returning None if not possible @@ -302,6 +287,7 @@ fn make_ule_struct_impl( let mut as_ule_conversions = vec![]; let mut from_ule_conversions = vec![]; + let mut const_from_aligned_conversions = vec![]; for (i, field) in struc.fields.iter().enumerate() { let ty = &field.ty; @@ -312,10 +298,13 @@ fn make_ule_struct_impl( from_ule_conversions.push( quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)), ); + // TODO: need to match here and below on #ty, and then call the appropriate function, as in zeroslice! macro. + const_from_aligned_conversions.push(quote!(#ident: <#ty as zerovec::ule::AsULE>::ULE::from_aligned(aligned.#ident))); } else { as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i))); from_ule_conversions .push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i))); + const_from_aligned_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::ULE::from_aligned(aligned.#i))); }; } @@ -368,7 +357,18 @@ fn make_ule_struct_impl( quote!() }; - // TODO: implement ConstAsULE for struct types + + let const_from_aligned_conversions = utils::wrap_field_inits(&const_from_aligned_conversions, &struc.fields); + let ule_impl = quote!( + impl #ule_name { + pub const fn from_aligned(aligned: #name) -> Self { + Self #const_from_aligned_conversions + } + + // TODO: need to come up with a safe zero expression for the array initializer. + // zerovec::impl_ule_from_array!(#name, #ule_name, ) + } + ); quote!( #asule_impl @@ -380,5 +380,7 @@ fn make_ule_struct_impl( #maybe_ord_impls #maybe_hash + + #ule_impl ) } diff --git a/utils/zerovec/src/ule/chars.rs b/utils/zerovec/src/ule/chars.rs index 61693cfcf18..b94055ae553 100644 --- a/utils/zerovec/src/ule/chars.rs +++ b/utils/zerovec/src/ule/chars.rs @@ -6,7 +6,6 @@ //! ULE implementation for the `char` type. use super::*; -use crate::impl_const_as_ule_array; use crate::impl_ule_from_array; use core::cmp::Ordering; use core::convert::TryFrom; @@ -54,19 +53,6 @@ impl CharULE { } impl_ule_from_array!(char, CharULE, Self([0; 3])); - - /// Converts a [`char`] to a [`CharULE`]. This is equivalent to calling - /// [`AsULE::to_unaligned()`] - /// - /// See the type-level documentation for [`CharULE`] for more information. - #[allow(dead_code)] - #[inline] - pub const fn aligned_to_unaligned(c: char) -> CharULE { - let [u0, u1, u2, _u3] = (c as u32).to_le_bytes(); - CharULE([u0, u1, u2]) - } - - impl_const_as_ule_array!(char, CharULE, CharULE([0; 3])); } // Safety (based on the safety checklist on the ULE trait): @@ -101,7 +87,7 @@ impl AsULE for char { #[inline] fn to_unaligned(self) -> Self::ULE { - ::ConstConvert::aligned_to_unaligned(self) + ::ULE::from_aligned(self) } #[inline] @@ -118,11 +104,6 @@ impl AsULE for char { } } -impl ConstAsULE for char { - // The unique canonical relationship is char <=> CharULE - type ConstConvert = CharULE; -} - impl PartialOrd for CharULE { fn partial_cmp(&self, other: &Self) -> Option { char::from_unaligned(*self).partial_cmp(&char::from_unaligned(*other)) diff --git a/utils/zerovec/src/ule/constconvert.rs b/utils/zerovec/src/ule/constconvert.rs deleted file mode 100644 index 6befefb9ada..00000000000 --- a/utils/zerovec/src/ule/constconvert.rs +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of ICU4X. For terms of use, please see the file -// called LICENSE at the top level of the ICU4X source tree -// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). - -use crate::ule::AsULE; -use core::marker::PhantomData; - -// Type is only explicitly used internally for `ConstAsULE`. Due to the way public traits work, -// it must be public, however. -#[doc(hidden)] -#[allow(dead_code, missing_debug_implementations)] -pub struct ConstConvert -where - T: AsULE, -{ - _phantom: PhantomData<(T, U)>, -} diff --git a/utils/zerovec/src/ule/macros.rs b/utils/zerovec/src/ule/macros.rs index 8dc6af90460..c1e178a0b2a 100644 --- a/utils/zerovec/src/ule/macros.rs +++ b/utils/zerovec/src/ule/macros.rs @@ -9,9 +9,9 @@ /// Pass any (cheap to construct) value. #[macro_export] macro_rules! impl_ule_from_array { - ($aligned:ty, $unaligned:ty, $default:expr, $single:path) => { + ($aligned:ty, $unaligned:ty, $default:expr, $single:path, $fn_name:ident) => { #[doc = concat!("Convert an array of `", stringify!($aligned), "` to an array of `", stringify!($unaligned), "`.")] - pub const fn from_array(arr: [$aligned; N]) -> [Self; N] { + pub const fn $fn_name(arr: [$aligned; N]) -> [Self; N] { let mut result = [$default; N]; let mut i = 0; // Won't panic because i < N and arr has length N @@ -24,7 +24,7 @@ macro_rules! impl_ule_from_array { } }; ($aligned:ty, $unaligned:ty, $default:expr) => { - impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned); + $crate::impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned, from_array); }; } diff --git a/utils/zerovec/src/ule/mod.rs b/utils/zerovec/src/ule/mod.rs index 9c26d4fd0f2..00f04bd062a 100644 --- a/utils/zerovec/src/ule/mod.rs +++ b/utils/zerovec/src/ule/mod.rs @@ -11,7 +11,6 @@ //! See [the design doc](https://github.com/unicode-org/icu4x/blob/main/utils/zerovec/design_doc.md) for details on how these traits //! works under the hood. mod chars; -mod constconvert; #[cfg(doc)] pub mod custom; mod encode; diff --git a/utils/zerovec/src/ule/plain.rs b/utils/zerovec/src/ule/plain.rs index 7c23078a25c..3ec1d0897cb 100644 --- a/utils/zerovec/src/ule/plain.rs +++ b/utils/zerovec/src/ule/plain.rs @@ -6,9 +6,7 @@ //! ULE implementation for Plain Old Data types, including all sized integers. use super::*; -use crate::impl_const_as_ule_array; use crate::impl_ule_from_array; -use crate::ule::constconvert::ConstConvert; use crate::ZeroSlice; use core::num::{NonZeroI8, NonZeroU8}; @@ -113,7 +111,7 @@ macro_rules! impl_const_constructors { } macro_rules! impl_byte_slice_type { - ($type:ty, $size:literal) => { + ($single_fn:ident, $array_fn_name:ident, $type:ty, $size:literal) => { impl From<$type> for RawBytesULE<$size> { #[inline] fn from(value: $type) -> Self { @@ -135,18 +133,31 @@ macro_rules! impl_byte_slice_type { // have the same byte sequence on little-endian unsafe impl EqULE for $type {} - impl ConstConvert<$type, RawBytesULE<$size>> { - #[allow(dead_code)] - pub const fn aligned_to_unaligned(value: $type) -> RawBytesULE<$size> { - RawBytesULE(value.to_le_bytes()) + impl RawBytesULE<$size> { + pub const fn $single_fn(v: $type) -> Self { + RawBytesULE(v.to_le_bytes()) } - impl_const_as_ule_array!($type, RawBytesULE<$size>, RawBytesULE([0; $size])); + impl_ule_from_array!( + $type, + RawBytesULE<$size>, + RawBytesULE([0; $size]), + Self::$single_fn, + $array_fn_name + ); } + }; +} - impl ConstAsULE for $type { - type ConstConvert = ConstConvert<$type, RawBytesULE<$size>>; - } +macro_rules! impl_byte_slice_unsigned_type { + ($type:ty, $size:literal) => { + impl_byte_slice_type!(from_unsigned, from_unsigned_array, $type, $size); + }; +} + +macro_rules! impl_byte_slice_signed_type { + ($type:ty, $size:literal) => { + impl_byte_slice_type!(from_signed, from_signed_array, $type, $size); }; } @@ -155,15 +166,15 @@ impl_byte_slice_size!(u32, 4); impl_byte_slice_size!(u64, 8); impl_byte_slice_size!(u128, 16); -impl_byte_slice_type!(u16, 2); -impl_byte_slice_type!(u32, 4); -impl_byte_slice_type!(u64, 8); -impl_byte_slice_type!(u128, 16); +impl_byte_slice_unsigned_type!(u16, 2); +impl_byte_slice_unsigned_type!(u32, 4); +impl_byte_slice_unsigned_type!(u64, 8); +impl_byte_slice_unsigned_type!(u128, 16); -impl_byte_slice_type!(i16, 2); -impl_byte_slice_type!(i32, 4); -impl_byte_slice_type!(i64, 8); -impl_byte_slice_type!(i128, 16); +impl_byte_slice_signed_type!(i16, 2); +impl_byte_slice_signed_type!(i32, 4); +impl_byte_slice_signed_type!(i64, 8); +impl_byte_slice_signed_type!(i128, 16); impl_const_constructors!(u8, 1); impl_const_constructors!(u16, 2); diff --git a/utils/zerovec/src/ule/unvalidated.rs b/utils/zerovec/src/ule/unvalidated.rs index 33a8656a7df..219799a1f06 100644 --- a/utils/zerovec/src/ule/unvalidated.rs +++ b/utils/zerovec/src/ule/unvalidated.rs @@ -3,6 +3,7 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use super::{AsULE, RawBytesULE, VarULE}; +use crate::impl_ule_from_array; use crate::ule::EqULE; use crate::{map::ZeroMapKV, VarZeroSlice, VarZeroVec, ZeroVecError}; use alloc::boxed::Box; @@ -322,6 +323,22 @@ impl UnvalidatedChar { } } +impl RawBytesULE<3> { + #[doc(hidden)] + #[inline] + pub const fn from_unvalidated_char(uc: UnvalidatedChar) -> Self { + RawBytesULE(uc.0) + } + + impl_ule_from_array!( + UnvalidatedChar, + RawBytesULE<3>, + RawBytesULE([0; 3]), + Self::from_unvalidated_char, + from_unvalidated_char_array + ); +} + impl AsULE for UnvalidatedChar { type ULE = RawBytesULE<3>; diff --git a/utils/zerovec/src/zerovec/mod.rs b/utils/zerovec/src/zerovec/mod.rs index d8bf184c2c0..b7368a33490 100644 --- a/utils/zerovec/src/zerovec/mod.rs +++ b/utils/zerovec/src/zerovec/mod.rs @@ -929,8 +929,11 @@ impl FromIterator for ZeroVec<'_, T> { /// `const fn from_array(arr: [char; N]) -> [::ULE; N]`. /// * `$x` - The elements that the `ZeroSlice` will hold. /// -/// The `$array_fn` argument is optional if the `$aligned` type correctly implements [`ConstAsULE`]. -/// This is the case for types created with [`#[make_ule]`](crate::make_ule). +/// The `$array_fn` argument is optional if the `$aligned` type was created with [`#[make_ule]`](crate::make_ule). +/// For manually implemented ULE types, they must have an associated +/// `pub const fn from_array(arr: [T; N]) -> [::ULE; N]`. This means you must use +/// newtypes for the ULE types if more than one AsULE type maps to your ULE. See [`UnvalidatedChar`] +/// for an example of this. /// /// # Examples /// @@ -978,8 +981,41 @@ macro_rules! zeroslice { {const X: &[<$aligned as $crate::ule::AsULE>::ULE] = &$array_fn([$($x),+]); X} ) ); + (u8; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![u8; core::convert::identity; $($x),+] + ); + (i8; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![i8; core::convert::identity; $($x),+] + ); + (u16; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![u16; ::ULE::from_unsigned_array; $($x),+] + ); + (i16; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![i16; ::ULE::from_signed_array; $($x),+] + ); + (u32; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![u32; ::ULE::from_unsigned_array; $($x),+] + ); + (i32; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![i32; ::ULE::from_signed_array; $($x),+] + ); + (u64; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![u64; ::ULE::from_unsigned_array; $($x),+] + ); + (i64; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![i64; ::ULE::from_signed_array; $($x),+] + ); + (u128; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![u128; ::ULE::from_unsigned_array; $($x),+] + ); + (i128; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![i128; ::ULE::from_signed_array; $($x),+] + ); + (UnvalidatedChar; $($x:expr),+ $(,)?) => ( + $crate::zeroslice![UnvalidatedChar; ::ULE::from_unvalidated_char_array; $($x),+] + ); ($aligned:ty; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![$aligned; <$aligned as $crate::ule::ConstAsULE>::ConstConvert::aligned_to_unaligned_array; $($x),+] + $crate::zeroslice![$aligned; <$aligned as $crate::ule::AsULE>::ULE::from_array; $($x),+] ); } @@ -1016,6 +1052,23 @@ mod tests { use super::*; use crate::samples::*; + #[test] + fn test_macro_special_cases_compile() { + let _zeroslice: &ZeroSlice = zeroslice![u8; 0]; + let _zeroslice: &ZeroSlice = zeroslice![i8; 0]; + let _zeroslice: &ZeroSlice = zeroslice![u16; 0]; + let _zeroslice: &ZeroSlice = zeroslice![i16; 0]; + let _zeroslice: &ZeroSlice = zeroslice![u32; 0]; + let _zeroslice: &ZeroSlice = zeroslice![i32; 0]; + let _zeroslice: &ZeroSlice = zeroslice![u64; 0]; + let _zeroslice: &ZeroSlice = zeroslice![i64; 0]; + let _zeroslice: &ZeroSlice = zeroslice![u128; 0]; + let _zeroslice: &ZeroSlice = zeroslice![i128; 0]; + + let _zeroslice: &ZeroSlice = + zeroslice![UnvalidatedChar; UnvalidatedChar::from_char('a')]; + } + #[test] fn test_get() { { From 8e7be7f5fe37c0267b63176c096734287f15f337 Mon Sep 17 00:00:00 2001 From: Niels Saurer Date: Wed, 31 May 2023 10:44:27 +0000 Subject: [PATCH 4/4] wip --- components/datetime/src/fields/length.rs | 10 ++++-- components/datetime/src/fields/symbols.rs | 10 ++++-- components/properties/src/lib.rs | 4 +-- utils/zerovec/derive/src/make_ule.rs | 17 ++++++--- utils/zerovec/src/ule/macros.rs | 40 +++++++++++++++++++++ utils/zerovec/src/zerovec/mod.rs | 43 ++++------------------- 6 files changed, 77 insertions(+), 47 deletions(-) diff --git a/components/datetime/src/fields/length.rs b/components/datetime/src/fields/length.rs index bd9abae0258..30f41238656 100644 --- a/components/datetime/src/fields/length.rs +++ b/components/datetime/src/fields/length.rs @@ -58,7 +58,7 @@ pub enum FieldLength { impl FieldLength { #[inline] - pub(crate) fn idx(&self) -> u8 { + pub(crate) const fn idx(&self) -> u8 { match self { FieldLength::One => 1, FieldLength::TwoDigit => 2, @@ -66,7 +66,7 @@ impl FieldLength { FieldLength::Wide => 4, FieldLength::Narrow => 5, FieldLength::Six => 6, - FieldLength::Fixed(p) => 128 + p.min(&127), /* truncate to at most 127 digits to avoid overflow */ + FieldLength::Fixed(p) => 128 + if *p >= 127 { 127 } else { *p }, /* truncate to at most 127 digits to avoid overflow */ } } @@ -125,6 +125,12 @@ impl FieldLengthULE { .map(|_| ()) .map_err(|_| ZeroVecError::parse::()) } + + /// The same as [`AsULE::to_unaligned`]. + #[inline] + pub const fn from_aligned(fl: FieldLength) -> Self { + Self(fl.idx()) + } } // Safety checklist for ULE: diff --git a/components/datetime/src/fields/symbols.rs b/components/datetime/src/fields/symbols.rs index 02dfbe1b9e5..ae883e106a8 100644 --- a/components/datetime/src/fields/symbols.rs +++ b/components/datetime/src/fields/symbols.rs @@ -96,7 +96,7 @@ impl FieldSymbol { /// This model limits the available number of possible types and symbols to 16 each. #[inline] - pub(crate) fn idx(&self) -> u8 { + pub(crate) const fn idx(&self) -> u8 { let (high, low) = match self { FieldSymbol::Era => (0, 0), FieldSymbol::Year(year) => (1, year.idx()), @@ -186,6 +186,12 @@ impl FieldSymbolULE { .map(|_| ()) .map_err(|_| ZeroVecError::parse::()) } + + /// The same as [`AsULE::to_unaligned`]. + #[inline] + pub const fn from_aligned(fs: FieldSymbol) -> Self { + Self(fs.idx()) + } } // Safety checklist for ULE: @@ -378,7 +384,7 @@ macro_rules! field_type { /// and does not guarantee index stability between ICU4X /// versions. #[inline] - pub(crate) fn idx(self) -> u8 { + pub(crate) const fn idx(self) -> u8 { self as u8 } diff --git a/components/properties/src/lib.rs b/components/properties/src/lib.rs index fa670705e5b..773490c82f0 100644 --- a/components/properties/src/lib.rs +++ b/components/properties/src/lib.rs @@ -89,10 +89,10 @@ pub mod maps; // of the `CanonicalCombiningClass` struct inside the `props` // module. Please do not change the crate-module-qualified // name of that struct without coordination. -mod props; -mod expandme; pub mod bidi_data; pub mod exemplar_chars; +mod expandme; +mod props; pub mod provider; pub(crate) mod runtime; #[allow(clippy::exhaustive_structs)] // TODO diff --git a/utils/zerovec/derive/src/make_ule.rs b/utils/zerovec/derive/src/make_ule.rs index b0887ba5df1..ced5322efbb 100644 --- a/utils/zerovec/derive/src/make_ule.rs +++ b/utils/zerovec/derive/src/make_ule.rs @@ -181,6 +181,7 @@ fn make_ule_enum_impl( impl #ule_name { #[doc = #from_aligned_doc] + #[inline] pub const fn from_aligned(a: #name) -> #ule_name { // safety: the enum is repr(u8) and can be cast to a u8 unsafe { @@ -299,12 +300,14 @@ fn make_ule_struct_impl( quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)), ); // TODO: need to match here and below on #ty, and then call the appropriate function, as in zeroslice! macro. - const_from_aligned_conversions.push(quote!(#ident: <#ty as zerovec::ule::AsULE>::ULE::from_aligned(aligned.#ident))); + const_from_aligned_conversions + .push(quote!(#ident: zerovec::const_ule_conversion_fn!(#ty)(aligned.#ident))); } else { as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i))); from_ule_conversions .push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i))); - const_from_aligned_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::ULE::from_aligned(aligned.#i))); + const_from_aligned_conversions + .push(quote!(zerovec::const_ule_conversion_fn!(#ty)(aligned.#i))); }; } @@ -357,16 +360,20 @@ fn make_ule_struct_impl( quote!() }; + let ule_doc = "The same as [`AsULE::to_unaligned`]."; - let const_from_aligned_conversions = utils::wrap_field_inits(&const_from_aligned_conversions, &struc.fields); + let const_from_aligned_conversions = + utils::wrap_field_inits(&const_from_aligned_conversions, &struc.fields); let ule_impl = quote!( - impl #ule_name { + impl #ule_name { + #[doc = #ule_doc] + #[inline] pub const fn from_aligned(aligned: #name) -> Self { Self #const_from_aligned_conversions } // TODO: need to come up with a safe zero expression for the array initializer. - // zerovec::impl_ule_from_array!(#name, #ule_name, ) + // zerovec::impl_ule_from_array!(#name, #ule_name, unsafe { core::mem::transmute([0; core::mem::size_of::<#ule_name>()]) }) } ); diff --git a/utils/zerovec/src/ule/macros.rs b/utils/zerovec/src/ule/macros.rs index c1e178a0b2a..1f7cf65822e 100644 --- a/utils/zerovec/src/ule/macros.rs +++ b/utils/zerovec/src/ule/macros.rs @@ -49,3 +49,43 @@ macro_rules! impl_const_as_ule_array { $crate::impl_const_as_ule_array!($aligned, $unaligned, $default, Self::aligned_to_unaligned); }; } + +#[macro_export] +macro_rules! const_ule_conversion_fn { + (u8) => { + core::convert::identity + }; + (i8) => { + core::convert::identity + }; + (u16) => { + ::ULE::from_unsigned + }; + (i16) => { + ::ULE::from_signed + }; + (u32) => { + ::ULE::from_unsigned + }; + (i32) => { + ::ULE::from_signed + }; + (u64) => { + ::ULE::from_unsigned + }; + (i64) => { + ::ULE::from_signed + }; + (u128) => { + ::ULE::from_unsigned + }; + (i128) => { + ::ULE::from_signed + }; + (UnvalidatedChar) => { + ::ULE::from_unvalidated_char + }; + ($aligned:ty) => { + <$aligned as $crate::ule::AsULE>::ULE::from_aligned + }; +} diff --git a/utils/zerovec/src/zerovec/mod.rs b/utils/zerovec/src/zerovec/mod.rs index b7368a33490..77c7ccefc59 100644 --- a/utils/zerovec/src/zerovec/mod.rs +++ b/utils/zerovec/src/zerovec/mod.rs @@ -976,47 +976,18 @@ macro_rules! zeroslice { () => ( $crate::ZeroSlice::new_empty() ); + ($aligned:tt; $($x:expr),+ $(,)?) => ( + $crate::ZeroSlice::<$aligned>::from_ule_slice( + {const X: &[<$aligned as $crate::ule::AsULE>::ULE] = &[ + $($crate::const_ule_conversion_fn!($aligned)($x)),* + ]; X} + ) + ); ($aligned:ty; $array_fn:expr; $($x:expr),+ $(,)?) => ( $crate::ZeroSlice::<$aligned>::from_ule_slice( {const X: &[<$aligned as $crate::ule::AsULE>::ULE] = &$array_fn([$($x),+]); X} ) ); - (u8; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![u8; core::convert::identity; $($x),+] - ); - (i8; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![i8; core::convert::identity; $($x),+] - ); - (u16; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![u16; ::ULE::from_unsigned_array; $($x),+] - ); - (i16; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![i16; ::ULE::from_signed_array; $($x),+] - ); - (u32; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![u32; ::ULE::from_unsigned_array; $($x),+] - ); - (i32; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![i32; ::ULE::from_signed_array; $($x),+] - ); - (u64; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![u64; ::ULE::from_unsigned_array; $($x),+] - ); - (i64; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![i64; ::ULE::from_signed_array; $($x),+] - ); - (u128; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![u128; ::ULE::from_unsigned_array; $($x),+] - ); - (i128; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![i128; ::ULE::from_signed_array; $($x),+] - ); - (UnvalidatedChar; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![UnvalidatedChar; ::ULE::from_unvalidated_char_array; $($x),+] - ); - ($aligned:ty; $($x:expr),+ $(,)?) => ( - $crate::zeroslice![$aligned; <$aligned as $crate::ule::AsULE>::ULE::from_array; $($x),+] - ); } /// Creates a borrowed `ZeroVec`. Convenience wrapper for `zeroslice![...].as_zerovec()`.