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; } }