test_impl/
lib.rs

1mod macro_args;
2
3use macro_args::TraitAndImpls;
4use proc_macro2::TokenStream;
5use proc_macro_error::{emit_call_site_error, proc_macro_error};
6use quote::format_ident;
7use quote::quote;
8use syn::ItemFn;
9
10/// Takes the original function and repeats it for each of the implementations provided. Example:
11/// ```
12/// #[test]
13/// fn do_test() {
14///     ExampleTrait::do_thing()
15/// }
16/// ```
17/// becomes:
18/// ```
19/// #[test]
20/// fn do_test() {
21///     fn impl_ExampleStruct() {
22///         type ExampleTrait = ExampleStruct;
23///         ExampleTrait::do_thing();
24///     }
25///     impl_ExampleStruct();
26///
27///     fn impl_ExampleStruct2() {
28///         type ExampleTrait = ExampleStruct2;
29///         ExampleTrait::do_thing();
30///     }
31///     impl_ExampleStruct2();
32/// }
33///
34fn repeat_function_with_mappings(func: &ItemFn, trait_and_impls: TraitAndImpls) -> TokenStream {
35    let impl_blocks: Vec<TokenStream> = trait_and_impls
36        .structs
37        .iter()
38        .map(|struc| {
39            let fn_ident = format_ident!("impl_{}", struc.ident);
40            let trait_ident = &trait_and_impls.base_trait.ident;
41            let trait_generics = &trait_and_impls.base_trait.generics;
42
43            let struct_ident = &struc.ident;
44            let struct_generics = &struc.generics;
45            let stmts = &func.block.stmts;
46
47            quote! {
48                fn #fn_ident() {
49                    type #trait_ident#trait_generics = #struct_ident#struct_generics;
50                    #(#stmts)*
51                }
52
53                #fn_ident();
54            }
55        })
56        .collect();
57
58    let attrs = &func.attrs;
59    let vis = &func.vis;
60    let sig = &func.sig;
61
62    quote! {
63        #(#attrs)*
64        #[allow(non_snake_case)]
65        #vis #sig
66        {
67            #(#impl_blocks)*
68        }
69    }
70}
71
72/// Run this test multiple times, replacing all references to the trait specified with a specific implementation.
73/// Use it like this:
74///
75/// `#[test_impl(ExampleTrait = ExampleStruct, ExampleStruct2)]`
76#[proc_macro_attribute]
77#[proc_macro_error]
78pub fn test_impl(
79    args: proc_macro::TokenStream,
80    input: proc_macro::TokenStream,
81) -> proc_macro::TokenStream {
82    let args = match syn::parse::<TraitAndImpls>(args) {
83        Ok(a) => a,
84        Err(e) => {
85            emit_call_site_error!("Could not parse macro arguments: {}", e);
86            return proc_macro::TokenStream::new();
87        }
88    };
89
90    let fn_def = match syn::parse::<ItemFn>(input) {
91        Ok(f) => f,
92        Err(e) => {
93            emit_call_site_error!("You must use this macro with a function: {}", e);
94            return proc_macro::TokenStream::new();
95        }
96    };
97
98    let impl_checks = args.output_impl_checks(&fn_def.sig.ident);
99    let mapped = repeat_function_with_mappings(&fn_def, args);
100    (quote! {
101        #impl_checks
102        #mapped
103    })
104    .into()
105}