xref: /linux/rust/zerocopy-derive/derive/mod.rs (revision 5f85604cf0877b0369dfd68cd50cf61c0f134819)
1 // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2 
3 pub mod from_bytes;
4 pub mod into_bytes;
5 pub mod known_layout;
6 pub mod try_from_bytes;
7 pub mod unaligned;
8 
9 use proc_macro2::{Span, TokenStream};
10 use quote::quote;
11 use syn::{Data, Error};
12 
13 use crate::{
14     repr::StructUnionRepr,
15     util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
16 };
17 
18 pub(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 
32 pub(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 
60 pub(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 
94 pub(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 }
133