From df5262b19ca4fde16bc1a15eecb7438f0ecdbe42 Mon Sep 17 00:00:00 2001 From: Charles Lew Date: Wed, 31 May 2023 22:46:00 +0800 Subject: [PATCH 1/9] Add `#[doc(hidden)]` on derive generated items. (#189) --- derive/src/traits.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/derive/src/traits.rs b/derive/src/traits.rs index 767fb802..ec6afd90 100644 --- a/derive/src/traits.rs +++ b/derive/src/traits.rs @@ -538,6 +538,7 @@ fn generate_assert_no_padding(input: &DeriveInput) -> Result { }; Ok(quote_spanned! {span => const _: fn() = || { + #[doc(hidden)] struct TypeWithoutPadding([u8; #size_sum]); let _ = ::core::mem::transmute::<#struct_type, TypeWithoutPadding>; };}) @@ -554,6 +555,7 @@ fn generate_fields_are_trait( let field_types = get_field_types(&fields); Ok(quote_spanned! {span => #(const _: fn() = || { #[allow(clippy::missing_const_for_fn)] + #[doc(hidden)] fn check #impl_generics () #where_clause { fn assert_impl() {} assert_impl::<#field_types>(); From e528c1662f10c42aa70e3f8a2b3f59c818a37aeb Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Tue, 6 Jun 2023 23:48:41 +0200 Subject: [PATCH 2/9] Add missing `?Sized` for `TransparentWrapperAlloc` extension trait (#185) * Add missing `?Sized` for `TransparentWrapperAlloc` extension trait * chore: Release bytemuck version 1.13.2 --- Cargo.toml | 2 +- src/allocation.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9d6be11c..b85f122f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bytemuck" description = "A crate for mucking around with piles of bytes." -version = "1.13.1" +version = "1.13.2" authors = ["Lokathor "] repository = "https://github.com/Lokathor/bytemuck" readme = "README.md" diff --git a/src/allocation.rs b/src/allocation.rs index 1c1616bd..a2633b52 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -685,4 +685,5 @@ pub trait TransparentWrapperAlloc: } } } -impl> TransparentWrapperAlloc for T {} + +impl> TransparentWrapperAlloc for T {} From 8f130f2b79725b1af846647181dc3d23caa7faf3 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Tue, 6 Jun 2023 14:50:39 -0700 Subject: [PATCH 3/9] must_cast (#187) --- Cargo.toml | 3 + src/lib.rs | 5 ++ src/must.rs | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 src/must.rs diff --git a/Cargo.toml b/Cargo.toml index b85f122f..e4294572 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ zeroable_maybe_uninit = [] zeroable_atomics = [] min_const_generics = [] wasm_simd = [] # Until >= 1.54.0 is MSRV this is an off-by-default feature. +must_cast = [] # Until >= 1.57.0 is MSRV this is an off-by-default feature. aarch64_simd = [] # Until >= 1.59.0 is MSRV this is an off-by-default feature. # Do not use if you can avoid it, because this is unsound. @@ -42,6 +43,7 @@ features = [ "zeroable_atomics", "min_const_generics", "wasm_simd", + "must_cast", ] [package.metadata.playground] @@ -54,4 +56,5 @@ features = [ "zeroable_atomics", "min_const_generics", "wasm_simd", + "must_cast", ] diff --git a/src/lib.rs b/src/lib.rs index 7ee1f903..c49a3ba3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,6 +113,11 @@ pub use pod::*; mod pod_in_option; pub use pod_in_option::*; +#[cfg(feature = "must_cast")] +mod must; +#[cfg(feature = "must_cast")] +pub use must::*; + mod no_uninit; pub use no_uninit::*; diff --git a/src/must.rs b/src/must.rs new file mode 100644 index 00000000..5b254111 --- /dev/null +++ b/src/must.rs @@ -0,0 +1,181 @@ +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::let_unit_value)] +#![allow(clippy::let_underscore_untyped)] +#![allow(clippy::ptr_as_ptr)] + +use crate::{AnyBitPattern, NoUninit}; +use core::mem::{align_of, size_of}; + +struct Cast((A, B)); +impl Cast { + const ASSERT_ALIGN_GREATER_THAN_EQUAL : () = assert!(align_of::() >= align_of::()); + const ASSERT_SIZE_EQUAL : () = assert!(size_of::() == size_of::()); + const ASSERT_SIZE_MULTIPLE_OF : () = assert!((size_of::() == 0) == (size_of::() == 0) && (size_of::() % size_of::() == 0)); +} + +/// Cast `A` into `B` if infalliable, or fail to compile. +/// +/// Note that for this particular type of cast, alignment isn't a factor. The +/// input value is semantically copied into the function and then returned to a +/// new memory location which will have whatever the required alignment of the +/// output type is. +/// +/// ## Failure +/// +/// * If the types don't have the same size this fails to compile. +/// +/// ## Examples +/// ``` +/// // compiles: +/// let bytes : [u8; 2] = bytemuck::must_cast(12_u16); +/// ``` +/// ```compile_fail,E0080 +/// // fails to compile (size mismatch): +/// let bytes : [u8; 3] = bytemuck::must_cast(12_u16); +/// ``` +#[inline] +pub fn must_cast(a: A) -> B { + let _ = Cast::::ASSERT_SIZE_EQUAL; + unsafe { transmute!(a) } +} + +/// Convert `&A` into `&B` if infalliable, or fail to compile. +/// +/// ## Failure +/// +/// * If the target type has a greater alignment requirement. +/// * If the source type and target type aren't the same size. +/// +/// ## Examples +/// ``` +/// // compiles: +/// let bytes : &[u8; 2] = bytemuck::must_cast_ref(&12_u16); +/// ``` +/// ```compile_fail,E0080 +/// // fails to compile (size mismatch): +/// let bytes : &[u8; 3] = bytemuck::must_cast_ref(&12_u16); +/// ``` +/// ```compile_fail,E0080 +/// // fails to compile (alignment requirements increased): +/// let bytes : &u16 = bytemuck::must_cast_ref(&[1u8, 2u8]); +/// ``` +#[inline] +pub fn must_cast_ref(a: &A) -> &B { + let _ = Cast::::ASSERT_SIZE_EQUAL; + let _ = Cast::::ASSERT_ALIGN_GREATER_THAN_EQUAL; + unsafe { &*(a as *const A as *const B) } +} + +/// Convert a `&mut A` into `&mut B` if infalliable, or fail to compile. +/// +/// As [`must_cast_ref`], but `mut`. +/// +/// ## Examples +/// ``` +/// let mut i = 12_u16; +/// // compiles: +/// let bytes : &mut [u8; 2] = bytemuck::must_cast_mut(&mut i); +/// ``` +/// ```compile_fail,E0080 +/// # let mut bytes : [u8; 2] = [1, 2]; +/// # let bytes = &mut bytes[..]; +/// // fails to compile (alignment requirements increased): +/// let i : &mut u16 = bytemuck::must_cast_mut(bytes); +/// ``` +/// ```compile_fail,E0080 +/// # let mut i = 12_u16; +/// // fails to compile (size mismatch): +/// let bytes : &mut [u8; 3] = bytemuck::must_cast_mut(&mut i); +/// ``` +#[inline] +pub fn must_cast_mut< + A: NoUninit + AnyBitPattern, + B: NoUninit + AnyBitPattern, +>( + a: &mut A, +) -> &mut B { + let _ = Cast::::ASSERT_SIZE_EQUAL; + let _ = Cast::::ASSERT_ALIGN_GREATER_THAN_EQUAL; + unsafe { &mut *(a as *mut A as *mut B) } +} + +/// Convert `&[A]` into `&[B]` (possibly with a change in length) if infalliable, or fail to compile. +/// +/// * `input.as_ptr() as usize == output.as_ptr() as usize` +/// * `input.len() * size_of::() == output.len() * size_of::()` +/// +/// ## Failure +/// +/// * If the target type has a greater alignment requirement. +/// * If the target element type doesn't evenly fit into the the current element type +/// (eg: 3 `u16` values is 1.5 `u32` values, so that's a failure). +/// * Similarly, you can't convert between a [ZST](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts) +/// and a non-ZST. +/// +/// ## Examples +/// ``` +/// let indicies : &[u16] = &[1, 2, 3]; +/// // compiles: +/// let bytes : &[u8] = bytemuck::must_cast_slice(indicies); +/// ``` +/// ```compile_fail,E0080 +/// # let bytes : &[u8] = [1, 0, 2, 0, 3, 0]; +/// // fails to compile (bytes.len() might not be a multiple of 2): +/// let byte_pairs : &[[u8; 2]] = bytemuck::must_cast_slice(bytes); +/// ``` +/// ```compile_fail,E0080 +/// # let byte_pairs : &[[u8; 2]] = &[[1, 0], [2, 0], [3, 0]]; +/// // fails to compile (alignment requirements increased): +/// let indicies : &[u16] = bytemuck::must_cast_slice(byte_pairs); +/// ``` +#[inline] +pub fn must_cast_slice(a: &[A]) -> &[B] { + let _ = Cast::::ASSERT_SIZE_MULTIPLE_OF; + let _ = Cast::::ASSERT_ALIGN_GREATER_THAN_EQUAL; + let new_len = if size_of::() == size_of::() { + a.len() + } else { + a.len() * (size_of::() / size_of::()) + }; + unsafe { core::slice::from_raw_parts(a.as_ptr() as *const B, new_len) } +} + +/// Convert `&mut [A]` into `&mut [B]` (possibly with a change in length) if infalliable, or fail to compile. +/// +/// As [`must_cast_slice`], but `&mut`. +/// +/// ## Examples +/// ``` +/// let mut indicies = [1, 2, 3]; +/// let indicies : &mut [u16] = &mut indicies; +/// // compiles: +/// let bytes : &mut [u8] = bytemuck::must_cast_slice_mut(indicies); +/// ``` +/// ```compile_fail,E0080 +/// # let mut bytes = [1, 0, 2, 0, 3, 0]; +/// # let bytes : &mut [u8] = &mut bytes[..]; +/// // fails to compile (bytes.len() might not be a multiple of 2): +/// let byte_pairs : &mut [[u8; 2]] = bytemuck::must_cast_slice_mut(bytes); +/// ``` +/// ```compile_fail,E0080 +/// # let mut byte_pairs = [[1, 0], [2, 0], [3, 0]]; +/// # let byte_pairs : &mut [[u8; 2]] = &mut byte_pairs[..]; +/// // fails to compile (alignment requirements increased): +/// let indicies : &mut [u16] = bytemuck::must_cast_slice_mut(byte_pairs); +/// ``` +#[inline] +pub fn must_cast_slice_mut< + A: NoUninit + AnyBitPattern, + B: NoUninit + AnyBitPattern, +>( + a: &mut [A], +) -> &mut [B] { + let _ = Cast::::ASSERT_SIZE_MULTIPLE_OF; + let _ = Cast::::ASSERT_ALIGN_GREATER_THAN_EQUAL; + let new_len = if size_of::() == size_of::() { + a.len() + } else { + a.len() * (size_of::() / size_of::()) + }; + unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr() as *mut B, new_len) } +} From c286994891c1736d4a5824fb3e56f55cc8df831b Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Thu, 15 Jun 2023 10:54:23 -0500 Subject: [PATCH 4/9] must_cast: fix test failures and fmt. (#194) * cargo fmt src/must.rs * Fix must.rs test failures. * Under miri, use should_panic instead of compile_fail for must_cast doctests. * Add description for workaround. --- src/must.rs | 74 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/src/must.rs b/src/must.rs index 5b254111..8373e711 100644 --- a/src/must.rs +++ b/src/must.rs @@ -8,9 +8,30 @@ use core::mem::{align_of, size_of}; struct Cast((A, B)); impl Cast { - const ASSERT_ALIGN_GREATER_THAN_EQUAL : () = assert!(align_of::() >= align_of::()); - const ASSERT_SIZE_EQUAL : () = assert!(size_of::() == size_of::()); - const ASSERT_SIZE_MULTIPLE_OF : () = assert!((size_of::() == 0) == (size_of::() == 0) && (size_of::() % size_of::() == 0)); + const ASSERT_ALIGN_GREATER_THAN_EQUAL: () = + assert!(align_of::() >= align_of::()); + const ASSERT_SIZE_EQUAL: () = assert!(size_of::() == size_of::()); + const ASSERT_SIZE_MULTIPLE_OF: () = assert!( + (size_of::() == 0) == (size_of::() == 0) + && (size_of::() % size_of::() == 0) + ); +} + +// Workaround for https://github.com/rust-lang/miri/issues/2423. +// Miri currently doesn't see post-monomorphization errors until runtime, +// so `compile_fail` tests relying on post-monomorphization errors don't +// actually fail. Instead use `should_panic` under miri as a workaround. +#[cfg(miri)] +macro_rules! post_mono_compile_fail_doctest { + () => { + "```should_panic" + }; +} +#[cfg(not(miri))] +macro_rules! post_mono_compile_fail_doctest { + () => { + "```compile_fail,E0080" + }; } /// Cast `A` into `B` if infalliable, or fail to compile. @@ -27,9 +48,9 @@ impl Cast { /// ## Examples /// ``` /// // compiles: -/// let bytes : [u8; 2] = bytemuck::must_cast(12_u16); +/// let bytes: [u8; 2] = bytemuck::must_cast(12_u16); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// // fails to compile (size mismatch): /// let bytes : [u8; 3] = bytemuck::must_cast(12_u16); /// ``` @@ -49,13 +70,13 @@ pub fn must_cast(a: A) -> B { /// ## Examples /// ``` /// // compiles: -/// let bytes : &[u8; 2] = bytemuck::must_cast_ref(&12_u16); +/// let bytes: &[u8; 2] = bytemuck::must_cast_ref(&12_u16); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// // fails to compile (size mismatch): /// let bytes : &[u8; 3] = bytemuck::must_cast_ref(&12_u16); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// // fails to compile (alignment requirements increased): /// let bytes : &u16 = bytemuck::must_cast_ref(&[1u8, 2u8]); /// ``` @@ -74,15 +95,14 @@ pub fn must_cast_ref(a: &A) -> &B { /// ``` /// let mut i = 12_u16; /// // compiles: -/// let bytes : &mut [u8; 2] = bytemuck::must_cast_mut(&mut i); +/// let bytes: &mut [u8; 2] = bytemuck::must_cast_mut(&mut i); /// ``` -/// ```compile_fail,E0080 -/// # let mut bytes : [u8; 2] = [1, 2]; -/// # let bytes = &mut bytes[..]; +#[doc = post_mono_compile_fail_doctest!()] +/// # let mut bytes: &mut [u8; 2] = &mut [1, 2]; /// // fails to compile (alignment requirements increased): /// let i : &mut u16 = bytemuck::must_cast_mut(bytes); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// # let mut i = 12_u16; /// // fails to compile (size mismatch): /// let bytes : &mut [u8; 3] = bytemuck::must_cast_mut(&mut i); @@ -99,7 +119,8 @@ pub fn must_cast_mut< unsafe { &mut *(a as *mut A as *mut B) } } -/// Convert `&[A]` into `&[B]` (possibly with a change in length) if infalliable, or fail to compile. +/// Convert `&[A]` into `&[B]` (possibly with a change in length) if +/// infalliable, or fail to compile. /// /// * `input.as_ptr() as usize == output.as_ptr() as usize` /// * `input.len() * size_of::() == output.len() * size_of::()` @@ -107,23 +128,23 @@ pub fn must_cast_mut< /// ## Failure /// /// * If the target type has a greater alignment requirement. -/// * If the target element type doesn't evenly fit into the the current element type -/// (eg: 3 `u16` values is 1.5 `u32` values, so that's a failure). +/// * If the target element type doesn't evenly fit into the the current element +/// type (eg: 3 `u16` values is 1.5 `u32` values, so that's a failure). /// * Similarly, you can't convert between a [ZST](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts) /// and a non-ZST. /// /// ## Examples /// ``` -/// let indicies : &[u16] = &[1, 2, 3]; +/// let indicies: &[u16] = &[1, 2, 3]; /// // compiles: -/// let bytes : &[u8] = bytemuck::must_cast_slice(indicies); +/// let bytes: &[u8] = bytemuck::must_cast_slice(indicies); /// ``` -/// ```compile_fail,E0080 -/// # let bytes : &[u8] = [1, 0, 2, 0, 3, 0]; +#[doc = post_mono_compile_fail_doctest!()] +/// # let bytes : &[u8] = &[1, 0, 2, 0, 3, 0]; /// // fails to compile (bytes.len() might not be a multiple of 2): /// let byte_pairs : &[[u8; 2]] = bytemuck::must_cast_slice(bytes); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// # let byte_pairs : &[[u8; 2]] = &[[1, 0], [2, 0], [3, 0]]; /// // fails to compile (alignment requirements increased): /// let indicies : &[u16] = bytemuck::must_cast_slice(byte_pairs); @@ -140,24 +161,25 @@ pub fn must_cast_slice(a: &[A]) -> &[B] { unsafe { core::slice::from_raw_parts(a.as_ptr() as *const B, new_len) } } -/// Convert `&mut [A]` into `&mut [B]` (possibly with a change in length) if infalliable, or fail to compile. +/// Convert `&mut [A]` into `&mut [B]` (possibly with a change in length) if +/// infalliable, or fail to compile. /// /// As [`must_cast_slice`], but `&mut`. /// /// ## Examples /// ``` /// let mut indicies = [1, 2, 3]; -/// let indicies : &mut [u16] = &mut indicies; +/// let indicies: &mut [u16] = &mut indicies; /// // compiles: -/// let bytes : &mut [u8] = bytemuck::must_cast_slice_mut(indicies); +/// let bytes: &mut [u8] = bytemuck::must_cast_slice_mut(indicies); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// # let mut bytes = [1, 0, 2, 0, 3, 0]; /// # let bytes : &mut [u8] = &mut bytes[..]; /// // fails to compile (bytes.len() might not be a multiple of 2): /// let byte_pairs : &mut [[u8; 2]] = bytemuck::must_cast_slice_mut(bytes); /// ``` -/// ```compile_fail,E0080 +#[doc = post_mono_compile_fail_doctest!()] /// # let mut byte_pairs = [[1, 0], [2, 0], [3, 0]]; /// # let byte_pairs : &mut [[u8; 2]] = &mut byte_pairs[..]; /// // fails to compile (alignment requirements increased): From 588d89082d81301702d62a8ebeb13764cffb4dc2 Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Thu, 15 Jun 2023 17:43:27 -0500 Subject: [PATCH 5/9] Relax Pod for PhantomData (#195) As with Zeroable for PhantomData. --- src/pod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pod.rs b/src/pod.rs index 6cb24581..a98def81 100644 --- a/src/pod.rs +++ b/src/pod.rs @@ -60,7 +60,7 @@ unsafe impl Pod for *const T {} #[cfg(feature = "unsound_ptr_pod_impl")] unsafe impl PodInOption for NonNull {} -unsafe impl Pod for PhantomData {} +unsafe impl Pod for PhantomData {} unsafe impl Pod for PhantomPinned {} unsafe impl Pod for ManuallyDrop {} From 1ed1aaecf9d56cf13e3eb3dfc67808ac9e175569 Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:01:10 -0500 Subject: [PATCH 6/9] Remove FIXME and checks about NonZero[int] layout. (#199) The layout was documented as guaranteed in 1.70.0 https://github.com/rust-lang/rust/pull/94786/ --- src/checked.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/checked.rs b/src/checked.rs index 2eabfe82..a9c0e679 100644 --- a/src/checked.rs +++ b/src/checked.rs @@ -170,6 +170,7 @@ unsafe impl CheckedBitPattern for bool { } } +// Rust 1.70.0 documents that NonZero[int] has the same layout as [int]. macro_rules! impl_checked_for_nonzero { ($($nonzero:ty: $primitive:ty),* $(,)?) => { $( @@ -178,14 +179,7 @@ macro_rules! impl_checked_for_nonzero { #[inline] fn is_valid_bit_pattern(bits: &Self::Bits) -> bool { - // Note(zachs18): The size and alignment check are almost certainly - // not necessary, but Rust currently doesn't explicitly document that - // NonZero[int] has the same layout as [int], so we check it to be safe. - // In a const to reduce debug-profile overhead. - const LAYOUT_SAME: bool = - core::mem::size_of::<$nonzero>() == core::mem::size_of::<$primitive>() - && core::mem::align_of::<$nonzero>() == core::mem::align_of::<$primitive>(); - LAYOUT_SAME && *bits != 0 + *bits != 0 } } )* From 62a7ec9b389959912b23392af11887aff53138a2 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 17 Jul 2023 10:02:53 -0600 Subject: [PATCH 7/9] MIPS got demoted to tier 3 so we can't CI it anymore. --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index df8cd7b9..a3c27de8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -46,8 +46,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - # note: the mips targets are here so that we have big-endian coverage (both 32bit and 64bit) - target: [i686-unknown-linux-gnu, mips-unknown-linux-gnu, mips64-unknown-linux-gnuabi64] + # we once had mips runners for Big-endian coverage but those got demoted to tier 3. + target: [i686-unknown-linux-gnu] steps: - uses: hecrj/setup-rust-action@v1 with: From 3b81c85c60fcc09b489608e2ba2fc1c5f8260b23 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Tue, 25 Jul 2023 18:15:01 -0400 Subject: [PATCH 8/9] Update proc-macro2 (#201) --- derive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index e393eb74..e0970921 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true # syn seems to have broken backwards compatibility in this version https://github.com/dtolnay/syn/issues/1194 syn = "2.0.1" quote = "1" -proc-macro2 = "1" +proc-macro2 = "1.0.60" [dev-dependencies] bytemuck = { path = "../", features = ["derive"] } From 39b42b8fa364a1456e312dfb5005e3daca28d83d Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:50:58 -0500 Subject: [PATCH 9/9] Zeroable derive custom bounds (#196) * Add space because rust-analyzer thinks it's a weird literal otherwise. * Add custom bounds for Zeroable. * Cleanup custom bounds code. * Only parse explicit bounds for Zeroable. * Qualify syn types. * If no explicit bounds are given, apply the default bounds. * Add perfect derive semantics to `#[zeroable(bound = "...")]`. --- derive/src/lib.rs | 196 ++++++++++++++++++++++++++++++++++++++++--- derive/src/traits.rs | 9 +- 2 files changed, 192 insertions(+), 13 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 448e5035..23ddf5e9 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -77,7 +77,6 @@ pub fn derive_anybitpattern( /// /// ```rust /// # use bytemuck_derive::{Zeroable}; -/// /// #[derive(Copy, Clone, Zeroable)] /// #[repr(C)] /// struct Test { @@ -85,7 +84,65 @@ pub fn derive_anybitpattern( /// b: u16, /// } /// ``` -#[proc_macro_derive(Zeroable)] +/// +/// # Custom bounds +/// +/// Custom bounds for the derived `Zeroable` impl can be given using the +/// `#[zeroable(bound = "")]` helper attribute. +/// +/// Using this attribute additionally opts-in to "perfect derive" semantics, +/// where instead of adding bounds for each generic type parameter, bounds are +/// added for each field's type. +/// +/// ## Examples +/// +/// ```rust +/// # use bytemuck::Zeroable; +/// # use std::marker::PhantomData; +/// #[derive(Clone, Zeroable)] +/// #[zeroable(bound = "")] +/// struct AlwaysZeroable { +/// a: PhantomData, +/// } +/// +/// AlwaysZeroable::::zeroed(); +/// ``` +/// +/// ```rust,compile_fail +/// # use bytemuck::Zeroable; +/// # use std::marker::PhantomData; +/// #[derive(Clone, Zeroable)] +/// #[zeroable(bound = "T: Copy")] +/// struct ZeroableWhenTIsCopy { +/// a: PhantomData, +/// } +/// +/// ZeroableWhenTIsCopy::::zeroed(); +/// ``` +/// +/// The restriction that all fields must be Zeroable is still applied, and this +/// is enforced using the mentioned "perfect derive" semantics. +/// +/// ```rust +/// # use bytemuck::Zeroable; +/// #[derive(Clone, Zeroable)] +/// #[zeroable(bound = "")] +/// struct ZeroableWhenTIsZeroable { +/// a: T, +/// } +/// ZeroableWhenTIsZeroable::::zeroed(); +/// ``` +/// +/// ```rust,compile_fail +/// # use bytemuck::Zeroable; +/// # #[derive(Clone, Zeroable)] +/// # #[zeroable(bound = "")] +/// # struct ZeroableWhenTIsZeroable { +/// # a: T, +/// # } +/// ZeroableWhenTIsZeroable::::zeroed(); +/// ``` +#[proc_macro_derive(Zeroable, attributes(zeroable))] pub fn derive_zeroable( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { @@ -317,12 +374,127 @@ fn derive_marker_trait(input: DeriveInput) -> TokenStream { .unwrap_or_else(|err| err.into_compile_error()) } +/// Find `#[name(key = "value")]` helper attributes on the struct, and return +/// their `"value"`s parsed with `parser`. +/// +/// Returns an error if any attributes with the given `name` do not match the +/// expected format. Returns `Ok([])` if no attributes with `name` are found. +fn find_and_parse_helper_attributes( + attributes: &[syn::Attribute], name: &str, key: &str, parser: P, + example_value: &str, invalid_value_msg: &str, +) -> Result> { + let invalid_format_msg = + format!("{name} attribute must be `{name}({key} = \"{example_value}\")`",); + let values_to_check = attributes.iter().filter_map(|attr| match &attr.meta { + // If a `Path` matches our `name`, return an error, else ignore it. + // e.g. `#[zeroable]` + syn::Meta::Path(path) => path + .is_ident(name) + .then(|| Err(syn::Error::new_spanned(path, &invalid_format_msg))), + // If a `NameValue` matches our `name`, return an error, else ignore it. + // e.g. `#[zeroable = "hello"]` + syn::Meta::NameValue(namevalue) => { + namevalue.path.is_ident(name).then(|| { + Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg)) + }) + } + // If a `List` matches our `name`, match its contents to our format, else + // ignore it. If its contents match our format, return the value, else + // return an error. + syn::Meta::List(list) => list.path.is_ident(name).then(|| { + let namevalue: syn::MetaNameValue = syn::parse2(list.tokens.clone()) + .map_err(|_| { + syn::Error::new_spanned(&list.tokens, &invalid_format_msg) + })?; + if namevalue.path.is_ident(key) { + match namevalue.value { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(strlit), .. + }) => Ok(strlit), + _ => { + Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg)) + } + } + } else { + Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg)) + } + }), + }); + // Parse each value found with the given parser, and return them if no errors + // occur. + values_to_check + .map(|lit| { + let lit = lit?; + lit.parse_with(parser).map_err(|err| { + syn::Error::new_spanned(&lit, format!("{invalid_value_msg}: {err}")) + }) + }) + .collect() +} + fn derive_marker_trait_inner( mut input: DeriveInput, ) -> Result { - // Enforce Pod on all generic fields. let trait_ = Trait::ident(&input)?; - add_trait_marker(&mut input.generics, &trait_); + // If this trait allows explicit bounds, and any explicit bounds were given, + // then use those explicit bounds. Else, apply the default bounds (bound + // each generic type on this trait). + if let Some(name) = Trait::explicit_bounds_attribute_name() { + // See if any explicit bounds were given in attributes. + let explicit_bounds = find_and_parse_helper_attributes( + &input.attrs, + name, + "bound", + >::parse_terminated, + "Type: Trait", + "invalid where predicate", + )?; + + if !explicit_bounds.is_empty() { + // Explicit bounds were given. + // Enforce explicitly given bounds, and emit "perfect derive" (i.e. add + // bounds for each field's type). + let explicit_bounds = explicit_bounds + .into_iter() + .flatten() + .collect::>(); + + let predicates = &mut input.generics.make_where_clause().predicates; + + predicates.extend(explicit_bounds); + + let fields = match &input.data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => fields.clone(), + syn::Data::Union(_) => { + return Err(syn::Error::new_spanned( + trait_, + &"perfect derive is not supported for unions", + )); + } + syn::Data::Enum(_) => { + return Err(syn::Error::new_spanned( + trait_, + &"perfect derive is not supported for enums", + )); + } + }; + + for field in fields { + let ty = field.ty; + predicates.push(syn::parse_quote!( + #ty: #trait_ + )); + } + } else { + // No explicit bounds were given. + // Enforce trait bound on all type generics. + add_trait_marker(&mut input.generics, &trait_); + } + } else { + // This trait does not allow explicit bounds. + // Enforce trait bound on all type generics. + add_trait_marker(&mut input.generics, &trait_); + } let name = &input.ident; @@ -339,11 +511,8 @@ fn derive_marker_trait_inner( quote!() }; - let where_clause = if Trait::requires_where_clause() { - where_clause - } else { - None - }; + let where_clause = + if Trait::requires_where_clause() { where_clause } else { None }; Ok(quote! { #asserts @@ -364,9 +533,12 @@ fn add_trait_marker(generics: &mut syn::Generics, trait_name: &syn::Path) { let type_params = generics .type_params() .map(|param| ¶m.ident) - .map(|param| syn::parse_quote!( - #param: #trait_name - )).collect::>(); + .map(|param| { + syn::parse_quote!( + #param: #trait_name + ) + }) + .collect::>(); generics.make_where_clause().predicates.extend(type_params); } diff --git a/derive/src/traits.rs b/derive/src/traits.rs index ec6afd90..da512752 100644 --- a/derive/src/traits.rs +++ b/derive/src/traits.rs @@ -35,6 +35,9 @@ pub trait Derivable { fn requires_where_clause() -> bool { true } + fn explicit_bounds_attribute_name() -> Option<&'static str> { + None + } } pub struct Pod; @@ -126,6 +129,10 @@ impl Derivable for Zeroable { Data::Enum(_) => bail!("Deriving Zeroable is not supported for enums"), } } + + fn explicit_bounds_attribute_name() -> Option<&'static str> { + Some("zeroable") + } } pub struct NoUninit; @@ -532,7 +539,7 @@ fn generate_assert_no_padding(input: &DeriveInput) -> Result { let size_rest = quote_spanned!(span => #( + ::core::mem::size_of::<#field_types>() )*); - quote_spanned!(span => #size_first#size_rest) + quote_spanned!(span => #size_first #size_rest) } else { quote_spanned!(span => 0) };