zerodds_flatdata_derive/
lib.rs1#![warn(missing_docs)]
44
45use proc_macro::TokenStream;
46use proc_macro2::TokenStream as TokenStream2;
47use quote::quote;
48use sha2::{Digest, Sha256};
49use syn::{Attribute, Data, DeriveInput, Fields, parse_macro_input};
50
51#[proc_macro_derive(FlatStruct)]
53pub fn derive_flat_struct(input: TokenStream) -> TokenStream {
54 let input = parse_macro_input!(input as DeriveInput);
55 expand(input).unwrap_or_else(|e| e.to_compile_error().into())
56}
57
58fn expand(input: DeriveInput) -> Result<TokenStream, syn::Error> {
59 let name = &input.ident;
60 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
61
62 let layout_string = match &input.data {
63 Data::Struct(s) => layout_signature(name, &s.fields),
64 Data::Enum(_) => {
65 return Err(syn::Error::new_spanned(
66 &input,
67 "FlatStruct kann nicht auf enum derive werden — repr(C)-Enum-Layout ist nicht stable",
68 ));
69 }
70 Data::Union(_) => {
71 return Err(syn::Error::new_spanned(
72 &input,
73 "FlatStruct kann nicht auf union derive werden",
74 ));
75 }
76 };
77
78 if !has_repr_c_or_transparent(&input.attrs) {
79 return Err(syn::Error::new_spanned(
80 &input,
81 "FlatStruct verlangt #[repr(C)] oder #[repr(transparent)] — \
82 Default-repr-Rust hat undefiniertes Field-Layout, daher \
83 waere `as_bytes()`/`from_bytes_unchecked()` UB",
84 ));
85 }
86
87 let mut hasher = Sha256::new();
88 hasher.update(layout_string.as_bytes());
89 let digest = hasher.finalize();
90 let hash_bytes: [u8; 16] = match digest[..16].try_into() {
91 Ok(b) => b,
92 Err(_) => {
93 return Err(syn::Error::new_spanned(
94 &input,
95 "internal: sha256 truncate to 16 bytes failed",
96 ));
97 }
98 };
99 let hash_tokens = hash_bytes.iter().map(|b| quote! { #b });
100
101 let expanded: TokenStream2 = quote! {
102 #[automatically_derived]
109 unsafe impl #impl_generics ::zerodds_flatdata::FlatStruct for #name #ty_generics #where_clause {
110 const TYPE_HASH: [u8; 16] = [#( #hash_tokens ),*];
111 }
112 };
113 Ok(expanded.into())
114}
115
116fn has_repr_c_or_transparent(attrs: &[Attribute]) -> bool {
117 for attr in attrs {
118 if !attr.path().is_ident("repr") {
119 continue;
120 }
121 let mut found = false;
122 let _ = attr.parse_nested_meta(|meta| {
123 if meta.path.is_ident("C") || meta.path.is_ident("transparent") {
124 found = true;
125 }
126 Ok(())
127 });
128 if found {
129 return true;
130 }
131 }
132 false
133}
134
135fn layout_signature(name: &syn::Ident, fields: &Fields) -> String {
144 let mut s = name.to_string();
145 s.push('{');
146 match fields {
147 Fields::Named(f) => {
148 for (i, field) in f.named.iter().enumerate() {
149 if i > 0 {
150 s.push(',');
151 }
152 if let Some(id) = &field.ident {
153 s.push_str(&id.to_string());
154 s.push(':');
155 }
156 s.push_str(&type_signature(&field.ty));
157 }
158 }
159 Fields::Unnamed(f) => {
160 for (i, field) in f.unnamed.iter().enumerate() {
161 if i > 0 {
162 s.push(',');
163 }
164 s.push_str(&type_signature(&field.ty));
165 }
166 }
167 Fields::Unit => {
168 s.push_str("()");
169 }
170 }
171 s.push('}');
172 s
173}
174
175fn type_signature(ty: &syn::Type) -> String {
176 quote! { #ty }.to_string().replace(' ', "")
177}