summaryrefslogtreecommitdiffstats
path: root/third_party/rust/zerocopy-derive/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/zerocopy-derive/src/lib.rs')
-rw-r--r--third_party/rust/zerocopy-derive/src/lib.rs882
1 files changed, 882 insertions, 0 deletions
diff --git a/third_party/rust/zerocopy-derive/src/lib.rs b/third_party/rust/zerocopy-derive/src/lib.rs
new file mode 100644
index 0000000000..9af8a28a06
--- /dev/null
+++ b/third_party/rust/zerocopy-derive/src/lib.rs
@@ -0,0 +1,882 @@
+// 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::undocumented_unsafe_blocks)]
+#![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"]
+
+mod ext;
+mod repr;
+
+use {
+ proc_macro2::Span,
+ quote::quote,
+ syn::{
+ parse_quote, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit,
+ GenericParam, Ident, Lit,
+ },
+};
+
+use {crate::ext::*, crate::repr::*};
+
+// Unwraps a `Result<_, Vec<Error>>`, converting any `Err` value into a
+// `TokenStream` and returning it.
+macro_rules! try_or_print {
+ ($e:expr) => {
+ match $e {
+ Ok(x) => x,
+ Err(errors) => return print_all_errors(errors).into(),
+ }
+ };
+}
+
+// TODO(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 FromZeroes, FromBytes, AsBytes, 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.
+
+#[proc_macro_derive(KnownLayout)]
+pub fn derive_known_layout(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+
+ let is_repr_c_struct = match &ast.data {
+ Data::Struct(..) => {
+ let reprs = try_or_print!(repr::reprs::<Repr>(&ast.attrs));
+ if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) {
+ Some(reprs)
+ } else {
+ None
+ }
+ }
+ Data::Enum(..) | Data::Union(..) => None,
+ };
+
+ let fields = ast.data.field_types();
+
+ let (require_self_sized, extras) = if let (
+ Some(reprs),
+ Some((trailing_field, leading_fields)),
+ ) = (is_repr_c_struct, fields.split_last())
+ {
+ let repr_align = reprs
+ .iter()
+ .find_map(
+ |(_meta, repr)| {
+ if let Repr::Align(repr_align) = repr {
+ Some(repr_align)
+ } else {
+ None
+ }
+ },
+ )
+ .map(|repr_align| quote!(NonZeroUsize::new(#repr_align as usize)))
+ .unwrap_or(quote!(None));
+
+ let repr_packed = reprs
+ .iter()
+ .find_map(|(_meta, repr)| match repr {
+ Repr::Packed => Some(1),
+ Repr::PackedN(repr_packed) => Some(*repr_packed),
+ _ => None,
+ })
+ .map(|repr_packed| quote!(NonZeroUsize::new(#repr_packed as usize)))
+ .unwrap_or(quote!(None));
+
+ (
+ false,
+ quote!(
+ // SAFETY: `LAYOUT` accurately describes the layout of `Self`.
+ // The layout of `Self` is reflected using a sequence of
+ // invocations of `DstLayout::{new_zst,extend,pad_to_align}`.
+ // The documentation of these items vows that invocations in
+ // this manner will acurately 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::DstLayout = {
+ use ::zerocopy::macro_util::core_reexport::num::NonZeroUsize;
+ use ::zerocopy::{DstLayout, KnownLayout};
+
+ let repr_align = #repr_align;
+ let repr_packed = #repr_packed;
+
+ DstLayout::new_zst(repr_align)
+ #(.extend(DstLayout::for_type::<#leading_fields>(), repr_packed))*
+ .extend(<#trailing_field as KnownLayout>::LAYOUT, repr_packed)
+ .pad_to_align()
+ };
+
+ // SAFETY:
+ // - 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.
+ #[inline(always)]
+ fn raw_from_ptr_len(
+ bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
+ elems: usize,
+ ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
+ use ::zerocopy::{KnownLayout};
+ let trailing = <#trailing_field as KnownLayout>::raw_from_ptr_len(bytes, elems);
+ let slf = trailing.as_ptr() as *mut Self;
+ // SAFETY: Constructed from `trailing`, which is non-null.
+ unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
+ }
+ ),
+ )
+ } else {
+ // 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.
+ (
+ true,
+ quote!(
+ // 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::DstLayout = ::zerocopy::DstLayout::for_type::<Self>();
+
+ // SAFETY: `.cast` preserves address and provenance.
+ //
+ // TODO(#429): Add documentation to `.cast` that promises that
+ // it preserves provenance.
+ #[inline(always)]
+ fn raw_from_ptr_len(
+ bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
+ _elems: usize,
+ ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
+ bytes.cast::<Self>()
+ }
+ ),
+ )
+ };
+
+ match &ast.data {
+ Data::Struct(strct) => {
+ let require_trait_bound_on_field_types = if require_self_sized {
+ RequireBoundedFields::No
+ } else {
+ RequireBoundedFields::Trailing
+ };
+
+ // 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`.
+ impl_block(
+ &ast,
+ strct,
+ Trait::KnownLayout,
+ require_trait_bound_on_field_types,
+ require_self_sized,
+ None,
+ Some(extras),
+ )
+ }
+ Data::Enum(enm) => {
+ // A bound on the trailing field is not required, since enums cannot
+ // currently be unsized.
+ impl_block(
+ &ast,
+ enm,
+ Trait::KnownLayout,
+ RequireBoundedFields::No,
+ true,
+ None,
+ Some(extras),
+ )
+ }
+ Data::Union(unn) => {
+ // A bound on the trailing field is not required, since unions
+ // cannot currently be unsized.
+ impl_block(
+ &ast,
+ unn,
+ Trait::KnownLayout,
+ RequireBoundedFields::No,
+ true,
+ None,
+ Some(extras),
+ )
+ }
+ }
+ .into()
+}
+
+#[proc_macro_derive(FromZeroes)]
+pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+ match &ast.data {
+ Data::Struct(strct) => derive_from_zeroes_struct(&ast, strct),
+ Data::Enum(enm) => derive_from_zeroes_enum(&ast, enm),
+ Data::Union(unn) => derive_from_zeroes_union(&ast, unn),
+ }
+ .into()
+}
+
+#[proc_macro_derive(FromBytes)]
+pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+ match &ast.data {
+ Data::Struct(strct) => derive_from_bytes_struct(&ast, strct),
+ Data::Enum(enm) => derive_from_bytes_enum(&ast, enm),
+ Data::Union(unn) => derive_from_bytes_union(&ast, unn),
+ }
+ .into()
+}
+
+#[proc_macro_derive(AsBytes)]
+pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+ match &ast.data {
+ Data::Struct(strct) => derive_as_bytes_struct(&ast, strct),
+ Data::Enum(enm) => derive_as_bytes_enum(&ast, enm),
+ Data::Union(unn) => derive_as_bytes_union(&ast, unn),
+ }
+ .into()
+}
+
+#[proc_macro_derive(Unaligned)]
+pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+ match &ast.data {
+ Data::Struct(strct) => derive_unaligned_struct(&ast, strct),
+ Data::Enum(enm) => derive_unaligned_enum(&ast, enm),
+ Data::Union(unn) => derive_unaligned_union(&ast, unn),
+ }
+ .into()
+}
+
+const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
+ &[StructRepr::C],
+ &[StructRepr::Transparent],
+ &[StructRepr::Packed],
+ &[StructRepr::C, StructRepr::Packed],
+];
+
+// A struct is `FromZeroes` if:
+// - all fields are `FromZeroes`
+
+fn derive_from_zeroes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
+ impl_block(ast, strct, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
+}
+
+// An enum is `FromZeroes` if:
+// - all of its variants are fieldless
+// - one of the variants has a discriminant of `0`
+
+fn derive_from_zeroes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
+ if !enm.is_c_like() {
+ return Error::new_spanned(ast, "only C-like enums can implement FromZeroes")
+ .to_compile_error();
+ }
+
+ let has_explicit_zero_discriminant =
+ enm.variants.iter().filter_map(|v| v.discriminant.as_ref()).any(|(_, e)| {
+ if let Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) = e {
+ i.base10_parse::<usize>().ok() == Some(0)
+ } else {
+ false
+ }
+ });
+ // If the first variant of an enum does not specify its discriminant, it is set to zero:
+ // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations
+ let has_implicit_zero_discriminant =
+ enm.variants.iter().next().map(|v| v.discriminant.is_none()) == Some(true);
+
+ if !has_explicit_zero_discriminant && !has_implicit_zero_discriminant {
+ return Error::new_spanned(
+ ast,
+ "FromZeroes only supported on enums with a variant that has a discriminant of `0`",
+ )
+ .to_compile_error();
+ }
+
+ impl_block(ast, enm, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
+}
+
+// Like structs, unions are `FromZeroes` if
+// - all fields are `FromZeroes`
+
+fn derive_from_zeroes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
+ impl_block(ast, unn, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
+}
+
+// A struct is `FromBytes` if:
+// - all fields are `FromBytes`
+
+fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
+ impl_block(ast, strct, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
+}
+
+// An enum is `FromBytes` if:
+// - Every possible bit pattern must be valid, which means that every bit
+// pattern must correspond to a different enum variant. Thus, for an enum
+// whose layout takes up N bytes, there must be 2^N variants.
+// - Since we must know N, only representations which guarantee the layout's
+// size are allowed. These are `repr(uN)` and `repr(iN)` (`repr(C)` implies an
+// implementation-defined size). `usize` and `isize` technically guarantee the
+// layout's size, but would require us to know how large those are on the
+// target platform. This isn't terribly difficult - we could emit a const
+// expression that could call `core::mem::size_of` in order to determine the
+// size and check against the number of enum variants, but a) this would be
+// platform-specific and, b) even on Rust's smallest bit width platform (32),
+// this would require ~4 billion enum variants, which obviously isn't a thing.
+
+fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
+ if !enm.is_c_like() {
+ return Error::new_spanned(ast, "only C-like enums can implement FromBytes")
+ .to_compile_error();
+ }
+
+ let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast));
+
+ let variants_required = match reprs.as_slice() {
+ [EnumRepr::U8] | [EnumRepr::I8] => 1usize << 8,
+ [EnumRepr::U16] | [EnumRepr::I16] => 1usize << 16,
+ // `validate_reprs` has already validated that it's one of the preceding
+ // patterns.
+ _ => unreachable!(),
+ };
+ if enm.variants.len() != variants_required {
+ return Error::new_spanned(
+ ast,
+ format!(
+ "FromBytes only supported on {} enum with {} variants",
+ reprs[0], variants_required
+ ),
+ )
+ .to_compile_error();
+ }
+
+ impl_block(ast, enm, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
+}
+
+#[rustfmt::skip]
+const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = {
+ use EnumRepr::*;
+ Config {
+ allowed_combinations_message: r#"FromBytes requires repr of "u8", "u16", "i8", or "i16""#,
+ derive_unaligned: false,
+ allowed_combinations: &[
+ &[U8],
+ &[U16],
+ &[I8],
+ &[I16],
+ ],
+ disallowed_but_legal_combinations: &[
+ &[C],
+ &[U32],
+ &[I32],
+ &[U64],
+ &[I64],
+ &[Usize],
+ &[Isize],
+ ],
+ }
+};
+
+// Like structs, unions are `FromBytes` if
+// - all fields are `FromBytes`
+
+fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
+ impl_block(ast, unn, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
+}
+
+// A struct is `AsBytes` if:
+// - all fields are `AsBytes`
+// - `repr(C)` or `repr(transparent)` and
+// - no padding (size of struct equals sum of size of field types)
+// - `repr(packed)`
+
+fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
+ let reprs = try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
+ let is_transparent = reprs.contains(&StructRepr::Transparent);
+ let is_packed = reprs.contains(&StructRepr::Packed);
+
+ // TODO(#10): Support type parameters for non-transparent, non-packed
+ // structs.
+ if !ast.generics.params.is_empty() && !is_transparent && !is_packed {
+ return Error::new(
+ Span::call_site(),
+ "unsupported on generic structs that are not repr(transparent) or repr(packed)",
+ )
+ .to_compile_error();
+ }
+
+ // We don't need a padding check if the struct is repr(transparent) or
+ // repr(packed).
+ // - 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 `AsBytes` (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 `AsBytes` (meaning they don't have any padding).
+ let padding_check = if is_transparent || is_packed { None } else { Some(PaddingCheck::Struct) };
+ impl_block(ast, strct, Trait::AsBytes, RequireBoundedFields::Yes, false, padding_check, None)
+}
+
+const STRUCT_UNION_AS_BYTES_CFG: Config<StructRepr> = Config {
+ // Since `disallowed_but_legal_combinations` is empty, this message will
+ // never actually be emitted.
+ allowed_combinations_message: r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""#,
+ derive_unaligned: false,
+ allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
+ disallowed_but_legal_combinations: &[],
+};
+
+// An enum is `AsBytes` if it is C-like and has a defined repr.
+
+fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
+ if !enm.is_c_like() {
+ return Error::new_spanned(ast, "only C-like enums can implement AsBytes")
+ .to_compile_error();
+ }
+
+ // We don't care what the repr is; we only care that it is one of the
+ // allowed ones.
+ let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast));
+ impl_block(ast, enm, Trait::AsBytes, RequireBoundedFields::No, false, None, None)
+}
+
+#[rustfmt::skip]
+const ENUM_AS_BYTES_CFG: Config<EnumRepr> = {
+ use EnumRepr::*;
+ Config {
+ // Since `disallowed_but_legal_combinations` is empty, this message will
+ // never actually be emitted.
+ allowed_combinations_message: r#"AsBytes requires repr of "C", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", or "isize""#,
+ derive_unaligned: false,
+ allowed_combinations: &[
+ &[C],
+ &[U8],
+ &[U16],
+ &[I8],
+ &[I16],
+ &[U32],
+ &[I32],
+ &[U64],
+ &[I64],
+ &[Usize],
+ &[Isize],
+ ],
+ disallowed_but_legal_combinations: &[],
+ }
+};
+
+// A union is `AsBytes` if:
+// - all fields are `AsBytes`
+// - `repr(C)`, `repr(transparent)`, or `repr(packed)`
+// - no padding (size of union equals size of each field type)
+
+fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
+ // TODO(#10): Support type parameters.
+ if !ast.generics.params.is_empty() {
+ return Error::new(Span::call_site(), "unsupported on types with type parameters")
+ .to_compile_error();
+ }
+
+ try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
+
+ impl_block(
+ ast,
+ unn,
+ Trait::AsBytes,
+ RequireBoundedFields::Yes,
+ false,
+ Some(PaddingCheck::Union),
+ None,
+ )
+}
+
+// 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(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
+ let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
+ let require_trait_bounds_on_field_types = (!reprs.contains(&StructRepr::Packed)).into();
+
+ impl_block(ast, strct, Trait::Unaligned, require_trait_bounds_on_field_types, false, None, None)
+}
+
+const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = Config {
+ // Since `disallowed_but_legal_combinations` is empty, this message will
+ // never actually be emitted.
+ allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#,
+ derive_unaligned: true,
+ allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
+ disallowed_but_legal_combinations: &[],
+};
+
+// An enum is `Unaligned` if:
+// - No `repr(align(N > 1))`
+// - `repr(u8)` or `repr(i8)`
+
+fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
+ if !enm.is_c_like() {
+ return Error::new_spanned(ast, "only C-like enums can implement Unaligned")
+ .to_compile_error();
+ }
+
+ // The only valid reprs are `u8` and `i8`, and optionally `align(1)`. We
+ // don't actually care what the reprs are so long as they satisfy that
+ // requirement.
+ let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast));
+
+ // C-like enums cannot currently have type parameters, so this value of true
+ // for `require_trait_bound_on_field_types` doesn't really do anything. But
+ // it's marginally more future-proof in case that restriction is lifted in
+ // the future.
+ impl_block(ast, enm, Trait::Unaligned, RequireBoundedFields::Yes, false, None, None)
+}
+
+#[rustfmt::skip]
+const ENUM_UNALIGNED_CFG: Config<EnumRepr> = {
+ use EnumRepr::*;
+ Config {
+ allowed_combinations_message:
+ r#"Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1)))"#,
+ derive_unaligned: true,
+ allowed_combinations: &[
+ &[U8],
+ &[I8],
+ ],
+ disallowed_but_legal_combinations: &[
+ &[C],
+ &[U16],
+ &[U32],
+ &[U64],
+ &[Usize],
+ &[I16],
+ &[I32],
+ &[I64],
+ &[Isize],
+ ],
+ }
+};
+
+// 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(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
+ let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
+ let require_trait_bound_on_field_types = (!reprs.contains(&StructRepr::Packed)).into();
+
+ impl_block(ast, unn, Trait::Unaligned, require_trait_bound_on_field_types, false, None, None)
+}
+
+// This enum describes what kind of padding check needs to be generated for the
+// associated impl.
+enum PaddingCheck {
+ // Check that the sum of the fields' sizes exactly equals the struct's size.
+ Struct,
+ // Check that the size of each field exactly equals the union's size.
+ Union,
+}
+
+impl PaddingCheck {
+ /// Returns the ident of the macro to call in order to validate that a type
+ /// passes the padding check encoded by `PaddingCheck`.
+ fn validator_macro_ident(&self) -> Ident {
+ let s = match self {
+ PaddingCheck::Struct => "struct_has_padding",
+ PaddingCheck::Union => "union_has_padding",
+ };
+
+ Ident::new(s, Span::call_site())
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum Trait {
+ KnownLayout,
+ FromZeroes,
+ FromBytes,
+ AsBytes,
+ Unaligned,
+}
+
+impl Trait {
+ fn ident(&self) -> Ident {
+ Ident::new(format!("{:?}", self).as_str(), Span::call_site())
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum RequireBoundedFields {
+ No,
+ Yes,
+ Trailing,
+}
+
+impl From<bool> for RequireBoundedFields {
+ fn from(do_require: bool) -> Self {
+ match do_require {
+ true => Self::Yes,
+ false => Self::No,
+ }
+ }
+}
+
+fn impl_block<D: DataExt>(
+ input: &DeriveInput,
+ data: &D,
+ trt: Trait,
+ require_trait_bound_on_field_types: RequireBoundedFields,
+ require_self_sized: bool,
+ padding_check: Option<PaddingCheck>,
+ extras: Option<proc_macro2::TokenStream>,
+) -> proc_macro2::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 = &input.ident;
+ let trait_ident = trt.ident();
+ let field_types = data.field_types();
+
+ let bound_tt = |ty| parse_quote!(#ty: ::zerocopy::#trait_ident);
+ let field_type_bounds: Vec<_> = match (require_trait_bound_on_field_types, &field_types[..]) {
+ (RequireBoundedFields::Yes, _) => field_types.iter().map(bound_tt).collect(),
+ (RequireBoundedFields::No, _) | (RequireBoundedFields::Trailing, []) => vec![],
+ (RequireBoundedFields::Trailing, [.., last]) => vec![bound_tt(last)],
+ };
+
+ // Don't bother emitting a padding check if there are no fields.
+ #[allow(unstable_name_collisions)] // See `BoolExt` below
+ let padding_check_bound = padding_check.and_then(|check| (!field_types.is_empty()).then_some(check)).map(|check| {
+ let fields = field_types.iter();
+ let validator_macro = check.validator_macro_ident();
+ parse_quote!(
+ ::zerocopy::macro_util::HasPadding<#type_ident, {::zerocopy::#validator_macro!(#type_ident, #(#fields),*)}>:
+ ::zerocopy::macro_util::ShouldBe<false>
+ )
+ });
+
+ let self_sized_bound = if require_self_sized { Some(parse_quote!(Self: Sized)) } else { None };
+
+ let bounds = input
+ .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_sized_bound.iter());
+
+ // The parameters with trait bounds, but without type defaults.
+ let params = input.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(_) => {}
+ }
+ quote!(#param)
+ });
+
+ // The identifiers of the parameters without trait bounds or type defaults.
+ let param_idents = input.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})
+ }
+ });
+
+ quote! {
+ // TODO(#553): Add a test that generates a warning when
+ // `#[allow(deprecated)]` isn't present.
+ #[allow(deprecated)]
+ unsafe impl < #(#params),* > ::zerocopy::#trait_ident for #type_ident < #(#param_idents),* >
+ where
+ #(#bounds,)*
+ {
+ fn only_derive_is_allowed_to_implement_this_trait() {}
+
+ #extras
+ }
+ }
+}
+
+fn print_all_errors(errors: Vec<Error>) -> proc_macro2::TokenStream {
+ errors.iter().map(Error::to_compile_error).collect()
+}
+
+// A polyfill for `Option::then_some`, which was added after our MSRV.
+//
+// TODO(#67): Remove this once our MSRV is >= 1.62.
+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
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_config_repr_orderings() {
+ // Validate that the repr lists in the various configs are in the
+ // canonical order. If they aren't, then our algorithm to look up in
+ // those lists won't work.
+
+ // TODO(https://github.com/rust-lang/rust/issues/53485): Remove once
+ // `Vec::is_sorted` is stabilized.
+ fn is_sorted_and_deduped<T: Clone + Ord>(ts: &[T]) -> bool {
+ let mut sorted = ts.to_vec();
+ sorted.sort();
+ sorted.dedup();
+ ts == sorted.as_slice()
+ }
+
+ fn elements_are_sorted_and_deduped<T: Clone + Ord>(lists: &[&[T]]) -> bool {
+ lists.iter().all(|list| is_sorted_and_deduped(list))
+ }
+
+ fn config_is_sorted<T: KindRepr + Clone>(config: &Config<T>) -> bool {
+ elements_are_sorted_and_deduped(config.allowed_combinations)
+ && elements_are_sorted_and_deduped(config.disallowed_but_legal_combinations)
+ }
+
+ assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG));
+ assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG));
+ assert!(config_is_sorted(&ENUM_UNALIGNED_CFG));
+ }
+
+ #[test]
+ fn test_config_repr_no_overlap() {
+ // Validate that no set of reprs appears in both the
+ // `allowed_combinations` and `disallowed_but_legal_combinations` lists.
+
+ fn overlap<T: Eq>(a: &[T], b: &[T]) -> bool {
+ a.iter().any(|elem| b.contains(elem))
+ }
+
+ fn config_overlaps<T: KindRepr + Eq>(config: &Config<T>) -> bool {
+ overlap(config.allowed_combinations, config.disallowed_but_legal_combinations)
+ }
+
+ assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG));
+ assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG));
+ assert!(!config_overlaps(&ENUM_UNALIGNED_CFG));
+ }
+}