profiler_macros 0.1.0

Procedural macros for the profiler crate
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Meta};

#[proc_macro_derive(Metrics, attributes(new))]
pub fn derive_metrics(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let _vis = &input.vis;

    let Data::Struct(data_struct) = &input.data else {
        panic!("Metrics can only be derived for structs");
    };

    let Fields::Named(fields) = &data_struct.fields else {
        panic!("Metrics can only be derived for structs with named fields");
    };

    let mut default_fields = Vec::new();
    let mut starts = Vec::new();
    let mut results = Vec::new();
    let mut start_calls = Vec::new();
    let mut end_calls = Vec::new();
    let mut result_tuple_fields = Vec::new();
    let mut field_names = Vec::new();
    let mut format_match_arms = Vec::new();
    let mut result_to_f64_match_arms = Vec::new();

    for (idx, field) in fields.named.iter().enumerate() {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;

        let mut custom_new = None;
        for attr in &field.attrs {
            if attr.path().is_ident("new") {
                if let Meta::List(list) = &attr.meta {
                    custom_new = Some(list.tokens.clone());
                }
            }
        }

        if let Some(tokens) = custom_new {
            default_fields.push(quote! {
                #field_name: <#field_type>::new(#tokens)
            });
        } else {
            default_fields.push(quote! {
                #field_name: <#field_type as ::core::default::Default>::default()
            });
        }

        starts.push(quote! { <#field_type as crate::SingleMetric>::Start });
        results.push(quote! { <#field_type as crate::SingleMetric>::Result });

        start_calls.push(quote! { self.#field_name.start() });

        let idx_lit = syn::Index::from(idx);
        end_calls.push(quote! { let #field_name = self.#field_name.end(start.#idx_lit); });
        result_tuple_fields.push(quote! { #field_name });

        let field_name_str = field_name.to_string();
        field_names.push(quote! { #field_name_str });

        format_match_arms.push(quote! {
            #idx => self.#field_name.format_value(value)
        });

        result_to_f64_match_arms.push(quote! {
            #idx => self.#field_name.result_to_f64(&result.#idx_lit)
        });
    }

    let assert_name = syn::Ident::new(
        &format!("_DERIVE_ASSERT_{}", name),
        proc_macro2::Span::call_site(),
    );

    let expanded = quote! {
        impl ::core::default::Default for #name {
            fn default() -> Self {
                Self {
                    #(#default_fields),*
                }
            }
        }

        const #assert_name: () = {
            const fn assert_send_sync<T: Send + Sync + 'static>() {}
            assert_send_sync::<#name>();
        };

        impl crate::Metrics for #name {
            type Start = (#(#starts,)*);
            type Result = (#(#results,)*);

            fn start(&self) -> Self::Start {
                use crate::SingleMetric;
                (#(#start_calls,)*)
            }

            fn end(&self, start: Self::Start) -> Self::Result {
                use crate::SingleMetric;
                #(#end_calls)*
                (#(#result_tuple_fields,)*)
            }

            fn metrics_names() -> &'static [&'static str] {
                &[#(#field_names),*]
            }

            fn format_value(&self, metric_idx: usize, value: f64) -> (String, &'static str) {
                use crate::SingleMetric;
                match metric_idx {
                    #(#format_match_arms,)*
                    _ => crate::format_unit_helper(value),
                }
            }

            fn result_to_f64(&self, metric_idx: usize, result: &Self::Result) -> f64 {
                use crate::SingleMetric;
                match metric_idx {
                    #(#result_to_f64_match_arms,)*
                    _ => panic!("Invalid metric index"),
                }
            }
        }
    };

    TokenStream::from(expanded)
}