xref: /linux/rust/zerocopy-derive/derive/mod.rs (revision 12d5fc96ebbb3b948861805b32a4c3a96105ccd9)
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 project;
7 pub mod try_from_bytes;
8 pub mod unaligned;
9 
10 use proc_macro2::{Span, TokenStream};
11 use quote::quote;
12 use syn::{Data, Error};
13 
14 use crate::{
15     repr::StructUnionRepr,
16     util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
17 };
18 
19 pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
20     Ok(match &ctx.ast.data {
21         Data::Struct(strct) => {
22             ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
23         }
24         Data::Enum(enm) => {
25             ImplBlockBuilder::new(ctx, enm, Trait::Immutable, FieldBounds::ALL_SELF).build()
26         }
27         Data::Union(unn) => {
28             ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
29         }
30     })
31 }
32 
33 pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
34     // This doesn't delegate to `impl_block` because `impl_block` assumes it is
35     // deriving a `zerocopy`-defined trait, and these trait impls share a common
36     // shape that `Hash` does not. In particular, `zerocopy` traits contain a
37     // method that only `zerocopy_derive` macros are supposed to implement, and
38     // `impl_block` generating this trait method is incompatible with `Hash`.
39     let type_ident = &ctx.ast.ident;
40     let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
41     let where_predicates = where_clause.map(|clause| &clause.predicates);
42     let zerocopy_crate = &ctx.zerocopy_crate;
43     let core = ctx.core_path();
44     Ok(quote! {
45         impl #impl_generics #core::hash::Hash for #type_ident #ty_generics
46         where
47             Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
48             #where_predicates
49         {
50             fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
51                 #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self))
52             }
53 
54             fn hash_slice<H: #core::hash::Hasher>(data: &[Self], state: &mut H) {
55                 #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data))
56             }
57         }
58     })
59 }
60 
61 pub(crate) fn derive_eq(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
62     // This doesn't delegate to `impl_block` because `impl_block` assumes it is
63     // deriving a `zerocopy`-defined trait, and these trait impls share a common
64     // shape that `Eq` does not. In particular, `zerocopy` traits contain a
65     // method that only `zerocopy_derive` macros are supposed to implement, and
66     // `impl_block` generating this trait method is incompatible with `Eq`.
67     let type_ident = &ctx.ast.ident;
68     let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
69     let where_predicates = where_clause.map(|clause| &clause.predicates);
70     let zerocopy_crate = &ctx.zerocopy_crate;
71     let core = ctx.core_path();
72     Ok(quote! {
73         impl #impl_generics #core::cmp::PartialEq for #type_ident #ty_generics
74         where
75             Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
76             #where_predicates
77         {
78             fn eq(&self, other: &Self) -> bool {
79                 #core::cmp::PartialEq::eq(
80                     #zerocopy_crate::IntoBytes::as_bytes(self),
81                     #zerocopy_crate::IntoBytes::as_bytes(other),
82                 )
83             }
84         }
85 
86         impl #impl_generics #core::cmp::Eq for #type_ident #ty_generics
87         where
88             Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
89             #where_predicates
90         {
91         }
92     })
93 }
94 
95 pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
96     let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
97 
98     match &ctx.ast.data {
99         Data::Struct(_) => {}
100         Data::Enum(_) | Data::Union(_) => {
101             return ctx
102                 .error_or_skip(Error::new(Span::call_site(), "can only be applied to structs"));
103         }
104     };
105 
106     if !(repr.is_c() || repr.is_transparent()) {
107         return ctx.error_or_skip(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 ctx.error_or_skip(Error::new(Span::call_site(), "must at least one field"));
118     };
119 
120     let zerocopy_crate = &ctx.zerocopy_crate;
121     let elem = quote!(<#trailing_field as #zerocopy_crate::SplitAt>::Elem);
122     let elem =
123         if repr.get_packed().is_some() { quote!(#zerocopy_crate::Unalign<#elem>) } else { elem };
124 
125     // SAFETY: The checks above require `repr(C)` or `repr(transparent)`, and
126     // `FieldBounds::TRAILING_SELF` requires the trailing field to be `SplitAt`.
127     // Its `Elem` therefore has the size, bit validity, and `UnsafeCell`
128     // coverage of the actual trailing element. For an unpacked struct, the
129     // trailing field is aligned, so its `SplitAt` alignment guarantee applies.
130     // For a packed struct, wrapping its `Elem` in `Unalign` preserves those
131     // representation properties and gives alignment 1. Thus the trailing
132     // slice can be accessed as `[#elem]` even if packing misaligns its field.
133     // Packing does not change the field's internal layout [1], so its element
134     // size and byte offsets are preserved in either case.
135     //
136     // [1] Per https://doc.rust-lang.org/1.93.0/reference/type-layout.html#layout.repr.alignment.intro:
137     //
138     //   `packed` may also alter the padding between fields (although it will
139     //   not alter the padding inside of any field).
140     Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::SplitAt, FieldBounds::TRAILING_SELF)
141         .inner_extras(quote! {
142             type Elem = #elem;
143         })
144         .build())
145 }
146