Skip to main content

zerocopy_derive/derive/
mod.rs

1// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2
3pub mod from_bytes;
4pub mod into_bytes;
5pub mod known_layout;
6pub mod try_from_bytes;
7pub mod unaligned;
8
9use proc_macro2::{Span, TokenStream};
10use quote::quote;
11use syn::{Data, Error};
12
13use crate::{
14    repr::StructUnionRepr,
15    util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
16};
17
18pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream {
19    match &ctx.ast.data {
20        Data::Struct(strct) => {
21            ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
22        }
23        Data::Enum(enm) => {
24            ImplBlockBuilder::new(ctx, enm, Trait::Immutable, FieldBounds::ALL_SELF).build()
25        }
26        Data::Union(unn) => {
27            ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
28        }
29    }
30}
31
32pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
33    // This doesn't delegate to `impl_block` because `impl_block` assumes it is
34    // deriving a `zerocopy`-defined trait, and these trait impls share a common
35    // shape that `Hash` does not. In particular, `zerocopy` traits contain a
36    // method that only `zerocopy_derive` macros are supposed to implement, and
37    // `impl_block` generating this trait method is incompatible with `Hash`.
38    let type_ident = &ctx.ast.ident;
39    let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
40    let where_predicates = where_clause.map(|clause| &clause.predicates);
41    let zerocopy_crate = &ctx.zerocopy_crate;
42    let core = ctx.core_path();
43    Ok(quote! {
44        impl #impl_generics #core::hash::Hash for #type_ident #ty_generics
45        where
46            Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
47            #where_predicates
48        {
49            fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
50                #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self))
51            }
52
53            fn hash_slice<H: #core::hash::Hasher>(data: &[Self], state: &mut H) {
54                #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data))
55            }
56        }
57    })
58}
59
60pub(crate) fn derive_eq(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
61    // This doesn't delegate to `impl_block` because `impl_block` assumes it is
62    // deriving a `zerocopy`-defined trait, and these trait impls share a common
63    // shape that `Eq` does not. In particular, `zerocopy` traits contain a
64    // method that only `zerocopy_derive` macros are supposed to implement, and
65    // `impl_block` generating this trait method is incompatible with `Eq`.
66    let type_ident = &ctx.ast.ident;
67    let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
68    let where_predicates = where_clause.map(|clause| &clause.predicates);
69    let zerocopy_crate = &ctx.zerocopy_crate;
70    let core = ctx.core_path();
71    Ok(quote! {
72        impl #impl_generics #core::cmp::PartialEq for #type_ident #ty_generics
73        where
74            Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
75            #where_predicates
76        {
77            fn eq(&self, other: &Self) -> bool {
78                #core::cmp::PartialEq::eq(
79                    #zerocopy_crate::IntoBytes::as_bytes(self),
80                    #zerocopy_crate::IntoBytes::as_bytes(other),
81                )
82            }
83        }
84
85        impl #impl_generics #core::cmp::Eq for #type_ident #ty_generics
86        where
87            Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
88            #where_predicates
89        {
90        }
91    })
92}
93
94pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
95    let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
96
97    match &ctx.ast.data {
98        Data::Struct(_) => {}
99        Data::Enum(_) | Data::Union(_) => {
100            return Err(Error::new(Span::call_site(), "can only be applied to structs"));
101        }
102    };
103
104    if repr.get_packed().is_some() {
105        return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute"));
106    }
107
108    if !(repr.is_c() || repr.is_transparent()) {
109        return Err(Error::new(
110            Span::call_site(),
111            "must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable",
112        ));
113    }
114
115    let fields = ctx.ast.data.fields();
116    let trailing_field = if let Some(((_, _, trailing_field), _)) = fields.split_last() {
117        trailing_field
118    } else {
119        return Err(Error::new(Span::call_site(), "must at least one field"));
120    };
121
122    let zerocopy_crate = &ctx.zerocopy_crate;
123    // SAFETY: `#ty`, per the above checks, is `repr(C)` or `repr(transparent)`
124    // and is not packed; its trailing field is guaranteed to be well-aligned
125    // for its type. By invariant on `FieldBounds::TRAILING_SELF`, the trailing
126    // slice of the trailing field is also well-aligned for its type.
127    Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::SplitAt, FieldBounds::TRAILING_SELF)
128        .inner_extras(quote! {
129            type Elem = <#trailing_field as #zerocopy_crate::SplitAt>::Elem;
130        })
131        .build())
132}