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/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..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; - 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 f87fadb482d..ced5322efbb 100644 --- a/utils/zerovec/derive/src/make_ule.rs +++ b/utils/zerovec/derive/src/make_ule.rs @@ -159,6 +159,8 @@ fn make_ule_enum_impl( let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}"); + 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. // (achieved by `#[repr(transparent)]` on a type that satisfies this invariant @@ -177,6 +179,24 @@ fn make_ule_enum_impl( #[doc = #doc] #vis struct #ule_name(u8); + 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 { + ::core::mem::transmute(a) + } + } + + 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 + unsafe { ::core::mem::transmute(0u8) } + ); + } + unsafe impl zerovec::ule::ULE for #ule_name { #[inline] fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> { @@ -268,6 +288,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; @@ -278,10 +299,15 @@ 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: 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!(zerovec::const_ule_conversion_fn!(#ty)(aligned.#i))); }; } @@ -334,6 +360,23 @@ 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 ule_impl = quote!( + 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, unsafe { core::mem::transmute([0; core::mem::size_of::<#ule_name>()]) }) + } + ); + quote!( #asule_impl @@ -344,5 +387,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 cc6d49e0c57..b94055ae553 100644 --- a/utils/zerovec/src/ule/chars.rs +++ b/utils/zerovec/src/ule/chars.rs @@ -87,7 +87,7 @@ impl AsULE for char { #[inline] fn to_unaligned(self) -> Self::ULE { - CharULE::from_aligned(self) + ::ULE::from_aligned(self) } #[inline] diff --git a/utils/zerovec/src/ule/macros.rs b/utils/zerovec/src/ule/macros.rs index 955b1eb2e41..1f7cf65822e 100644 --- a/utils/zerovec/src/ule/macros.rs +++ b/utils/zerovec/src/ule/macros.rs @@ -9,9 +9,31 @@ /// Pass any (cheap to construct) value. #[macro_export] macro_rules! impl_ule_from_array { + ($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 $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 + #[allow(clippy::indexing_slicing)] + while i < N { + result[i] = $single(arr[i]); + i += 1; + } + result + } + }; + ($aligned:ty, $unaligned:ty, $default:expr) => { + $crate::impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned, from_array); + }; +} + +#[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), "`.")] - pub const fn from_array(arr: [$aligned; N]) -> [Self; N] { + #[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 @@ -24,6 +46,46 @@ macro_rules! impl_ule_from_array { } }; ($aligned:ty, $unaligned:ty, $default:expr) => { - impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned); + $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/ule/mod.rs b/utils/zerovec/src/ule/mod.rs index ef148f8bb14..00f04bd062a 100644 --- a/utils/zerovec/src/ule/mod.rs +++ b/utils/zerovec/src/ule/mod.rs @@ -239,6 +239,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..3ec1d0897cb 100644 --- a/utils/zerovec/src/ule/plain.rs +++ b/utils/zerovec/src/ule/plain.rs @@ -111,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 { @@ -132,6 +132,32 @@ 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 RawBytesULE<$size> { + pub const fn $single_fn(v: $type) -> Self { + RawBytesULE(v.to_le_bytes()) + } + + impl_ule_from_array!( + $type, + RawBytesULE<$size>, + RawBytesULE([0; $size]), + Self::$single_fn, + $array_fn_name + ); + } + }; +} + +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); }; } @@ -140,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 9f1c439cd4f..77c7ccefc59 100644 --- a/utils/zerovec/src/zerovec/mod.rs +++ b/utils/zerovec/src/zerovec/mod.rs @@ -929,18 +929,26 @@ 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 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 /// /// 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: @@ -968,6 +976,13 @@ 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} @@ -984,7 +999,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 +1010,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() ); @@ -1005,6 +1023,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() { {