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 project;
7pub mod try_from_bytes;
8pub mod unaligned;
9
10use proc_macro2::{Span, TokenStream};
11use quote::quote;
12use syn::{Data, Error};
13
14use crate::{
15    repr::StructUnionRepr,
16    util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
17};
18
19pub(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
33pub(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
61pub(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
95pub(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}