match-by-hash 1.0.1

Match statement, but for any value and with a hash function
Documentation
use proc_macro2::{TokenStream, Ident, Span};
use quote::quote;

use crate::input::Input;

#[cfg(debug_assertions)]
fn check_collisions(items: &[Ident]) -> TokenStream {
    let len = items.len();
    quote!(
        const __NO_COLLISIONS: () = {
            let hashes: [usize; #len] = [#(#items),*];
            let mut i: usize = 0;
            while i < #len {
                let mut j: usize = i + 1;
                while j < #len {
                    if hashes[i] == hashes[j] {
                        panic!("Collision occured at keys");
                    }
                    j += 1;
                }
                i += 1;
            }
        };
    )
}

pub fn into_stream(input: &Input) -> TokenStream {
    let hash = &input.hash_function;
    let target = &input.target;
    let default = &input.default_arm;

    let arms = input.arms.iter();

    let arm_keys = arms.clone().map(|x| &x.key);

    let arm_exprs = arms.clone().map(|x| &x.value);

    let hash_names: Vec<_> = (0usize..arms.len()).map(|x| {
        Ident::new(&format!("__HASH{}", x), Span::call_site())
    }).collect();

    let const_hashes = hash_names.iter()
        .zip(arm_keys.clone())
        .map(|(name, value)| quote!(const #name: usize = #hash(#value);));

    #[cfg(debug_assertions)]
    let collision_check = check_collisions(&hash_names);
    #[cfg(not(debug_assertions))]
    let collision_check = TokenStream::new();

    let arms_quoted = hash_names.iter()
        .zip(arm_keys)
        .zip(arm_exprs)
        .map(|((a, b), c)| (a, b, c))
        .map(|(hash, key, expr)| quote!(#hash => if v == #key { Some(#expr) } else { None }));

    quote!({
        #(#const_hashes)*
        #collision_check
        let v = #target;
        let result = match #hash(v) {
            #(#arms_quoted,)*
            _ => None,
        };
        match result {
            Some(v) => v,
            None => #default,
        }
    })
}