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
#[macro_use]
extern crate syn;
extern crate proc_macro;

use crate::case_parser::CasesFn;
use quote::quote;
use syn::{Attribute, Block, Expr, Ident, Type, Visibility};

mod case_parser;

#[proc_macro_attribute]
pub fn parameterized(
    _args: proc_macro::TokenStream,
    fn_body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let inputs = parse_macro_input!(fn_body as CasesFn);

    let visibility = inputs.fn_visibility();
    let mod_ident = inputs.fn_ident();
    let other_attributes = inputs.regular_attrs();
    let test_parameters = inputs.fn_parameters();
    let arguments = inputs.case_attrs();
    let body = inputs.fn_body();
    let test_cases = arguments
        .iter()
        .map(|exprs| exprs.values.iter().collect::<Vec<&Expr>>())
        .enumerate()
        .map(|(nth, exprs)| {
            create_test_case(
                Ident::new(&format!("case_{}", nth), inputs.fn_span()),
                &test_parameters,
                &exprs,
                body,
                visibility,
                &other_attributes,
            )
        });

    (quote! {
        #[cfg(test)]
        #visibility mod #mod_ident {
            use super::*;

            #(#test_cases)*
        }

    })
    .into()
}

fn create_test_case(
    ident: Ident,
    params: &[(&Ident, &Type)],
    exprs: &[&Expr],
    body: &Block,
    vis: &Visibility,
    attributes: &[&Attribute],
) -> proc_macro2::TokenStream {
    assert_eq!(
        params.len(),
        exprs.len(),
        "[sif_macro] A case has an insufficient amount of arguments ({} parameter(s) registered, but {} argument(s) were supplied)",
        params.len(),
        exprs.len()
    );

    let bindings = (0..params.len()).map(|i| create_binding(params[i], exprs[i]));

    quote! {
        #[test]
        #(#attributes)*
        #vis fn #ident() {
            #(#bindings)*
            #body
        }
    }
}

fn create_binding(param: (&Ident, &Type), expr: &Expr) -> proc_macro2::TokenStream {
    let (ident, typ) = param;

    quote! {
        let #ident: #typ = #expr;
    }
}