micro-timer-macros 0.4.0

Macros for the micro-timer crate
Documentation
// micro-timer-macros
//
// Do not use this crate. It is only meant for internal use. Use `micro-timer`
// instead.
//
// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>

/// `extern crate` Required even for 2018 edition
extern crate proc_macro;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;

/// Logs the time elapsed for the body of the target function for each call.
///
/// ```compile_fail
/// use micro_timer::timed;
///
/// #[timed]  // Can only be used on functions
/// struct Thing;
/// ```
///
/// ```compile_fail
/// use micro_timer::timed;
///
/// #[timed]  // Can only be used on sync functions
/// async fn func() {}
/// ```
#[proc_macro_attribute]
pub fn timed(
    attrs: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    inner_timed(attrs.into(), item.into()).into()
}

/// This is the unit-testable version using `proc_macro2`
fn inner_timed(_attr_ts: TokenStream, fn_ts: TokenStream) -> TokenStream {
    let ast = syn::parse2(fn_ts.clone()).unwrap();
    let func = match parse_function(ast) {
        Ok(f) => f,
        Err(stream) => return stream,
    };

    let mut outer = func.clone();
    let original_func_name = func.sig.ident.to_string();
    let inner_block: TokenStream = func
        .block
        .stmts
        .iter()
        .map(|s| s.to_token_stream())
        .collect();
    let span = outer.sig.ident.span();

    let block = quote_spanned! {
        span=>
        {
            let __micro_timer_instant = ::std::time::Instant::now();
            let __micro_timer_guard =
                ::micro_timer::scopeguard::guard_on_success(
                    __micro_timer_instant,
                    |timer| {
                        if ::std::thread::panicking() {
                            return
                        }
                        crate::log::trace!(
                            "Duration of `{}`: {:?}",
                            #original_func_name,
                            timer.elapsed()
                        );
                    }
                );

            #inner_block
        }
    };

    outer.block = syn::parse2(block).unwrap();

    quote! {#outer}
}

fn parse_function(item: syn::Item) -> Result<syn::ItemFn, TokenStream> {
    match item {
        syn::Item::Fn(func) => {
            if func.sig.asyncness.is_some() {
                return Err(quote_spanned! {
                    func.span()=>
                    compile_error!("Cannot use `#[timed]` on async functions")
                    #func
                });
            }
            Ok(func)
        }
        i => Err(quote_spanned! {
            i.span()=>
            compile_error!("`#[timed]` can only be used on functions");
            #i
        }),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn test_output() {
        let input = syn::parse_quote! {
            fn my_super_function(_value: usize) -> usize {
                let timer = 10;
                timer
            }
        };

        let expected: TokenStream = syn::parse_quote! {
            fn my_super_function(_value: usize) -> usize {
                let __micro_timer_instant = ::std::time::Instant::now();
                let __micro_timer_guard =
                    ::micro_timer::scopeguard::guard_on_success(
                        __micro_timer_instant,
                        |timer| {
                            if ::std::thread::panicking() {
                                return
                            }
                            crate::log::trace!(
                                "Duration of `{}`: {:?}",
                                "my_super_function",
                                timer.elapsed()
                            );
                        }
                    );
                let timer = 10;
                timer
            }
        };
        let output = inner_timed(TokenStream::new(), input);
        assert_eq!(output.to_string(), expected.to_string());
    }
}