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