extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput};
const FNV_PRIME: u64 = 0x100000001b3;
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
fn hasher(mut hash: u64, bytes: &[u8]) -> u64 {
if hash == 0 {
hash = FNV_OFFSET;
}
for b in bytes {
hash ^= *b as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
#[proc_macro_derive(RTA)]
pub fn derive_rta(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = input.ident;
let mut hash = hasher(0, ident.to_string().as_bytes());
let fields = match input.data {
Data::Struct(s) => s.fields,
_ => panic!("RTA can only be derived for a *struct*"),
};
for field in fields {
let ty = field.ty;
let ty_str = quote!(#ty).to_string();
hash = hasher(hash, ty_str.as_bytes());
}
let expanded = quote! {
unsafe impl rta::RTA for #ident {
const HASH: u64 = #hash;
}
};
TokenStream::from(expanded)
}
#[cfg(test)]
mod tests {
use super::hasher;
mod hash {
use super::*;
#[test]
fn hasher_same_input_same_hash() {
let h1 = hasher(0, b"u64");
let h2 = hasher(0, b"u64");
assert_eq!(h1, h2);
}
#[test]
fn hasher_different_inputs_different_hashes() {
let h1 = hasher(0, b"u64");
let h2 = hasher(0, b"u32");
assert_ne!(h1, h2);
}
#[test]
fn hasher_order_sensitive() {
let h1 = hasher(0, b"u64u32");
let h2 = hasher(0, b"u32u64");
assert_ne!(h1, h2);
}
#[test]
fn hasher_incremental_equals_one_shot() {
let mut h = hasher(0, b"u64");
h = hasher(h, b"u32");
let one_shot = hasher(0, b"u64u32");
assert_eq!(h, one_shot);
}
#[test]
fn hasher_empty_is_offset_basis() {
let h = hasher(0, b"");
assert_eq!(h, 0xcbf29ce484222325);
}
}
}