1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use quote::quote;
use syn::__private::TokenStream2;
use syn::{parse_macro_input, Data, DeriveInput, Fields};

#[proc_macro]
pub fn expr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = TokenStream2::from(input);
    let str_input = input.to_string();
    proc_macro::TokenStream::from(
        quote! {::shoulda::core::specifics::panic::Expression::new(#input, #str_input.to_string())},
    )
}

#[proc_macro_derive(Shoulda)]
pub fn shoulda(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;
    let body = match input.data {
        Data::Struct(s) => match s.fields {
            Fields::Named(n) => n
                .named
                .iter()
                .map(|x| x.ident.as_ref().unwrap())
                .map(|x| {
                    quote! {
                        self.#x.should_eq::<Epsilon>(&other.#x)
                    }
                })
                .map(|x| x.to_string())
                .collect::<Vec<String>>()
                .join(" && ")
                .parse::<TokenStream2>()
                .unwrap(),
            Fields::Unnamed(u) => u
                .unnamed
                .iter()
                .enumerate()
                .map(|(x, _)| x.to_string().parse::<TokenStream2>().unwrap())
                .map(|x| {
                    quote! {
                        self.#x.should_eq::<Epsilon>(&other.#x)
                    }
                })
                .map(|x| x.to_string())
                .collect::<Vec<String>>()
                .join(" && ")
                .parse::<TokenStream2>()
                .unwrap(),
            Fields::Unit => quote! {
                true
            },
        },
        Data::Enum(e) => {
            let matches = e.variants.iter().map(|x| {
                let variant = &x.ident;
                match &x.fields {
                    Fields::Named(_) => {
                    let size = x.fields.iter().enumerate().map(|x| (x.1.ident.as_ref().unwrap().to_string(), format!("__{}",  x.0))).collect::<Vec<(String, String)>>();
                    let a_var_args = format!("{{{}}}", size.iter().map(|(a,b)|format!("{}:{}a", a,b)).collect::<Vec<String>>().join(","));
                    let b_var_args = format!("{{{}}}", size.iter().map(|(a,b)|format!("{}:{}b", a,b)).collect::<Vec<String>>().join(","));
                    let eval: String = size.iter().map(|x|format!("{0}a.should_eq::<Epsilon>({0}b)", x.1)).collect::<Vec<String>>().join(" && ");
                    format!("({name}::{variant}{a_var_args}, {name}::{variant}{b_var_args}) => {eval}, ",
                            name = name, variant = variant, a_var_args = a_var_args, b_var_args = b_var_args, eval = eval)
                        .parse::<TokenStream2>()
                        .unwrap()
                    }
                    Fields::Unnamed(_) => {
                    let size = x.fields.iter().enumerate().map(|x| format!("__{}", x.0)).collect::<Vec<String>>();
                    let a_var_args = format!("({})", size.iter().map(|x|format!("{}a", x)).collect::<Vec<String>>().join(","));
                    let b_var_args = format!("({})", size.iter().map(|x|format!("{}b", x)).collect::<Vec<String>>().join(","));
                    let eval: String = size.iter().map(|x|format!("{0}a.should_eq::<Epsilon>({0}b)", x)).collect::<Vec<String>>().join(" && ");
                    format!("({name}::{variant}{a_var_args}, {name}::{variant}{b_var_args}) => {eval}, ",
                            name = name, variant = variant, a_var_args = a_var_args, b_var_args = b_var_args, eval = eval)
                        .parse::<TokenStream2>()
                        .unwrap()
                    }
                    Fields::Unit => quote! {
                        (#name::#variant, #name::#variant) => true,
                    }
                }
            }).fold(String::new(), |acc, x| {
                acc + x.to_string().as_str()
            }).parse::<TokenStream2>().unwrap();
            quote! {
                match (self, other) {
                    #matches
                    _ => false
                }
            }
        }
        Data::Union(_) => panic!("assertable union types not supported"),
    };

    let generics = input.generics;

    let expanded = quote! {
        impl#generics ::shoulda::core::shoulda_equal::ShouldaEqual for #name#generics {
            fn should_eq<Epsilon: ::shoulda::core::epsilon_provider::EpsilonProvider>(&self, other: &Self) -> bool {
                #body
            }
        }
    };
    //panic!("{}", expanded.to_string());
    proc_macro::TokenStream::from(expanded)
}