#![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 quote::quote_spanned;
use {
proc_macro2::Span,
quote::quote,
syn::{
parse_quote, parse_quote_spanned, Data, DataEnum, DataStruct, DataUnion, DeriveInput,
Error, Expr, ExprLit, GenericParam, Ident, Lit, Path, Type, WherePredicate,
},
};
use {crate::ext::*, crate::repr::*};
macro_rules! try_or_print {
($e:expr) => {
match $e {
Ok(x) => x,
Err(errors) => return print_all_errors(errors).into(),
}
};
}
macro_rules! derive {
($trait:ident => $outer:ident => $inner:ident) => {
#[proc_macro_derive($trait)]
pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(ts as DeriveInput);
$inner(&ast).into()
}
};
}
derive!(KnownLayout => derive_known_layout => derive_known_layout_inner);
derive!(Immutable => derive_no_cell => derive_no_cell_inner);
derive!(TryFromBytes => derive_try_from_bytes => derive_try_from_bytes_inner);
derive!(FromZeros => derive_from_zeros => derive_from_zeros_inner);
derive!(FromBytes => derive_from_bytes => derive_from_bytes_inner);
derive!(IntoBytes => derive_into_bytes => derive_into_bytes_inner);
derive!(Unaligned => derive_unaligned => derive_unaligned_inner);
#[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(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)
}
fn derive_known_layout_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
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.fields();
let (self_bounds, extras) = if let (Some(reprs), Some((trailing_field, leading_fields))) =
(is_repr_c_struct, fields.split_last())
{
let (_name, trailing_field_ty) = trailing_field;
let leading_fields_tys = leading_fields.iter().map(|(_name, ty)| ty);
let core_path = quote!(::zerocopy::macro_util::core_reexport);
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!(#core_path::num::NonZeroUsize::new(#repr_align as usize)))
.unwrap_or(quote!(#core_path::option::Option::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!(#core_path::num::NonZeroUsize::new(#repr_packed as usize)))
.unwrap_or(quote!(#core_path::option::Option::None));
(
SelfBounds::None,
quote!(
type PointerMetadata = <#trailing_field_ty as ::zerocopy::KnownLayout>::PointerMetadata;
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_tys>(), repr_packed))*
.extend(<#trailing_field_ty as KnownLayout>::LAYOUT, repr_packed)
.pad_to_align()
};
#[inline(always)]
fn raw_from_ptr_len(
bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
meta: Self::PointerMetadata,
) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
use ::zerocopy::KnownLayout;
let trailing = <#trailing_field_ty as KnownLayout>::raw_from_ptr_len(bytes, meta);
let slf = trailing.as_ptr() as *mut Self;
unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
}
#[inline(always)]
fn pointer_to_metadata(ptr: ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self>) -> Self::PointerMetadata {
let ptr = unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(ptr.as_ptr() as *mut _) };
<#trailing_field_ty>::pointer_to_metadata(ptr)
}
),
)
} else {
(
SelfBounds::SIZED,
quote!(
type PointerMetadata = ();
const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>();
#[inline(always)]
fn raw_from_ptr_len(
bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
_meta: (),
) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
bytes.cast::<Self>()
}
#[inline(always)]
fn pointer_to_metadata(
_ptr: ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self>,
) -> () {
}
),
)
};
match &ast.data {
Data::Struct(strct) => {
let require_trait_bound_on_field_types = if self_bounds == SelfBounds::SIZED {
FieldBounds::None
} else {
FieldBounds::TRAILING_SELF
};
impl_block(
ast,
strct,
Trait::KnownLayout,
require_trait_bound_on_field_types,
self_bounds,
None,
Some(extras),
)
}
Data::Enum(enm) => {
impl_block(
ast,
enm,
Trait::KnownLayout,
FieldBounds::None,
SelfBounds::SIZED,
None,
Some(extras),
)
}
Data::Union(unn) => {
impl_block(
ast,
unn,
Trait::KnownLayout,
FieldBounds::None,
SelfBounds::SIZED,
None,
Some(extras),
)
}
}
}
fn derive_no_cell_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
match &ast.data {
Data::Struct(strct) => impl_block(
ast,
strct,
Trait::Immutable,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
),
Data::Enum(enm) => impl_block(
ast,
enm,
Trait::Immutable,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
),
Data::Union(unn) => impl_block(
ast,
unn,
Trait::Immutable,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
),
}
}
fn derive_try_from_bytes_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
match &ast.data {
Data::Struct(strct) => derive_try_from_bytes_struct(ast, strct),
Data::Enum(enm) => derive_try_from_bytes_enum(ast, enm),
Data::Union(unn) => derive_try_from_bytes_union(ast, unn),
}
}
fn derive_from_zeros_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
let try_from_bytes = derive_try_from_bytes_inner(ast);
let from_zeros = match &ast.data {
Data::Struct(strct) => derive_from_zeros_struct(ast, strct),
Data::Enum(enm) => derive_from_zeros_enum(ast, enm),
Data::Union(unn) => derive_from_zeros_union(ast, unn),
};
IntoIterator::into_iter([try_from_bytes, from_zeros]).collect()
}
fn derive_from_bytes_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
let from_zeros = derive_from_zeros_inner(ast);
let from_bytes = 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),
};
IntoIterator::into_iter([from_zeros, from_bytes]).collect()
}
fn derive_into_bytes_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
match &ast.data {
Data::Struct(strct) => derive_into_bytes_struct(ast, strct),
Data::Enum(enm) => derive_into_bytes_enum(ast, enm),
Data::Union(unn) => derive_into_bytes_union(ast, unn),
}
}
fn derive_unaligned_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
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),
}
}
fn derive_try_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
let extras = Some({
let fields = strct.fields();
let field_names = fields.iter().map(|(name, _ty)| name);
let field_tys = fields.iter().map(|(_name, ty)| ty);
quote!(
fn is_bit_valid<A: ::zerocopy::pointer::invariant::Aliasing + ::zerocopy::pointer::invariant::AtLeast<::zerocopy::pointer::invariant::Shared>>(
mut candidate: ::zerocopy::Maybe<Self, A>
) -> bool {
true #(&& {
let field_candidate = unsafe {
let project = |slf: *mut Self|
::zerocopy::macro_util::core_reexport::ptr::addr_of_mut!((*slf).#field_names);
candidate.reborrow().project(project)
};
<#field_tys as ::zerocopy::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
impl_block(
ast,
strct,
Trait::TryFromBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
extras,
)
}
fn derive_try_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
let extras = Some({
let fields = unn.fields();
let field_names = fields.iter().map(|(name, _ty)| name);
let field_tys = fields.iter().map(|(_name, ty)| ty);
quote!(
fn is_bit_valid<A: ::zerocopy::pointer::invariant::Aliasing + ::zerocopy::pointer::invariant::AtLeast<::zerocopy::pointer::invariant::Shared>>(
mut candidate: ::zerocopy::Maybe<Self, A>
) -> bool {
false #(|| {
let field_candidate = unsafe {
let project = |slf: *mut Self|
::zerocopy::macro_util::core_reexport::ptr::addr_of_mut!((*slf).#field_names);
candidate.reborrow().project(project)
};
<#field_tys as ::zerocopy::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
impl_block(
ast,
unn,
Trait::TryFromBytes,
field_type_trait_bounds,
SelfBounds::None,
None,
extras,
)
}
const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
&[StructRepr::C],
&[StructRepr::Transparent],
&[StructRepr::Packed],
&[StructRepr::C, StructRepr::Packed],
];
fn derive_try_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
if !enm.is_fieldless() {
return Error::new_spanned(ast, "only field-less enums can implement TryFromBytes")
.to_compile_error();
}
let reprs = try_or_print!(ENUM_TRY_FROM_BYTES_CFG.validate_reprs(ast));
let from_bytes = enum_size_from_repr(reprs.as_slice())
.map(|size| {
enm.is_fieldless() && enm.variants.len() == 1usize << size
})
.unwrap_or(false);
let variant_names = enm.variants.iter().map(|v| &v.ident);
let is_bit_valid_body = if from_bytes {
quote!({
let _ = candidate;
true
})
} else {
quote!(
use ::zerocopy::macro_util::core_reexport;
let discriminant = unsafe { candidate.cast_unsized(|p: *mut Self| p as *mut [core_reexport::primitive::u8; core_reexport::mem::size_of::<Self>()]) };
let discriminant = unsafe { discriminant.assume_valid() };
let discriminant = discriminant.read_unaligned();
false #(|| {
let v = Self::#variant_names{};
let d: [core_reexport::primitive::u8; core_reexport::mem::size_of::<Self>()] = unsafe { core_reexport::mem::transmute(v) };
discriminant == d
})*
)
};
let extras = Some(quote!(
fn is_bit_valid<A: ::zerocopy::pointer::invariant::Aliasing + ::zerocopy::pointer::invariant::AtLeast<::zerocopy::pointer::invariant::Shared>>(
candidate: ::zerocopy::Ptr<
'_,
Self,
(
A,
::zerocopy::pointer::invariant::Any,
::zerocopy::pointer::invariant::Initialized,
),
>,
) -> ::zerocopy::macro_util::core_reexport::primitive::bool {
#is_bit_valid_body
}
));
impl_block(ast, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF, SelfBounds::None, None, extras)
}
#[rustfmt::skip]
const ENUM_TRY_FROM_BYTES_CFG: Config<EnumRepr> = {
use EnumRepr::*;
Config {
allowed_combinations_message: r#"TryFromBytes requires repr of "C", "u8", "u16", "u32", "u64", "usize", "i8", or "i16", "i32", "i64", or "isize""#,
derive_unaligned: false,
allowed_combinations: &[
&[C],
&[U8],
&[U16],
&[U32],
&[U64],
&[Usize],
&[I8],
&[I16],
&[I32],
&[I64],
&[Isize],
],
disallowed_but_legal_combinations: &[],
}
};
fn derive_from_zeros_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
impl_block(ast, strct, Trait::FromZeros, FieldBounds::ALL_SELF, SelfBounds::None, None, None)
}
fn derive_from_zeros_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
if !enm.is_fieldless() {
return Error::new_spanned(ast, "only field-less enums can implement FromZeros")
.to_compile_error();
}
try_or_print!(ENUM_FROM_ZEROS_INTO_BYTES_CFG.validate_reprs(ast));
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
}
});
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,
"FromZeros only supported on enums with a variant that has a discriminant of `0`",
)
.to_compile_error();
}
impl_block(ast, enm, Trait::FromZeros, FieldBounds::ALL_SELF, SelfBounds::None, None, None)
}
fn derive_from_zeros_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
impl_block(ast, unn, Trait::FromZeros, field_type_trait_bounds, SelfBounds::None, None, None)
}
fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
impl_block(ast, strct, Trait::FromBytes, FieldBounds::ALL_SELF, SelfBounds::None, None, None)
}
fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
if !enm.is_fieldless() {
return Error::new_spanned(ast, "only field-less enums can implement FromBytes")
.to_compile_error();
}
let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast));
let variants_required = 1usize
<< enum_size_from_repr(reprs.as_slice())
.expect("internal error: `validate_reprs` has already validated that the reprs guarantee the enum's size");
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, FieldBounds::ALL_SELF, SelfBounds::None, None, None)
}
fn enum_size_from_repr(reprs: &[EnumRepr]) -> Option<usize> {
match reprs {
[EnumRepr::U8] | [EnumRepr::I8] => Some(8),
[EnumRepr::U16] | [EnumRepr::I16] => Some(16),
_ => 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],
],
}
};
fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
impl_block(ast, unn, Trait::FromBytes, field_type_trait_bounds, SelfBounds::None, None, None)
}
fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
let reprs = try_or_print!(STRUCT_UNION_INTO_BYTES_CFG.validate_reprs(ast));
let is_transparent = reprs.contains(&StructRepr::Transparent);
let is_packed = reprs.contains(&StructRepr::Packed);
let num_fields = strct.fields().len();
let (padding_check, require_unaligned_fields) = if is_transparent || is_packed {
(None, false)
} else if reprs.contains(&StructRepr::C) && num_fields <= 1 {
(None, false)
} else if ast.generics.params.is_empty() {
(Some(PaddingCheck::Struct), false)
} else {
assert!(reprs.contains(&StructRepr::C));
(None, true)
};
let field_bounds = if require_unaligned_fields {
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)])
} else {
FieldBounds::ALL_SELF
};
impl_block(ast, strct, Trait::IntoBytes, field_bounds, SelfBounds::None, padding_check, None)
}
const STRUCT_UNION_INTO_BYTES_CFG: Config<StructRepr> = Config {
allowed_combinations_message: r#"IntoBytes requires either a) repr "C" or "transparent" with all fields implementing IntoBytes or, b) repr "packed""#,
derive_unaligned: false,
allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
disallowed_but_legal_combinations: &[],
};
fn derive_into_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
if !enm.is_fieldless() {
return Error::new_spanned(ast, "only field-less enums can implement IntoBytes")
.to_compile_error();
}
try_or_print!(ENUM_FROM_ZEROS_INTO_BYTES_CFG.validate_reprs(ast));
impl_block(ast, enm, Trait::IntoBytes, FieldBounds::None, SelfBounds::None, None, None)
}
#[rustfmt::skip]
const ENUM_FROM_ZEROS_INTO_BYTES_CFG: Config<EnumRepr> = {
use EnumRepr::*;
Config {
allowed_combinations_message: r#"Deriving this trait 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: &[],
}
};
fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
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_INTO_BYTES_CFG.validate_reprs(ast));
impl_block(
ast,
unn,
Trait::IntoBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
Some(PaddingCheck::Union),
None,
)
}
fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
let field_bounds = if !reprs.contains(&StructRepr::Packed) {
FieldBounds::ALL_SELF
} else {
FieldBounds::None
};
impl_block(ast, strct, Trait::Unaligned, field_bounds, SelfBounds::None, None, None)
}
const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = Config {
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: &[],
};
fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
if !enm.is_fieldless() {
return Error::new_spanned(ast, "only field-less enums can implement Unaligned")
.to_compile_error();
}
let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast));
impl_block(ast, enm, Trait::Unaligned, FieldBounds::ALL_SELF, SelfBounds::None, 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],
],
}
};
fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
let field_type_trait_bounds = if !reprs.contains(&StructRepr::Packed) {
FieldBounds::ALL_SELF
} else {
FieldBounds::None
};
impl_block(ast, unn, Trait::Unaligned, field_type_trait_bounds, SelfBounds::None, None, None)
}
enum PaddingCheck {
Struct,
Union,
}
impl 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(Copy, Clone, Debug, Eq, PartialEq)]
enum Trait {
KnownLayout,
Immutable,
TryFromBytes,
FromZeros,
FromBytes,
IntoBytes,
Unaligned,
Sized,
}
impl Trait {
fn path(&self) -> Path {
let span = Span::call_site();
let root = if *self == Self::Sized {
quote_spanned!(span=> ::zerocopy::macro_util::core_reexport::marker)
} else {
quote_spanned!(span=> ::zerocopy)
};
let ident = Ident::new(&format!("{:?}", self), span);
parse_quote_spanned! {span=> #root::#ident}
}
}
#[derive(Debug, Eq, PartialEq)]
enum TraitBound {
Slf,
Other(Trait),
}
#[derive(Debug, Eq, PartialEq)]
enum FieldBounds<'a> {
None,
All(&'a [TraitBound]),
Trailing(&'a [TraitBound]),
}
impl<'a> FieldBounds<'a> {
const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
}
#[derive(Debug, Eq, PartialEq)]
enum SelfBounds<'a> {
None,
All(&'a [Trait]),
}
impl<'a> SelfBounds<'a> {
const SIZED: Self = Self::All(&[Trait::Sized]);
}
fn normalize_bounds(slf: Trait, bounds: &[TraitBound]) -> impl '_ + Iterator<Item = Trait> {
bounds.iter().map(move |bound| match bound {
TraitBound::Slf => slf,
TraitBound::Other(trt) => *trt,
})
}
fn impl_block<D: DataExt>(
input: &DeriveInput,
data: &D,
trt: Trait,
field_type_trait_bounds: FieldBounds,
self_type_trait_bounds: SelfBounds,
padding_check: Option<PaddingCheck>,
extras: Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let type_ident = &input.ident;
let trait_path = trt.path();
let fields = data.fields();
fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>) -> WherePredicate {
let traits = traits.map(|t| t.path());
parse_quote!(#ty: #(#traits)+*)
}
let field_type_bounds: Vec<_> = match (field_type_trait_bounds, &fields[..]) {
(FieldBounds::All(traits), _) => {
fields.iter().map(|(_name, ty)| bound_tt(ty, normalize_bounds(trt, traits))).collect()
}
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
(FieldBounds::Trailing(traits), [.., last]) => {
vec![bound_tt(last.1, normalize_bounds(trt, traits))]
}
};
#[allow(unstable_name_collisions)] #[allow(clippy::incompatible_msrv)] let padding_check_bound = padding_check.and_then(|check| (!fields.is_empty()).then_some(check)).map(|check| {
let fields = fields.iter().map(|(_name, ty)| ty);
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_bounds: Option<WherePredicate> = match self_type_trait_bounds {
SelfBounds::None => None,
SelfBounds::All(traits) => Some(bound_tt(&parse_quote!(Self), traits.iter().copied())),
};
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_bounds.iter());
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)
});
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! {
#[allow(deprecated)]
unsafe impl < #(#params),* > #trait_path 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()
}
#[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
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_repr_orderings() {
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() {
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));
}
}