Linux kbuild/kconfig development
 help / color / mirror / Atom feed
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


  parent reply	other threads:[~2026-06-02 17:30 UTC|newest]

Thread overview: 25+ 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-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox