cfx_vm_tracer_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DataStruct, DeriveInput, Error, Fields, Ident};
4
5type Result<T> = std::result::Result<T, Error>;
6
7macro_rules! unwrap_or_compile_error {
8    ($e:expr) => {
9        match $e {
10            Ok(x) => x,
11            Err(e) => return e.into_compile_error().into(),
12        }
13    };
14}
15
16#[proc_macro_derive(AsTracer, attributes(skip_tracer))]
17pub fn generate_as_tracer_function(input: TokenStream) -> TokenStream {
18    let input = syn::parse_macro_input!(input as DeriveInput);
19    let name = &input.ident;
20
21    let data = unwrap_or_compile_error!(get_struct_data(&input));
22    let skipped_fields = unwrap_or_compile_error!(check_all_fields(data));
23    let field_names = unwrap_or_compile_error!(get_field_names(data, name));
24    unwrap_or_compile_error!(check_num_fields(&field_names, name, 10));
25
26    let mut match_arms = Vec::new();
27    let match_more_fields = if skipped_fields {
28        quote!(, ..)
29    } else {
30        quote!()
31    };
32
33    for mask in 0..(1 << field_names.len()) {
34        let mut this_combination = Vec::new();
35        let mut tuple_elements = Vec::new();
36        for (index, field) in field_names.iter().enumerate() {
37            if (mask >> index) & 1 == 1 {
38                this_combination.push(quote! { #field: Some(#field) });
39                tuple_elements.push(quote! { #field });
40            } else {
41                this_combination.push(quote! { #field: None });
42            }
43        }
44        let match_arm = match tuple_elements.len() {
45            0 => quote! {
46                #name {#(#this_combination),* #match_more_fields} => Box::new(()) // as Box<dyn MyTrait>
47            },
48            1 => quote! {
49                #name {#(#this_combination),* #match_more_fields} => Box::new(#(#tuple_elements),*) // as Box<dyn MyTrait>
50            },
51            _ => quote! {
52                #name {#(#this_combination),* #match_more_fields} => Box::new((#(#tuple_elements),*)) // as Box<dyn MyTrait>
53            },
54        };
55        match_arms.push(match_arm);
56    }
57
58    let expanded = quote! {
59        impl AsTracer for #name {
60            fn as_tracer<'a>(&'a mut self) -> Box<dyn 'a + TracerTrait> {
61                match self {
62                    #(#match_arms,)*
63                }
64            }
65        }
66    };
67
68    expanded.into()
69}
70
71#[proc_macro_derive(DrainTrace)]
72pub fn generate_drain_trace_function(input: TokenStream) -> TokenStream {
73    let input = syn::parse_macro_input!(input as DeriveInput);
74    let name = &input.ident;
75
76    let data = unwrap_or_compile_error!(get_struct_data(&input));
77    let field_names = unwrap_or_compile_error!(get_field_names(data, name));
78
79    let drain_statements = field_names.iter().map(|field| {
80        quote! { self.#field.drain_trace(map); }
81    });
82
83    let expanded = quote! {
84        impl DrainTrace for #name {
85            fn drain_trace(self, map: &mut typemap::ShareDebugMap) {
86                #(#drain_statements)*
87            }
88        }
89    };
90
91    expanded.into()
92}
93
94fn get_struct_data(input: &DeriveInput) -> Result<&DataStruct> {
95    match &input.data {
96        Data::Struct(data) => Ok(data),
97        _ => Err(Error::new_spanned(&input.ident, "Only struct is supported")),
98    }
99}
100
101fn check_all_fields(data: &DataStruct) -> Result<bool> {
102    let mut type_error = vec![];
103    let mut skipped_field = false;
104    for field in &data.fields {
105        if field
106            .attrs
107            .iter()
108            .any(|attr| attr.path.is_ident("skip_tracer"))
109        {
110            skipped_field = true;
111            continue;
112        }
113
114        if let syn::Type::Path(type_path) = &field.ty {
115            if type_path
116                .path
117                .segments
118                .last()
119                .map_or(false, |seg| seg.ident == "Option")
120            {
121                continue;
122            }
123        }
124        type_error.push(Error::new_spanned(
125            &field.ty,
126            "All fields must be of type Option",
127        ));
128    }
129    if !type_error.is_empty() {
130        let mut type_error_iter = type_error.into_iter();
131        let mut error = type_error_iter.next().unwrap();
132        error.extend(type_error_iter);
133        Err(error)
134    } else {
135        Ok(skipped_field)
136    }
137}
138
139fn get_field_names<'a>(
140    data: &'a DataStruct, name: &Ident,
141) -> Result<Vec<&'a Option<Ident>>> {
142    match &data.fields {
143        Fields::Named(fields) => Ok(fields
144            .named
145            .iter()
146            .filter(|f| {
147                !f.attrs.iter().any(|attr| attr.path.is_ident("skip_tracer"))
148            })
149            .map(|f| &f.ident)
150            .collect::<Vec<_>>()),
151        _ => Err(Error::new_spanned(&name, "Only named struct is supported")),
152    }
153}
154
155fn check_num_fields(
156    field_names: &Vec<&Option<Ident>>, name: &Ident, max_entries: usize,
157) -> Result<()> {
158    if field_names.len() > max_entries {
159        Err(Error::new_spanned(
160            name,
161            "Too many fields in the struct! Limit is 10.",
162        ))
163    } else {
164        Ok(())
165    }
166}