#![allow(unknown_lints)]
#![deny(renamed_and_removed_lints)]
#![deny(clippy::all, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)]
#![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"]
mod r#enum;
mod ext;
#[cfg(test)]
mod output_tests;
mod repr;
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use syn::{
parse_quote, Attribute, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr,
ExprLit, ExprUnary, GenericParam, Ident, Lit, Meta, Path, Type, UnOp, WherePredicate,
};
use crate::{ext::*, repr::*};
macro_rules! derive {
($trait:ident => $outer:ident => $inner:ident) => {
#[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 zerocopy_crate = match extract_zerocopy_crate(&ast.attrs) {
Ok(zerocopy_crate) => zerocopy_crate,
Err(e) => return e.into_compile_error().into(),
};
$inner(&ast, Trait::$trait, &zerocopy_crate).into_ts().into()
}
};
}
trait IntoTokenStream {
fn into_ts(self) -> TokenStream;
}
impl IntoTokenStream for TokenStream {
fn into_ts(self) -> TokenStream {
self
}
}
impl IntoTokenStream for Result<TokenStream, Error> {
fn into_ts(self) -> TokenStream {
match self {
Ok(ts) => ts,
Err(err) => err.to_compile_error(),
}
}
}
fn extract_zerocopy_crate(attrs: &[Attribute]) -> Result<Path, Error> {
let mut path = parse_quote!(::zerocopy);
for attr in 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() {
path = path_lit;
return Ok(());
}
}
return Err(Error::new(
Span::call_site(),
"`crate` attribute requires a path as the value",
));
}
Err(Error::new(
Span::call_site(),
format!("unknown attribute encountered: {}", meta.path.into_token_stream()),
))
})?;
}
}
}
Ok(path)
}
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);
derive!(ByteHash => derive_hash => derive_hash_inner);
derive!(ByteEq => derive_eq => derive_eq_inner);
derive!(SplitAt => derive_split_at => derive_split_at_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,
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let is_repr_c_struct = match &ast.data {
Data::Struct(..) => {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
if repr.is_c() {
Some(repr)
} else {
None
}
}
Data::Enum(..) | Data::Union(..) => None,
};
let fields = ast.data.fields();
let (self_bounds, inner_extras, outer_extras) = if let (
Some(repr),
Some((trailing_field, leading_fields)),
) = (is_repr_c_struct, 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_path = quote!(#zerocopy_crate::util::macro_util::core_reexport);
let repr_align = repr
.get_align()
.map(|align| {
let align = align.t.get();
quote!(#core_path::num::NonZeroUsize::new(#align as usize))
})
.unwrap_or_else(|| quote!(#core_path::option::Option::None));
let repr_packed = repr
.get_packed()
.map(|packed| {
let packed = packed.get();
quote!(#core_path::num::NonZeroUsize::new(#packed as usize))
})
.unwrap_or_else(|| quote!(#core_path::option::Option::None));
let make_methods = |trailing_field_ty| {
quote! {
#[inline(always)]
fn raw_from_ptr_len(
bytes: #zerocopy_crate::util::macro_util::core_reexport::ptr::NonNull<u8>,
meta: Self::PointerMetadata,
) -> #zerocopy_crate::util::macro_util::core_reexport::ptr::NonNull<Self> {
use #zerocopy_crate::KnownLayout;
let trailing = <#trailing_field_ty as KnownLayout>::raw_from_ptr_len(bytes, meta);
let slf = trailing.as_ptr() as *mut Self;
unsafe { #zerocopy_crate::util::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
}
#[inline(always)]
fn pointer_to_metadata(ptr: *mut Self) -> Self::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, _) = ast.generics.split_for_impl();
quote!(
type PointerMetadata = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::PointerMetadata;
type MaybeUninit = __ZerocopyKnownLayoutMaybeUninit #ty_generics;
const LAYOUT: #zerocopy_crate::DstLayout = {
use #zerocopy_crate::util::macro_util::core_reexport::num::NonZeroUsize;
use #zerocopy_crate::{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()
};
#methods
)
};
let outer_extras = {
let ident = &ast.ident;
let vis = &ast.vis;
let params = &ast.generics.params;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let predicates = if let Some(where_clause) = where_clause {
where_clause.predicates.clone()
} else {
Default::default()
};
let field_index =
|name| Ident::new(&format!("__Zerocopy_Field_{}", name), ident.span());
let field_indices: Vec<_> =
fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();
let field_defs = field_indices.iter().zip(&fields).map(|(idx, (vis, _, _))| {
quote! {
#[allow(non_camel_case_types)]
#vis struct #idx;
}
});
let field_impls = field_indices.iter().zip(&fields).map(|(idx, (_, _, ty))| quote! {
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));
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
});
quote! {
#(#field_defs)*
#(#field_impls)*
#repr
#[doc(hidden)]
#[allow(private_bounds)]
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
#(#zerocopy_crate::util::macro_util::core_reexport::mem::MaybeUninit<
<#ident #ty_generics as
#zerocopy_crate::util::macro_util::Field<#leading_field_indices>
>::Type
>,)*
#zerocopy_crate::util::macro_util::core_reexport::mem::ManuallyDrop<
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
>
)
where
#trailing_field_ty: #zerocopy_crate::KnownLayout,
#predicates;
unsafe impl #impl_generics #zerocopy_crate::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
where
#trailing_field_ty: #zerocopy_crate::KnownLayout,
#predicates
{
#[allow(clippy::missing_inline_in_public_items)]
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
}
}
};
(SelfBounds::None, inner_extras, Some(outer_extras))
} else {
(
SelfBounds::SIZED,
quote!(
type PointerMetadata = ();
type MaybeUninit =
#zerocopy_crate::util::macro_util::core_reexport::mem::MaybeUninit<Self>;
const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_type::<Self>();
#[inline(always)]
fn raw_from_ptr_len(
bytes: #zerocopy_crate::util::macro_util::core_reexport::ptr::NonNull<u8>,
_meta: (),
) -> #zerocopy_crate::util::macro_util::core_reexport::ptr::NonNull<Self>
{
bytes.cast::<Self>()
}
#[inline(always)]
fn pointer_to_metadata(_ptr: *mut Self) -> () {}
),
None,
)
};
Ok(match &ast.data {
Data::Struct(strct) => {
let require_trait_bound_on_field_types = if self_bounds == SelfBounds::SIZED {
FieldBounds::None
} else {
FieldBounds::TRAILING_SELF
};
ImplBlockBuilder::new(
ast,
strct,
Trait::KnownLayout,
require_trait_bound_on_field_types,
zerocopy_crate,
)
.self_type_trait_bounds(self_bounds)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
Data::Enum(enm) => {
ImplBlockBuilder::new(ast, enm, Trait::KnownLayout, FieldBounds::None, zerocopy_crate)
.self_type_trait_bounds(SelfBounds::SIZED)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
Data::Union(unn) => {
ImplBlockBuilder::new(ast, unn, Trait::KnownLayout, FieldBounds::None, zerocopy_crate)
.self_type_trait_bounds(SelfBounds::SIZED)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
})
}
fn derive_no_cell_inner(
ast: &DeriveInput,
_top_level: Trait,
zerocopy_crate: &Path,
) -> TokenStream {
match &ast.data {
Data::Struct(strct) => ImplBlockBuilder::new(
ast,
strct,
Trait::Immutable,
FieldBounds::ALL_SELF,
zerocopy_crate,
)
.build(),
Data::Enum(enm) => {
ImplBlockBuilder::new(ast, enm, Trait::Immutable, FieldBounds::ALL_SELF, zerocopy_crate)
.build()
}
Data::Union(unn) => {
ImplBlockBuilder::new(ast, unn, Trait::Immutable, FieldBounds::ALL_SELF, zerocopy_crate)
.build()
}
}
}
fn derive_try_from_bytes_inner(
ast: &DeriveInput,
top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
match &ast.data {
Data::Struct(strct) => derive_try_from_bytes_struct(ast, strct, top_level, zerocopy_crate),
Data::Enum(enm) => derive_try_from_bytes_enum(ast, enm, top_level, zerocopy_crate),
Data::Union(unn) => Ok(derive_try_from_bytes_union(ast, unn, top_level, zerocopy_crate)),
}
}
fn derive_from_zeros_inner(
ast: &DeriveInput,
top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let try_from_bytes = derive_try_from_bytes_inner(ast, top_level, zerocopy_crate)?;
let from_zeros = match &ast.data {
Data::Struct(strct) => derive_from_zeros_struct(ast, strct, zerocopy_crate),
Data::Enum(enm) => derive_from_zeros_enum(ast, enm, zerocopy_crate)?,
Data::Union(unn) => derive_from_zeros_union(ast, unn, zerocopy_crate),
};
Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
}
fn derive_from_bytes_inner(
ast: &DeriveInput,
top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let from_zeros = derive_from_zeros_inner(ast, top_level, zerocopy_crate)?;
let from_bytes = match &ast.data {
Data::Struct(strct) => derive_from_bytes_struct(ast, strct, zerocopy_crate),
Data::Enum(enm) => derive_from_bytes_enum(ast, enm, zerocopy_crate)?,
Data::Union(unn) => derive_from_bytes_union(ast, unn, zerocopy_crate),
};
Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
}
fn derive_into_bytes_inner(
ast: &DeriveInput,
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
match &ast.data {
Data::Struct(strct) => derive_into_bytes_struct(ast, strct, zerocopy_crate),
Data::Enum(enm) => derive_into_bytes_enum(ast, enm, zerocopy_crate),
Data::Union(unn) => derive_into_bytes_union(ast, unn, zerocopy_crate),
}
}
fn derive_unaligned_inner(
ast: &DeriveInput,
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
match &ast.data {
Data::Struct(strct) => derive_unaligned_struct(ast, strct, zerocopy_crate),
Data::Enum(enm) => derive_unaligned_enum(ast, enm, zerocopy_crate),
Data::Union(unn) => derive_unaligned_union(ast, unn, zerocopy_crate),
}
}
fn derive_hash_inner(
ast: &DeriveInput,
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let type_ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
Ok(quote! {
#[allow(deprecated)]
#[automatically_derived]
impl #impl_generics #zerocopy_crate::util::macro_util::core_reexport::hash::Hash for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
fn hash<H>(&self, state: &mut H)
where
H: #zerocopy_crate::util::macro_util::core_reexport::hash::Hasher,
{
#zerocopy_crate::util::macro_util::core_reexport::hash::Hasher::write(
state,
#zerocopy_crate::IntoBytes::as_bytes(self)
)
}
fn hash_slice<H>(data: &[Self], state: &mut H)
where
H: #zerocopy_crate::util::macro_util::core_reexport::hash::Hasher,
{
#zerocopy_crate::util::macro_util::core_reexport::hash::Hasher::write(
state,
#zerocopy_crate::IntoBytes::as_bytes(data)
)
}
}
})
}
fn derive_eq_inner(
ast: &DeriveInput,
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let type_ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
Ok(quote! {
#[allow(deprecated)]
#[automatically_derived]
impl #impl_generics #zerocopy_crate::util::macro_util::core_reexport::cmp::PartialEq for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
fn eq(&self, other: &Self) -> bool {
#zerocopy_crate::util::macro_util::core_reexport::cmp::PartialEq::eq(
#zerocopy_crate::IntoBytes::as_bytes(self),
#zerocopy_crate::IntoBytes::as_bytes(other),
)
}
}
#[allow(deprecated)]
#[automatically_derived]
impl #impl_generics #zerocopy_crate::util::macro_util::core_reexport::cmp::Eq for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
}
})
}
fn derive_split_at_inner(
ast: &DeriveInput,
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
match &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 = 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"));
};
Ok(ImplBlockBuilder::new(
ast,
&ast.data,
Trait::SplitAt,
FieldBounds::TRAILING_SELF,
zerocopy_crate,
)
.inner_extras(quote! {
type Elem = <#trailing_field as ::zerocopy::SplitAt>::Elem;
})
.build())
}
fn derive_try_from_bytes_struct(
ast: &DeriveInput,
strct: &DataStruct,
top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let extras =
try_gen_trivial_is_bit_valid(ast, top_level, zerocopy_crate).unwrap_or_else(|| {
let fields = strct.fields();
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
quote!(
fn is_bit_valid<___ZerocopyAliasing>(
mut candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
use #zerocopy_crate::util::macro_util::core_reexport;
use #zerocopy_crate::pointer::PtrInner;
true #(&& {
let field_candidate = unsafe {
let project = |slf: PtrInner<'_, Self>| {
let slf = slf.as_non_null().as_ptr();
let field = core_reexport::ptr::addr_of_mut!((*slf).#field_names);
let ptr = unsafe { core_reexport::ptr::NonNull::new_unchecked(field) };
unsafe { PtrInner::new(ptr) }
};
candidate.reborrow().cast_unsized_unchecked(project)
};
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
Ok(ImplBlockBuilder::new(
ast,
strct,
Trait::TryFromBytes,
FieldBounds::ALL_SELF,
zerocopy_crate,
)
.inner_extras(extras)
.build())
}
fn derive_try_from_bytes_union(
ast: &DeriveInput,
unn: &DataUnion,
top_level: Trait,
zerocopy_crate: &Path,
) -> TokenStream {
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
let extras =
try_gen_trivial_is_bit_valid(ast, top_level, zerocopy_crate).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);
quote!(
fn is_bit_valid<___ZerocopyAliasing>(
mut candidate: #zerocopy_crate::Maybe<'_, Self,___ZerocopyAliasing>
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
use #zerocopy_crate::util::macro_util::core_reexport;
use #zerocopy_crate::pointer::PtrInner;
false #(|| {
let field_candidate = unsafe {
let project = |slf: PtrInner<'_, Self>| {
let slf = slf.as_non_null().as_ptr();
let field = core_reexport::ptr::addr_of_mut!((*slf).#field_names);
let ptr = unsafe { core_reexport::ptr::NonNull::new_unchecked(field) };
unsafe { PtrInner::new(ptr) }
};
candidate.reborrow().cast_unsized_unchecked(project)
};
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
ImplBlockBuilder::new(ast, unn, Trait::TryFromBytes, field_type_trait_bounds, zerocopy_crate)
.inner_extras(extras)
.build()
}
fn derive_try_from_bytes_enum(
ast: &DeriveInput,
enm: &DataEnum,
top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
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(ast, top_level, zerocopy_crate);
let extra = match (trivial_is_bit_valid, could_be_from_bytes) {
(Some(is_bit_valid), _) => is_bit_valid,
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(zerocopy_crate) },
(None, false) => {
r#enum::derive_is_bit_valid(&ast.ident, &repr, &ast.generics, enm, zerocopy_crate)?
}
};
Ok(ImplBlockBuilder::new(ast, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF, zerocopy_crate)
.inner_extras(extra)
.build())
}
fn try_gen_trivial_is_bit_valid(
ast: &DeriveInput,
top_level: Trait,
zerocopy_crate: &Path,
) -> Option<proc_macro2::TokenStream> {
if top_level == Trait::FromBytes && ast.generics.params.is_empty() {
Some(quote!(
fn is_bit_valid<___ZerocopyAliasing>(
_candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
if false {
fn assert_is_from_bytes<T>()
where
T: #zerocopy_crate::FromBytes,
T: ?#zerocopy_crate::util::macro_util::core_reexport::marker::Sized,
{
}
assert_is_from_bytes::<Self>();
}
true
}
))
} else {
None
}
}
unsafe fn gen_trivial_is_bit_valid_unchecked(zerocopy_crate: &Path) -> proc_macro2::TokenStream {
quote!(
fn is_bit_valid<___ZerocopyAliasing>(
_candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
true
}
)
}
fn derive_from_zeros_struct(
ast: &DeriveInput,
strct: &DataStruct,
zerocopy_crate: &Path,
) -> TokenStream {
ImplBlockBuilder::new(ast, strct, Trait::FromZeros, FieldBounds::ALL_SELF, zerocopy_crate)
.build()
}
fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
let mut next_negative_discriminant = Some(0);
let mut has_unknown_discriminants = false;
for (i, v) in enm.variants.iter().enumerate() {
match v.discriminant.as_ref() {
None => {
match next_negative_discriminant.as_mut() {
Some(0) => return Ok(i),
Some(n) => *n -= 1,
None => (),
}
}
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 => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
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),
Some(x) => next_negative_discriminant = Some(x - 1),
None => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
_ => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
},
_ => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
Err(has_unknown_discriminants)
}
fn derive_from_zeros_enum(
ast: &DeriveInput,
enm: &DataEnum,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
match repr {
Repr::Compound(
Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ },
_,
) => {}
Repr::Transparent(_)
| Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => return Err(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(),
Err(true) => {
return Err(Error::new_spanned(
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.",
));
}
Err(false) => {
return Err(Error::new_spanned(
ast,
"FromZeros only supported on enums with a variant that has a discriminant of `0`",
));
}
};
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(
ast,
enm,
Trait::FromZeros,
FieldBounds::Explicit(explicit_bounds),
zerocopy_crate,
)
.build())
}
fn derive_from_zeros_union(
ast: &DeriveInput,
unn: &DataUnion,
zerocopy_crate: &Path,
) -> TokenStream {
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
ImplBlockBuilder::new(ast, unn, Trait::FromZeros, field_type_trait_bounds, zerocopy_crate)
.build()
}
fn derive_from_bytes_struct(
ast: &DeriveInput,
strct: &DataStruct,
zerocopy_crate: &Path,
) -> TokenStream {
ImplBlockBuilder::new(ast, strct, Trait::FromBytes, FieldBounds::ALL_SELF, zerocopy_crate)
.build()
}
fn derive_from_bytes_enum(
ast: &DeriveInput,
enm: &DataEnum,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
let variants_required = 1usize << enum_size_from_repr(&repr)?;
if enm.variants.len() != variants_required {
return Err(Error::new_spanned(
ast,
format!(
"FromBytes only supported on {} enum with {} variants",
repr.repr_type_name(),
variants_required
),
));
}
Ok(ImplBlockBuilder::new(ast, enm, Trait::FromBytes, FieldBounds::ALL_SELF, zerocopy_crate)
.build())
}
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 | 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),
}
}
fn derive_from_bytes_union(
ast: &DeriveInput,
unn: &DataUnion,
zerocopy_crate: &Path,
) -> TokenStream {
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
ImplBlockBuilder::new(ast, unn, Trait::FromBytes, field_type_trait_bounds, zerocopy_crate)
.build()
}
fn derive_into_bytes_struct(
ast: &DeriveInput,
strct: &DataStruct,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&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 {
(None, false)
} else if is_c && !repr.is_align_gt_1() && num_fields <= 1 {
(None, false)
} else if ast.generics.params.is_empty() {
(Some(PaddingCheck::Struct), false)
} else if is_c && !repr.is_align_gt_1() {
(None, true)
} else {
return Err(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(ast, strct, Trait::IntoBytes, field_bounds, zerocopy_crate)
.padding_check(padding_check)
.build())
}
fn derive_into_bytes_enum(
ast: &DeriveInput,
enm: &DataEnum,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
if !repr.is_c() && !repr.is_primitive() {
return Err(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 = r#enum::generate_tag_enum(&repr, enm);
Ok(ImplBlockBuilder::new(ast, enm, Trait::IntoBytes, FieldBounds::ALL_SELF, zerocopy_crate)
.padding_check(PaddingCheck::Enum { tag_type_definition })
.build())
}
fn derive_into_bytes_union(
ast: &DeriveInput,
unn: &DataUnion,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) {
quote!()
} else {
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!(
const _: () = {
#[cfg(not(zerocopy_derive_union_into_bytes))]
#zerocopy_crate::util::macro_util::core_reexport::compile_error!(#error_message);
};
)
};
if !ast.generics.params.is_empty() {
return Err(Error::new(Span::call_site(), "unsupported on types with type parameters"));
}
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
return Err(Error::new(
Span::call_site(),
"must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
));
}
let impl_block =
ImplBlockBuilder::new(ast, unn, Trait::IntoBytes, FieldBounds::ALL_SELF, zerocopy_crate)
.padding_check(PaddingCheck::Union)
.build();
Ok(quote!(#cfg_compile_error #impl_block))
}
fn derive_unaligned_struct(
ast: &DeriveInput,
strct: &DataStruct,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&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 Err(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(ast, strct, Trait::Unaligned, field_bounds, zerocopy_crate).build())
}
fn derive_unaligned_enum(
ast: &DeriveInput,
enm: &DataEnum,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
if !repr.is_u8() && !repr.is_i8() {
return Err(Error::new(Span::call_site(), "must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment"));
}
Ok(ImplBlockBuilder::new(ast, enm, Trait::Unaligned, FieldBounds::ALL_SELF, zerocopy_crate)
.build())
}
fn derive_unaligned_union(
ast: &DeriveInput,
unn: &DataUnion,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&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 Err(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(ast, unn, Trait::Unaligned, field_type_trait_bounds, zerocopy_crate)
.build())
}
enum PaddingCheck {
Struct,
Union,
Enum { tag_type_definition: TokenStream },
}
impl PaddingCheck {
fn validator_macro_ident(&self) -> Ident {
let s = match self {
PaddingCheck::Struct => "struct_has_padding",
PaddingCheck::Union => "union_has_padding",
PaddingCheck::Enum { .. } => "enum_has_padding",
};
Ident::new(s, Span::call_site())
}
fn validator_macro_context(&self) -> Option<&TokenStream> {
match self {
PaddingCheck::Struct | PaddingCheck::Union => None,
PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Trait {
KnownLayout,
Immutable,
TryFromBytes,
FromZeros,
FromBytes,
IntoBytes,
Unaligned,
Sized,
ByteHash,
ByteEq,
SplitAt,
}
impl ToTokens for Trait {
fn to_tokens(&self, tokens: &mut TokenStream) {
let s = match self {
Trait::KnownLayout => "KnownLayout",
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());
tokens.extend(core::iter::once(TokenTree::Ident(ident)));
}
}
impl Trait {
fn crate_path(&self, zerocopy_crate: &Path) -> Path {
match self {
Self::Sized => {
parse_quote!(#zerocopy_crate::util::macro_util::core_reexport::marker::#self)
}
_ => parse_quote!(#zerocopy_crate::#self),
}
}
}
#[derive(Debug, Eq, PartialEq)]
enum TraitBound {
Slf,
Other(Trait),
}
enum FieldBounds<'a> {
None,
All(&'a [TraitBound]),
Trailing(&'a [TraitBound]),
Explicit(Vec<WherePredicate>),
}
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]),
}
#[allow(clippy::needless_lifetimes)]
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,
})
}
struct ImplBlockBuilder<'a, D: DataExt> {
input: &'a DeriveInput,
data: &'a D,
trt: Trait,
field_type_trait_bounds: FieldBounds<'a>,
zerocopy_crate: &'a Path,
self_type_trait_bounds: SelfBounds<'a>,
padding_check: Option<PaddingCheck>,
inner_extras: Option<TokenStream>,
outer_extras: Option<TokenStream>,
}
impl<'a, D: DataExt> ImplBlockBuilder<'a, D> {
fn new(
input: &'a DeriveInput,
data: &'a D,
trt: Trait,
field_type_trait_bounds: FieldBounds<'a>,
zerocopy_crate: &'a Path,
) -> Self {
Self {
input,
data,
trt,
field_type_trait_bounds,
zerocopy_crate,
self_type_trait_bounds: SelfBounds::None,
padding_check: None,
inner_extras: None,
outer_extras: None,
}
}
fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
self.self_type_trait_bounds = self_type_trait_bounds;
self
}
fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
self.padding_check = padding_check.into();
self
}
fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
self.inner_extras = Some(inner_extras);
self
}
fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
self.outer_extras = outer_extras.into();
self
}
fn build(self) -> TokenStream {
let type_ident = &self.input.ident;
let trait_path = self.trt.crate_path(self.zerocopy_crate);
let fields = self.data.fields();
let variants = self.data.variants();
let tag = self.data.tag();
let zerocopy_crate = self.zerocopy_crate;
fn bound_tt(
ty: &Type,
traits: impl Iterator<Item = Trait>,
zerocopy_crate: &Path,
) -> WherePredicate {
let traits = traits.map(|t| t.crate_path(zerocopy_crate));
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), zerocopy_crate)
})
.collect(),
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
(FieldBounds::Trailing(traits), [.., last]) => {
vec![bound_tt(last.2, normalize_bounds(self.trt, traits), zerocopy_crate)]
}
(FieldBounds::Explicit(bounds), _) => bounds,
};
#[allow(unstable_name_collisions)] let padding_check_bound = self
.padding_check
.and_then(|check| (!fields.is_empty()).then_some(check))
.map(|check| {
let variant_types = variants.iter().map(|var| {
let types = var.iter().map(|(_vis, _name, ty)| ty);
quote!([#(#types),*])
});
let validator_context = check.validator_macro_context();
let validator_macro = check.validator_macro_ident();
let t = tag.iter();
parse_quote! {
(): #zerocopy_crate::util::macro_util::PaddingFree<
Self,
{
#validator_context
#zerocopy_crate::#validator_macro!(Self, #(#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().copied(), zerocopy_crate))
}
};
let bounds = self
.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 = self.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 = self.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})
}
});
let inner_extras = self.inner_extras;
let impl_tokens = quote! {
#[allow(deprecated)]
#[automatically_derived]
unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
where
#(#bounds,)*
{
fn only_derive_is_allowed_to_implement_this_trait() {}
#inner_extras
}
};
if let Some(outer_extras) = self.outer_extras {
quote! {
const _: () = {
#impl_tokens
#outer_extras
};
}
} else {
impl_tokens
}
}
}
#[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
}
}
}