micro_timer_macros/
lib.rs

1// micro-timer-macros
2//
3// Do not use this crate. It is only meant for internal use. Use `micro-timer`
4// instead.
5//
6// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
7
8/// `extern crate` Required even for 2018 edition
9extern crate proc_macro;
10use proc_macro2::TokenStream;
11use quote::{quote, quote_spanned, ToTokens};
12use syn::spanned::Spanned;
13
14/// Logs the time elapsed for the body of the target function for each call.
15///
16/// ```compile_fail
17/// use micro_timer::timed;
18///
19/// #[timed]  // Can only be used on functions
20/// struct Thing;
21/// ```
22///
23/// ```compile_fail
24/// use micro_timer::timed;
25///
26/// #[timed]  // Can only be used on sync functions
27/// async fn func() {}
28/// ```
29#[proc_macro_attribute]
30pub fn timed(
31    attrs: proc_macro::TokenStream,
32    item: proc_macro::TokenStream,
33) -> proc_macro::TokenStream {
34    inner_timed(attrs.into(), item.into()).into()
35}
36
37/// This is the unit-testable version using `proc_macro2`
38fn inner_timed(_attr_ts: TokenStream, fn_ts: TokenStream) -> TokenStream {
39    let ast = syn::parse2(fn_ts.clone()).unwrap();
40    let func = match parse_function(ast) {
41        Ok(f) => f,
42        Err(stream) => return stream,
43    };
44
45    let mut outer = func.clone();
46    let original_func_name = func.sig.ident.to_string();
47    let inner_block: TokenStream = func
48        .block
49        .stmts
50        .iter()
51        .map(|s| s.to_token_stream())
52        .collect();
53    let span = outer.sig.ident.span();
54
55    let block = quote_spanned! {
56        span=>
57        {
58            let __micro_timer_instant = ::std::time::Instant::now();
59            let __micro_timer_guard =
60                ::micro_timer::scopeguard::guard_on_success(
61                    __micro_timer_instant,
62                    |timer| {
63                        if ::std::thread::panicking() {
64                            return
65                        }
66                        crate::log::trace!(
67                            "Duration of `{}`: {:?}",
68                            #original_func_name,
69                            timer.elapsed()
70                        );
71                    }
72                );
73
74            #inner_block
75        }
76    };
77
78    outer.block = syn::parse2(block).unwrap();
79
80    quote! {#outer}
81}
82
83fn parse_function(item: syn::Item) -> Result<syn::ItemFn, TokenStream> {
84    match item {
85        syn::Item::Fn(func) => {
86            if func.sig.asyncness.is_some() {
87                return Err(quote_spanned! {
88                    func.span()=>
89                    compile_error!("Cannot use `#[timed]` on async functions")
90                    #func
91                });
92            }
93            Ok(func)
94        }
95        i => Err(quote_spanned! {
96            i.span()=>
97            compile_error!("`#[timed]` can only be used on functions");
98            #i
99        }),
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use pretty_assertions::assert_eq;
107
108    #[test]
109    fn test_output() {
110        let input = syn::parse_quote! {
111            fn my_super_function(_value: usize) -> usize {
112                let timer = 10;
113                timer
114            }
115        };
116
117        let expected: TokenStream = syn::parse_quote! {
118            fn my_super_function(_value: usize) -> usize {
119                let __micro_timer_instant = ::std::time::Instant::now();
120                let __micro_timer_guard =
121                    ::micro_timer::scopeguard::guard_on_success(
122                        __micro_timer_instant,
123                        |timer| {
124                            if ::std::thread::panicking() {
125                                return
126                            }
127                            crate::log::trace!(
128                                "Duration of `{}`: {:?}",
129                                "my_super_function",
130                                timer.elapsed()
131                            );
132                        }
133                    );
134                let timer = 10;
135                timer
136            }
137        };
138        let output = inner_timed(TokenStream::new(), input);
139        assert_eq!(output.to_string(), expected.to_string());
140    }
141}