Skip to main content

compiler_test_derive/
lib.rs

1#[cfg(proc_macro)]
2extern crate proc_macro;
3
4use proc_macro2::TokenStream;
5use quote::quote;
6use std::path::Path;
7use syn::*;
8
9mod ignores;
10
11#[cfg(proc_macro)]
12#[proc_macro_attribute]
13pub fn compiler_test(
14    attrs: proc_macro::TokenStream,
15    input: proc_macro::TokenStream,
16) -> proc_macro::TokenStream {
17    compiler_test_impl(attrs.into(), input.into()).into()
18}
19
20fn compiler_test_impl(attrs: TokenStream, input: TokenStream) -> TokenStream {
21    let path: Option<ExprPath> = parse2::<ExprPath>(attrs).ok();
22    let mut my_fn: ItemFn = match syn::parse2(input) {
23        Ok(f) => f,
24        Err(e) => return e.into_compile_error(),
25    };
26    let fn_name = &my_fn.sig.ident;
27
28    // Let's build the ignores to append an `#[ignore]` macro to the
29    // autogenerated tests in case the test appears in the `ignores.txt` path;
30
31    let tests_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
32        .ancestors()
33        .nth(2)
34        .unwrap();
35    let ignores_txt_path = tests_dir.join("ignores.txt");
36
37    let ignores = crate::ignores::Ignores::build_from_path(ignores_txt_path);
38
39    let should_ignore = |test_name: &str, compiler_name: &str, engine_name: &str| {
40        let compiler_name = compiler_name.to_lowercase();
41        let engine_name = engine_name.to_lowercase();
42        // We construct the path manually because we can't get the
43        // source_file location from the `Span` (it's only available in nightly)
44        let full_path = format!(
45            "{}::{}::{}::{}",
46            quote! { #path },
47            test_name,
48            compiler_name,
49            engine_name
50        )
51        .replace(' ', "");
52
53        // println!("{} -> Should ignore: {}", full_path, should_ignore);
54        ignores.should_ignore_host(&engine_name, &compiler_name, &full_path)
55    };
56    let construct_engine_test = |func: &::syn::ItemFn,
57                                 compiler_name: &str,
58                                 engine_name: &str,
59                                 engine_feature_name: &str,
60                                 experimental_artifact: bool,
61                                 dynamic_memory: bool|
62     -> ::proc_macro2::TokenStream {
63        let config_compiler = ::quote::format_ident!("{}", compiler_name);
64        let test_name = ::quote::format_ident!("{}", engine_name.to_lowercase());
65        let mut config = quote! { crate::Config::new(crate::Compiler::#config_compiler) };
66        if experimental_artifact {
67            config = quote! { #config.with_experimental_artifact() };
68        }
69        if dynamic_memory {
70            config = quote! { #config.with_dynamic_memory() };
71        }
72        let experimental_artifact_cfg =
73            experimental_artifact.then(|| quote! { #[cfg(target_os = "linux")] });
74        let mut new_sig = func.sig.clone();
75        let attrs = func
76            .attrs
77            .clone()
78            .iter()
79            .fold(quote! {}, |acc, new| quote! {#acc #new});
80        new_sig.ident = test_name;
81        new_sig.inputs = ::syn::punctuated::Punctuated::new();
82        let f = quote! {
83            #[test_log::test]
84            #attrs
85            #[cfg(feature = #engine_feature_name)]
86            #experimental_artifact_cfg
87            #new_sig {
88                #fn_name(#config)
89            }
90        };
91        if should_ignore(
92            &func.sig.ident.to_string().replace("r#", ""),
93            compiler_name,
94            engine_name,
95        ) && !cfg!(test)
96        {
97            quote! {
98                #[ignore]
99                #f
100            }
101        } else {
102            f
103        }
104    };
105
106    let construct_compiler_test =
107        |func: &::syn::ItemFn, compiler_name: &str| -> ::proc_macro2::TokenStream {
108            let mod_name = ::quote::format_ident!("{}", compiler_name.to_lowercase());
109            let engine_test = construct_engine_test(
110                func,
111                compiler_name,
112                compiler_name,
113                &compiler_name.to_lowercase(),
114                false,
115                false,
116            );
117            let native_compiler = compiler_name != "V8";
118            let experimental_artifact_test = native_compiler.then(|| {
119                construct_engine_test(
120                    func,
121                    compiler_name,
122                    &format!("{compiler_name}_exp_artifact"),
123                    &compiler_name.to_lowercase(),
124                    true,
125                    false,
126                )
127            });
128            let dynamic_memory_experimental_artifact_test = native_compiler.then(|| {
129                construct_engine_test(
130                    func,
131                    compiler_name,
132                    &format!("{compiler_name}_dynamic_memory_exp_artifact"),
133                    &compiler_name.to_lowercase(),
134                    true,
135                    true,
136                )
137            });
138            let compiler_name_lowercase = compiler_name.to_lowercase();
139
140            quote! {
141                #[cfg(feature = #compiler_name_lowercase)]
142                mod #mod_name {
143                    use super::*;
144
145                    #engine_test
146                    #experimental_artifact_test
147                    #dynamic_memory_experimental_artifact_test
148                }
149            }
150        };
151
152    let singlepass_compiler_test = construct_compiler_test(&my_fn, "Singlepass");
153    let cranelift_compiler_test = construct_compiler_test(&my_fn, "Cranelift");
154    let llvm_compiler_test = construct_compiler_test(&my_fn, "LLVM");
155    let v8_compiler_test = construct_compiler_test(&my_fn, "V8");
156
157    // We remove the method decorators
158    my_fn.attrs = vec![];
159
160    let x = quote! {
161        #[cfg(test)]
162        mod #fn_name {
163            use super::*;
164
165            #[allow(unused)]
166            #my_fn
167
168            #singlepass_compiler_test
169            #cranelift_compiler_test
170            #llvm_compiler_test
171            #v8_compiler_test
172        }
173    };
174
175    #[allow(clippy::useless_conversion)]
176    x.into()
177}
178
179#[cfg(test)]
180mod tests;