From: Miguel Ojeda <ojeda@kernel.org>
To: Miguel Ojeda <ojeda@kernel.org>,
Nathan Chancellor <nathan@kernel.org>,
Nicolas Schier <nsc@kernel.org>
Cc: "Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
rust-for-linux@vger.kernel.org, linux-kbuild@vger.kernel.org,
"Joshua Liebow-Feeser" <joshlf@google.com>,
"Jack Wrenn" <jswrenn@amazon.com>
Subject: [PATCH 13/18] rust: zerocopy-derive: import crate
Date: Tue, 2 Jun 2026 19:29:14 +0200 [thread overview]
Message-ID: <20260602172920.30342-14-ojeda@kernel.org> (raw)
In-Reply-To: <20260602172920.30342-1-ojeda@kernel.org>
This is a subset of the Rust `zerocopy-derive` crate, version v0.8.50
(released 2026-05-31), licensed under "BSD-2-Clause OR Apache-2.0 OR
MIT", from:
https://github.com/google/zerocopy/tree/v0.8.50/zerocopy-derive/src
The files are copied as-is, with no modifications whatsoever (not even
adding the SPDX identifiers).
For copyright details, please see:
https://github.com/google/zerocopy/blob/v0.8.50/README.md?plain=1
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-BSD
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-APACHE
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-MIT
The next two patches modify these files as needed for use within the
kernel. This patch split allows reviewers to double-check the import
and to clearly see the differences introduced.
The following script may be used to verify the contents:
for path in $(cd rust/zerocopy-derive/ && find . -type f); do
curl --silent --show-error --location \
https://github.com/google/zerocopy/raw/v0.8.50/zerocopy-derive/src/$path \
| diff --unified rust/zerocopy-derive/$path - && echo $path: OK
done
Cc: Joshua Liebow-Feeser <joshlf@google.com>
Cc: Jack Wrenn <jswrenn@amazon.com>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
---
rust/zerocopy-derive/derive/from_bytes.rs | 190 ++++
rust/zerocopy-derive/derive/into_bytes.rs | 162 ++++
rust/zerocopy-derive/derive/known_layout.rs | 348 +++++++
rust/zerocopy-derive/derive/mod.rs | 130 +++
rust/zerocopy-derive/derive/try_from_bytes.rs | 763 ++++++++++++++++
rust/zerocopy-derive/derive/unaligned.rs | 78 ++
rust/zerocopy-derive/lib.rs | 144 +++
rust/zerocopy-derive/repr.rs | 849 ++++++++++++++++++
rust/zerocopy-derive/util.rs | 843 +++++++++++++++++
9 files changed, 3507 insertions(+)
create mode 100644 rust/zerocopy-derive/derive/from_bytes.rs
create mode 100644 rust/zerocopy-derive/derive/into_bytes.rs
create mode 100644 rust/zerocopy-derive/derive/known_layout.rs
create mode 100644 rust/zerocopy-derive/derive/mod.rs
create mode 100644 rust/zerocopy-derive/derive/try_from_bytes.rs
create mode 100644 rust/zerocopy-derive/derive/unaligned.rs
create mode 100644 rust/zerocopy-derive/lib.rs
create mode 100644 rust/zerocopy-derive/repr.rs
create mode 100644 rust/zerocopy-derive/util.rs
diff --git a/rust/zerocopy-derive/derive/from_bytes.rs b/rust/zerocopy-derive/derive/from_bytes.rs
new file mode 100644
index 000000000000..ad8b6233fe54
--- /dev/null
+++ b/rust/zerocopy-derive/derive/from_bytes.rs
@@ -0,0 +1,190 @@
+use proc_macro2::{Span, TokenStream};
+use syn::{
+ parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp,
+ WherePredicate,
+};
+
+use crate::{
+ derive::try_from_bytes::derive_try_from_bytes,
+ repr::{CompoundRepr, EnumRepr, Repr, Spanned},
+ util::{enum_size_from_repr, Ctx, FieldBounds, ImplBlockBuilder, Trait, TraitBound},
+};
+/// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
+/// zero. If `Err(bool)` is returned, the boolean is true if the enum has
+/// unknown discriminants (e.g. discriminants set to const expressions which we
+/// can't evaluate in a proc macro). If the enum has unknown discriminants, then
+/// it might have a zero variant that we just can't detect.
+pub(crate) fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
+ // Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
+ // the discriminant type may be signed or unsigned. Since we only care about
+ // tracking the discriminant when it's less than or equal to zero, we can
+ // avoid u128 -> i128 conversions and bounds checking by making the "next
+ // discriminant" value implicitly negative.
+ // Technically 64 bits is enough, but 128 is better for future compatibility
+ // with https://github.com/rust-lang/rust/issues/56071
+ let mut next_negative_discriminant = Some(0);
+
+ // Sometimes we encounter explicit discriminants that we can't know the
+ // value of (e.g. a constant expression that requires evaluation). These
+ // could evaluate to zero or a negative number, but we can't assume that
+ // they do (no false positives allowed!). So we treat them like strictly-
+ // positive values that can't result in any zero variants, and track whether
+ // we've encountered any unknown discriminants.
+ let mut has_unknown_discriminants = false;
+
+ for (i, v) in enm.variants.iter().enumerate() {
+ match v.discriminant.as_ref() {
+ // Implicit discriminant
+ None => {
+ match next_negative_discriminant.as_mut() {
+ Some(0) => return Ok(i),
+ // n is nonzero so subtraction is always safe
+ Some(n) => *n -= 1,
+ None => (),
+ }
+ }
+ // Explicit positive discriminant
+ Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
+ match int.base10_parse::<u128>().ok() {
+ Some(0) => return Ok(i),
+ Some(_) => next_negative_discriminant = None,
+ None => {
+ // Numbers should never fail to parse, but just in case:
+ has_unknown_discriminants = true;
+ next_negative_discriminant = None;
+ }
+ }
+ }
+ // Explicit negative discriminant
+ Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
+ Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
+ match int.base10_parse::<u128>().ok() {
+ Some(0) => return Ok(i),
+ // x is nonzero so subtraction is always safe
+ Some(x) => next_negative_discriminant = Some(x - 1),
+ None => {
+ // Numbers should never fail to parse, but just in
+ // case:
+ has_unknown_discriminants = true;
+ next_negative_discriminant = None;
+ }
+ }
+ }
+ // Unknown negative discriminant (e.g. const repr)
+ _ => {
+ has_unknown_discriminants = true;
+ next_negative_discriminant = None;
+ }
+ },
+ // Unknown discriminant (e.g. const expr)
+ _ => {
+ has_unknown_discriminants = true;
+ next_negative_discriminant = None;
+ }
+ }
+ }
+
+ Err(has_unknown_discriminants)
+}
+pub(crate) fn derive_from_zeros(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
+ let try_from_bytes = derive_try_from_bytes(ctx, top_level)?;
+ let from_zeros = match &ctx.ast.data {
+ Data::Struct(strct) => derive_from_zeros_struct(ctx, strct),
+ Data::Enum(enm) => derive_from_zeros_enum(ctx, enm)?,
+ Data::Union(unn) => derive_from_zeros_union(ctx, unn),
+ };
+ Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
+}
+pub(crate) fn derive_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
+ let from_zeros = derive_from_zeros(ctx, top_level)?;
+ let from_bytes = match &ctx.ast.data {
+ Data::Struct(strct) => derive_from_bytes_struct(ctx, strct),
+ Data::Enum(enm) => derive_from_bytes_enum(ctx, enm)?,
+ Data::Union(unn) => derive_from_bytes_union(ctx, unn),
+ };
+
+ Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
+}
+fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
+ ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build()
+}
+fn derive_from_zeros_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
+ let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
+
+ // We don't actually care what the repr is; we just care that it's one of
+ // the allowed ones.
+ match repr {
+ Repr::Compound(Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ }, _) => {
+ }
+ Repr::Transparent(_) | Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => {
+ return ctx.error_or_skip(
+ Error::new(
+ Span::call_site(),
+ "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
+ ),
+ );
+ }
+ }
+
+ let zero_variant = match find_zero_variant(enm) {
+ Ok(index) => enm.variants.iter().nth(index).unwrap(),
+ // Has unknown variants
+ Err(true) => {
+ return ctx.error_or_skip(Error::new_spanned(
+ &ctx.ast,
+ "FromZeros only supported on enums with a variant that has a discriminant of `0`\n\
+ help: This enum has discriminants which are not literal integers. One of those may \
+ define or imply which variant has a discriminant of zero. Use a literal integer to \
+ define or imply the variant with a discriminant of zero.",
+ ));
+ }
+ // Does not have unknown variants
+ Err(false) => {
+ return ctx.error_or_skip(Error::new_spanned(
+ &ctx.ast,
+ "FromZeros only supported on enums with a variant that has a discriminant of `0`",
+ ));
+ }
+ };
+
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let explicit_bounds = zero_variant
+ .fields
+ .iter()
+ .map(|field| {
+ let ty = &field.ty;
+ parse_quote! { #ty: #zerocopy_crate::FromZeros }
+ })
+ .collect::<Vec<WherePredicate>>();
+
+ Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds))
+ .build())
+}
+fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
+ let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
+ ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build()
+}
+fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
+ ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build()
+}
+fn derive_from_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
+ let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
+
+ let variants_required = 1usize << enum_size_from_repr(&repr)?;
+ if enm.variants.len() != variants_required {
+ return ctx.error_or_skip(Error::new_spanned(
+ &ctx.ast,
+ format!(
+ "FromBytes only supported on {} enum with {} variants",
+ repr.repr_type_name(),
+ variants_required
+ ),
+ ));
+ }
+
+ Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromBytes, FieldBounds::ALL_SELF).build())
+}
+fn derive_from_bytes_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
+ let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
+ ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build()
+}
diff --git a/rust/zerocopy-derive/derive/into_bytes.rs b/rust/zerocopy-derive/derive/into_bytes.rs
new file mode 100644
index 000000000000..8c1e1009dd91
--- /dev/null
+++ b/rust/zerocopy-derive/derive/into_bytes.rs
@@ -0,0 +1,162 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type};
+
+use crate::{
+ repr::{EnumRepr, StructUnionRepr},
+ util::{
+ generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait,
+ TraitBound,
+ },
+};
+pub(crate) fn derive_into_bytes(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
+ match &ctx.ast.data {
+ Data::Struct(strct) => derive_into_bytes_struct(ctx, strct),
+ Data::Enum(enm) => derive_into_bytes_enum(ctx, enm),
+ Data::Union(unn) => derive_into_bytes_union(ctx, unn),
+ }
+}
+fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
+ let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
+
+ let is_transparent = repr.is_transparent();
+ let is_c = repr.is_c();
+ let is_packed_1 = repr.is_packed_1();
+ let num_fields = strct.fields().len();
+
+ let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 {
+ // No padding check needed.
+ // - repr(transparent): The layout and ABI of the whole struct is the
+ // same as its only non-ZST field (meaning there's no padding outside
+ // of that field) and we require that field to be `IntoBytes` (meaning
+ // there's no padding in that field).
+ // - repr(packed): Any inter-field padding bytes are removed, meaning
+ // that any padding bytes would need to come from the fields, all of
+ // which we require to be `IntoBytes` (meaning they don't have any
+ // padding). Note that this holds regardless of other `repr`
+ // attributes, including `repr(Rust)`. [1]
+ //
+ // [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-alignment-modifiers:
+ //
+ // An important consequence of these rules is that a type with
+ // `#[repr(packed(1))]`` (or `#[repr(packed)]``) will have no
+ // inter-field padding.
+ (None, false)
+ } else if is_c && !repr.is_align_gt_1() && num_fields <= 1 {
+ // No padding check needed. A repr(C) struct with zero or one field has
+ // no padding unless #[repr(align)] explicitly adds padding, which we
+ // check for in this branch's condition.
+ (None, false)
+ } else if ctx.ast.generics.params.is_empty() {
+ // Is the last field a syntactic slice, i.e., `[SomeType]`.
+ let is_syntactic_dst =
+ strct.fields().last().map(|(_, _, ty)| matches!(ty, Type::Slice(_))).unwrap_or(false);
+ // Since there are no generics, we can emit a padding check. All reprs
+ // guarantee that fields won't overlap [1], so the padding check is
+ // sound. This is more permissive than the next case, which requires
+ // that all field types implement `Unaligned`.
+ //
+ // [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-rust-representation:
+ //
+ // The only data layout guarantees made by [`repr(Rust)`] are those
+ // required for soundness. They are:
+ // ...
+ // 2. The fields do not overlap.
+ // ...
+ if is_c && is_syntactic_dst {
+ (Some(PaddingCheck::ReprCStruct), false)
+ } else {
+ (Some(PaddingCheck::Struct), false)
+ }
+ } else if is_c && !repr.is_align_gt_1() {
+ // We can't use a padding check since there are generic type arguments.
+ // Instead, we require all field types to implement `Unaligned`. This
+ // ensures that the `repr(C)` layout algorithm will not insert any
+ // padding unless #[repr(align)] explicitly adds padding, which we check
+ // for in this branch's condition.
+ //
+ // FIXME(#10): Support type parameters for non-transparent, non-packed
+ // structs without requiring `Unaligned`.
+ (None, true)
+ } else {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout",
+ ));
+ };
+
+ let field_bounds = if require_unaligned_fields {
+ FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)])
+ } else {
+ FieldBounds::ALL_SELF
+ };
+
+ Ok(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds)
+ .padding_check(padding_check)
+ .build())
+}
+
+fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
+ let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
+ if !repr.is_c() && !repr.is_primitive() {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
+ ));
+ }
+
+ let tag_type_definition = generate_tag_enum(ctx, &repr, enm);
+ Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF)
+ .padding_check(PaddingCheck::Enum { tag_type_definition })
+ .build())
+}
+
+fn derive_into_bytes_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
+ // See #1792 for more context.
+ //
+ // By checking for `zerocopy_derive_union_into_bytes` both here and in the
+ // generated code, we ensure that `--cfg zerocopy_derive_union_into_bytes`
+ // need only be passed *either* when compiling this crate *or* when
+ // compiling the user's crate. The former is preferable, but in some
+ // situations (such as when cross-compiling using `cargo build --target`),
+ // it doesn't get propagated to this crate's build by default.
+ let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) {
+ quote!()
+ } else {
+ let core = ctx.core_path();
+ let error_message = "requires --cfg zerocopy_derive_union_into_bytes;
+please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802";
+ quote!(
+ #[allow(unused_attributes, unexpected_cfgs)]
+ const _: () = {
+ #[cfg(not(zerocopy_derive_union_into_bytes))]
+ #core::compile_error!(#error_message);
+ };
+ )
+ };
+
+ // FIXME(#10): Support type parameters.
+ if !ctx.ast.generics.params.is_empty() {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "unsupported on types with type parameters",
+ ));
+ }
+
+ // Because we don't support generics, we don't need to worry about
+ // special-casing different reprs. So long as there is *some* repr which
+ // guarantees the layout, our `PaddingCheck::Union` guarantees that there is
+ // no padding.
+ let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
+ if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
+ ));
+ }
+
+ let impl_block = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF)
+ .padding_check(PaddingCheck::Union)
+ .build();
+ Ok(quote!(#cfg_compile_error #impl_block))
+}
diff --git a/rust/zerocopy-derive/derive/known_layout.rs b/rust/zerocopy-derive/derive/known_layout.rs
new file mode 100644
index 000000000000..b91b4de0098c
--- /dev/null
+++ b/rust/zerocopy-derive/derive/known_layout.rs
@@ -0,0 +1,348 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{parse_quote, Data, Error, Type};
+
+use crate::{
+ repr::StructUnionRepr,
+ util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, SelfBounds, Trait},
+};
+
+fn derive_known_layout_for_repr_c_struct<'a>(
+ ctx: &'a Ctx,
+ repr: &StructUnionRepr,
+ fields: &[(&'a syn::Visibility, TokenStream, &'a Type)],
+) -> Option<(SelfBounds<'a>, TokenStream, Option<TokenStream>)> {
+ let (trailing_field, leading_fields) = fields.split_last()?;
+
+ let (_vis, trailing_field_name, trailing_field_ty) = trailing_field;
+ let leading_fields_tys = leading_fields.iter().map(|(_vis, _name, ty)| ty);
+
+ let core = ctx.core_path();
+ let repr_align = repr
+ .get_align()
+ .map(|align| {
+ let align = align.t.get();
+ quote!(#core::num::NonZeroUsize::new(#align as usize))
+ })
+ .unwrap_or_else(|| quote!(#core::option::Option::None));
+ let repr_packed = repr
+ .get_packed()
+ .map(|packed| {
+ let packed = packed.get();
+ quote!(#core::num::NonZeroUsize::new(#packed as usize))
+ })
+ .unwrap_or_else(|| quote!(#core::option::Option::None));
+
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let make_methods = |trailing_field_ty| {
+ quote! {
+ // SAFETY:
+ // - The returned pointer has the same address and provenance as
+ // `bytes`:
+ // - The recursive call to `raw_from_ptr_len` preserves both
+ // address and provenance.
+ // - The `as` cast preserves both address and provenance.
+ // - `NonNull::new_unchecked` preserves both address and
+ // provenance.
+ // - If `Self` is a slice DST, the returned pointer encodes
+ // `elems` elements in the trailing slice:
+ // - This is true of the recursive call to `raw_from_ptr_len`.
+ // - `trailing.as_ptr() as *mut Self` preserves trailing slice
+ // element count [1].
+ // - `NonNull::new_unchecked` preserves trailing slice element
+ // count.
+ //
+ // [1] Per https://doc.rust-lang.org/reference/expressions/operator-expr.html#pointer-to-pointer-cast:
+ //
+ // `*const T`` / `*mut T` can be cast to `*const U` / `*mut U`
+ // with the following behavior:
+ // ...
+ // - If `T` and `U` are both unsized, the pointer is also
+ // returned unchanged. In particular, the metadata is
+ // preserved exactly.
+ //
+ // For instance, a cast from `*const [T]` to `*const [U]`
+ // preserves the number of elements. ... The same holds
+ // for str and any compound type whose unsized tail is a
+ // slice type, such as struct `Foo(i32, [u8])` or
+ // `(u64, Foo)`.
+ #[inline(always)]
+ fn raw_from_ptr_len(
+ bytes: #core::ptr::NonNull<u8>,
+ meta: <Self as #zerocopy_crate::KnownLayout>::PointerMetadata,
+ ) -> #core::ptr::NonNull<Self> {
+ let trailing = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::raw_from_ptr_len(bytes, meta);
+ let slf = trailing.as_ptr() as *mut Self;
+ // SAFETY: Constructed from `trailing`, which is non-null.
+ unsafe { #core::ptr::NonNull::new_unchecked(slf) }
+ }
+
+ #[inline(always)]
+ fn pointer_to_metadata(ptr: *mut Self) -> <Self as #zerocopy_crate::KnownLayout>::PointerMetadata {
+ <#trailing_field_ty>::pointer_to_metadata(ptr as *mut _)
+ }
+ }
+ };
+
+ let inner_extras = {
+ let leading_fields_tys = leading_fields_tys.clone();
+ let methods = make_methods(*trailing_field_ty);
+ let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
+
+ quote!(
+ type PointerMetadata = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::PointerMetadata;
+
+ type MaybeUninit = __ZerocopyKnownLayoutMaybeUninit #ty_generics;
+
+ // SAFETY: `LAYOUT` accurately describes the layout of `Self`.
+ // The documentation of `DstLayout::for_repr_c_struct` vows that
+ // invocations in this manner will accurately describe a type,
+ // so long as:
+ //
+ // - that type is `repr(C)`,
+ // - its fields are enumerated in the order they appear,
+ // - the presence of `repr_align` and `repr_packed` are
+ // correctly accounted for.
+ //
+ // We respect all three of these preconditions here. This
+ // expansion is only used if `is_repr_c_struct`, we enumerate
+ // the fields in order, and we extract the values of `align(N)`
+ // and `packed(N)`.
+ const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_repr_c_struct(
+ #repr_align,
+ #repr_packed,
+ &[
+ #(#zerocopy_crate::DstLayout::for_type::<#leading_fields_tys>(),)*
+ <#trailing_field_ty as #zerocopy_crate::KnownLayout>::LAYOUT
+ ],
+ );
+
+ #methods
+ )
+ };
+
+ let outer_extras = {
+ let ident = &ctx.ast.ident;
+ let vis = &ctx.ast.vis;
+ let params = &ctx.ast.generics.params;
+ let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
+
+ let predicates = if let Some(where_clause) = where_clause {
+ where_clause.predicates.clone()
+ } else {
+ Default::default()
+ };
+
+ // Generate a valid ident for a type-level handle to a field of a
+ // given `name`.
+ let field_index = |name: &TokenStream| ident!(("__Zerocopy_Field_{}", name), ident.span());
+
+ let field_indices: Vec<_> =
+ fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();
+
+ // Define the collection of type-level field handles.
+ let field_defs = field_indices.iter().zip(fields).map(|(idx, (vis, _, _))| {
+ quote! {
+ #vis struct #idx;
+ }
+ });
+
+ let field_impls = field_indices.iter().zip(fields).map(|(idx, (_, _, ty))| quote! {
+ // SAFETY: `#ty` is the type of `#ident`'s field at `#idx`.
+ //
+ // We implement `Field` for each field of the struct to create a
+ // projection from the field index to its type. This allows us
+ // to refer to the field's type in a way that respects `Self`
+ // hygiene. If we just copy-pasted the tokens of `#ty`, we
+ // would not respect `Self` hygiene, as `Self` would refer to
+ // the helper struct we are generating, not the derive target
+ // type.
+ unsafe impl #impl_generics #zerocopy_crate::util::macro_util::Field<#idx> for #ident #ty_generics
+ where
+ #predicates
+ {
+ type Type = #ty;
+ }
+ });
+
+ let trailing_field_index = field_index(trailing_field_name);
+ let leading_field_indices =
+ leading_fields.iter().map(|(_vis, name, _ty)| field_index(name));
+
+ // We use `Field` to project the type of the trailing field. This is
+ // required to ensure that if the field type uses `Self`, it
+ // resolves to the derive target type, not the helper struct we are
+ // generating.
+ let trailing_field_ty = quote! {
+ <#ident #ty_generics as
+ #zerocopy_crate::util::macro_util::Field<#trailing_field_index>
+ >::Type
+ };
+
+ let methods = make_methods(&parse_quote! {
+ <#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
+ });
+
+ let core = ctx.core_path();
+
+ quote! {
+ #(#field_defs)*
+
+ #(#field_impls)*
+
+ // SAFETY: This has the same layout as the derive target type,
+ // except that it admits uninit bytes. This is ensured by using
+ // the same repr as the target type, and by using field types
+ // which have the same layout as the target type's fields,
+ // except that they admit uninit bytes. We indirect through
+ // `Field` to ensure that occurrences of `Self` resolve to
+ // `#ty`, not `__ZerocopyKnownLayoutMaybeUninit` (see #2116).
+ #repr
+ #[doc(hidden)]
+ #vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
+ #(#core::mem::MaybeUninit<
+ <#ident #ty_generics as
+ #zerocopy_crate::util::macro_util::Field<#leading_field_indices>
+ >::Type
+ >,)*
+ // NOTE(#2302): We wrap in `ManuallyDrop` here in case the
+ // type we're operating on is both generic and
+ // `repr(packed)`. In that case, Rust needs to know that the
+ // type is *either* `Sized` or has a trivial `Drop`.
+ // `ManuallyDrop` has a trivial `Drop`, and so satisfies
+ // this requirement.
+ #core::mem::ManuallyDrop<
+ <#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
+ >
+ )
+ where
+ #trailing_field_ty: #zerocopy_crate::KnownLayout,
+ #predicates;
+
+ // SAFETY: We largely defer to the `KnownLayout` implementation
+ // on the derive target type (both by using the same tokens, and
+ // by deferring to impl via type-level indirection). This is
+ // sound, since `__ZerocopyKnownLayoutMaybeUninit` is guaranteed
+ // to have the same layout as the derive target type, except
+ // that `__ZerocopyKnownLayoutMaybeUninit` admits uninit bytes.
+ unsafe impl #impl_generics #zerocopy_crate::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
+ where
+ #trailing_field_ty: #zerocopy_crate::KnownLayout,
+ #predicates
+ {
+ fn only_derive_is_allowed_to_implement_this_trait() {}
+
+ type PointerMetadata = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::PointerMetadata;
+
+ type MaybeUninit = Self;
+
+ const LAYOUT: #zerocopy_crate::DstLayout = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::LAYOUT;
+
+ #methods
+ }
+ }
+ };
+
+ Some((SelfBounds::None, inner_extras, Some(outer_extras)))
+}
+
+pub(crate) fn derive(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
+ // If this is a `repr(C)` struct, then `c_struct_repr` contains the entire
+ // `repr` attribute.
+ let c_struct_repr = match &ctx.ast.data {
+ Data::Struct(..) => {
+ let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
+ if repr.is_c() {
+ Some(repr)
+ } else {
+ None
+ }
+ }
+ Data::Enum(..) | Data::Union(..) => None,
+ };
+
+ let fields = ctx.ast.data.fields();
+
+ let (self_bounds, inner_extras, outer_extras) = c_struct_repr
+ .as_ref()
+ .and_then(|repr| {
+ derive_known_layout_for_repr_c_struct(ctx, repr, &fields)
+ })
+ .unwrap_or_else(|| {
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let core = ctx.core_path();
+
+ // For enums, unions, and non-`repr(C)` structs, we require that
+ // `Self` is sized, and as a result don't need to reason about the
+ // internals of the type.
+ (
+ SelfBounds::SIZED,
+ quote!(
+ type PointerMetadata = ();
+ type MaybeUninit =
+ #core::mem::MaybeUninit<Self>;
+
+ // SAFETY: `LAYOUT` is guaranteed to accurately describe the
+ // layout of `Self`, because that is the documented safety
+ // contract of `DstLayout::for_type`.
+ const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_type::<Self>();
+
+ // SAFETY: `.cast` preserves address and provenance.
+ //
+ // FIXME(#429): Add documentation to `.cast` that promises that
+ // it preserves provenance.
+ #[inline(always)]
+ fn raw_from_ptr_len(bytes: #core::ptr::NonNull<u8>, _meta: ()) -> #core::ptr::NonNull<Self> {
+ bytes.cast::<Self>()
+ }
+
+ #[inline(always)]
+ fn pointer_to_metadata(_ptr: *mut Self) -> () {}
+ ),
+ None,
+ )
+ });
+ Ok(match &ctx.ast.data {
+ Data::Struct(strct) => {
+ let require_trait_bound_on_field_types =
+ if matches!(self_bounds, SelfBounds::All(&[Trait::Sized])) {
+ FieldBounds::None
+ } else {
+ FieldBounds::TRAILING_SELF
+ };
+
+ // A bound on the trailing field is required, since structs are
+ // unsized if their trailing field is unsized. Reflecting the layout
+ // of an usized trailing field requires that the field is
+ // `KnownLayout`.
+ ImplBlockBuilder::new(
+ ctx,
+ strct,
+ Trait::KnownLayout,
+ require_trait_bound_on_field_types,
+ )
+ .self_type_trait_bounds(self_bounds)
+ .inner_extras(inner_extras)
+ .outer_extras(outer_extras)
+ .build()
+ }
+ Data::Enum(enm) => {
+ // A bound on the trailing field is not required, since enums cannot
+ // currently be unsized.
+ ImplBlockBuilder::new(ctx, enm, Trait::KnownLayout, FieldBounds::None)
+ .self_type_trait_bounds(SelfBounds::SIZED)
+ .inner_extras(inner_extras)
+ .outer_extras(outer_extras)
+ .build()
+ }
+ Data::Union(unn) => {
+ // A bound on the trailing field is not required, since unions
+ // cannot currently be unsized.
+ ImplBlockBuilder::new(ctx, unn, Trait::KnownLayout, FieldBounds::None)
+ .self_type_trait_bounds(SelfBounds::SIZED)
+ .inner_extras(inner_extras)
+ .outer_extras(outer_extras)
+ .build()
+ }
+ })
+}
diff --git a/rust/zerocopy-derive/derive/mod.rs b/rust/zerocopy-derive/derive/mod.rs
new file mode 100644
index 000000000000..a3d066ed2b4d
--- /dev/null
+++ b/rust/zerocopy-derive/derive/mod.rs
@@ -0,0 +1,130 @@
+pub mod from_bytes;
+pub mod into_bytes;
+pub mod known_layout;
+pub mod try_from_bytes;
+pub mod unaligned;
+
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::{Data, Error};
+
+use crate::{
+ repr::StructUnionRepr,
+ util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
+};
+
+pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream {
+ match &ctx.ast.data {
+ Data::Struct(strct) => {
+ ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
+ }
+ Data::Enum(enm) => {
+ ImplBlockBuilder::new(ctx, enm, Trait::Immutable, FieldBounds::ALL_SELF).build()
+ }
+ Data::Union(unn) => {
+ ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
+ }
+ }
+}
+
+pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
+ // This doesn't delegate to `impl_block` because `impl_block` assumes it is
+ // deriving a `zerocopy`-defined trait, and these trait impls share a common
+ // shape that `Hash` does not. In particular, `zerocopy` traits contain a
+ // method that only `zerocopy_derive` macros are supposed to implement, and
+ // `impl_block` generating this trait method is incompatible with `Hash`.
+ let type_ident = &ctx.ast.ident;
+ let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
+ let where_predicates = where_clause.map(|clause| &clause.predicates);
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let core = ctx.core_path();
+ Ok(quote! {
+ impl #impl_generics #core::hash::Hash for #type_ident #ty_generics
+ where
+ Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
+ #where_predicates
+ {
+ fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
+ #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self))
+ }
+
+ fn hash_slice<H: #core::hash::Hasher>(data: &[Self], state: &mut H) {
+ #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data))
+ }
+ }
+ })
+}
+
+pub(crate) fn derive_eq(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
+ // This doesn't delegate to `impl_block` because `impl_block` assumes it is
+ // deriving a `zerocopy`-defined trait, and these trait impls share a common
+ // shape that `Eq` does not. In particular, `zerocopy` traits contain a
+ // method that only `zerocopy_derive` macros are supposed to implement, and
+ // `impl_block` generating this trait method is incompatible with `Eq`.
+ let type_ident = &ctx.ast.ident;
+ let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
+ let where_predicates = where_clause.map(|clause| &clause.predicates);
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let core = ctx.core_path();
+ Ok(quote! {
+ impl #impl_generics #core::cmp::PartialEq for #type_ident #ty_generics
+ where
+ Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
+ #where_predicates
+ {
+ fn eq(&self, other: &Self) -> bool {
+ #core::cmp::PartialEq::eq(
+ #zerocopy_crate::IntoBytes::as_bytes(self),
+ #zerocopy_crate::IntoBytes::as_bytes(other),
+ )
+ }
+ }
+
+ impl #impl_generics #core::cmp::Eq for #type_ident #ty_generics
+ where
+ Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
+ #where_predicates
+ {
+ }
+ })
+}
+
+pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
+ let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
+
+ match &ctx.ast.data {
+ Data::Struct(_) => {}
+ Data::Enum(_) | Data::Union(_) => {
+ return Err(Error::new(Span::call_site(), "can only be applied to structs"));
+ }
+ };
+
+ if repr.get_packed().is_some() {
+ return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute"));
+ }
+
+ if !(repr.is_c() || repr.is_transparent()) {
+ return Err(Error::new(
+ Span::call_site(),
+ "must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable",
+ ));
+ }
+
+ let fields = ctx.ast.data.fields();
+ let trailing_field = if let Some(((_, _, trailing_field), _)) = fields.split_last() {
+ trailing_field
+ } else {
+ return Err(Error::new(Span::call_site(), "must at least one field"));
+ };
+
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ // SAFETY: `#ty`, per the above checks, is `repr(C)` or `repr(transparent)`
+ // and is not packed; its trailing field is guaranteed to be well-aligned
+ // for its type. By invariant on `FieldBounds::TRAILING_SELF`, the trailing
+ // slice of the trailing field is also well-aligned for its type.
+ Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::SplitAt, FieldBounds::TRAILING_SELF)
+ .inner_extras(quote! {
+ type Elem = <#trailing_field as #zerocopy_crate::SplitAt>::Elem;
+ })
+ .build())
+}
diff --git a/rust/zerocopy-derive/derive/try_from_bytes.rs b/rust/zerocopy-derive/derive/try_from_bytes.rs
new file mode 100644
index 000000000000..4e36ab40bcf8
--- /dev/null
+++ b/rust/zerocopy-derive/derive/try_from_bytes.rs
@@ -0,0 +1,763 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{
+ parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
+ Expr, Fields, Ident, Index, Type,
+};
+
+use crate::{
+ repr::{EnumRepr, StructUnionRepr},
+ util::{
+ const_block, enum_size_from_repr, generate_tag_enum, Ctx, DataExt, FieldBounds,
+ ImplBlockBuilder, Trait, TraitBound,
+ },
+};
+fn tag_ident(variant_ident: &Ident) -> Ident {
+ ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
+}
+
+/// Generates a constant for the tag associated with each variant of the enum.
+/// When we match on the enum's tag, each arm matches one of these constants. We
+/// have to use constants here because:
+///
+/// - The type that we're matching on is not the type of the tag, it's an
+/// integer of the same size as the tag type and with the same bit patterns.
+/// - We can't read the enum tag as an enum because the bytes may not represent
+/// a valid variant.
+/// - Patterns do not currently support const expressions, so we have to assign
+/// these constants to names rather than use them inline in the `match`
+/// statement.
+fn generate_tag_consts(data: &DataEnum) -> TokenStream {
+ let tags = data.variants.iter().map(|v| {
+ let variant_ident = &v.ident;
+ let tag_ident = tag_ident(variant_ident);
+
+ quote! {
+ // This casts the enum variant to its discriminant, and then
+ // converts the discriminant to the target integral type via a
+ // numeric cast [1].
+ //
+ // Because these are the same size, this is defined to be a no-op
+ // and therefore is a lossless conversion [2].
+ //
+ // [1] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#enum-cast:
+ //
+ // Casts an enum to its discriminant.
+ //
+ // [2] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#numeric-cast:
+ //
+ // Casting between two integers of the same size (e.g. i32 -> u32)
+ // is a no-op.
+ const #tag_ident: ___ZerocopyTagPrimitive =
+ ___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
+ }
+ });
+
+ quote! {
+ #(#tags)*
+ }
+}
+
+fn variant_struct_ident(variant_ident: &Ident) -> Ident {
+ ident!(("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
+}
+
+/// Generates variant structs for the given enum variant.
+///
+/// These are structs associated with each variant of an enum. They are
+/// `repr(C)` tuple structs with the same fields as the variant after a
+/// `MaybeUninit<___ZerocopyInnerTag>`.
+///
+/// In order to unify the generated types for `repr(C)` and `repr(int)` enums,
+/// we use a "fused" representation with fields for both an inner tag and an
+/// outer tag. Depending on the repr, we will set one of these tags to the tag
+/// type and the other to `()`. This lets us generate the same code but put the
+/// tags in different locations.
+fn generate_variant_structs(ctx: &Ctx, data: &DataEnum) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
+
+ let enum_name = &ctx.ast.ident;
+
+ // All variant structs have a `PhantomData<MyEnum<...>>` field because we
+ // don't know which generic parameters each variant will use, and unused
+ // generic parameters are a compile error.
+ let core = ctx.core_path();
+ let phantom_ty = quote! {
+ #core::marker::PhantomData<#enum_name #ty_generics>
+ };
+
+ let variant_structs = data.variants.iter().filter_map(|variant| {
+ // We don't generate variant structs for unit variants because we only
+ // need to check the tag. This helps cut down our generated code a bit.
+ if matches!(variant.fields, Fields::Unit) {
+ return None;
+ }
+
+ let variant_struct_ident = variant_struct_ident(&variant.ident);
+ let field_types = variant.fields.iter().map(|f| &f.ty);
+
+ let variant_struct = parse_quote! {
+ #[repr(C)]
+ struct #variant_struct_ident #impl_generics (
+ #core::mem::MaybeUninit<___ZerocopyInnerTag>,
+ #(#field_types,)*
+ #phantom_ty,
+ ) #where_clause;
+ };
+
+ // We do this rather than emitting `#[derive(::zerocopy::TryFromBytes)]`
+ // because that is not hygienic, and this is also more performant.
+ let try_from_bytes_impl =
+ derive_try_from_bytes(&ctx.with_input(&variant_struct), Trait::TryFromBytes)
+ .expect("derive_try_from_bytes should not fail on synthesized type");
+
+ Some(quote! {
+ #variant_struct
+ #try_from_bytes_impl
+ })
+ });
+
+ quote! {
+ #(#variant_structs)*
+ }
+}
+
+fn variants_union_field_ident(ident: &Ident) -> Ident {
+ // Field names are prefixed with `__field_` to prevent name collision
+ // with the `__nonempty` field.
+ ident!(("__field_{}", ident), ident.span())
+}
+
+fn generate_variants_union(ctx: &Ctx, data: &DataEnum) -> TokenStream {
+ let generics = &ctx.ast.generics;
+ let (_, ty_generics, _) = generics.split_for_impl();
+
+ let fields = data.variants.iter().filter_map(|variant| {
+ // We don't generate variant structs for unit variants because we only
+ // need to check the tag. This helps cut down our generated code a bit.
+ if matches!(variant.fields, Fields::Unit) {
+ return None;
+ }
+
+ let field_name = variants_union_field_ident(&variant.ident);
+ let variant_struct_ident = variant_struct_ident(&variant.ident);
+
+ let core = ctx.core_path();
+ Some(quote! {
+ #field_name: #core::mem::ManuallyDrop<#variant_struct_ident #ty_generics>,
+ })
+ });
+
+ let variants_union = parse_quote! {
+ #[repr(C)]
+ union ___ZerocopyVariants #generics {
+ #(#fields)*
+ // Enums can have variants with no fields, but unions must
+ // have at least one field. So we just add a trailing unit
+ // to ensure that this union always has at least one field.
+ // Because this union is `repr(C)`, this unit type does not
+ // affect the layout.
+ __nonempty: (),
+ }
+ };
+
+ let has_field =
+ derive_has_field_struct_union(&ctx.with_input(&variants_union), &variants_union.data);
+
+ quote! {
+ #variants_union
+ #has_field
+ }
+}
+
+/// Generates an implementation of `is_bit_valid` for an arbitrary enum.
+///
+/// The general process is:
+///
+/// 1. Generate a tag enum. This is an enum with the same repr, variants, and
+/// corresponding discriminants as the original enum, but without any fields
+/// on the variants. This gives us access to an enum where the variants have
+/// the same discriminants as the one we're writing `is_bit_valid` for.
+/// 2. Make constants from the variants of the tag enum. We need these because
+/// we can't put const exprs in match arms.
+/// 3. Generate variant structs. These are structs which have the same fields as
+/// each variant of the enum, and are `#[repr(C)]` with an optional "inner
+/// tag".
+/// 4. Generate a variants union, with one field for each variant struct type.
+/// 5. And finally, our raw enum is a `#[repr(C)]` struct of an "outer tag" and
+/// the variants union.
+///
+/// See these reference links for fully-worked example decompositions.
+///
+/// - `repr(C)`: <https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields>
+/// - `repr(int)`: <https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields>
+/// - `repr(C, int)`: <https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc>
+pub(crate) fn derive_is_bit_valid(
+ ctx: &Ctx,
+ data: &DataEnum,
+ repr: &EnumRepr,
+) -> Result<TokenStream, Error> {
+ let trait_path = Trait::TryFromBytes.crate_path(ctx);
+ let tag_enum = generate_tag_enum(ctx, repr, data);
+ let tag_consts = generate_tag_consts(data);
+
+ let (outer_tag_type, inner_tag_type) = if repr.is_c() {
+ (quote! { ___ZerocopyTag }, quote! { () })
+ } else if repr.is_primitive() {
+ (quote! { () }, quote! { ___ZerocopyTag })
+ } else {
+ return Err(Error::new(
+ ctx.ast.span(),
+ "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
+ ));
+ };
+
+ let variant_structs = generate_variant_structs(ctx, data);
+ let variants_union = generate_variants_union(ctx, data);
+
+ let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
+
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
+ .inner_extras(quote! {
+ type Tag = ___ZerocopyTag;
+ type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
+ })
+ .build();
+ let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
+ let variant_ident = &variant.unwrap().ident;
+ let variants_union_field_ident = variants_union_field_ident(variant_ident);
+ let field: Box<syn::Type> = parse_quote!(());
+ fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
+ // Rust does not presently support explicit visibility modifiers on
+ // enum fields, but we guard against the possibility to ensure this
+ // derive remains sound.
+ assert!(matches!(vis, syn::Visibility::Inherited));
+ let variant_struct_field_index = Index::from(idx + 1);
+ let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
+ let has_field_trait = Trait::HasField {
+ variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
+ // Since Rust does not presently support explicit visibility
+ // modifiers on enum fields, any public type is suitable here;
+ // we use `()`.
+ field: field.clone(),
+ field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
+ };
+ let has_field_path = has_field_trait.crate_path(ctx);
+ let has_field = ImplBlockBuilder::new(
+ ctx,
+ data,
+ has_field_trait,
+ FieldBounds::None,
+ )
+ .inner_extras(quote! {
+ type Type = #ty;
+
+ #[inline(always)]
+ fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
+ use #zerocopy_crate::pointer::cast::{CastSized, Projection};
+
+ slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
+ .project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
+ .project::<_, Projection<_, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>>()
+ .project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
+ .project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
+ .as_ptr()
+ }
+ })
+ .build();
+
+ let project = ImplBlockBuilder::new(
+ ctx,
+ data,
+ Trait::ProjectField {
+ variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
+ // Since Rust does not presently support explicit visibility
+ // modifiers on enum fields, any public type is suitable
+ // here; we use `()`.
+ field: field.clone(),
+ field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
+ invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
+ },
+ FieldBounds::None,
+ )
+ .param_extras(vec![
+ parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
+ parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
+ ])
+ .inner_extras(quote! {
+ type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
+ type Invariants = (Aliasing, Alignment, #zerocopy_crate::invariant::Initialized);
+ })
+ .build();
+
+ quote! {
+ #has_field
+ #project
+ }
+ })
+ });
+
+ let core = ctx.core_path();
+ let match_arms = data.variants.iter().map(|variant| {
+ let tag_ident = tag_ident(&variant.ident);
+ let variant_struct_ident = variant_struct_ident(&variant.ident);
+ let variants_union_field_ident = variants_union_field_ident(&variant.ident);
+
+ if matches!(variant.fields, Fields::Unit) {
+ // Unit variants don't need any further validation beyond checking
+ // the tag.
+ quote! {
+ #tag_ident => true
+ }
+ } else {
+ quote! {
+ #tag_ident => {
+ // SAFETY: Since we know that the tag is `#tag_ident`, we
+ // know that no other `&`s exist which refer to this enum
+ // as any other variant.
+ let variant_md = variants.cast::<
+ _,
+ #zerocopy_crate::pointer::cast::Projection<
+ // #zerocopy_crate::ReadOnly<_>,
+ _,
+ { #zerocopy_crate::REPR_C_UNION_VARIANT_ID },
+ { #zerocopy_crate::ident_id!(#variants_union_field_ident) }
+ >,
+ _
+ >();
+ let variant = variant_md.cast::<
+ #zerocopy_crate::ReadOnly<#variant_struct_ident #ty_generics>,
+ #zerocopy_crate::pointer::cast::CastSized,
+ (#zerocopy_crate::pointer::BecauseRead, _)
+ >();
+ <
+ #variant_struct_ident #ty_generics as #trait_path
+ >::is_bit_valid(variant)
+ }
+ }
+ }
+ });
+
+ let generics = &ctx.ast.generics;
+ let raw_enum: DeriveInput = parse_quote! {
+ #[repr(C)]
+ struct ___ZerocopyRawEnum #generics {
+ tag: ___ZerocopyOuterTag,
+ variants: ___ZerocopyVariants #ty_generics,
+ }
+ };
+
+ let self_ident = &ctx.ast.ident;
+ let invariants_eq_impl = quote! {
+ // SAFETY: `___ZerocopyRawEnum` is designed to have the same layout,
+ // validity, and invariants as `Self`.
+ unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
+ };
+
+ let raw_enum_projections =
+ derive_has_field_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data);
+
+ let raw_enum = quote! {
+ #raw_enum
+ #invariants_eq_impl
+ #raw_enum_projections
+ };
+
+ Ok(quote! {
+ // SAFETY: We use `is_bit_valid` to validate that the bit pattern of the
+ // enum's tag corresponds to one of the enum's discriminants. Then, we
+ // check the bit validity of each field of the corresponding variant.
+ // Thus, this is a sound implementation of `is_bit_valid`.
+ #[inline]
+ fn is_bit_valid<___ZcAlignment>(
+ mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
+ ) -> #core::primitive::bool
+ where
+ ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
+ {
+ #tag_enum
+
+ type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
+ { #core::mem::size_of::<___ZerocopyTag>() },
+ >;
+
+ #tag_consts
+
+ type ___ZerocopyOuterTag = #outer_tag_type;
+ type ___ZerocopyInnerTag = #inner_tag_type;
+
+ #variant_structs
+
+ #variants_union
+
+ #raw_enum
+
+ #has_tag
+
+ #(#has_fields)*
+
+ let tag = {
+ // SAFETY:
+ // - The provided cast addresses a subset of the bytes addressed
+ // by `candidate` because it addresses the starting tag of the
+ // enum.
+ // - Because the pointer is cast from `candidate`, it has the
+ // same provenance as it.
+ // - There are no `UnsafeCell`s in the tag because it is a
+ // primitive integer.
+ // - `tag_ptr` is casted from `candidate`, whose referent is
+ // `Initialized`. Since we have not written uninitialized
+ // bytes into the referent, `tag_ptr` is also `Initialized`.
+ //
+ // FIXME(#2874): Revise this to a `cast` once `candidate`
+ // references a `ReadOnly<Self>`.
+ let tag_ptr = unsafe {
+ candidate.reborrow().project_transmute_unchecked::<
+ _,
+ #zerocopy_crate::invariant::Initialized,
+ #zerocopy_crate::pointer::cast::CastSized
+ >()
+ };
+ tag_ptr.recall_validity::<_, (_, (_, _))>().read::<#zerocopy_crate::BecauseImmutable>()
+ };
+
+ let mut raw_enum = candidate.cast::<
+ #zerocopy_crate::ReadOnly<___ZerocopyRawEnum #ty_generics>,
+ #zerocopy_crate::pointer::cast::CastSized,
+ (#zerocopy_crate::pointer::BecauseRead, _)
+ >();
+
+ let variants = #zerocopy_crate::into_inner!(raw_enum.project::<
+ _,
+ { #zerocopy_crate::STRUCT_VARIANT_ID },
+ { #zerocopy_crate::ident_id!(variants) }
+ >());
+
+ match tag {
+ #(#match_arms,)*
+ _ => false,
+ }
+ }
+ })
+}
+pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
+ match &ctx.ast.data {
+ Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level),
+ Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level),
+ Data::Union(unn) => Ok(derive_try_from_bytes_union(ctx, unn, top_level)),
+ }
+}
+fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
+ let fields = ctx.ast.data.fields();
+ if fields.is_empty() {
+ return quote! {};
+ }
+
+ let field_tokens = fields.iter().map(|(vis, ident, _)| {
+ let ident = ident!(("ẕ{}", ident), ident.span());
+ quote!(
+ #vis enum #ident {}
+ )
+ });
+
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let variant_id: Box<Expr> = match &ctx.ast.data {
+ Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
+ Data::Union(_) => {
+ let is_repr_c = StructUnionRepr::from_attrs(&ctx.ast.attrs)
+ .map(|repr| repr.is_c())
+ .unwrap_or(false);
+ if is_repr_c {
+ parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
+ } else {
+ parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
+ }
+ }
+ _ => unreachable!(),
+ };
+
+ let core = ctx.core_path();
+ let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
+ .inner_extras(quote! {
+ type Tag = ();
+ type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
+ })
+ .build();
+ let has_fields = fields.iter().map(move |(_, ident, ty)| {
+ let field_token = ident!(("ẕ{}", ident), ident.span());
+ let field: Box<Type> = parse_quote!(#field_token);
+ let field_id: Box<Expr> = parse_quote!({ #zerocopy_crate::ident_id!(#ident) });
+ let has_field_trait = Trait::HasField {
+ variant_id: variant_id.clone(),
+ field: field.clone(),
+ field_id: field_id.clone(),
+ };
+ let has_field_path = has_field_trait.crate_path(ctx);
+ ImplBlockBuilder::new(
+ ctx,
+ data,
+ has_field_trait,
+ FieldBounds::None,
+ )
+ .inner_extras(quote! {
+ type Type = #ty;
+
+ #[inline(always)]
+ fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
+ let slf = slf.as_ptr();
+ // SAFETY: By invariant on `PtrInner`, `slf` is a non-null
+ // pointer whose referent is zero-sized or lives in a valid
+ // allocation. Since `#ident` is a struct or union field of
+ // `Self`, this projection preserves or shrinks the referent
+ // size, and so the resulting referent also fits in the same
+ // allocation.
+ unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
+ }
+ }).outer_extras(if matches!(&ctx.ast.data, Data::Struct(..)) {
+ let fields_preserve_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
+ .map(|repr| repr.get_packed().is_none())
+ .unwrap();
+ let alignment = if fields_preserve_alignment {
+ quote! { Alignment }
+ } else {
+ quote! { #zerocopy_crate::invariant::Unaligned }
+ };
+ // SAFETY: See comments on items.
+ ImplBlockBuilder::new(
+ ctx,
+ data,
+ Trait::ProjectField {
+ variant_id: variant_id.clone(),
+ field: field.clone(),
+ field_id: field_id.clone(),
+ invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
+ },
+ FieldBounds::None,
+ )
+ .param_extras(vec![
+ parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
+ parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
+ ])
+ .inner_extras(quote! {
+ // SAFETY: Projection into structs is always infallible.
+ type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
+ // SAFETY: The alignment of the projected `Ptr` is `Unaligned`
+ // if the structure is packed; otherwise inherited from the
+ // outer `Ptr`. If the validity of the outer pointer is
+ // `Initialized`, so too is the validity of its fields.
+ type Invariants = (Aliasing, #alignment, #zerocopy_crate::invariant::Initialized);
+ })
+ .build()
+ } else {
+ quote! {}
+ })
+ .build()
+ });
+
+ const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
+}
+fn derive_try_from_bytes_struct(
+ ctx: &Ctx,
+ strct: &DataStruct,
+ top_level: Trait,
+) -> Result<TokenStream, Error> {
+ let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let fields = strct.fields();
+ let field_names = fields.iter().map(|(_vis, name, _ty)| name);
+ let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
+ let core = ctx.core_path();
+ quote!(
+ // SAFETY: We use `is_bit_valid` to validate that each field is
+ // bit-valid, and only return `true` if all of them are. The bit
+ // validity of a struct is just the composition of the bit
+ // validities of its fields, so this is a sound implementation
+ // of `is_bit_valid`.
+ #[inline]
+ fn is_bit_valid<___ZcAlignment>(
+ mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
+ ) -> #core::primitive::bool
+ where
+ ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
+ {
+ true #(&& {
+ let field_candidate = #zerocopy_crate::into_inner!(candidate.reborrow().project::<
+ _,
+ { #zerocopy_crate::STRUCT_VARIANT_ID },
+ { #zerocopy_crate::ident_id!(#field_names) }
+ >());
+ <#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
+ })*
+ }
+ )
+ });
+ Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF)
+ .inner_extras(extras)
+ .outer_extras(derive_has_field_struct_union(ctx, strct))
+ .build())
+}
+fn derive_try_from_bytes_union(ctx: &Ctx, unn: &DataUnion, top_level: Trait) -> TokenStream {
+ let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
+
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let variant_id: Box<Expr> = {
+ let is_repr_c =
+ StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false);
+ if is_repr_c {
+ parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
+ } else {
+ parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
+ }
+ };
+
+ let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
+ let fields = unn.fields();
+ let field_names = fields.iter().map(|(_vis, name, _ty)| name);
+ let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
+ let core = ctx.core_path();
+ quote!(
+ // SAFETY: We use `is_bit_valid` to validate that any field is
+ // bit-valid; we only return `true` if at least one of them is.
+ // The bit validity of a union is not yet well defined in Rust,
+ // but it is guaranteed to be no more strict than this
+ // definition. See #696 for a more in-depth discussion.
+ #[inline]
+ fn is_bit_valid<___ZcAlignment>(
+ mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
+ ) -> #core::primitive::bool
+ where
+ ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
+ {
+ false #(|| {
+ // SAFETY:
+ // - Since `ReadOnly<Self>: Immutable` unconditionally,
+ // neither `*slf` nor the returned pointer's referent
+ // permit interior mutation.
+ // - Both source and destination validity are
+ // `Initialized`, which is always a sound
+ // transmutation.
+ let field_candidate = unsafe {
+ candidate.reborrow().project_transmute_unchecked::<
+ _,
+ _,
+ #zerocopy_crate::pointer::cast::Projection<
+ _,
+ #variant_id,
+ { #zerocopy_crate::ident_id!(#field_names) }
+ >
+ >()
+ };
+
+ <#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
+ })*
+ }
+ )
+ });
+ ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds)
+ .inner_extras(extras)
+ .outer_extras(derive_has_field_struct_union(ctx, unn))
+ .build()
+}
+fn derive_try_from_bytes_enum(
+ ctx: &Ctx,
+ enm: &DataEnum,
+ top_level: Trait,
+) -> Result<TokenStream, Error> {
+ let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
+
+ // If an enum has no fields, it has a well-defined integer representation,
+ // and every possible bit pattern corresponds to a valid discriminant tag,
+ // then it *could* be `FromBytes` (even if the user hasn't derived
+ // `FromBytes`). This holds if, for `repr(uN)` or `repr(iN)`, there are 2^N
+ // variants.
+ let could_be_from_bytes = enum_size_from_repr(&repr)
+ .map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size)
+ .unwrap_or(false);
+
+ let trivial_is_bit_valid = try_gen_trivial_is_bit_valid(ctx, top_level);
+ let extra = match (trivial_is_bit_valid, could_be_from_bytes) {
+ (Some(is_bit_valid), _) => is_bit_valid,
+ // SAFETY: It would be sound for the enum to implement `FromBytes`, as
+ // required by `gen_trivial_is_bit_valid_unchecked`.
+ (None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ctx) },
+ (None, false) => match derive_is_bit_valid(ctx, enm, &repr) {
+ Ok(extra) => extra,
+ Err(_) if ctx.skip_on_error => return Ok(TokenStream::new()),
+ Err(e) => return Err(e),
+ },
+ };
+
+ Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF)
+ .inner_extras(extra)
+ .build())
+}
+fn try_gen_trivial_is_bit_valid(ctx: &Ctx, top_level: Trait) -> Option<proc_macro2::TokenStream> {
+ // If the top-level trait is `FromBytes` and `Self` has no type parameters,
+ // then the `FromBytes` derive will fail compilation if `Self` is not
+ // actually soundly `FromBytes`, and so we can rely on that for our
+ // `is_bit_valid` impl. It's plausible that we could make changes - or Rust
+ // could make changes (such as the "trivial bounds" language feature) - that
+ // make this no longer true. To hedge against these, we include an explicit
+ // `Self: FromBytes` check in the generated `is_bit_valid`, which is
+ // bulletproof.
+ //
+ // If `ctx.skip_on_error` is true, we can't rely on the `FromBytes` derive
+ // to fail compilation if `Self` is not actually soundly `FromBytes`.
+ if matches!(top_level, Trait::FromBytes)
+ && ctx.ast.generics.params.is_empty()
+ && !ctx.skip_on_error
+ {
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let core = ctx.core_path();
+ Some(quote!(
+ // SAFETY: See inline.
+ #[inline(always)]
+ fn is_bit_valid<___ZcAlignment>(
+ _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
+ ) -> #core::primitive::bool
+ where
+ ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
+ {
+ if false {
+ fn assert_is_from_bytes<T>()
+ where
+ T: #zerocopy_crate::FromBytes,
+ T: ?#core::marker::Sized,
+ {
+ }
+
+ assert_is_from_bytes::<Self>();
+ }
+
+ // SAFETY: The preceding code only compiles if `Self:
+ // FromBytes`. Thus, this code only compiles if all initialized
+ // byte sequences represent valid instances of `Self`.
+ true
+ }
+ ))
+ } else {
+ None
+ }
+}
+
+/// # Safety
+///
+/// All initialized bit patterns must be valid for `Self`.
+unsafe fn gen_trivial_is_bit_valid_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let core = ctx.core_path();
+ quote!(
+ // SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
+ // promised that all initialized bit patterns are valid for `Self`.
+ #[inline(always)]
+ fn is_bit_valid<___ZcAlignment>(
+ _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
+ ) -> #core::primitive::bool
+ where
+ ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
+ {
+ true
+ }
+ )
+}
diff --git a/rust/zerocopy-derive/derive/unaligned.rs b/rust/zerocopy-derive/derive/unaligned.rs
new file mode 100644
index 000000000000..819d84984a03
--- /dev/null
+++ b/rust/zerocopy-derive/derive/unaligned.rs
@@ -0,0 +1,78 @@
+use proc_macro2::{Span, TokenStream};
+use syn::{Data, DataEnum, DataStruct, DataUnion, Error};
+
+use crate::{
+ repr::{EnumRepr, StructUnionRepr},
+ util::{Ctx, FieldBounds, ImplBlockBuilder, Trait},
+};
+
+pub(crate) fn derive_unaligned(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
+ match &ctx.ast.data {
+ Data::Struct(strct) => derive_unaligned_struct(ctx, strct),
+ Data::Enum(enm) => derive_unaligned_enum(ctx, enm),
+ Data::Union(unn) => derive_unaligned_union(ctx, unn),
+ }
+}
+
+/// A struct is `Unaligned` if:
+/// - `repr(align)` is no more than 1 and either
+/// - `repr(C)` or `repr(transparent)` and
+/// - all fields `Unaligned`
+/// - `repr(packed)`
+fn derive_unaligned_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
+ let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
+ repr.unaligned_validate_no_align_gt_1()?;
+
+ let field_bounds = if repr.is_packed_1() {
+ FieldBounds::None
+ } else if repr.is_c() || repr.is_transparent() {
+ FieldBounds::ALL_SELF
+ } else {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
+ ));
+ };
+
+ Ok(ImplBlockBuilder::new(ctx, strct, Trait::Unaligned, field_bounds).build())
+}
+
+/// An enum is `Unaligned` if:
+/// - No `repr(align(N > 1))`
+/// - `repr(u8)` or `repr(i8)`
+fn derive_unaligned_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
+ let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
+ repr.unaligned_validate_no_align_gt_1()?;
+
+ if !repr.is_u8() && !repr.is_i8() {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment",
+ ));
+ }
+
+ Ok(ImplBlockBuilder::new(ctx, enm, Trait::Unaligned, FieldBounds::ALL_SELF).build())
+}
+
+/// Like structs, a union is `Unaligned` if:
+/// - `repr(align)` is no more than 1 and either
+/// - `repr(C)` or `repr(transparent)` and
+/// - all fields `Unaligned`
+/// - `repr(packed)`
+fn derive_unaligned_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
+ let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
+ repr.unaligned_validate_no_align_gt_1()?;
+
+ let field_type_trait_bounds = if repr.is_packed_1() {
+ FieldBounds::None
+ } else if repr.is_c() || repr.is_transparent() {
+ FieldBounds::ALL_SELF
+ } else {
+ return ctx.error_or_skip(Error::new(
+ Span::call_site(),
+ "must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
+ ));
+ };
+
+ Ok(ImplBlockBuilder::new(ctx, unn, Trait::Unaligned, field_type_trait_bounds).build())
+}
diff --git a/rust/zerocopy-derive/lib.rs b/rust/zerocopy-derive/lib.rs
new file mode 100644
index 000000000000..a1d10a2ada27
--- /dev/null
+++ b/rust/zerocopy-derive/lib.rs
@@ -0,0 +1,144 @@
+// Copyright 2019 The Fuchsia Authors
+//
+// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
+// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
+// This file may not be copied, modified, or distributed except according to
+// those terms.
+
+//! Derive macros for [zerocopy]'s traits.
+//!
+//! [zerocopy]: https://docs.rs/zerocopy
+
+// Sometimes we want to use lints which were added after our MSRV.
+// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
+// this attribute, any unknown lint would cause a CI failure when testing with
+// our MSRV.
+#![allow(unknown_lints)]
+#![deny(renamed_and_removed_lints)]
+#![deny(
+ clippy::all,
+ clippy::missing_safety_doc,
+ clippy::multiple_unsafe_ops_per_block,
+ clippy::undocumented_unsafe_blocks
+)]
+// We defer to own discretion on type complexity.
+#![allow(clippy::type_complexity)]
+// Inlining format args isn't supported on our MSRV.
+#![allow(clippy::uninlined_format_args)]
+#![deny(
+ rustdoc::bare_urls,
+ rustdoc::broken_intra_doc_links,
+ rustdoc::invalid_codeblock_attributes,
+ rustdoc::invalid_html_tags,
+ rustdoc::invalid_rust_codeblocks,
+ rustdoc::missing_crate_level_docs,
+ rustdoc::private_intra_doc_links
+)]
+#![recursion_limit = "128"]
+
+macro_rules! ident {
+ (($fmt:literal $(, $arg:expr)*), $span:expr) => {
+ syn::Ident::new(&format!($fmt $(, crate::util::to_ident_str($arg))*), $span)
+ };
+}
+
+mod derive;
+#[cfg(test)]
+mod output_tests;
+mod repr;
+mod util;
+
+use syn::{DeriveInput, Error};
+
+use crate::util::*;
+
+// FIXME(https://github.com/rust-lang/rust/issues/54140): Some errors could be
+// made better if we could add multiple lines of error output like this:
+//
+// error: unsupported representation
+// --> enum.rs:28:8
+// |
+// 28 | #[repr(transparent)]
+// |
+// help: required by the derive of FromBytes
+//
+// Instead, we have more verbose error messages like "unsupported representation
+// for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum"
+//
+// This will probably require Span::error
+// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
+// which is currently unstable. Revisit this once it's stable.
+
+/// Defines a derive function named `$outer` which parses its input
+/// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function.
+///
+/// Note that the separate `$outer` parameter is required - proc macro functions
+/// are currently required to live at the crate root, and so the caller must
+/// specify the name in order to avoid name collisions.
+macro_rules! derive {
+ ($trait:ident => $outer:ident => $inner:path) => {
+ #[proc_macro_derive($trait, attributes(zerocopy))]
+ pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+ let ctx = match Ctx::try_from_derive_input(ast) {
+ Ok(ctx) => ctx,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let ts = $inner(&ctx, Trait::$trait).into_ts();
+ // We wrap in `const_block` as a backstop in case any derive fails
+ // to wrap its output in `const_block` (and thus fails to annotate)
+ // with the full set of `#[allow(...)]` attributes).
+ let ts = const_block([Some(ts)]);
+ #[cfg(test)]
+ crate::util::testutil::check_hygiene(ts.clone());
+ ts.into()
+ }
+ };
+}
+
+trait IntoTokenStream {
+ fn into_ts(self) -> proc_macro2::TokenStream;
+}
+
+impl IntoTokenStream for proc_macro2::TokenStream {
+ fn into_ts(self) -> proc_macro2::TokenStream {
+ self
+ }
+}
+
+impl IntoTokenStream for Result<proc_macro2::TokenStream, Error> {
+ fn into_ts(self) -> proc_macro2::TokenStream {
+ match self {
+ Ok(ts) => ts,
+ Err(err) => err.to_compile_error(),
+ }
+ }
+}
+
+derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive);
+derive!(Immutable => derive_immutable => crate::derive::derive_immutable);
+derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes);
+derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros);
+derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes);
+derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes);
+derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned);
+derive!(ByteHash => derive_hash => crate::derive::derive_hash);
+derive!(ByteEq => derive_eq => crate::derive::derive_eq);
+derive!(SplitAt => derive_split_at => crate::derive::derive_split_at);
+
+/// Deprecated: prefer [`FromZeros`] instead.
+#[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
+#[doc(hidden)]
+#[proc_macro_derive(FromZeroes)]
+pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ derive_from_zeros(ts)
+}
+
+/// Deprecated: prefer [`IntoBytes`] instead.
+#[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")]
+#[doc(hidden)]
+#[proc_macro_derive(AsBytes)]
+pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ derive_into_bytes(ts)
+}
diff --git a/rust/zerocopy-derive/repr.rs b/rust/zerocopy-derive/repr.rs
new file mode 100644
index 000000000000..57014b38b2da
--- /dev/null
+++ b/rust/zerocopy-derive/repr.rs
@@ -0,0 +1,849 @@
+// Copyright 2019 The Fuchsia Authors
+//
+// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
+// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
+// This file may not be copied, modified, or distributed except according to
+// those terms.
+
+use core::{
+ convert::{Infallible, TryFrom},
+ num::NonZeroU32,
+};
+
+use proc_macro2::{Span, TokenStream};
+use quote::{quote_spanned, ToTokens, TokenStreamExt as _};
+use syn::{
+ punctuated::Punctuated, spanned::Spanned as _, token::Comma, Attribute, Error, LitInt, Meta,
+ MetaList,
+};
+
+/// The computed representation of a type.
+///
+/// This is the result of processing all `#[repr(...)]` attributes on a type, if
+/// any. A `Repr` is only capable of representing legal combinations of
+/// `#[repr(...)]` attributes.
+#[cfg_attr(test, derive(Copy, Clone, Debug))]
+pub(crate) enum Repr<Prim, Packed> {
+ /// `#[repr(transparent)]`
+ Transparent(Span),
+ /// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`
+ /// optionally combined with `repr(packed(...))` or `repr(align(...))`
+ Compound(Spanned<CompoundRepr<Prim>>, Option<Spanned<AlignRepr<Packed>>>),
+}
+
+/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`.
+#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
+pub(crate) enum CompoundRepr<Prim> {
+ C,
+ Rust,
+ Primitive(Prim),
+}
+
+/// `repr(Int)`
+#[derive(Copy, Clone)]
+#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
+pub(crate) enum PrimitiveRepr {
+ U8,
+ U16,
+ U32,
+ U64,
+ U128,
+ Usize,
+ I8,
+ I16,
+ I32,
+ I64,
+ I128,
+ Isize,
+}
+
+/// `repr(packed(...))` or `repr(align(...))`
+#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
+pub(crate) enum AlignRepr<Packed> {
+ Packed(Packed),
+ Align(NonZeroU32),
+}
+
+/// The representations which can legally appear on a struct or union type.
+pub(crate) type StructUnionRepr = Repr<Infallible, NonZeroU32>;
+
+/// The representations which can legally appear on an enum type.
+pub(crate) type EnumRepr = Repr<PrimitiveRepr, Infallible>;
+
+impl<Prim, Packed> Repr<Prim, Packed> {
+ /// Gets the name of this "repr type" - the non-align `repr(X)` that is used
+ /// in prose to refer to this type.
+ ///
+ /// For example, we would refer to `#[repr(C, align(4))] struct Foo { ... }`
+ /// as a "`repr(C)` struct".
+ pub(crate) fn repr_type_name(&self) -> &str
+ where
+ Prim: Copy + With<PrimitiveRepr>,
+ {
+ use CompoundRepr::*;
+ use PrimitiveRepr::*;
+ use Repr::*;
+ match self {
+ Transparent(_span) => "repr(transparent)",
+ Compound(Spanned { t: repr, span: _ }, _align) => match repr {
+ C => "repr(C)",
+ Rust => "repr(Rust)",
+ Primitive(prim) => prim.with(|prim| match prim {
+ U8 => "repr(u8)",
+ U16 => "repr(u16)",
+ U32 => "repr(u32)",
+ U64 => "repr(u64)",
+ U128 => "repr(u128)",
+ Usize => "repr(usize)",
+ I8 => "repr(i8)",
+ I16 => "repr(i16)",
+ I32 => "repr(i32)",
+ I64 => "repr(i64)",
+ I128 => "repr(i128)",
+ Isize => "repr(isize)",
+ }),
+ },
+ }
+ }
+
+ pub(crate) fn is_transparent(&self) -> bool {
+ matches!(self, Repr::Transparent(_))
+ }
+
+ pub(crate) fn is_c(&self) -> bool {
+ use CompoundRepr::*;
+ matches!(self, Repr::Compound(Spanned { t: C, span: _ }, _align))
+ }
+
+ pub(crate) fn is_primitive(&self) -> bool {
+ use CompoundRepr::*;
+ matches!(self, Repr::Compound(Spanned { t: Primitive(_), span: _ }, _align))
+ }
+
+ pub(crate) fn get_packed(&self) -> Option<&Packed> {
+ use AlignRepr::*;
+ use Repr::*;
+ if let Compound(_, Some(Spanned { t: Packed(p), span: _ })) = self {
+ Some(p)
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn get_align(&self) -> Option<Spanned<NonZeroU32>> {
+ use AlignRepr::*;
+ use Repr::*;
+ if let Compound(_, Some(Spanned { t: Align(n), span })) = self {
+ Some(Spanned::new(*n, *span))
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn is_align_gt_1(&self) -> bool {
+ self.get_align().map(|n| n.t.get() > 1).unwrap_or(false)
+ }
+
+ /// When deriving `Unaligned`, validate that the decorated type has no
+ /// `#[repr(align(N))]` attribute where `N > 1`. If no such attribute exists
+ /// (including if `N == 1`), this returns `Ok(())`, and otherwise it returns
+ /// a descriptive error.
+ pub(crate) fn unaligned_validate_no_align_gt_1(&self) -> Result<(), Error> {
+ if let Some(n) = self.get_align().filter(|n| n.t.get() > 1) {
+ Err(Error::new(
+ n.span,
+ "cannot derive `Unaligned` on type with alignment greater than 1",
+ ))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl<Prim> Repr<Prim, NonZeroU32> {
+ /// Does `self` describe a `#[repr(packed)]` or `#[repr(packed(1))]` type?
+ pub(crate) fn is_packed_1(&self) -> bool {
+ self.get_packed().map(|n| n.get() == 1).unwrap_or(false)
+ }
+}
+
+impl<Packed> Repr<PrimitiveRepr, Packed> {
+ fn get_primitive(&self) -> Option<&PrimitiveRepr> {
+ use CompoundRepr::*;
+ use Repr::*;
+ if let Compound(Spanned { t: Primitive(p), span: _ }, _align) = self {
+ Some(p)
+ } else {
+ None
+ }
+ }
+
+ /// Does `self` describe a `#[repr(u8)]` type?
+ pub(crate) fn is_u8(&self) -> bool {
+ matches!(self.get_primitive(), Some(PrimitiveRepr::U8))
+ }
+
+ /// Does `self` describe a `#[repr(i8)]` type?
+ pub(crate) fn is_i8(&self) -> bool {
+ matches!(self.get_primitive(), Some(PrimitiveRepr::I8))
+ }
+}
+
+impl<Prim, Packed> ToTokens for Repr<Prim, Packed>
+where
+ Prim: With<PrimitiveRepr> + Copy,
+ Packed: With<NonZeroU32> + Copy,
+{
+ fn to_tokens(&self, ts: &mut TokenStream) {
+ use Repr::*;
+ match self {
+ Transparent(span) => ts.append_all(quote_spanned! { *span=> #[repr(transparent)] }),
+ Compound(repr, align) => {
+ repr.to_tokens(ts);
+ if let Some(align) = align {
+ align.to_tokens(ts);
+ }
+ }
+ }
+ }
+}
+
+impl<Prim: With<PrimitiveRepr> + Copy> ToTokens for Spanned<CompoundRepr<Prim>> {
+ fn to_tokens(&self, ts: &mut TokenStream) {
+ use CompoundRepr::*;
+ match &self.t {
+ C => ts.append_all(quote_spanned! { self.span=> #[repr(C)] }),
+ Rust => ts.append_all(quote_spanned! { self.span=> #[repr(Rust)] }),
+ Primitive(prim) => prim.with(|prim| Spanned::new(prim, self.span).to_tokens(ts)),
+ }
+ }
+}
+
+impl ToTokens for Spanned<PrimitiveRepr> {
+ fn to_tokens(&self, ts: &mut TokenStream) {
+ use PrimitiveRepr::*;
+ match self.t {
+ U8 => ts.append_all(quote_spanned! { self.span => #[repr(u8)] }),
+ U16 => ts.append_all(quote_spanned! { self.span => #[repr(u16)] }),
+ U32 => ts.append_all(quote_spanned! { self.span => #[repr(u32)] }),
+ U64 => ts.append_all(quote_spanned! { self.span => #[repr(u64)] }),
+ U128 => ts.append_all(quote_spanned! { self.span => #[repr(u128)] }),
+ Usize => ts.append_all(quote_spanned! { self.span => #[repr(usize)] }),
+ I8 => ts.append_all(quote_spanned! { self.span => #[repr(i8)] }),
+ I16 => ts.append_all(quote_spanned! { self.span => #[repr(i16)] }),
+ I32 => ts.append_all(quote_spanned! { self.span => #[repr(i32)] }),
+ I64 => ts.append_all(quote_spanned! { self.span => #[repr(i64)] }),
+ I128 => ts.append_all(quote_spanned! { self.span => #[repr(i128)] }),
+ Isize => ts.append_all(quote_spanned! { self.span => #[repr(isize)] }),
+ }
+ }
+}
+
+impl<Packed: With<NonZeroU32> + Copy> ToTokens for Spanned<AlignRepr<Packed>> {
+ fn to_tokens(&self, ts: &mut TokenStream) {
+ use AlignRepr::*;
+ // We use `syn::Index` instead of `u32` because `quote_spanned!`
+ // serializes `u32` literals as `123u32`, not just `123`. Rust doesn't
+ // recognize that as a valid argument to `#[repr(align(...))]` or
+ // `#[repr(packed(...))]`.
+ let to_index = |n: NonZeroU32| syn::Index { index: n.get(), span: self.span };
+ match self.t {
+ Packed(n) => n.with(|n| {
+ let n = to_index(n);
+ ts.append_all(quote_spanned! { self.span => #[repr(packed(#n))] })
+ }),
+ Align(n) => {
+ let n = to_index(n);
+ ts.append_all(quote_spanned! { self.span => #[repr(align(#n))] })
+ }
+ }
+ }
+}
+
+/// The result of parsing a single `#[repr(...)]` attribute or a single
+/// directive inside a compound `#[repr(..., ...)]` attribute.
+#[derive(Copy, Clone, PartialEq, Eq)]
+#[cfg_attr(test, derive(Debug))]
+pub(crate) enum RawRepr {
+ Transparent,
+ C,
+ Rust,
+ U8,
+ U16,
+ U32,
+ U64,
+ U128,
+ Usize,
+ I8,
+ I16,
+ I32,
+ I64,
+ I128,
+ Isize,
+ Align(NonZeroU32),
+ PackedN(NonZeroU32),
+ Packed,
+}
+
+/// The error from converting from a `RawRepr`.
+#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
+pub(crate) enum FromRawReprError<E> {
+ /// The `RawRepr` doesn't affect the high-level repr we're parsing (e.g.
+ /// it's `align(...)` and we're parsing a `CompoundRepr`).
+ None,
+ /// The `RawRepr` is invalid for the high-level repr we're parsing (e.g.
+ /// it's `packed` repr and we're parsing an `AlignRepr` for an enum type).
+ Err(E),
+}
+
+/// The representation hint is not supported for the decorated type.
+#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
+pub(crate) struct UnsupportedReprError;
+
+impl<Prim: With<PrimitiveRepr>> TryFrom<RawRepr> for CompoundRepr<Prim> {
+ type Error = FromRawReprError<UnsupportedReprError>;
+ fn try_from(
+ raw: RawRepr,
+ ) -> Result<CompoundRepr<Prim>, FromRawReprError<UnsupportedReprError>> {
+ use RawRepr::*;
+ match raw {
+ C => Ok(CompoundRepr::C),
+ Rust => Ok(CompoundRepr::Rust),
+ raw @ (U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize) => {
+ Prim::try_with_or(
+ || match raw {
+ U8 => Ok(PrimitiveRepr::U8),
+ U16 => Ok(PrimitiveRepr::U16),
+ U32 => Ok(PrimitiveRepr::U32),
+ U64 => Ok(PrimitiveRepr::U64),
+ U128 => Ok(PrimitiveRepr::U128),
+ Usize => Ok(PrimitiveRepr::Usize),
+ I8 => Ok(PrimitiveRepr::I8),
+ I16 => Ok(PrimitiveRepr::I16),
+ I32 => Ok(PrimitiveRepr::I32),
+ I64 => Ok(PrimitiveRepr::I64),
+ I128 => Ok(PrimitiveRepr::I128),
+ Isize => Ok(PrimitiveRepr::Isize),
+ Transparent | C | Rust | Align(_) | PackedN(_) | Packed => {
+ Err(UnsupportedReprError)
+ }
+ },
+ UnsupportedReprError,
+ )
+ .map(CompoundRepr::Primitive)
+ .map_err(FromRawReprError::Err)
+ }
+ Transparent | Align(_) | PackedN(_) | Packed => Err(FromRawReprError::None),
+ }
+ }
+}
+
+impl<Pcked: With<NonZeroU32>> TryFrom<RawRepr> for AlignRepr<Pcked> {
+ type Error = FromRawReprError<UnsupportedReprError>;
+ fn try_from(raw: RawRepr) -> Result<AlignRepr<Pcked>, FromRawReprError<UnsupportedReprError>> {
+ use RawRepr::*;
+ match raw {
+ Packed | PackedN(_) => Pcked::try_with_or(
+ || match raw {
+ Packed => Ok(NonZeroU32::new(1).unwrap()),
+ PackedN(n) => Ok(n),
+ U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
+ | Transparent | C | Rust | Align(_) => Err(UnsupportedReprError),
+ },
+ UnsupportedReprError,
+ )
+ .map(AlignRepr::Packed)
+ .map_err(FromRawReprError::Err),
+ Align(n) => Ok(AlignRepr::Align(n)),
+ U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
+ | Transparent | C | Rust => Err(FromRawReprError::None),
+ }
+ }
+}
+
+/// The error from extracting a high-level repr type from a list of `RawRepr`s.
+#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
+enum FromRawReprsError<E> {
+ /// One of the `RawRepr`s is invalid for the high-level repr we're parsing
+ /// (e.g. there's a `packed` repr and we're parsing an `AlignRepr` for an
+ /// enum type).
+ Single(E),
+ /// Two `RawRepr`s appear which both affect the high-level repr we're
+ /// parsing (e.g., the list is `#[repr(align(2), packed)]`). Note that we
+ /// conservatively treat redundant reprs as conflicting (e.g.
+ /// `#[repr(packed, packed)]`).
+ Conflict,
+}
+
+/// Tries to extract a high-level repr from a list of `RawRepr`s.
+fn try_from_raw_reprs<'a, E, R: TryFrom<RawRepr, Error = FromRawReprError<E>>>(
+ r: impl IntoIterator<Item = &'a Spanned<RawRepr>>,
+) -> Result<Option<Spanned<R>>, Spanned<FromRawReprsError<E>>> {
+ // Walk the list of `RawRepr`s and attempt to convert each to an `R`. Bail
+ // if we find any errors. If we find more than one which converts to an `R`,
+ // bail with a `Conflict` error.
+ r.into_iter().try_fold(None, |found: Option<Spanned<R>>, raw| {
+ let new = match Spanned::<R>::try_from(*raw) {
+ Ok(r) => r,
+ // This `RawRepr` doesn't convert to an `R`, so keep the current
+ // found `R`, if any.
+ Err(FromRawReprError::None) => return Ok(found),
+ // This repr is unsupported for the decorated type (e.g.
+ // `repr(packed)` on an enum).
+ Err(FromRawReprError::Err(Spanned { t: err, span })) => {
+ return Err(Spanned::new(FromRawReprsError::Single(err), span))
+ }
+ };
+
+ if let Some(found) = found {
+ // We already found an `R`, but this `RawRepr` also converts to an
+ // `R`, so that's a conflict.
+ //
+ // `Span::join` returns `None` if the two spans are from different
+ // files or if we're not on the nightly compiler. In that case, just
+ // use `new`'s span.
+ let span = found.span.join(new.span).unwrap_or(new.span);
+ Err(Spanned::new(FromRawReprsError::Conflict, span))
+ } else {
+ Ok(Some(new))
+ }
+ })
+}
+
+/// The error returned from [`Repr::from_attrs`].
+#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
+enum FromAttrsError {
+ FromRawReprs(FromRawReprsError<UnsupportedReprError>),
+ Unrecognized,
+}
+
+impl From<FromRawReprsError<UnsupportedReprError>> for FromAttrsError {
+ fn from(err: FromRawReprsError<UnsupportedReprError>) -> FromAttrsError {
+ FromAttrsError::FromRawReprs(err)
+ }
+}
+
+impl From<UnrecognizedReprError> for FromAttrsError {
+ fn from(_err: UnrecognizedReprError) -> FromAttrsError {
+ FromAttrsError::Unrecognized
+ }
+}
+
+impl From<Spanned<FromAttrsError>> for Error {
+ fn from(err: Spanned<FromAttrsError>) -> Error {
+ let Spanned { t: err, span } = err;
+ match err {
+ FromAttrsError::FromRawReprs(FromRawReprsError::Single(
+ _err @ UnsupportedReprError,
+ )) => Error::new(span, "unsupported representation hint for the decorated type"),
+ FromAttrsError::FromRawReprs(FromRawReprsError::Conflict) => {
+ // NOTE: This says "another" rather than "a preceding" because
+ // when one of the reprs involved is `transparent`, we detect
+ // that condition in `Repr::from_attrs`, and at that point we
+ // can't tell which repr came first, so we might report this on
+ // the first involved repr rather than the second, third, etc.
+ Error::new(span, "this conflicts with another representation hint")
+ }
+ FromAttrsError::Unrecognized => Error::new(span, "unrecognized representation hint"),
+ }
+ }
+}
+
+impl<Prim, Packed> Repr<Prim, Packed> {
+ fn from_attrs_inner(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Spanned<FromAttrsError>>
+ where
+ Prim: With<PrimitiveRepr>,
+ Packed: With<NonZeroU32>,
+ {
+ let raw_reprs = RawRepr::from_attrs(attrs).map_err(Spanned::from)?;
+
+ let transparent = {
+ let mut transparents = raw_reprs.iter().filter_map(|Spanned { t, span }| match t {
+ RawRepr::Transparent => Some(span),
+ _ => None,
+ });
+ let first = transparents.next();
+ let second = transparents.next();
+ match (first, second) {
+ (None, None) => None,
+ (Some(span), None) => Some(*span),
+ (Some(_), Some(second)) => {
+ return Err(Spanned::new(
+ FromAttrsError::FromRawReprs(FromRawReprsError::Conflict),
+ *second,
+ ))
+ }
+ // An iterator can't produce a value only on the second call to
+ // `.next()`.
+ (None, Some(_)) => unreachable!(),
+ }
+ };
+
+ let compound: Option<Spanned<CompoundRepr<Prim>>> =
+ try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
+ let align: Option<Spanned<AlignRepr<Packed>>> =
+ try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
+
+ if let Some(span) = transparent {
+ if compound.is_some() || align.is_some() {
+ // Arbitrarily report the problem on the `transparent` span. Any
+ // span will do.
+ return Err(Spanned::new(FromRawReprsError::Conflict.into(), span));
+ }
+
+ Ok(Repr::Transparent(span))
+ } else {
+ Ok(Repr::Compound(
+ compound.unwrap_or(Spanned::new(CompoundRepr::Rust, Span::call_site())),
+ align,
+ ))
+ }
+ }
+}
+
+impl<Prim, Packed> Repr<Prim, Packed> {
+ pub(crate) fn from_attrs(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Error>
+ where
+ Prim: With<PrimitiveRepr>,
+ Packed: With<NonZeroU32>,
+ {
+ Repr::from_attrs_inner(attrs).map_err(Into::into)
+ }
+}
+
+/// The representation hint could not be parsed or was unrecognized.
+struct UnrecognizedReprError;
+
+impl RawRepr {
+ fn from_attrs(
+ attrs: &[Attribute],
+ ) -> Result<Vec<Spanned<RawRepr>>, Spanned<UnrecognizedReprError>> {
+ let mut reprs = Vec::new();
+ for attr in attrs {
+ // Ignore documentation attributes.
+ if attr.path().is_ident("doc") {
+ continue;
+ }
+ if let Meta::List(ref meta_list) = attr.meta {
+ if meta_list.path.is_ident("repr") {
+ let parsed: Punctuated<Meta, Comma> =
+ match meta_list.parse_args_with(Punctuated::parse_terminated) {
+ Ok(parsed) => parsed,
+ Err(_) => {
+ return Err(Spanned::new(
+ UnrecognizedReprError,
+ meta_list.tokens.span(),
+ ))
+ }
+ };
+ for meta in parsed {
+ let s = meta.span();
+ reprs.push(
+ RawRepr::from_meta(&meta)
+ .map(|r| Spanned::new(r, s))
+ .map_err(|e| Spanned::new(e, s))?,
+ );
+ }
+ }
+ }
+ }
+
+ Ok(reprs)
+ }
+
+ fn from_meta(meta: &Meta) -> Result<RawRepr, UnrecognizedReprError> {
+ let (path, list) = match meta {
+ Meta::Path(path) => (path, None),
+ Meta::List(list) => (&list.path, Some(list)),
+ _ => return Err(UnrecognizedReprError),
+ };
+
+ let ident = path.get_ident().ok_or(UnrecognizedReprError)?;
+
+ // Only returns `Ok` for non-zero power-of-two values.
+ let parse_nzu64 = |list: &MetaList| {
+ list.parse_args::<LitInt>()
+ .and_then(|int| int.base10_parse::<NonZeroU32>())
+ .map_err(|_| UnrecognizedReprError)
+ .and_then(|nz| {
+ if nz.get().is_power_of_two() {
+ Ok(nz)
+ } else {
+ Err(UnrecognizedReprError)
+ }
+ })
+ };
+
+ use RawRepr::*;
+ Ok(match (ident.to_string().as_str(), list) {
+ ("u8", None) => U8,
+ ("u16", None) => U16,
+ ("u32", None) => U32,
+ ("u64", None) => U64,
+ ("u128", None) => U128,
+ ("usize", None) => Usize,
+ ("i8", None) => I8,
+ ("i16", None) => I16,
+ ("i32", None) => I32,
+ ("i64", None) => I64,
+ ("i128", None) => I128,
+ ("isize", None) => Isize,
+ ("C", None) => C,
+ ("transparent", None) => Transparent,
+ ("Rust", None) => Rust,
+ ("packed", None) => Packed,
+ ("packed", Some(list)) => PackedN(parse_nzu64(list)?),
+ ("align", Some(list)) => Align(parse_nzu64(list)?),
+ _ => return Err(UnrecognizedReprError),
+ })
+ }
+}
+
+pub(crate) use util::*;
+mod util {
+ use super::*;
+ /// A value with an associated span.
+ #[derive(Copy, Clone)]
+ #[cfg_attr(test, derive(Debug))]
+ pub(crate) struct Spanned<T> {
+ pub(crate) t: T,
+ pub(crate) span: Span,
+ }
+
+ impl<T> Spanned<T> {
+ pub(super) fn new(t: T, span: Span) -> Spanned<T> {
+ Spanned { t, span }
+ }
+
+ pub(super) fn from<U>(s: Spanned<U>) -> Spanned<T>
+ where
+ T: From<U>,
+ {
+ let Spanned { t: u, span } = s;
+ Spanned::new(u.into(), span)
+ }
+
+ /// Delegates to `T: TryFrom`, preserving span information in both the
+ /// success and error cases.
+ pub(super) fn try_from<E, U>(
+ u: Spanned<U>,
+ ) -> Result<Spanned<T>, FromRawReprError<Spanned<E>>>
+ where
+ T: TryFrom<U, Error = FromRawReprError<E>>,
+ {
+ let Spanned { t: u, span } = u;
+ T::try_from(u).map(|t| Spanned { t, span }).map_err(|err| match err {
+ FromRawReprError::None => FromRawReprError::None,
+ FromRawReprError::Err(e) => FromRawReprError::Err(Spanned::new(e, span)),
+ })
+ }
+ }
+
+ // Used to permit implementing `With<T> for T: Inhabited` and for
+ // `Infallible` without a blanket impl conflict.
+ pub(crate) trait Inhabited {}
+ impl Inhabited for PrimitiveRepr {}
+ impl Inhabited for NonZeroU32 {}
+
+ pub(crate) trait With<T> {
+ fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O;
+ fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, err: E) -> Result<Self, E>
+ where
+ Self: Sized;
+ }
+
+ impl<T: Inhabited> With<T> for T {
+ fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O {
+ f(self)
+ }
+
+ fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, _err: E) -> Result<Self, E> {
+ f()
+ }
+ }
+
+ impl<T> With<T> for Infallible {
+ fn with<O, F: FnOnce(T) -> O>(self, _f: F) -> O {
+ match self {}
+ }
+
+ fn try_with_or<E, F: FnOnce() -> Result<T, E>>(_f: F, err: E) -> Result<Self, E> {
+ Err(err)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use syn::parse_quote;
+
+ use super::*;
+
+ impl<T> From<T> for Spanned<T> {
+ fn from(t: T) -> Spanned<T> {
+ Spanned::new(t, Span::call_site())
+ }
+ }
+
+ // We ignore spans for equality in testing since real spans are hard to
+ // synthesize and don't implement `PartialEq`.
+ impl<T: PartialEq> PartialEq for Spanned<T> {
+ fn eq(&self, other: &Spanned<T>) -> bool {
+ self.t.eq(&other.t)
+ }
+ }
+
+ impl<T: Eq> Eq for Spanned<T> {}
+
+ impl<Prim: PartialEq, Packed: PartialEq> PartialEq for Repr<Prim, Packed> {
+ fn eq(&self, other: &Repr<Prim, Packed>) -> bool {
+ match (self, other) {
+ (Repr::Transparent(_), Repr::Transparent(_)) => true,
+ (Repr::Compound(sc, sa), Repr::Compound(oc, oa)) => (sc, sa) == (oc, oa),
+ _ => false,
+ }
+ }
+ }
+
+ fn s() -> Span {
+ Span::call_site()
+ }
+
+ #[test]
+ fn test() {
+ // Test that a given `#[repr(...)]` attribute parses and returns the
+ // given `Repr` or error.
+ macro_rules! test {
+ ($(#[$attr:meta])* => $repr:expr) => {
+ test!(@inner $(#[$attr])* => Repr => Ok($repr));
+ };
+ // In the error case, the caller must explicitly provide the name of
+ // the `Repr` type to assist in type inference.
+ (@error $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
+ test!(@inner $(#[$attr])* => $typ => Err($repr));
+ };
+ (@inner $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
+ let attr: Attribute = parse_quote!($(#[$attr])*);
+ let mut got = $typ::from_attrs_inner(&[attr]);
+ let expect: Result<Repr<_, _>, _> = $repr;
+ if false {
+ // Force Rust to infer `got` as having the same type as
+ // `expect`.
+ got = expect;
+ }
+ assert_eq!(got, expect, stringify!($(#[$attr])*));
+ };
+ }
+
+ use AlignRepr::*;
+ use CompoundRepr::*;
+ use PrimitiveRepr::*;
+ let nz = |n: u32| NonZeroU32::new(n).unwrap();
+
+ test!(#[repr(transparent)] => StructUnionRepr::Transparent(s()));
+ test!(#[repr()] => StructUnionRepr::Compound(Rust.into(), None));
+ test!(#[repr(packed)] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(1)).into())));
+ test!(#[repr(packed(2))] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(2)).into())));
+ test!(#[repr(align(1))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
+ test!(#[repr(align(2))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
+ test!(#[repr(C)] => StructUnionRepr::Compound(C.into(), None));
+ test!(#[repr(C, packed)] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(1)).into())));
+ test!(#[repr(C, packed(2))] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(2)).into())));
+ test!(#[repr(C, align(1))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(1)).into())));
+ test!(#[repr(C, align(2))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(2)).into())));
+
+ test!(#[repr(transparent)] => EnumRepr::Transparent(s()));
+ test!(#[repr()] => EnumRepr::Compound(Rust.into(), None));
+ test!(#[repr(align(1))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
+ test!(#[repr(align(2))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
+
+ macro_rules! for_each_compound_repr {
+ ($($r:tt => $var:expr),*) => {
+ $(
+ test!(#[repr($r)] => EnumRepr::Compound($var.into(), None));
+ test!(#[repr($r, align(1))] => EnumRepr::Compound($var.into(), Some(Align(nz(1)).into())));
+ test!(#[repr($r, align(2))] => EnumRepr::Compound($var.into(), Some(Align(nz(2)).into())));
+ )*
+ }
+ }
+
+ for_each_compound_repr!(
+ C => C,
+ u8 => Primitive(U8),
+ u16 => Primitive(U16),
+ u32 => Primitive(U32),
+ u64 => Primitive(U64),
+ usize => Primitive(Usize),
+ i8 => Primitive(I8),
+ i16 => Primitive(I16),
+ i32 => Primitive(I32),
+ i64 => Primitive(I64),
+ isize => Primitive(Isize)
+ );
+
+ use FromAttrsError::*;
+ use FromRawReprsError::*;
+
+ // Run failure tests which are valid for both `StructUnionRepr` and
+ // `EnumRepr`.
+ macro_rules! for_each_repr_type {
+ ($($repr:ident),*) => {
+ $(
+ // Invalid packed or align attributes
+ test!(@error #[repr(packed(0))] => $repr => Unrecognized.into());
+ test!(@error #[repr(packed(3))] => $repr => Unrecognized.into());
+ test!(@error #[repr(align(0))] => $repr => Unrecognized.into());
+ test!(@error #[repr(align(3))] => $repr => Unrecognized.into());
+
+ // Conflicts
+ test!(@error #[repr(transparent, transparent)] => $repr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(transparent, C)] => $repr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(transparent, Rust)] => $repr => FromRawReprs(Conflict).into());
+
+ test!(@error #[repr(C, transparent)] => $repr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(C, C)] => $repr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(C, Rust)] => $repr => FromRawReprs(Conflict).into());
+
+ test!(@error #[repr(Rust, transparent)] => $repr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(Rust, C)] => $repr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(Rust, Rust)] => $repr => FromRawReprs(Conflict).into());
+ )*
+ }
+ }
+
+ for_each_repr_type!(StructUnionRepr, EnumRepr);
+
+ // Enum-specific conflicts.
+ //
+ // We don't bother to test every combination since that would be a huge
+ // number (enums can have primitive reprs u8, u16, u32, u64, usize, i8,
+ // i16, i32, i64, and isize). Instead, since the conflict logic doesn't
+ // care what specific value of `PrimitiveRepr` is present, we assume
+ // that testing against u8 alone is fine.
+ test!(@error #[repr(transparent, u8)] => EnumRepr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(u8, transparent)] => EnumRepr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(C, u8)] => EnumRepr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(u8, C)] => EnumRepr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(Rust, u8)] => EnumRepr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(u8, Rust)] => EnumRepr => FromRawReprs(Conflict).into());
+ test!(@error #[repr(u8, u8)] => EnumRepr => FromRawReprs(Conflict).into());
+
+ // Illegal struct/union reprs
+ test!(@error #[repr(u8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(u16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(u32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(u64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(usize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(i8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(i16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(i32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(i64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(isize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+
+ // Illegal enum reprs
+ test!(@error #[repr(packed)] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(packed(1))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ test!(@error #[repr(packed(2))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
+ }
+}
diff --git a/rust/zerocopy-derive/util.rs b/rust/zerocopy-derive/util.rs
new file mode 100644
index 000000000000..4ec28bf95758
--- /dev/null
+++ b/rust/zerocopy-derive/util.rs
@@ -0,0 +1,843 @@
+// Copyright 2019 The Fuchsia Authors
+//
+// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
+// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
+// This file may not be copied, modified, or distributed except according to
+// those terms.
+
+use std::num::NonZeroU32;
+
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, quote_spanned, ToTokens};
+use syn::{
+ parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
+ Expr, ExprLit, Field, GenericParam, Ident, Index, Lit, LitStr, Meta, Path, Type, Variant,
+ Visibility, WherePredicate,
+};
+
+use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned};
+
+pub(crate) struct Ctx {
+ pub(crate) ast: DeriveInput,
+ pub(crate) zerocopy_crate: Path,
+
+ // The value of the last `#[zerocopy(on_error = ...)]` attribute, or `false`
+ // if none is provided.
+ pub(crate) skip_on_error: bool,
+
+ // The span of the last `#[zerocopy(on_error = ...)]` attribute, if any.
+ pub(crate) on_error_span: Option<proc_macro2::Span>,
+}
+
+impl Ctx {
+ /// Attempt to extract a crate path from the provided attributes. Defaults to
+ /// `::zerocopy` if not found.
+ pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {
+ let mut path = parse_quote!(::zerocopy);
+ let mut skip_on_error = false;
+ let mut on_error_span = None;
+
+ for attr in &ast.attrs {
+ if let Meta::List(ref meta_list) = attr.meta {
+ if meta_list.path.is_ident("zerocopy") {
+ attr.parse_nested_meta(|meta| {
+ if meta.path.is_ident("crate") {
+ let expr = meta.value().and_then(|value| value.parse());
+ if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {
+ if let Ok(path_lit) = lit.parse::<Ident>() {
+ path = parse_quote!(::#path_lit);
+ return Ok(());
+ }
+ }
+
+ return Err(Error::new(
+ Span::call_site(),
+ "`crate` attribute requires a path as the value",
+ ));
+ }
+
+ if meta.path.is_ident("on_error") {
+ on_error_span = Some(meta.path.span());
+ let value = meta.value()?;
+ let s: LitStr = value.parse()?;
+ match s.value().as_str() {
+ "skip" => skip_on_error = true,
+ "fail" => skip_on_error = false,
+ _ => return Err(Error::new(
+ s.span(),
+ "unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",
+ )),
+ }
+ return Ok(());
+ }
+
+ Err(Error::new(
+ Span::call_site(),
+ format!(
+ "unknown attribute encountered: {}",
+ meta.path.into_token_stream()
+ ),
+ ))
+ })?;
+ }
+ }
+ }
+
+ Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span })
+ }
+
+ pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {
+ Self {
+ ast: input.clone(),
+ zerocopy_crate: self.zerocopy_crate.clone(),
+ skip_on_error: self.skip_on_error,
+ on_error_span: self.on_error_span,
+ }
+ }
+
+ pub(crate) fn core_path(&self) -> TokenStream {
+ let zerocopy_crate = &self.zerocopy_crate;
+ quote!(#zerocopy_crate::util::macro_util::core_reexport)
+ }
+
+ pub(crate) fn cfg_compile_error(&self) -> TokenStream {
+ // By checking both during the compilation of the proc macro *and* in
+ // the generated code, we ensure that `--cfg
+ // zerocopy_unstable_derive_on_error` need only be passed *either* when
+ // compiling this crate *or* when compiling the user's crate. The former
+ // is preferable, but in some situations (such as when cross-compiling
+ // using `cargo build --target`), it doesn't get propagated to this
+ // crate's build by default.
+ if cfg!(zerocopy_unstable_derive_on_error) {
+ quote!()
+ } else if let Some(span) = self.on_error_span {
+ let core = self.core_path();
+ let error_message = "`on_error` is experimental; pass '--cfg zerocopy_unstable_derive_on_error' to enable";
+ quote::quote_spanned! {span=>
+ #[allow(unused_attributes, unexpected_cfgs)]
+ const _: () = {
+ #[cfg(not(zerocopy_unstable_derive_on_error))]
+ #core::compile_error!(#error_message);
+ };
+ }
+ } else {
+ quote!()
+ }
+ }
+
+ pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {
+ if self.skip_on_error {
+ Ok(self.cfg_compile_error())
+ } else {
+ Err(error)
+ }
+ }
+}
+
+pub(crate) trait DataExt {
+ /// Extracts the names and types of all fields. For enums, extracts the
+ /// names and types of fields from each variant. For tuple structs, the
+ /// names are the indices used to index into the struct (ie, `0`, `1`, etc).
+ ///
+ /// FIXME: Extracting field names for enums doesn't really make sense. Types
+ /// makes sense because we don't care about where they live - we just care
+ /// about transitive ownership. But for field names, we'd only use them when
+ /// generating is_bit_valid, which cares about where they live.
+ fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
+
+ fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;
+
+ fn tag(&self) -> Option<Ident>;
+}
+
+impl DataExt for Data {
+ fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
+ match self {
+ Data::Struct(strc) => strc.fields(),
+ Data::Enum(enm) => enm.fields(),
+ Data::Union(un) => un.fields(),
+ }
+ }
+
+ fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
+ match self {
+ Data::Struct(strc) => strc.variants(),
+ Data::Enum(enm) => enm.variants(),
+ Data::Union(un) => un.variants(),
+ }
+ }
+
+ fn tag(&self) -> Option<Ident> {
+ match self {
+ Data::Struct(strc) => strc.tag(),
+ Data::Enum(enm) => enm.tag(),
+ Data::Union(un) => un.tag(),
+ }
+ }
+}
+
+impl DataExt for DataStruct {
+ fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
+ map_fields(&self.fields)
+ }
+
+ fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
+ vec![(None, self.fields())]
+ }
+
+ fn tag(&self) -> Option<Ident> {
+ None
+ }
+}
+
+impl DataExt for DataEnum {
+ fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
+ map_fields(self.variants.iter().flat_map(|var| &var.fields))
+ }
+
+ fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
+ self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
+ }
+
+ fn tag(&self) -> Option<Ident> {
+ Some(Ident::new("___ZerocopyTag", Span::call_site()))
+ }
+}
+
+impl DataExt for DataUnion {
+ fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
+ map_fields(&self.fields.named)
+ }
+
+ fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
+ vec![(None, self.fields())]
+ }
+
+ fn tag(&self) -> Option<Ident> {
+ None
+ }
+}
+
+fn map_fields<'a>(
+ fields: impl 'a + IntoIterator<Item = &'a Field>,
+) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
+ fields
+ .into_iter()
+ .enumerate()
+ .map(|(idx, f)| {
+ (
+ &f.vis,
+ f.ident
+ .as_ref()
+ .map(ToTokens::to_token_stream)
+ .unwrap_or_else(|| Index::from(idx).to_token_stream()),
+ &f.ty,
+ )
+ })
+ .collect()
+}
+
+pub(crate) fn to_ident_str(t: &impl ToString) -> String {
+ let s = t.to_string();
+ if let Some(stripped) = s.strip_prefix("r#") {
+ stripped.to_string()
+ } else {
+ s
+ }
+}
+
+/// This enum describes what kind of padding check needs to be generated for the
+/// associated impl.
+pub(crate) enum PaddingCheck {
+ /// Check that the sum of the fields' sizes exactly equals the struct's
+ /// size.
+ Struct,
+ /// Check that a `repr(C)` struct has no padding.
+ ReprCStruct,
+ /// Check that the size of each field exactly equals the union's size.
+ Union,
+ /// Check that every variant of the enum contains no padding.
+ ///
+ /// Because doing so requires a tag enum, this padding check requires an
+ /// additional `TokenStream` which defines the tag enum as `___ZerocopyTag`.
+ Enum { tag_type_definition: TokenStream },
+}
+
+impl PaddingCheck {
+ /// Returns the idents of the trait to use and the macro to call in order to
+ /// validate that a type passes the relevant padding check.
+ pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {
+ let (trt, mcro) = match self {
+ PaddingCheck::Struct => ("PaddingFree", "struct_padding"),
+ PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),
+ PaddingCheck::Union => ("PaddingFree", "union_padding"),
+ PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),
+ };
+
+ let trt = Ident::new(trt, Span::call_site());
+ let mcro = Ident::new(mcro, Span::call_site());
+ (trt, mcro)
+ }
+
+ /// Sometimes performing the padding check requires some additional
+ /// "context" code. For enums, this is the definition of the tag enum.
+ pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {
+ match self {
+ PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,
+ PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub(crate) enum Trait {
+ KnownLayout,
+ HasTag,
+ HasField {
+ variant_id: Box<Expr>,
+ field: Box<Type>,
+ field_id: Box<Expr>,
+ },
+ ProjectField {
+ variant_id: Box<Expr>,
+ field: Box<Type>,
+ field_id: Box<Expr>,
+ invariants: Box<Type>,
+ },
+ Immutable,
+ TryFromBytes,
+ FromZeros,
+ FromBytes,
+ IntoBytes,
+ Unaligned,
+ Sized,
+ ByteHash,
+ ByteEq,
+ SplitAt,
+}
+
+impl ToTokens for Trait {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ // According to [1], the format of the derived `Debug`` output is not
+ // stable and therefore not guaranteed to represent the variant names.
+ // Indeed with the (unstable) `fmt-debug` compiler flag [2], it can
+ // return only a minimalized output or empty string. To make sure this
+ // code will work in the future and independent of the compiler flag, we
+ // translate the variants to their names manually here.
+ //
+ // [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability
+ // [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html
+ let s = match self {
+ Trait::HasField { .. } => "HasField",
+ Trait::ProjectField { .. } => "ProjectField",
+ Trait::KnownLayout => "KnownLayout",
+ Trait::HasTag => "HasTag",
+ Trait::Immutable => "Immutable",
+ Trait::TryFromBytes => "TryFromBytes",
+ Trait::FromZeros => "FromZeros",
+ Trait::FromBytes => "FromBytes",
+ Trait::IntoBytes => "IntoBytes",
+ Trait::Unaligned => "Unaligned",
+ Trait::Sized => "Sized",
+ Trait::ByteHash => "ByteHash",
+ Trait::ByteEq => "ByteEq",
+ Trait::SplitAt => "SplitAt",
+ };
+ let ident = Ident::new(s, Span::call_site());
+ let arguments: Option<syn::AngleBracketedGenericArguments> = match self {
+ Trait::HasField { variant_id, field, field_id } => {
+ Some(parse_quote!(<#field, #variant_id, #field_id>))
+ }
+ Trait::ProjectField { variant_id, field, field_id, invariants } => {
+ Some(parse_quote!(<#field, #invariants, #variant_id, #field_id>))
+ }
+ Trait::KnownLayout
+ | Trait::HasTag
+ | Trait::Immutable
+ | Trait::TryFromBytes
+ | Trait::FromZeros
+ | Trait::FromBytes
+ | Trait::IntoBytes
+ | Trait::Unaligned
+ | Trait::Sized
+ | Trait::ByteHash
+ | Trait::ByteEq
+ | Trait::SplitAt => None,
+ };
+ tokens.extend(quote!(#ident #arguments));
+ }
+}
+
+impl Trait {
+ pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let core = ctx.core_path();
+ match self {
+ Self::Sized => parse_quote!(#core::marker::#self),
+ _ => parse_quote!(#zerocopy_crate::#self),
+ }
+ }
+}
+
+pub(crate) enum TraitBound {
+ Slf,
+ Other(Trait),
+}
+
+pub(crate) enum FieldBounds<'a> {
+ None,
+ All(&'a [TraitBound]),
+ Trailing(&'a [TraitBound]),
+ Explicit(Vec<WherePredicate>),
+}
+
+impl<'a> FieldBounds<'a> {
+ pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
+ pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
+}
+
+pub(crate) enum SelfBounds<'a> {
+ None,
+ All(&'a [Trait]),
+}
+
+// FIXME(https://github.com/rust-lang/rust-clippy/issues/12908): This is a false
+// positive. Explicit lifetimes are actually necessary here.
+#[allow(clippy::needless_lifetimes)]
+impl<'a> SelfBounds<'a> {
+ pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
+}
+
+/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.
+pub(crate) fn normalize_bounds<'a>(
+ slf: &'a Trait,
+ bounds: &'a [TraitBound],
+) -> impl 'a + Iterator<Item = Trait> {
+ bounds.iter().map(move |bound| match bound {
+ TraitBound::Slf => slf.clone(),
+ TraitBound::Other(trt) => trt.clone(),
+ })
+}
+
+pub(crate) struct ImplBlockBuilder<'a> {
+ ctx: &'a Ctx,
+ data: &'a dyn DataExt,
+ trt: Trait,
+ field_type_trait_bounds: FieldBounds<'a>,
+ self_type_trait_bounds: SelfBounds<'a>,
+ padding_check: Option<PaddingCheck>,
+ param_extras: Vec<GenericParam>,
+ inner_extras: Option<TokenStream>,
+ outer_extras: Option<TokenStream>,
+}
+
+impl<'a> ImplBlockBuilder<'a> {
+ pub(crate) fn new(
+ ctx: &'a Ctx,
+ data: &'a dyn DataExt,
+ trt: Trait,
+ field_type_trait_bounds: FieldBounds<'a>,
+ ) -> Self {
+ Self {
+ ctx,
+ data,
+ trt,
+ field_type_trait_bounds,
+ self_type_trait_bounds: SelfBounds::None,
+ padding_check: None,
+ param_extras: Vec::new(),
+ inner_extras: None,
+ outer_extras: None,
+ }
+ }
+
+ pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
+ self.self_type_trait_bounds = self_type_trait_bounds;
+ self
+ }
+
+ pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
+ self.padding_check = padding_check.into();
+ self
+ }
+
+ pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {
+ self.param_extras.extend(param_extras);
+ self
+ }
+
+ pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
+ self.inner_extras = Some(inner_extras);
+ self
+ }
+
+ pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
+ self.outer_extras = outer_extras.into();
+ self
+ }
+
+ pub(crate) fn build(self) -> TokenStream {
+ // In this documentation, we will refer to this hypothetical struct:
+ //
+ // #[derive(FromBytes)]
+ // struct Foo<T, I: Iterator>
+ // where
+ // T: Copy,
+ // I: Clone,
+ // I::Item: Clone,
+ // {
+ // a: u8,
+ // b: T,
+ // c: I::Item,
+ // }
+ //
+ // We extract the field types, which in this case are `u8`, `T`, and
+ // `I::Item`. We re-use the existing parameters and where clauses. If
+ // `require_trait_bound == true` (as it is for `FromBytes), we add where
+ // bounds for each field's type:
+ //
+ // impl<T, I: Iterator> FromBytes for Foo<T, I>
+ // where
+ // T: Copy,
+ // I: Clone,
+ // I::Item: Clone,
+ // T: FromBytes,
+ // I::Item: FromBytes,
+ // {
+ // }
+ //
+ // NOTE: It is standard practice to only emit bounds for the type
+ // parameters themselves, not for field types based on those parameters
+ // (e.g., `T` vs `T::Foo`). For a discussion of why this is standard
+ // practice, see https://github.com/rust-lang/rust/issues/26925.
+ //
+ // The reason we diverge from this standard is that doing it that way
+ // for us would be unsound. E.g., consider a type, `T` where `T:
+ // FromBytes` but `T::Foo: !FromBytes`. It would not be sound for us to
+ // accept a type with a `T::Foo` field as `FromBytes` simply because `T:
+ // FromBytes`.
+ //
+ // While there's no getting around this requirement for us, it does have
+ // the pretty serious downside that, when lifetimes are involved, the
+ // trait solver ties itself in knots:
+ //
+ // #[derive(Unaligned)]
+ // #[repr(C)]
+ // struct Dup<'a, 'b> {
+ // a: PhantomData<&'a u8>,
+ // b: PhantomData<&'b u8>,
+ // }
+ //
+ // error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned`
+ // --> src/main.rs:6:10
+ // |
+ // 6 | #[derive(Unaligned)]
+ // | ^^^^^^^^^
+ // |
+ // = note: required by `zerocopy::Unaligned`
+
+ let type_ident = &self.ctx.ast.ident;
+ let trait_path = self.trt.crate_path(self.ctx);
+ let fields = self.data.fields();
+ let variants = self.data.variants();
+ let tag = self.data.tag();
+ let zerocopy_crate = &self.ctx.zerocopy_crate;
+
+ fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>, ctx: &Ctx) -> WherePredicate {
+ let traits = traits.map(|t| t.crate_path(ctx));
+ parse_quote!(#ty: #(#traits)+*)
+ }
+ let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) {
+ (FieldBounds::All(traits), _) => fields
+ .iter()
+ .map(|(_vis, _name, ty)| {
+ bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx)
+ })
+ .collect(),
+ (FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
+ (FieldBounds::Trailing(traits), [.., last]) => {
+ vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)]
+ }
+ (FieldBounds::Explicit(bounds), _) => bounds,
+ };
+
+ let padding_check_bound = self
+ .padding_check
+ .map(|check| {
+ // Parse the repr for `align` and `packed` modifiers. Note that
+ // `Repr::<PrimitiveRepr, NonZeroU32>` is more permissive than
+ // what Rust supports for structs, enums, or unions, and thus
+ // reliably extracts these modifiers for any kind of type.
+ let repr =
+ Repr::<PrimitiveRepr, NonZeroU32>::from_attrs(&self.ctx.ast.attrs).unwrap();
+ let core = self.ctx.core_path();
+ let option = quote! { #core::option::Option };
+ let nonzero = quote! { #core::num::NonZeroUsize };
+ let none = quote! { #option::None::<#nonzero> };
+ let repr_align =
+ repr.get_align().map(|spanned| {
+ let n = spanned.t.get();
+ quote_spanned! { spanned.span => (#nonzero::new(#n as usize)) }
+ }).unwrap_or(quote! { (#none) });
+ let repr_packed =
+ repr.get_packed().map(|packed| {
+ let n = packed.get();
+ quote! { (#nonzero::new(#n as usize)) }
+ }).unwrap_or(quote! { (#none) });
+ let variant_types = variants.iter().map(|(_, fields)| {
+ let types = fields.iter().map(|(_vis, _name, ty)| ty);
+ quote!([#((#types)),*])
+ });
+ let validator_context = check.validator_macro_context();
+ let (trt, validator_macro) = check.validator_trait_and_macro_idents();
+ let t = tag.iter();
+ parse_quote! {
+ (): #zerocopy_crate::util::macro_util::#trt<
+ Self,
+ {
+ #validator_context
+ #zerocopy_crate::#validator_macro!(Self, #repr_align, #repr_packed, #(#t,)* #(#variant_types),*)
+ }
+ >
+ }
+ });
+
+ let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
+ SelfBounds::None => None,
+ SelfBounds::All(traits) => {
+ Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx))
+ }
+ };
+
+ let bounds = self
+ .ctx
+ .ast
+ .generics
+ .where_clause
+ .as_ref()
+ .map(|where_clause| where_clause.predicates.iter())
+ .into_iter()
+ .flatten()
+ .chain(field_type_bounds.iter())
+ .chain(padding_check_bound.iter())
+ .chain(self_bounds.iter());
+
+ // The parameters with trait bounds, but without type defaults.
+ let mut params: Vec<_> = self
+ .ctx
+ .ast
+ .generics
+ .params
+ .clone()
+ .into_iter()
+ .map(|mut param| {
+ match &mut param {
+ GenericParam::Type(ty) => ty.default = None,
+ GenericParam::Const(cnst) => cnst.default = None,
+ GenericParam::Lifetime(_) => {}
+ }
+ parse_quote!(#param)
+ })
+ .chain(self.param_extras)
+ .collect();
+
+ // For MSRV purposes, ensure that lifetimes precede types precede const
+ // generics.
+ params.sort_by_cached_key(|param| match param {
+ GenericParam::Lifetime(_) => 0,
+ GenericParam::Type(_) => 1,
+ GenericParam::Const(_) => 2,
+ });
+
+ // The identifiers of the parameters without trait bounds or type
+ // defaults.
+ let param_idents = self.ctx.ast.generics.params.iter().map(|param| match param {
+ GenericParam::Type(ty) => {
+ let ident = &ty.ident;
+ quote!(#ident)
+ }
+ GenericParam::Lifetime(l) => {
+ let ident = &l.lifetime;
+ quote!(#ident)
+ }
+ GenericParam::Const(cnst) => {
+ let ident = &cnst.ident;
+ quote!({#ident})
+ }
+ });
+
+ let inner_extras = self.inner_extras;
+ let allow_trivial_bounds =
+ if self.ctx.skip_on_error { quote!(#[allow(trivial_bounds)]) } else { quote!() };
+ let impl_tokens = quote! {
+ #allow_trivial_bounds
+ unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
+ where
+ #(#bounds,)*
+ {
+ fn only_derive_is_allowed_to_implement_this_trait() {}
+
+ #inner_extras
+ }
+ };
+
+ let outer_extras = self.outer_extras.filter(|e| !e.is_empty());
+ let cfg_compile_error = self.ctx.cfg_compile_error();
+ const_block([Some(cfg_compile_error), Some(impl_tokens), outer_extras])
+ }
+}
+
+// A polyfill for `Option::then_some`, which was added after our MSRV.
+//
+// The `#[allow(unused)]` is necessary because, on sufficiently recent toolchain
+// versions, `b.then_some(...)` resolves to the inherent method rather than to
+// this trait, and so this trait is considered unused.
+//
+// FIXME(#67): Remove this once our MSRV is >= 1.62.
+#[allow(unused)]
+trait BoolExt {
+ fn then_some<T>(self, t: T) -> Option<T>;
+}
+
+impl BoolExt for bool {
+ fn then_some<T>(self, t: T) -> Option<T> {
+ if self {
+ Some(t)
+ } else {
+ None
+ }
+ }
+}
+
+pub(crate) fn const_block(items: impl IntoIterator<Item = Option<TokenStream>>) -> TokenStream {
+ let items = items.into_iter().flatten();
+ quote! {
+ #[allow(
+ // FIXME(#553): Add a test that generates a warning when
+ // `#[allow(deprecated)]` isn't present.
+ deprecated,
+ // Required on some rustc versions due to a lint that is only
+ // triggered when `derive(KnownLayout)` is applied to `repr(C)`
+ // structs that are generated by macros. See #2177 for details.
+ private_bounds,
+ non_local_definitions,
+ non_camel_case_types,
+ non_upper_case_globals,
+ non_snake_case,
+ non_ascii_idents,
+ clippy::missing_inline_in_public_items,
+ )]
+ #[deny(ambiguous_associated_items)]
+ // While there are not currently any warnings that this suppresses
+ // (that we're aware of), it's good future-proofing hygiene.
+ #[automatically_derived]
+ const _: () = {
+ #(#items)*
+ };
+ }
+}
+pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream {
+ let zerocopy_crate = &ctx.zerocopy_crate;
+ let variants = data.variants.iter().map(|v| {
+ let ident = &v.ident;
+ if let Some((eq, discriminant)) = &v.discriminant {
+ quote! { #ident #eq #discriminant }
+ } else {
+ quote! { #ident }
+ }
+ });
+
+ // Don't include any `repr(align)` when generating the tag enum, as that
+ // could add padding after the tag but before any variants, which is not the
+ // correct behavior.
+ let repr = match repr {
+ EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
+ EnumRepr::Compound(c, _) => quote! { #c },
+ };
+
+ quote! {
+ #repr
+ #[allow(dead_code)]
+ pub enum ___ZerocopyTag {
+ #(#variants,)*
+ }
+
+ // SAFETY: `___ZerocopyTag` has no fields, and so it does not permit
+ // interior mutation.
+ unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag {
+ fn only_derive_is_allowed_to_implement_this_trait() {}
+ }
+ }
+}
+pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
+ use CompoundRepr::*;
+ use PrimitiveRepr::*;
+ use Repr::*;
+ match repr {
+ Transparent(span)
+ | Compound(
+ Spanned {
+ t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | Usize | Isize),
+ span,
+ },
+ _,
+ ) => Err(Error::new(
+ *span,
+ "`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`",
+ )),
+ Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
+ Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
+ }
+}
+
+#[cfg(test)]
+pub(crate) mod testutil {
+ use proc_macro2::TokenStream;
+ use syn::visit::{self, Visit};
+
+ /// Checks for hygiene violations in the generated code.
+ ///
+ /// # Panics
+ ///
+ /// Panics if a hygiene violation is found.
+ pub(crate) fn check_hygiene(ts: TokenStream) {
+ struct AmbiguousItemVisitor;
+
+ impl<'ast> Visit<'ast> for AmbiguousItemVisitor {
+ fn visit_path(&mut self, i: &'ast syn::Path) {
+ if i.segments.len() > 1 && i.segments.first().unwrap().ident == "Self" {
+ panic!(
+ "Found ambiguous path `{}` in generated output. \
+ All associated item access must be fully qualified (e.g., `<Self as Trait>::Item`) \
+ to prevent hygiene issues.",
+ quote::quote!(#i)
+ );
+ }
+ visit::visit_path(self, i);
+ }
+ }
+
+ let file = syn::parse2::<syn::File>(ts).expect("failed to parse generated output as File");
+ AmbiguousItemVisitor.visit_file(&file);
+ }
+
+ #[test]
+ fn test_check_hygiene_success() {
+ check_hygiene(quote::quote! {
+ fn foo() {
+ let _ = <Self as Trait>::Item;
+ }
+ });
+ }
+
+ #[test]
+ #[should_panic(expected = "Found ambiguous path `Self :: Ambiguous`")]
+ fn test_check_hygiene_failure() {
+ check_hygiene(quote::quote! {
+ fn foo() {
+ let _ = Self::Ambiguous;
+ }
+ });
+ }
+}
--
2.54.0
next prev parent reply other threads:[~2026-06-02 17:30 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-02 17:29 [PATCH 00/18] `zerocopy` support Miguel Ojeda
2026-06-02 17:29 ` [PATCH 01/18] scripts: generate_rust_analyzer: support passing env vars Miguel Ojeda
2026-06-02 17:35 ` Miguel Ojeda
2026-06-02 17:46 ` Tamir Duberstein
2026-06-02 17:52 ` Miguel Ojeda
2026-06-02 17:53 ` Tamir Duberstein
2026-06-02 17:29 ` [PATCH 02/18] rust: kbuild: show the right `quiet_cmd_rustc_procmacrolibrary` Miguel Ojeda
2026-06-02 17:29 ` [PATCH 03/18] rust: kbuild: remove unused variable Miguel Ojeda
2026-06-02 17:29 ` [PATCH 04/18] rust: kbuild: define `procmacro-name` function Miguel Ojeda
2026-06-02 17:29 ` [PATCH 05/18] rust: kbuild: define `procmacro-extension` variable Miguel Ojeda
2026-06-02 17:29 ` [PATCH 06/18] rust: kbuild: support per-target environment variables Miguel Ojeda
2026-06-02 17:29 ` [PATCH 07/18] rust: kbuild: support `skip_clippy` for `rustc_procmacro` Miguel Ojeda
2026-06-02 17:29 ` [PATCH 08/18] rust: zerocopy: import crate Miguel Ojeda
2026-06-02 17:29 ` [PATCH 09/18] rust: zerocopy: add SPDX License Identifiers Miguel Ojeda
2026-06-02 17:29 ` [PATCH 10/18] rust: zerocopy: remove float `Display` support Miguel Ojeda
2026-06-02 17:29 ` [PATCH 11/18] rust: zerocopy: add `README.md` Miguel Ojeda
2026-06-02 17:29 ` [PATCH 12/18] rust: zerocopy: enable support in kbuild Miguel Ojeda
2026-06-02 17:29 ` Miguel Ojeda [this message]
2026-06-02 17:29 ` [PATCH 14/18] rust: zerocopy-derive: add SPDX License Identifiers Miguel Ojeda
2026-06-02 17:29 ` [PATCH 15/18] rust: zerocopy-derive: avoid generating non-ASCII identifiers Miguel Ojeda
2026-06-02 17:29 ` [PATCH 16/18] rust: zerocopy-derive: add `README.md` Miguel Ojeda
2026-06-02 17:29 ` [PATCH 17/18] rust: zerocopy-derive: enable support in kbuild Miguel Ojeda
2026-06-02 17:29 ` [PATCH 18/18] gpu: nova-core: firmware: parse `FalconUCodeDescV2` via `zerocopy` Miguel Ojeda
2026-06-03 19:42 ` Danilo Krummrich
2026-06-05 14:19 ` Alexandre Courbot
2026-06-02 17:42 ` [PATCH 00/18] `zerocopy` support Miguel Ojeda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260602172920.30342-14-ojeda@kernel.org \
--to=ojeda@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=joshlf@google.com \
--cc=jswrenn@amazon.com \
--cc=linux-kbuild@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.