Skip to main content

zerocopy_derive/derive/
mod.rs

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