sample_test_macros/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3extern crate quote;
4extern crate syn;
5
6use std::mem;
7
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::{
11    parse::{Parse, Parser},
12    spanned::Spanned,
13};
14
15#[proc_macro_attribute]
16pub fn sample_test(_args: TokenStream, input: TokenStream) -> TokenStream {
17    let output = match syn::Item::parse.parse(input.clone()) {
18        Ok(syn::Item::Fn(mut item_fn)) => {
19            let mut inputs = syn::punctuated::Punctuated::new();
20            let mut samplers: syn::punctuated::Punctuated<_, syn::token::Comma> =
21                syn::punctuated::Punctuated::new();
22            let mut errors = Vec::new();
23
24            item_fn
25                .sig
26                .inputs
27                .iter_mut()
28                .for_each(|input| match *input {
29                    syn::FnArg::Typed(syn::PatType {
30                        ref mut ty,
31                        ref mut attrs,
32                        ..
33                    }) => {
34                        let ix = attrs
35                            .iter()
36                            .position(|a| a.path.segments.iter().map(|s| &s.ident).eq(["sample"]));
37                        if let Some(ix) = ix {
38                            samplers.push(attrs.remove(ix).tokens);
39                            inputs.push(syn::BareFnArg {
40                                attrs: attrs.clone(),
41                                name: None,
42                                ty: *ty.clone(),
43                            })
44                        }
45                    }
46                    _ => errors.push(syn::parse::Error::new(
47                        input.span(),
48                        "unsupported kind of function argument",
49                    )),
50                });
51
52            if errors.is_empty() {
53                let attrs = mem::replace(&mut item_fn.attrs, Vec::new());
54                let name = &item_fn.sig.ident;
55                let fn_type = syn::TypeBareFn {
56                    lifetimes: None,
57                    unsafety: item_fn.sig.unsafety.clone(),
58                    abi: item_fn.sig.abi.clone(),
59                    fn_token: <syn::Token![fn]>::default(),
60                    paren_token: syn::token::Paren::default(),
61                    inputs,
62                    variadic: item_fn.sig.variadic.clone(),
63                    output: item_fn.sig.output.clone(),
64                };
65
66                let x = samplers.clone();
67                quote! {
68                    #[test]
69                    #(#attrs)*
70                    fn #name() {
71                        let sampler = (#x,);
72
73                        #item_fn
74                        ::sample_test::tester::sample_test(sampler, #name as #fn_type)
75                    }
76                }
77            } else {
78                errors
79                    .iter()
80                    .map(syn::parse::Error::to_compile_error)
81                    .collect()
82            }
83        }
84        Ok(syn::Item::Static(mut item_static)) => {
85            let attrs = mem::replace(&mut item_static.attrs, Vec::new());
86            let name = &item_static.ident;
87
88            quote! {
89                #[test]
90                #(#attrs)*
91                fn #name() {
92                    #item_static
93                    ::quickcheck::quickcheck(#name)
94                }
95            }
96        }
97        _ => {
98            let span = proc_macro2::TokenStream::from(input).span();
99            let msg = "#[sample_test] is only supported on statics and functions";
100
101            syn::parse::Error::new(span, msg).to_compile_error()
102        }
103    };
104
105    output.into()
106}
107
108#[cfg(test)]
109mod tests {}