func_trace/
lib.rs

1//! A procedural macro for tracing the execution of functions.
2//!
3//! Adding `#[trace]` to the top of any function will insert `println!` statements at the beginning
4//! and the end of that function, notifying you of when that function was entered and exited and
5//! printing the argument and return values.  This is useful for quickly debugging whether functions
6//! that are supposed to be called are actually called without manually inserting print statements.
7//!
8//! Note that this macro requires all arguments to the function and the return value to have types
9//! that implement `Debug`. You can disable the printing of certain arguments if necessary.
10//!
11//! You can also add `#[trace]` to `impl`s and `mod`s to enable tracing for all functions in the
12//! `impl` or `mod`. If you use `#[trace]` on a `mod` or `impl` as well as on a method or function
13//! inside one of those elements, then only the outermost `#[trace]` is used.
14//!
15//! `#[trace]` takes a few optional arguments that configure things like the prefixes to use,
16//! enabling/disabling particular arguments or functions, and more. See the
17//! [documentation](macro@trace) for details.
18//!
19//! ## Example
20//!
21//! See the examples in `examples/`. You can run the following example with
22//! `cargo run --example example_prefix`.
23//! ```
24//! use func_trace::trace;
25//!
26//! func_trace::init_depth_var!();
27//!
28//! fn main() {
29//!     foo(1, 2);
30//! }
31//!
32//! #[trace]
33//! fn foo(a: i32, b: i32) {
34//!     println!("I'm in foo!");
35//!     bar((a, b));
36//! }
37//!
38//! #[trace(prefix_enter="[ENTER]", prefix_exit="[EXIT]")]
39//! fn bar((a, b): (i32, i32)) -> i32 {
40//!     println!("I'm in bar!");
41//!     if a == 1 {
42//!         2
43//!     } else {
44//!         b
45//!     }
46//! }
47//! ```
48//!
49//! Output:
50//! ```text
51//! [+] Entering foo(a = 1, b = 2)
52//! I'm in foo!
53//!  [ENTER] Entering bar(a = 1, b = 2)
54//! I'm in bar!
55//!  [EXIT] Exiting bar = 2
56//! [-] Exiting foo = ()
57//! ```
58//!
59//! Note the convenience [`func_trace::init_depth_var!()`](macro@init_depth_var) macro which declares and
60//! initializes the thread-local `DEPTH` variable that is used for indenting the output. Calling
61//! `func_trace::init_depth_var!()` is equivalent to writing:
62//! ```
63//! use std::cell::Cell;
64//!
65//! thread_local! {
66//!     static DEPTH: Cell<usize> = Cell::new(0);
67//! }
68//! ```
69//!
70//! The only time it can be omitted is when `#[trace]` is applied to `mod`s as it's defined for you
71//! automatically (see `examples/example_mod.rs`). Note that the `DEPTH` variable isn't shared
72//! between `mod`s, so indentation won't be perfect when tracing functions in multiple `mod`s. Also
73//! note that using trace as an inner attribute (`#![trace]`) is not supported at this time.
74
75mod args;
76
77use quote::{quote, ToTokens};
78use syn::{
79    parse::{Parse, Parser},
80    parse_quote,
81};
82
83/// A convenience macro for declaring the `DEPTH` variable used for indenting the output
84///
85/// Calling this macro is equivalent to:
86/// ```
87/// use std::cell::Cell;
88///
89/// thread_local! {
90///     static DEPTH: Cell<usize> = Cell::new(0);
91/// }
92/// ```
93///
94/// It is required to declare a `DEPTH` variable unless using `#[trace]` on a `mod`, in which case
95/// the variable is declared for you.
96#[proc_macro]
97pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
98    let output = if input.is_empty() {
99        quote! {
100            ::std::thread_local! {
101                static DEPTH: ::std::cell::Cell<usize> = ::std::cell::Cell::new(0);
102            }
103        }
104    } else {
105        let input2 = proc_macro2::TokenStream::from(input);
106        syn::Error::new_spanned(input2, "`init_depth_var` takes no arguments").to_compile_error()
107    };
108
109    output.into()
110}
111
112/// Enables tracing the execution of functions
113///
114/// It supports the following optional arguments (see the `examples` folder for examples of using
115/// each of these):
116///
117/// - `prefix_enter` - The prefix of the `println!` statement when a function is entered. Defaults
118/// to `[+]`.
119///
120/// - `prefix_exit` - The prefix of the `println!` statement when a function is exited. Defaults to
121/// `[-]`.
122///
123/// - `enable` - When applied to a `mod` or `impl`, `enable` takes a list of function names to
124/// print, not printing any functions that are not part of this list. All functions are enabled by
125/// default. When applied to an `impl` method or a function, `enable` takes a list of arguments to
126/// print, not printing any arguments that are not part of the list. All arguments are enabled by
127/// default.
128///
129/// - `disable` - When applied to a `mod` or `impl`, `disable` takes a list of function names to not
130/// print, printing all other functions in the `mod` or `impl`. No functions are disabled by
131/// default. When applied to an `impl` method or a function, `disable` takes a list of arguments to
132/// not print, printing all other arguments. No arguments are disabled by default.
133///
134/// - `pause` - When given as an argument to `#[trace]`, execution is paused after each line of
135/// tracing output until enter is pressed. This allows you to trace through a program step by
136/// step. Disabled by default.
137///
138/// - `pretty` - Pretty print the output (use `{:#?}` instead of `{:?}`). Disabled by default.
139///
140/// - `logging` - Use `log::trace!` from the `log` crate instead of `println`. Disabled by default.
141///
142/// Note that `enable` and `disable` can not be used together, and doing so will result in an error.
143#[proc_macro_attribute]
144pub fn trace(
145    args: proc_macro::TokenStream,
146    input: proc_macro::TokenStream,
147) -> proc_macro::TokenStream {
148    let raw_args = syn::parse_macro_input!(args as syn::AttributeArgs);
149    let args = match args::Args::from_raw_args(raw_args) {
150        Ok(args) => args,
151        Err(errors) => {
152            return errors
153                .iter()
154                .map(syn::Error::to_compile_error)
155                .collect::<proc_macro2::TokenStream>()
156                .into()
157        }
158    };
159
160    let output = if let Ok(item) = syn::Item::parse.parse(input.clone()) {
161        expand_item(&args, item)
162    } else if let Ok(impl_item) = syn::ImplItem::parse.parse(input.clone()) {
163        expand_impl_item(&args, impl_item)
164    } else {
165        let input2 = proc_macro2::TokenStream::from(input);
166        syn::Error::new_spanned(input2, "expected one of: `fn`, `impl`, `mod`").to_compile_error()
167    };
168
169    output.into()
170}
171
172#[derive(Clone, Copy)]
173enum AttrApplied {
174    Directly,
175    Indirectly,
176}
177
178fn expand_item(args: &args::Args, mut item: syn::Item) -> proc_macro2::TokenStream {
179    transform_item(args, AttrApplied::Directly, &mut item);
180
181    match item {
182        syn::Item::Fn(_) | syn::Item::Mod(_) | syn::Item::Impl(_) => item.into_token_stream(),
183        _ => syn::Error::new_spanned(item, "#[trace] is not supported for this item")
184            .to_compile_error(),
185    }
186}
187
188fn expand_impl_item(args: &args::Args, mut impl_item: syn::ImplItem) -> proc_macro2::TokenStream {
189    transform_impl_item(args, AttrApplied::Directly, &mut impl_item);
190
191    match impl_item {
192        syn::ImplItem::Method(_) => impl_item.into_token_stream(),
193        _ => syn::Error::new_spanned(impl_item, "#[trace] is not supported for this impl item")
194            .to_compile_error(),
195    }
196}
197
198fn transform_item(args: &args::Args, attr_applied: AttrApplied, item: &mut syn::Item) {
199    match *item {
200        syn::Item::Fn(ref mut item_fn) => transform_fn(args, attr_applied, item_fn),
201        syn::Item::Mod(ref mut item_mod) => transform_mod(args, attr_applied, item_mod),
202        syn::Item::Impl(ref mut item_impl) => transform_impl(args, attr_applied, item_impl),
203        _ => (),
204    }
205}
206
207fn transform_fn(args: &args::Args, attr_applied: AttrApplied, item_fn: &mut syn::ItemFn) {
208    item_fn.block = Box::new(construct_traced_block(
209        &args,
210        attr_applied,
211        &item_fn.sig,
212        &item_fn.block,
213    ));
214}
215
216fn transform_mod(args: &args::Args, attr_applied: AttrApplied, item_mod: &mut syn::ItemMod) {
217    assert!(
218        (item_mod.content.is_some() && item_mod.semi.is_none())
219            || (item_mod.content.is_none() && item_mod.semi.is_some())
220    );
221
222    if item_mod.semi.is_some() {
223        unimplemented!();
224    }
225
226    if let Some((_, items)) = item_mod.content.as_mut() {
227        items.iter_mut().for_each(|item| {
228            if let AttrApplied::Directly = attr_applied {
229                match *item {
230                    syn::Item::Fn(syn::ItemFn {
231                        sig: syn::Signature { ref ident, .. },
232                        ..
233                    })
234                    | syn::Item::Mod(syn::ItemMod { ref ident, .. }) => match args.filter {
235                        args::Filter::Enable(ref idents) if !idents.contains(ident) => {
236                            return;
237                        }
238                        args::Filter::Disable(ref idents) if idents.contains(ident) => {
239                            return;
240                        }
241                        _ => (),
242                    },
243                    _ => (),
244                }
245            }
246
247            transform_item(args, AttrApplied::Indirectly, item);
248        });
249
250        items.insert(
251            0,
252            parse_quote! {
253                ::std::thread_local! {
254                    static DEPTH: ::std::cell::Cell<usize> = ::std::cell::Cell::new(0);
255                }
256            },
257        );
258    }
259}
260
261fn transform_impl(args: &args::Args, attr_applied: AttrApplied, item_impl: &mut syn::ItemImpl) {
262    item_impl.items.iter_mut().for_each(|impl_item| {
263        if let syn::ImplItem::Method(ref mut impl_item_method) = *impl_item {
264            if let AttrApplied::Directly = attr_applied {
265                let ident = &impl_item_method.sig.ident;
266
267                match args.filter {
268                    args::Filter::Enable(ref idents) if !idents.contains(ident) => {
269                        return;
270                    }
271                    args::Filter::Disable(ref idents) if idents.contains(ident) => {
272                        return;
273                    }
274                    _ => (),
275                }
276            }
277
278            impl_item_method.block = construct_traced_block(
279                &args,
280                AttrApplied::Indirectly,
281                &impl_item_method.sig,
282                &impl_item_method.block,
283            );
284        }
285    });
286}
287
288fn transform_impl_item(
289    args: &args::Args,
290    attr_applied: AttrApplied,
291    impl_item: &mut syn::ImplItem,
292) {
293    // Will probably add more cases in the future
294    #[allow(clippy::single_match)]
295    match *impl_item {
296        syn::ImplItem::Method(ref mut impl_item_method) => {
297            transform_method(args, attr_applied, impl_item_method)
298        }
299        _ => (),
300    }
301}
302
303fn transform_method(
304    args: &args::Args,
305    attr_applied: AttrApplied,
306    impl_item_method: &mut syn::ImplItemMethod,
307) {
308    impl_item_method.block = construct_traced_block(
309        &args,
310        attr_applied,
311        &impl_item_method.sig,
312        &impl_item_method.block,
313    );
314}
315
316fn construct_traced_block(
317    args: &args::Args,
318    attr_applied: AttrApplied,
319    sig: &syn::Signature,
320    original_block: &syn::Block,
321) -> syn::Block {
322    let arg_idents = extract_arg_idents(args, attr_applied, &sig);
323    let arg_idents_format = arg_idents
324        .iter()
325        .map(|arg_ident| format!("{} = {{:?}}", arg_ident))
326        .collect::<Vec<_>>()
327        .join(", ");
328
329    let pretty = if args.pretty { "#" } else { "" };
330    let entering_format = format!(
331        "{{:depth$}}{} Entering {}({})",
332        args.prefix_enter, sig.ident, arg_idents_format
333    );
334    let exiting_format = format!(
335        "{{:depth$}}{} Exiting {} = {{:{}?}}",
336        args.prefix_exit, sig.ident, pretty
337    );
338
339    let pause_stmt = if args.pause {
340        quote! {{
341            use std::io::{self, BufRead};
342            let stdin = io::stdin();
343            stdin.lock().lines().next();
344        }}
345    } else {
346        quote!()
347    };
348
349    let printer = if args.logging {
350        quote! { log::trace! }
351    } else {
352        quote! { println! }
353    };
354
355    parse_quote! {{
356        #printer(#entering_format, "", #(#arg_idents,)* depth = DEPTH.with(|d| d.get()));
357        #pause_stmt
358        DEPTH.with(|d| d.set(d.get() + 2));
359        let mut fn_closure = move || #original_block;
360        let fn_return_value = fn_closure();
361        DEPTH.with(|d| d.set(d.get() - 2));
362        #printer(#exiting_format, "", fn_return_value, depth = DEPTH.with(|d| d.get()));
363        #pause_stmt
364        fn_return_value
365    }}
366}
367
368fn extract_arg_idents(
369    args: &args::Args,
370    attr_applied: AttrApplied,
371    sig: &syn::Signature,
372) -> Vec<proc_macro2::Ident> {
373    fn process_pat(
374        args: &args::Args,
375        attr_applied: AttrApplied,
376        pat: &syn::Pat,
377        arg_idents: &mut Vec<proc_macro2::Ident>,
378    ) {
379        match *pat {
380            syn::Pat::Ident(ref pat_ident) => {
381                let ident = &pat_ident.ident;
382
383                if let AttrApplied::Directly = attr_applied {
384                    match args.filter {
385                        args::Filter::Enable(ref idents) if !idents.contains(ident) => {
386                            return;
387                        }
388                        args::Filter::Disable(ref idents) if idents.contains(ident) => {
389                            return;
390                        }
391                        _ => (),
392                    }
393                }
394
395                arg_idents.push(ident.clone());
396            }
397            syn::Pat::Tuple(ref pat_tuple) => {
398                pat_tuple.elems.iter().for_each(|pat| {
399                    process_pat(args, attr_applied, pat, arg_idents);
400                });
401            }
402            _ => unimplemented!(),
403        }
404    }
405
406    let mut arg_idents = vec![];
407
408    for input in &sig.inputs {
409        match input {
410            syn::FnArg::Receiver(_) => (), // ignore `self`
411            syn::FnArg::Typed(arg_typed) => {
412                process_pat(args, attr_applied, &arg_typed.pat, &mut arg_idents);
413            }
414        }
415    }
416
417    arg_idents
418}