Skip to content

Commit

Permalink
make zeroslice macros magic
Browse files Browse the repository at this point in the history
  • Loading branch information
skius committed May 25, 2023
1 parent 8f3cb8e commit a8119fb
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 1 deletion.
36 changes: 36 additions & 0 deletions utils/zerovec/derive/src/make_ule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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> {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -334,6 +368,8 @@ fn make_ule_struct_impl(
quote!()
};

// TODO: implement ConstAsULE for struct types

quote!(
#asule_impl

Expand Down
21 changes: 20 additions & 1 deletion utils/zerovec/src/ule/chars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -87,7 +101,7 @@ impl AsULE for char {

#[inline]
fn to_unaligned(self) -> Self::ULE {
CharULE::from_aligned(self)
<Self as ConstAsULE>::ConstConvert::aligned_to_unaligned(self)
}

#[inline]
Expand All @@ -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<Ordering> {
char::from_unaligned(*self).partial_cmp(&char::from_unaligned(*other))
Expand Down
17 changes: 17 additions & 0 deletions utils/zerovec/src/ule/constconvert.rs
Original file line number Diff line number Diff line change
@@ -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<T, U>
where
T: AsULE<ULE = U>,
{
_phantom: PhantomData<(T, U)>,
}
22 changes: 22 additions & 0 deletions utils/zerovec/src/ule/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<const N: usize>(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);
};
}
32 changes: 32 additions & 0 deletions utils/zerovec/src/ule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<const N: usize>([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<T, T::ULE>`. 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
Expand Down
15 changes: 15 additions & 0 deletions utils/zerovec/src/ule/plain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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>>;
}
};
}

Expand Down
93 changes: 93 additions & 0 deletions utils/zerovec/src/zerovec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,99 @@ impl<T: AsULE> FromIterator<T> 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<const N: usize>(arr: [char; N]) -> [<char as AsULE>::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};
///
/// const SIGNATURE: &ZeroSlice<char> = zeroslice![char; 'b', 'y', 'e', '✌'];
/// const EMPTY: &ZeroSlice<u32> = zeroslice![];
/// let empty: &ZeroSlice<u32> = zeroslice![];
/// 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:
///
/// ```
/// use zerovec::{ZeroSlice, zeroslice};
///
/// mod conversion {
/// use zerovec::ule::RawBytesULE;
/// pub(super) const fn i16_array_to_be_array<const N: usize>(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<i16> = 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}
)
);
($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()`.
///
/// See [`zeroslice!`](crate::zeroslice) for more information.
///
/// # Examples
///
/// ```
/// use zerovec::{ZeroVec, zerovec, ule::AsULE};
///
/// const SIGNATURE: ZeroVec<char> = zerovec![char; 'a', 'y', 'e', '✌'];
/// assert!(!SIGNATURE.is_owned());
///
/// const EMPTY: ZeroVec<u32> = zerovec![];
/// assert!(!EMPTY.is_owned());
/// ```
#[macro_export]
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()
);
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit a8119fb

Please sign in to comment.