#![warn(missing_docs)]
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use sha2::{Digest, Sha256};
use syn::{Attribute, Data, DeriveInput, Fields, parse_macro_input};
#[proc_macro_derive(FlatStruct)]
pub fn derive_flat_struct(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand(input).unwrap_or_else(|e| e.to_compile_error().into())
}
fn expand(input: DeriveInput) -> Result<TokenStream, syn::Error> {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let layout_string = match &input.data {
Data::Struct(s) => layout_signature(name, &s.fields),
Data::Enum(_) => {
return Err(syn::Error::new_spanned(
&input,
"FlatStruct kann nicht auf enum derive werden — repr(C)-Enum-Layout ist nicht stable",
));
}
Data::Union(_) => {
return Err(syn::Error::new_spanned(
&input,
"FlatStruct kann nicht auf union derive werden",
));
}
};
if !has_repr_c_or_transparent(&input.attrs) {
return Err(syn::Error::new_spanned(
&input,
"FlatStruct verlangt #[repr(C)] oder #[repr(transparent)] — \
Default-repr-Rust hat undefiniertes Field-Layout, daher \
waere `as_bytes()`/`from_bytes_unchecked()` UB",
));
}
let mut hasher = Sha256::new();
hasher.update(layout_string.as_bytes());
let digest = hasher.finalize();
let hash_bytes: [u8; 16] = match digest[..16].try_into() {
Ok(b) => b,
Err(_) => {
return Err(syn::Error::new_spanned(
&input,
"internal: sha256 truncate to 16 bytes failed",
));
}
};
let hash_tokens = hash_bytes.iter().map(|b| quote! { #b });
let expanded: TokenStream2 = quote! {
#[automatically_derived]
unsafe impl #impl_generics ::zerodds_flatdata::FlatStruct for #name #ty_generics #where_clause {
const TYPE_HASH: [u8; 16] = [#( #hash_tokens ),*];
}
};
Ok(expanded.into())
}
fn has_repr_c_or_transparent(attrs: &[Attribute]) -> bool {
for attr in attrs {
if !attr.path().is_ident("repr") {
continue;
}
let mut found = false;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("C") || meta.path.is_ident("transparent") {
found = true;
}
Ok(())
});
if found {
return true;
}
}
false
}
fn layout_signature(name: &syn::Ident, fields: &Fields) -> String {
let mut s = name.to_string();
s.push('{');
match fields {
Fields::Named(f) => {
for (i, field) in f.named.iter().enumerate() {
if i > 0 {
s.push(',');
}
if let Some(id) = &field.ident {
s.push_str(&id.to_string());
s.push(':');
}
s.push_str(&type_signature(&field.ty));
}
}
Fields::Unnamed(f) => {
for (i, field) in f.unnamed.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&type_signature(&field.ty));
}
}
Fields::Unit => {
s.push_str("()");
}
}
s.push('}');
s
}
fn type_signature(ty: &syn::Type) -> String {
quote! { #ty }.to_string().replace(' ', "")
}