1#![doc = include_str!("../README.md")]
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{parse_macro_input, Data, DeriveInput};
8
9#[proc_macro_derive(RTA)]
23pub fn derive_rta(input: TokenStream) -> TokenStream {
24 let input = parse_macro_input!(input as DeriveInput);
25 let ident = input.ident;
26
27 if !has_repr_c(&input.attrs) {
28 return syn::Error::new_spanned(ident, "RTA derive error: struct must use #[repr(C)] for stable layout")
29 .to_compile_error()
30 .into();
31 }
32
33 let mut hash = hasher::hash(0, ident.to_string().as_bytes());
34
35 let fields = match input.data {
36 Data::Struct(s) => s.fields,
37 _ => {
38 return syn::Error::new_spanned(ident, "RTA derive error: RTA can only be derived for a struct")
39 .to_compile_error()
40 .into();
41 }
42 };
43
44 let field_sizes: Vec<_> = fields
45 .iter()
46 .map(|f| {
47 let ty = &f.ty;
48 quote! { core::mem::size_of::<#ty>() }
49 })
50 .collect();
51
52 for field in fields {
53 if let Some(ident) = field.ident {
54 hash = hasher::hash(hash, b"|");
55 hash = hasher::hash(hash, ident.to_string().as_bytes());
56 }
57
58 let ty = field.ty;
59 let ty_str = quote!(#ty).to_string();
60
61 hash = hasher::hash(hash, b":");
62 hash = hasher::hash(hash, ty_str.as_bytes());
63 }
64
65 let expanded = quote! {
66 unsafe impl rta::RTA for #ident {
67 const HASH: u64 = #hash;
68 const SIZE: usize = core::mem::size_of::<Self>();
69 }
70
71 const _: () = {
72 let field_sum =
73 0 #( + #field_sizes )*;
74
75 assert!(
76 core::mem::size_of::<#ident>() > 0,
77 concat!(
78 "RTA derive error: struct `",
79 stringify!(#ident),
80 "` cannot be zero-sized"
81 )
82 );
83
84 assert!(
85 core::mem::size_of::<#ident>() == field_sum,
86 concat!(
87 "RTA derive error: struct `",
88 stringify!(#ident),
89 "` contains padding. ",
90 "All fields must pack exactly with #[repr(C)]. ",
91 "Reorder fields or add explicit padding fields."
92 )
93 );
94
95 assert!(
96 core::mem::size_of::<#ident>() % 8 == 0,
97 concat!(
98 "RTA derive error: struct `",
99 stringify!(#ident),
100 "` size must be a multiple of 8 bytes ",
101 "Add padding fields or reorder members."
102 )
103 );
104 };
105 };
106
107 TokenStream::from(expanded)
108}
109
110fn has_repr_c(attrs: &[syn::Attribute]) -> bool {
111 attrs.iter().any(|attr| {
112 if !attr.path().is_ident("repr") {
113 return false;
114 }
115
116 attr.parse_args_with(syn::punctuated::Punctuated::<syn::Ident, syn::Token![,]>::parse_terminated)
117 .map(|idents| idents.iter().any(|i| i == "C"))
118 .unwrap_or(false)
119 })
120}
121
122mod hasher {
123 const FNV_PRIME: u64 = 0x100000001b3;
124 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
125
126 pub const fn hash(mut hash: u64, bytes: &[u8]) -> u64 {
128 if hash == 0 {
129 hash = FNV_OFFSET;
130 }
131
132 let mut i = 0;
133 while i < bytes.len() {
134 hash ^= bytes[i] as u64;
135 hash = hash.wrapping_mul(FNV_PRIME);
136 i += 1;
137 }
138
139 hash
140 }
141
142 #[cfg(test)]
143 mod tests {
144 use super::hash;
145
146 #[test]
147 fn same_input_same_hash() {
148 let h1 = hash(0, b"u64");
149 let h2 = hash(0, b"u64");
150 assert_eq!(h1, h2);
151 }
152
153 #[test]
154 fn different_inputs_different_hashes() {
155 let h1 = hash(0, b"u64");
156 let h2 = hash(0, b"u32");
157 assert_ne!(h1, h2);
158 }
159
160 #[test]
161 fn order_sensitive_hashing() {
162 let h1 = hash(0, b"u64u32");
163 let h2 = hash(0, b"u32u64");
164 assert_ne!(h1, h2);
165 }
166
167 #[test]
168 fn hasher_incremental_equals_one_shot() {
169 let mut h = hash(0, b"u64");
170 h = hash(h, b"u32");
171
172 let one_shot = hash(0, b"u64u32");
173
174 assert_eq!(h, one_shot);
175 }
176
177 #[test]
178 fn hasher_empty_is_offset_basis() {
179 let h = hash(0, b"");
180 assert_eq!(h, 0xcbf29ce484222325);
181 }
182 }
183}