From f40d0e82386823e86692441a7513e6db7a5e2f3a Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 09:14:41 -0400 Subject: [PATCH 01/12] Initial suppport for entering and exiting custom formatting Argument parsing done --- src/args.rs | 81 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/src/args.rs b/src/args.rs index 480472c..b578c1d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -5,6 +5,8 @@ use syn::{self, spanned::Spanned}; pub(crate) struct Args { pub(crate) prefix_enter: String, pub(crate) prefix_exit: String, + pub(crate) format_enter: Option, + pub(crate) format_exit: Option, pub(crate) filter: Filter, pub(crate) pause: bool, pub(crate) pretty: bool, @@ -23,6 +25,17 @@ const DEFAULT_PAUSE: bool = false; const DEFAULT_PRETTY: bool = false; const DEFAULT_LOGGING: bool = false; +macro_rules! try_extract_str { + ($lit:expr, $meta:expr, $arg_ty:ident) => {{ + match *$lit { + syn::Lit::Str(ref lit_str) => Ok(Arg::$arg_ty($meta.span(), lit_str.value())), + _ => Err(vec![syn::Error::new_spanned( + $lit, + format!("`{}` must have a string value", stringify!($arg_ty)), + )]), + } + }}; +} impl Args { pub(crate) fn from_raw_args(raw_args: syn::AttributeArgs) -> Result> { // Different types of arguments accepted by `#[trace]`; @@ -35,6 +48,8 @@ impl Args { Pause(proc_macro2::Span, bool), Pretty(proc_macro2::Span, bool), Logging(proc_macro2::Span, bool), + FormatEnter(proc_macro2::Span, String), + FormatExit(proc_macro2::Span, String), } // Parse arguments @@ -43,6 +58,8 @@ impl Args { enum ArgName { PrefixEnter, PrefixExit, + FormatEnter, + FormatExit, Enable, Disable, Pause, @@ -54,6 +71,8 @@ impl Args { let arg_name = match ident.to_string().as_str() { "prefix_enter" => ArgName::PrefixEnter, "prefix_exit" => ArgName::PrefixExit, + "format_enter" => ArgName::FormatEnter, + "format_exit" => ArgName::FormatExit, "enable" => ArgName::Enable, "disable" => ArgName::Disable, "pause" => ArgName::Pause, @@ -79,6 +98,18 @@ impl Args { "`prefix_exit` requires a string value", )] }; + let format_enter_type_error = || { + vec![syn::Error::new_spanned( + ident.clone(), + "`prefix_enter` requires a string value", + )] + }; + let format_exit_type_error = || { + vec![syn::Error::new_spanned( + ident.clone(), + "`prefix_exit` requires a string value", + )] + }; let enable_type_error = || { vec![syn::Error::new_spanned( ident.clone(), @@ -115,11 +146,12 @@ impl Args { ArgName::Pause => Ok(Arg::Pause(meta.span(), true)), ArgName::Pretty => Ok(Arg::Pretty(meta.span(), true)), ArgName::Logging => Ok(Arg::Logging(meta.span(), true)), - ArgName::PrefixEnter => Err(prefix_enter_type_error()), ArgName::PrefixExit => Err(prefix_exit_type_error()), ArgName::Enable => Err(enable_type_error()), ArgName::Disable => Err(disable_type_error()), + ArgName::FormatEnter => Err(format_enter_type_error()), + ArgName::FormatExit => Err(format_exit_type_error()), }, syn::Meta::List(syn::MetaList { ref nested, .. }) => match arg_name { ArgName::Enable => { @@ -172,27 +204,14 @@ impl Args { ArgName::Pause => Err(pause_type_error()), ArgName::Pretty => Err(pretty_type_error()), ArgName::Logging => Err(logging_type_error()), + ArgName::FormatEnter => Err(format_enter_type_error()), + ArgName::FormatExit => Err(format_exit_type_error()), }, syn::Meta::NameValue(syn::MetaNameValue { ref lit, .. }) => match arg_name { - ArgName::PrefixEnter => match *lit { - syn::Lit::Str(ref lit_str) => { - Ok(Arg::PrefixEnter(meta.span(), lit_str.value())) - } - _ => Err(vec![syn::Error::new_spanned( - lit, - "`prefix_enter` must have a string value", - )]), - }, - ArgName::PrefixExit => match *lit { - syn::Lit::Str(ref lit_str) => { - Ok(Arg::PrefixExit(meta.span(), lit_str.value())) - } - _ => Err(vec![syn::Error::new_spanned( - lit, - "`prefix_exit` must have a string value", - )]), - }, - + ArgName::PrefixEnter => try_extract_str!(lit, meta, PrefixEnter), + ArgName::PrefixExit => try_extract_str!(lit, meta, PrefixExit), + ArgName::FormatEnter => try_extract_str!(lit, meta, FormatEnter), + ArgName::FormatExit => try_extract_str!(lit, meta, FormatExit), ArgName::Enable => Err(enable_type_error()), ArgName::Disable => Err(disable_type_error()), ArgName::Pause => Err(pause_type_error()), @@ -209,6 +228,8 @@ impl Args { let mut prefix_enter_args = vec![]; let mut prefix_exit_args = vec![]; + let mut format_enter_args = vec![]; + let mut format_exit_args = vec![]; let mut enable_args = vec![]; let mut disable_args = vec![]; let mut pause_args = vec![]; @@ -227,6 +248,8 @@ impl Args { Arg::Pause(span, b) => pause_args.push((span, b)), Arg::Pretty(span, b) => pretty_args.push((span, b)), Arg::Logging(span, b) => logging_args.push((span, b)), + Arg::FormatEnter(span, s) => format_enter_args.push((span, s)), + Arg::FormatExit(span, s) => format_exit_args.push((span, s)), }, Err(es) => errors.extend(es), } @@ -247,6 +270,20 @@ impl Args { .map(|(span, _)| syn::Error::new(*span, "duplicate `prefix_exit`")), ); } + if format_enter_args.len() >= 2 { + errors.extend( + prefix_enter_args + .iter() + .map(|(span, _)| syn::Error::new(*span, "duplicate `format_enter`")), + ); + } + if format_exit_args.len() >= 2 { + errors.extend( + prefix_exit_args + .iter() + .map(|(span, _)| syn::Error::new(*span, "duplicate `format_exit`")), + ); + } if enable_args.len() >= 2 { errors.extend( enable_args @@ -306,6 +343,8 @@ impl Args { .unwrap_or_else(|| DEFAULT_PREFIX_ENTER.to_owned()); let prefix_exit = first_no_span!(prefix_exit_args).unwrap_or_else(|| DEFAULT_PREFIX_EXIT.to_owned()); + let format_enter = first_no_span!(format_enter_args); + let format_exit = first_no_span!(format_exit_args); let filter = match (first_no_span!(enable_args), first_no_span!(disable_args)) { (None, None) => Filter::None, (Some(idents), None) => Filter::Enable(idents), @@ -323,6 +362,8 @@ impl Args { pause, pretty, logging, + format_enter, + format_exit, }) } else { Err(errors) From 6c5cb4cfba6ab370dd84d2bbf2bc7f7767b9b5cc Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 11:12:21 -0400 Subject: [PATCH 02/12] Added example/test for custom formatting --- examples/example_custom_format.rs | 19 +++++++++++++++++++ .../test_custom_format.expected | 2 ++ 2 files changed, 21 insertions(+) create mode 100644 examples/example_custom_format.rs create mode 100644 examples/expected_test_outputs/test_custom_format.expected diff --git a/examples/example_custom_format.rs b/examples/example_custom_format.rs new file mode 100644 index 0000000..bca015e --- /dev/null +++ b/examples/example_custom_format.rs @@ -0,0 +1,19 @@ +use trace::trace; + +trace::init_depth_var!(); + +fn main() { + foo(4, 4) +} + +#[trace(format_enter = "{y} and {x} {x}")] +fn foo(x: u32, y: u32) { + +} + +#[cfg(test)] +#[macro_use] +mod trace_test; + +#[cfg(test)] +trace_test!(test_custom_format, main()); \ No newline at end of file diff --git a/examples/expected_test_outputs/test_custom_format.expected b/examples/expected_test_outputs/test_custom_format.expected new file mode 100644 index 0000000..3281362 --- /dev/null +++ b/examples/expected_test_outputs/test_custom_format.expected @@ -0,0 +1,2 @@ +[+] Entering foo(4 and 4 4) +[-] Exiting foo = () \ No newline at end of file From 3f28de6e4730f9ac9c9debc739f010ba064fd551 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 11:56:46 -0400 Subject: [PATCH 03/12] Custom formatting now works, just needs examples, and better errors --- examples/example_custom_format.rs | 6 +- .../test_custom_format.expected | 2 +- src/lib.rs | 92 +++++++++++++++++-- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/examples/example_custom_format.rs b/examples/example_custom_format.rs index bca015e..1cd2665 100644 --- a/examples/example_custom_format.rs +++ b/examples/example_custom_format.rs @@ -7,13 +7,11 @@ fn main() { } #[trace(format_enter = "{y} and {x} {x}")] -fn foo(x: u32, y: u32) { - -} +fn foo(x: u32, y: u32) {} #[cfg(test)] #[macro_use] mod trace_test; #[cfg(test)] -trace_test!(test_custom_format, main()); \ No newline at end of file +trace_test!(test_custom_format, main()); diff --git a/examples/expected_test_outputs/test_custom_format.expected b/examples/expected_test_outputs/test_custom_format.expected index 3281362..c18cbfd 100644 --- a/examples/expected_test_outputs/test_custom_format.expected +++ b/examples/expected_test_outputs/test_custom_format.expected @@ -1,2 +1,2 @@ [+] Entering foo(4 and 4 4) -[-] Exiting foo = () \ No newline at end of file +[-] Exiting foo = () diff --git a/src/lib.rs b/src/lib.rs index 2ff6076..579d1db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,7 @@ mod args; +use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, Parser}, @@ -320,16 +321,24 @@ fn construct_traced_block( original_block: &syn::Block, ) -> syn::Block { let arg_idents = extract_arg_idents(args, attr_applied, sig); - let arg_idents_format = arg_idents - .iter() - .map(|arg_ident| format!("{} = {{:?}}", arg_ident)) - .collect::>() - .join(", "); - + let (arg_idents_format, arg_idents) = if let Some(fmt_str) = &args.format_enter { + parse_fmt_str(fmt_str, arg_idents) + } else { + ( + Ok(arg_idents + .iter() + .map(|arg_ident| format!("{} = {{:?}}", arg_ident)) + .collect::>() + .join(", ")), + arg_idents, + ) + }; let pretty = if args.pretty { "#" } else { "" }; let entering_format = format!( "{{:depth$}}{} Entering {}({})", - args.prefix_enter, sig.ident, arg_idents_format + args.prefix_enter, + sig.ident, + arg_idents_format.unwrap() ); let exiting_format = format!( "{{:depth$}}{} Exiting {} = {{:{}?}}", @@ -364,6 +373,75 @@ fn construct_traced_block( }} } +fn parse_fmt_str( + fmt_str: &str, + mut arg_idents: Vec, +) -> (Result, Vec) { + let mut fixed_format_str = String::new(); + let mut kept_arg_idents = Vec::new(); + let mut fmt_iter = fmt_str.chars(); + while let Some(fmt_char) = fmt_iter.next() { + match fmt_char { + '{' => { + let mut last_char = ' '; + let mut ident = String::new(); + let mut found_end_bracket = false; + while let Some(ident_char) = fmt_iter.next() { + match ident_char { + '}' => { + found_end_bracket = true; + break; + } + _ => { + last_char = ident_char; + if !ident_char.is_whitespace() { + ident.push(ident_char); + } else { + for blank_char in fmt_iter.by_ref() { + match blank_char { + '}' => { + found_end_bracket = true; + break; + } + c if c.is_whitespace() => { + last_char = ident_char; + } + _ => { + return ( + Err(syn::Error::new(Span::call_site(), "")), + kept_arg_idents, + ) + } + } + } + } + } + } + } + if !found_end_bracket { + return (Err(syn::Error::new(Span::call_site(), "")), kept_arg_idents); + } + if let Some(index) = kept_arg_idents + .iter() + .position(|arg_ident| *arg_ident == ident) + { + fixed_format_str.push_str(&format!("{{{}}}", index + 1)) + } else if let Some(index) = + arg_idents.iter().position(|arg_ident| *arg_ident == ident) + { + kept_arg_idents.push(arg_idents.remove(index)); + fixed_format_str.push_str(&format!("{{{}}}", kept_arg_idents.len())) + } else { + fixed_format_str.push_str(&format!("{{{ident}}}")) + } + } + '}' => fixed_format_str.push_str("}}"), + _ => fixed_format_str.push(fmt_char), + } + } + (Ok(fixed_format_str), kept_arg_idents) +} + fn extract_arg_idents( args: &args::Args, attr_applied: AttrApplied, From 28d8bdf1f349d16ec752ce06154756d50fb2fd5e Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 13:12:49 -0400 Subject: [PATCH 04/12] Seperated the code for parsing custom formating --- src/lib.rs | 125 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 579d1db..0ecacc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ mod args; -use proc_macro2::Span; +use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, Parser}, @@ -382,59 +382,10 @@ fn parse_fmt_str( let mut fmt_iter = fmt_str.chars(); while let Some(fmt_char) = fmt_iter.next() { match fmt_char { - '{' => { - let mut last_char = ' '; - let mut ident = String::new(); - let mut found_end_bracket = false; - while let Some(ident_char) = fmt_iter.next() { - match ident_char { - '}' => { - found_end_bracket = true; - break; - } - _ => { - last_char = ident_char; - if !ident_char.is_whitespace() { - ident.push(ident_char); - } else { - for blank_char in fmt_iter.by_ref() { - match blank_char { - '}' => { - found_end_bracket = true; - break; - } - c if c.is_whitespace() => { - last_char = ident_char; - } - _ => { - return ( - Err(syn::Error::new(Span::call_site(), "")), - kept_arg_idents, - ) - } - } - } - } - } - } - } - if !found_end_bracket { - return (Err(syn::Error::new(Span::call_site(), "")), kept_arg_idents); - } - if let Some(index) = kept_arg_idents - .iter() - .position(|arg_ident| *arg_ident == ident) - { - fixed_format_str.push_str(&format!("{{{}}}", index + 1)) - } else if let Some(index) = - arg_idents.iter().position(|arg_ident| *arg_ident == ident) - { - kept_arg_idents.push(arg_idents.remove(index)); - fixed_format_str.push_str(&format!("{{{}}}", kept_arg_idents.len())) - } else { - fixed_format_str.push_str(&format!("{{{ident}}}")) - } - } + '{' => match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents) { + Ok(interpolated) => fixed_format_str.push_str(&interpolated), + Err(e) => return (Err(e), kept_arg_idents), + }, '}' => fixed_format_str.push_str("}}"), _ => fixed_format_str.push(fmt_char), } @@ -442,6 +393,72 @@ fn parse_fmt_str( (Ok(fixed_format_str), kept_arg_idents) } +fn fix_interpolated( + last_char: char, + ident: String, + arg_idents: &mut Vec, + kept_arg_idents: &mut Vec, +) -> Result { + if last_char != '}' { + return Err(syn::Error::new(Span::call_site(), "")); + } + let predicate = |arg_ident: &Ident| *arg_ident == ident; + if let Some(index) = kept_arg_idents.iter().position(predicate) { + Ok(format!("{{{}}}", index + 1)) + } else if let Some(index) = arg_idents.iter().position(predicate) { + kept_arg_idents.push(arg_idents.remove(index)); + Ok(format!("{{{}}}", kept_arg_idents.len())) + } else { + Ok(format!("{{{ident}}}")) + } +} + +fn parse_interpolated( + fmt_iter: &mut std::str::Chars<'_>, + arg_idents: &mut Vec, + kept_arg_idents: &mut Vec, +) -> Result { + let mut last_char = ' '; + let mut ident = String::new(); + while let Some(ident_char) = fmt_iter.next() { + match ident_char { + '}' => { + last_char = '}'; + break; + } + _ => { + last_char = ident_char; + if !ident_char.is_whitespace() { + ident.push(ident_char); + } else { + skip_whitespace_and_check(fmt_iter, &mut last_char, ident_char)?; + } + } + } + } + fix_interpolated(last_char, ident, arg_idents, kept_arg_idents) +} + +fn skip_whitespace_and_check( + fmt_iter: &mut std::str::Chars<'_>, + last_char: &mut char, + ident_char: char, +) -> Result<(), syn::Error> { + for blank_char in fmt_iter.by_ref() { + match blank_char { + '}' => { + *last_char = '}'; + break; + } + c if c.is_whitespace() => { + *last_char = ident_char; + } + _ => return Err(syn::Error::new(Span::call_site(), "")), + } + } + Ok(()) +} + fn extract_arg_idents( args: &args::Args, attr_applied: AttrApplied, From 3cb42d21c70747c6b987c15c94802541f901843f Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 14:12:11 -0400 Subject: [PATCH 05/12] the parser for custom formatting gives back more reasonable errors Also fixed bug in parser which lead to non interpolated things being interpolated --- examples/example_custom_format.rs | 2 +- src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example_custom_format.rs b/examples/example_custom_format.rs index 1cd2665..51defa0 100644 --- a/examples/example_custom_format.rs +++ b/examples/example_custom_format.rs @@ -6,7 +6,7 @@ fn main() { foo(4, 4) } -#[trace(format_enter = "{y} and {x} {x}")] +#[trace(format_enter = "{y} and {x} {7}")] fn foo(x: u32, y: u32) {} #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 0ecacc9..2fa44c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -400,7 +400,7 @@ fn fix_interpolated( kept_arg_idents: &mut Vec, ) -> Result { if last_char != '}' { - return Err(syn::Error::new(Span::call_site(), "")); + return Err(syn::Error::new(Span::call_site(), format!("expected `}}`, but found `{last_char}`."))); } let predicate = |arg_ident: &Ident| *arg_ident == ident; if let Some(index) = kept_arg_idents.iter().position(predicate) { @@ -409,7 +409,7 @@ fn fix_interpolated( kept_arg_idents.push(arg_idents.remove(index)); Ok(format!("{{{}}}", kept_arg_idents.len())) } else { - Ok(format!("{{{ident}}}")) + Ok(format!("{{{{{ident}}}}}")) } } @@ -453,7 +453,7 @@ fn skip_whitespace_and_check( c if c.is_whitespace() => { *last_char = ident_char; } - _ => return Err(syn::Error::new(Span::call_site(), "")), + _ => return Err(syn::Error::new(Span::call_site(), format!("expected `}}`, but found `{blank_char}."))), } } Ok(()) From 285da5b918e09fb80caec6229bae611d5d513b73 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 16:05:43 -0400 Subject: [PATCH 06/12] custom formatting also for return values use {r} to interpolate return values the parser is getting a drop messy --- examples/example_custom_format.rs | 8 ++- .../test_custom_format.expected | 4 +- src/lib.rs | 65 ++++++++++++------- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/examples/example_custom_format.rs b/examples/example_custom_format.rs index 51defa0..acb4c9c 100644 --- a/examples/example_custom_format.rs +++ b/examples/example_custom_format.rs @@ -3,11 +3,13 @@ use trace::trace; trace::init_depth_var!(); fn main() { - foo(4, 4) + foo(5, 4); } -#[trace(format_enter = "{y} and {x} {7}")] -fn foo(x: u32, y: u32) {} +#[trace(format_enter = "{y} and {z} {7}", format_exit = "{r} * {r}")] +fn foo(z: u32, y: u32) -> u32 { + z +} #[cfg(test)] #[macro_use] diff --git a/examples/expected_test_outputs/test_custom_format.expected b/examples/expected_test_outputs/test_custom_format.expected index c18cbfd..74a95e9 100644 --- a/examples/expected_test_outputs/test_custom_format.expected +++ b/examples/expected_test_outputs/test_custom_format.expected @@ -1,2 +1,2 @@ -[+] Entering foo(4 and 4 4) -[-] Exiting foo = () +[+] Entering foo(4 and 5 {7}) +[-] Exiting foo = 5 diff --git a/src/lib.rs b/src/lib.rs index 2fa44c4..72a4d76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,11 +74,11 @@ mod args; -use proc_macro2::{Ident, Span}; +use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, Parser}, - parse_quote, + parse_quote, Block, }; /// A convenience macro for declaring the `DEPTH` variable used for indenting the output @@ -320,9 +320,9 @@ fn construct_traced_block( sig: &syn::Signature, original_block: &syn::Block, ) -> syn::Block { - let arg_idents = extract_arg_idents(args, attr_applied, sig); - let (arg_idents_format, arg_idents) = if let Some(fmt_str) = &args.format_enter { - parse_fmt_str(fmt_str, arg_idents) + let arg_idents = extract_arg_idents(args, attr_applied, sig).iter().map(|ident| ident.to_token_stream()).collect(); + let (enter_format, arg_idents) = if let Some(fmt_str) = &args.format_enter { + parse_fmt_str(fmt_str, arg_idents, false) } else { ( Ok(arg_idents @@ -333,16 +333,20 @@ fn construct_traced_block( arg_idents, ) }; - let pretty = if args.pretty { "#" } else { "" }; + let exit_val = vec![quote!("t")]; + let (exit_format, exit_val) = if let Some(fmt_str) = &args.format_exit { + parse_fmt_str(&fmt_str, exit_val, true) + } else if args.pretty { (Ok("{:#?}".to_string()), exit_val) } else { (Ok("{:?}".to_string()), exit_val) }; + let should_interpolate = exit_val.len() > 0; let entering_format = format!( "{{:depth$}}{} Entering {}({})", args.prefix_enter, sig.ident, - arg_idents_format.unwrap() + enter_format.unwrap() ); let exiting_format = format!( - "{{:depth$}}{} Exiting {} = {{:{}?}}", - args.prefix_exit, sig.ident, pretty + "{{:depth$}}{} Exiting {} = {}", + args.prefix_exit, sig.ident, exit_format.unwrap() ); let pause_stmt = if args.pause { @@ -360,29 +364,36 @@ fn construct_traced_block( } else { quote! { println! } }; - - parse_quote! {{ + let print_exit = if should_interpolate { + quote!{{#printer(#exiting_format, "",fn_return_value, depth = DEPTH.with(|d| d.get()));}} + } else { + quote!(#printer(#exiting_format, "", depth = DEPTH.with(|d| d.get()));) + }; + let res: Block= parse_quote! {{ #printer(#entering_format, "", #(#arg_idents,)* depth = DEPTH.with(|d| d.get())); #pause_stmt DEPTH.with(|d| d.set(d.get() + 1)); let fn_return_value = #original_block; DEPTH.with(|d| d.set(d.get() - 1)); - #printer(#exiting_format, "", fn_return_value, depth = DEPTH.with(|d| d.get())); + #print_exit #pause_stmt fn_return_value - }} + }}; + println!("{}", res.to_token_stream()); + res } fn parse_fmt_str( fmt_str: &str, - mut arg_idents: Vec, -) -> (Result, Vec) { + mut arg_idents: Vec, + exit: bool, +) -> (Result, Vec) { let mut fixed_format_str = String::new(); let mut kept_arg_idents = Vec::new(); let mut fmt_iter = fmt_str.chars(); while let Some(fmt_char) = fmt_iter.next() { match fmt_char { - '{' => match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents) { + '{' => match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents, exit) { Ok(interpolated) => fixed_format_str.push_str(&interpolated), Err(e) => return (Err(e), kept_arg_idents), }, @@ -396,27 +407,35 @@ fn parse_fmt_str( fn fix_interpolated( last_char: char, ident: String, - arg_idents: &mut Vec, - kept_arg_idents: &mut Vec, + arg_idents: &mut Vec, + kept_arg_idents: &mut Vec, + exit: bool ) -> Result { if last_char != '}' { return Err(syn::Error::new(Span::call_site(), format!("expected `}}`, but found `{last_char}`."))); } - let predicate = |arg_ident: &Ident| *arg_ident == ident; + let predicate = |arg_ident: &TokenStream| arg_ident.to_string() == ident; if let Some(index) = kept_arg_idents.iter().position(predicate) { Ok(format!("{{{}}}", index + 1)) } else if let Some(index) = arg_idents.iter().position(predicate) { kept_arg_idents.push(arg_idents.remove(index)); Ok(format!("{{{}}}", kept_arg_idents.len())) - } else { + } else if exit && ident == "r" { + if kept_arg_idents.len() == 0 { + std::mem::swap(arg_idents, kept_arg_idents) + } + Ok(format!("{{1}}")) + } + else { Ok(format!("{{{{{ident}}}}}")) } } fn parse_interpolated( fmt_iter: &mut std::str::Chars<'_>, - arg_idents: &mut Vec, - kept_arg_idents: &mut Vec, + arg_idents: &mut Vec, + kept_arg_idents: &mut Vec, + exit: bool, ) -> Result { let mut last_char = ' '; let mut ident = String::new(); @@ -436,7 +455,7 @@ fn parse_interpolated( } } } - fix_interpolated(last_char, ident, arg_idents, kept_arg_idents) + fix_interpolated(last_char, ident, arg_idents, kept_arg_idents, exit) } fn skip_whitespace_and_check( From 4f68234edadda77ba6db725d4dbca91ebdfa1bb1 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 16:23:33 -0400 Subject: [PATCH 07/12] added some documentation for custom formatting --- src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 72a4d76..ed0a062 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,6 +140,26 @@ pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// - `logging` - Use `log::trace!` from the `log` crate instead of `println`. Disabled by default. /// +/// - `format_enter` - The format (anything after the prefix) of of `println!` statements when a function +/// is entered. Allows parameter interpolation like: +/// ```rust +/// #[trace(format_enter = "i is {i}")] +/// fn foo(i: i32) { +/// println!("foo") +/// } +/// ``` +/// If you try to interpolate a non parameter like `"{1}"` when a function is entered it will just print `{1}`. +/// Disabled by default. +///- `format_exit` - The format (anything after the prefix) of of `println!` statements when a function +/// is exited. to interpolate the return value use `{r}` +/// ```rust +/// #[trace(format_exit = "returning {r}")] +/// fn foo() -> i32 { +/// 1 +/// } +/// ``` +/// If you try to interpolate anything else it will follow the same rules as `format_enter`. Disabled by default. +/// /// Note that `enable` and `disable` can not be used together, and doing so will result in an error. #[proc_macro_attribute] pub fn trace( From 4288489fd104d2b7775ccf30f990b8d9e3487ee2 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Wed, 28 Jun 2023 16:40:37 -0400 Subject: [PATCH 08/12] more clean up from implementing custom formatting --- .../test_custom_format.expected | 2 +- src/lib.rs | 63 ++++++++++++------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/examples/expected_test_outputs/test_custom_format.expected b/examples/expected_test_outputs/test_custom_format.expected index 74a95e9..1a2a777 100644 --- a/examples/expected_test_outputs/test_custom_format.expected +++ b/examples/expected_test_outputs/test_custom_format.expected @@ -1,2 +1,2 @@ [+] Entering foo(4 and 5 {7}) -[-] Exiting foo = 5 +[-] Exiting foo = 5 * 5 diff --git a/src/lib.rs b/src/lib.rs index ed0a062..6585b3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, Parser}, - parse_quote, Block, + parse_quote, }; /// A convenience macro for declaring the `DEPTH` variable used for indenting the output @@ -159,7 +159,7 @@ pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// } /// ``` /// If you try to interpolate anything else it will follow the same rules as `format_enter`. Disabled by default. -/// +/// /// Note that `enable` and `disable` can not be used together, and doing so will result in an error. #[proc_macro_attribute] pub fn trace( @@ -340,7 +340,10 @@ fn construct_traced_block( sig: &syn::Signature, original_block: &syn::Block, ) -> syn::Block { - let arg_idents = extract_arg_idents(args, attr_applied, sig).iter().map(|ident| ident.to_token_stream()).collect(); + let arg_idents = extract_arg_idents(args, attr_applied, sig) + .iter() + .map(|ident| ident.to_token_stream()) + .collect(); let (enter_format, arg_idents) = if let Some(fmt_str) = &args.format_enter { parse_fmt_str(fmt_str, arg_idents, false) } else { @@ -355,9 +358,13 @@ fn construct_traced_block( }; let exit_val = vec![quote!("t")]; let (exit_format, exit_val) = if let Some(fmt_str) = &args.format_exit { - parse_fmt_str(&fmt_str, exit_val, true) - } else if args.pretty { (Ok("{:#?}".to_string()), exit_val) } else { (Ok("{:?}".to_string()), exit_val) }; - let should_interpolate = exit_val.len() > 0; + parse_fmt_str(fmt_str, exit_val, true) + } else if args.pretty { + (Ok("{:#?}".to_string()), exit_val) + } else { + (Ok("{:?}".to_string()), exit_val) + }; + let should_interpolate = !exit_val.is_empty(); let entering_format = format!( "{{:depth$}}{} Entering {}({})", args.prefix_enter, @@ -366,7 +373,9 @@ fn construct_traced_block( ); let exiting_format = format!( "{{:depth$}}{} Exiting {} = {}", - args.prefix_exit, sig.ident, exit_format.unwrap() + args.prefix_exit, + sig.ident, + exit_format.unwrap() ); let pause_stmt = if args.pause { @@ -385,11 +394,11 @@ fn construct_traced_block( quote! { println! } }; let print_exit = if should_interpolate { - quote!{{#printer(#exiting_format, "",fn_return_value, depth = DEPTH.with(|d| d.get()));}} + quote! {{#printer(#exiting_format, "",fn_return_value, depth = DEPTH.with(|d| d.get()));}} } else { quote!(#printer(#exiting_format, "", depth = DEPTH.with(|d| d.get()));) }; - let res: Block= parse_quote! {{ + parse_quote! {{ #printer(#entering_format, "", #(#arg_idents,)* depth = DEPTH.with(|d| d.get())); #pause_stmt DEPTH.with(|d| d.set(d.get() + 1)); @@ -398,9 +407,7 @@ fn construct_traced_block( #print_exit #pause_stmt fn_return_value - }}; - println!("{}", res.to_token_stream()); - res + }} } fn parse_fmt_str( @@ -413,10 +420,13 @@ fn parse_fmt_str( let mut fmt_iter = fmt_str.chars(); while let Some(fmt_char) = fmt_iter.next() { match fmt_char { - '{' => match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents, exit) { - Ok(interpolated) => fixed_format_str.push_str(&interpolated), - Err(e) => return (Err(e), kept_arg_idents), - }, + '{' => { + match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents, exit) + { + Ok(interpolated) => fixed_format_str.push_str(&interpolated), + Err(e) => return (Err(e), kept_arg_idents), + } + } '}' => fixed_format_str.push_str("}}"), _ => fixed_format_str.push(fmt_char), } @@ -429,10 +439,13 @@ fn fix_interpolated( ident: String, arg_idents: &mut Vec, kept_arg_idents: &mut Vec, - exit: bool + exit: bool, ) -> Result { if last_char != '}' { - return Err(syn::Error::new(Span::call_site(), format!("expected `}}`, but found `{last_char}`."))); + return Err(syn::Error::new( + Span::call_site(), + format!("expected `}}`, but found `{last_char}`."), + )); } let predicate = |arg_ident: &TokenStream| arg_ident.to_string() == ident; if let Some(index) = kept_arg_idents.iter().position(predicate) { @@ -441,12 +454,11 @@ fn fix_interpolated( kept_arg_idents.push(arg_idents.remove(index)); Ok(format!("{{{}}}", kept_arg_idents.len())) } else if exit && ident == "r" { - if kept_arg_idents.len() == 0 { + if kept_arg_idents.is_empty() { std::mem::swap(arg_idents, kept_arg_idents) } - Ok(format!("{{1}}")) - } - else { + Ok("{1}".to_string()) + } else { Ok(format!("{{{{{ident}}}}}")) } } @@ -492,7 +504,12 @@ fn skip_whitespace_and_check( c if c.is_whitespace() => { *last_char = ident_char; } - _ => return Err(syn::Error::new(Span::call_site(), format!("expected `}}`, but found `{blank_char}."))), + _ => { + return Err(syn::Error::new( + Span::call_site(), + format!("expected `}}`, but found `{blank_char}."), + )) + } } } Ok(()) From 85067ade33686b7083806fa33d48a96d9ff3dab4 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Thu, 29 Jun 2023 18:59:47 -0400 Subject: [PATCH 09/12] made the logic for format_exit a little more simple --- src/lib.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6585b3a..3645a9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -345,7 +345,7 @@ fn construct_traced_block( .map(|ident| ident.to_token_stream()) .collect(); let (enter_format, arg_idents) = if let Some(fmt_str) = &args.format_enter { - parse_fmt_str(fmt_str, arg_idents, false) + parse_fmt_str(fmt_str, arg_idents) } else { ( Ok(arg_idents @@ -356,9 +356,13 @@ fn construct_traced_block( arg_idents, ) }; - let exit_val = vec![quote!("t")]; + // we set set exit val to be a vector with one element which is Ident called r + // this means that the format parser can indentify when then return value should be interprolated + // so if we want to use a different symbol to denote return value interpolation we just need to change the symbol in the following quote + // ie: `let exit_val = vec![quote!(return_value)];` if we wanted to use return_value to denote return value interpolation + let exit_val = vec![quote!(r)]; let (exit_format, exit_val) = if let Some(fmt_str) = &args.format_exit { - parse_fmt_str(fmt_str, exit_val, true) + parse_fmt_str(fmt_str, exit_val) } else if args.pretty { (Ok("{:#?}".to_string()), exit_val) } else { @@ -413,20 +417,16 @@ fn construct_traced_block( fn parse_fmt_str( fmt_str: &str, mut arg_idents: Vec, - exit: bool, ) -> (Result, Vec) { let mut fixed_format_str = String::new(); let mut kept_arg_idents = Vec::new(); let mut fmt_iter = fmt_str.chars(); while let Some(fmt_char) = fmt_iter.next() { match fmt_char { - '{' => { - match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents, exit) - { - Ok(interpolated) => fixed_format_str.push_str(&interpolated), - Err(e) => return (Err(e), kept_arg_idents), - } - } + '{' => match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents) { + Ok(interpolated) => fixed_format_str.push_str(&interpolated), + Err(e) => return (Err(e), kept_arg_idents), + }, '}' => fixed_format_str.push_str("}}"), _ => fixed_format_str.push(fmt_char), } @@ -439,7 +439,6 @@ fn fix_interpolated( ident: String, arg_idents: &mut Vec, kept_arg_idents: &mut Vec, - exit: bool, ) -> Result { if last_char != '}' { return Err(syn::Error::new( @@ -453,11 +452,6 @@ fn fix_interpolated( } else if let Some(index) = arg_idents.iter().position(predicate) { kept_arg_idents.push(arg_idents.remove(index)); Ok(format!("{{{}}}", kept_arg_idents.len())) - } else if exit && ident == "r" { - if kept_arg_idents.is_empty() { - std::mem::swap(arg_idents, kept_arg_idents) - } - Ok("{1}".to_string()) } else { Ok(format!("{{{{{ident}}}}}")) } @@ -467,7 +461,6 @@ fn parse_interpolated( fmt_iter: &mut std::str::Chars<'_>, arg_idents: &mut Vec, kept_arg_idents: &mut Vec, - exit: bool, ) -> Result { let mut last_char = ' '; let mut ident = String::new(); @@ -487,7 +480,7 @@ fn parse_interpolated( } } } - fix_interpolated(last_char, ident, arg_idents, kept_arg_idents, exit) + fix_interpolated(last_char, ident, arg_idents, kept_arg_idents) } fn skip_whitespace_and_check( From db77f537c565db2217d4da5fac79d8090ad455bc Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 30 Jun 2023 02:10:32 -0400 Subject: [PATCH 10/12] added error for when pretty and custom formatting are used together --- src/args.rs | 20 ++++++++++++++++++++ src/lib.rs | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index b578c1d..b769c44 100644 --- a/src/args.rs +++ b/src/args.rs @@ -331,6 +331,26 @@ impl Args { "cannot have both `enable` and `disable`", )); } + if pretty_args.len() == 1 && format_enter_args.len() == 1 { + errors.push(syn::Error::new( + pretty_args[0].0, + "cannot have both `pretty` and `format_enter`", + )); + errors.push(syn::Error::new( + format_enter_args[0].0, + "cannot have both `pretty` and `format_enter`", + )); + } + if pretty_args.len() == 1 && format_exit_args.len() == 1 { + errors.push(syn::Error::new( + pretty_args[0].0, + "cannot have both `pretty` and `format_exit`", + )); + errors.push(syn::Error::new( + format_exit_args[0].0, + "cannot have both `pretty` and `format_exit`", + )); + } if errors.is_empty() { macro_rules! first_no_span { diff --git a/src/lib.rs b/src/lib.rs index 3645a9a..63f0df2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,7 +160,9 @@ pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// ``` /// If you try to interpolate anything else it will follow the same rules as `format_enter`. Disabled by default. /// -/// Note that `enable` and `disable` can not be used together, and doing so will result in an error. +/// Note that `enable` and `disable` cannot be used together, and doing so will result in an error. +/// +/// Further note that `format_enter` or `format_exit` cannot be used together with with `pretty`, and doing so will result in an error. #[proc_macro_attribute] pub fn trace( args: proc_macro::TokenStream, From 42fb8237f6ae54c680011bfda0c5e0f7b58add44 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 30 Jun 2023 03:50:12 -0400 Subject: [PATCH 11/12] changed custom formatting interpolation custom formatting interpolation now follows (mostly) the same rules as `fomat!` custom formatting interpolation now report (mostly) the same errors as `format!` fixed some copy pasted error reporting --- examples/example_custom_format.rs | 2 +- src/args.rs | 8 ++-- src/lib.rs | 68 +++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/examples/example_custom_format.rs b/examples/example_custom_format.rs index acb4c9c..31705a5 100644 --- a/examples/example_custom_format.rs +++ b/examples/example_custom_format.rs @@ -6,7 +6,7 @@ fn main() { foo(5, 4); } -#[trace(format_enter = "{y} and {z} {7}", format_exit = "{r} * {r}")] +#[trace(format_enter = "{y} and {z} {{7}}", format_exit = "{r} * {r}")] fn foo(z: u32, y: u32) -> u32 { z } diff --git a/src/args.rs b/src/args.rs index b769c44..947992d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -101,13 +101,13 @@ impl Args { let format_enter_type_error = || { vec![syn::Error::new_spanned( ident.clone(), - "`prefix_enter` requires a string value", + "`format_enter` requires a string value", )] }; let format_exit_type_error = || { vec![syn::Error::new_spanned( ident.clone(), - "`prefix_exit` requires a string value", + "`format_exit` requires a string value", )] }; let enable_type_error = || { @@ -272,14 +272,14 @@ impl Args { } if format_enter_args.len() >= 2 { errors.extend( - prefix_enter_args + format_enter_args .iter() .map(|(span, _)| syn::Error::new(*span, "duplicate `format_enter`")), ); } if format_exit_args.len() >= 2 { errors.extend( - prefix_exit_args + format_exit_args .iter() .map(|(span, _)| syn::Error::new(*span, "duplicate `format_exit`")), ); diff --git a/src/lib.rs b/src/lib.rs index 63f0df2..11ff083 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,8 @@ mod args; +use std::{iter::Peekable, str::Chars}; + use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ @@ -148,17 +150,17 @@ pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// println!("foo") /// } /// ``` -/// If you try to interpolate a non parameter like `"{1}"` when a function is entered it will just print `{1}`. -/// Disabled by default. +/// Interpolation follows the same rules as `format!()` besides for the fact that there is no pretty printing, +/// that is anything interpolated will be debug formatted. Disabled by default. ///- `format_exit` - The format (anything after the prefix) of of `println!` statements when a function -/// is exited. to interpolate the return value use `{r}` +/// is exited. To interpolate the return value use `{r}` /// ```rust /// #[trace(format_exit = "returning {r}")] /// fn foo() -> i32 { /// 1 /// } /// ``` -/// If you try to interpolate anything else it will follow the same rules as `format_enter`. Disabled by default. +/// Otherwise formatting follows the same rules as `format_enter`. Disabled by default. /// /// Note that `enable` and `disable` cannot be used together, and doing so will result in an error. /// @@ -375,13 +377,25 @@ fn construct_traced_block( "{{:depth$}}{} Entering {}({})", args.prefix_enter, sig.ident, - enter_format.unwrap() + match enter_format { + Ok(ok) => ok, + Err(e) => { + let error = e.into_compile_error(); + return parse_quote! {{#error}}; + } + } ); let exiting_format = format!( "{{:depth$}}{} Exiting {} = {}", args.prefix_exit, sig.ident, - exit_format.unwrap() + match exit_format { + Ok(ok) => ok, + Err(e) => { + let error = e.into_compile_error(); + return parse_quote! {{#error}}; + } + } ); let pause_stmt = if args.pause { @@ -422,14 +436,30 @@ fn parse_fmt_str( ) -> (Result, Vec) { let mut fixed_format_str = String::new(); let mut kept_arg_idents = Vec::new(); - let mut fmt_iter = fmt_str.chars(); + let mut fmt_iter = fmt_str.chars().peekable(); while let Some(fmt_char) = fmt_iter.next() { match fmt_char { - '{' => match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents) { - Ok(interpolated) => fixed_format_str.push_str(&interpolated), - Err(e) => return (Err(e), kept_arg_idents), - }, - '}' => fixed_format_str.push_str("}}"), + '{' => { + if let Some('{') = fmt_iter.peek() { + fixed_format_str.push_str("{{"); + fmt_iter.next(); + } else { + match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents) { + Ok(interpolated) => fixed_format_str.push_str(&interpolated), + Err(e) => return (Err(e), kept_arg_idents), + } + } + } + '}' => { + if fmt_iter.next() != Some('}') { + return (Err(syn::Error::new( + Span::call_site(), + "invalid format string: unmatched `}` found\nif you intended to print `}`, you can escape it using `}}`" + )), kept_arg_idents); + } + + fixed_format_str.push_str("}}") + } _ => fixed_format_str.push(fmt_char), } } @@ -445,7 +475,7 @@ fn fix_interpolated( if last_char != '}' { return Err(syn::Error::new( Span::call_site(), - format!("expected `}}`, but found `{last_char}`."), + "invalid format string: expected `'}}'` but string was terminated\nif you intended to print `{{`, you can escape it using `{{`.", )); } let predicate = |arg_ident: &TokenStream| arg_ident.to_string() == ident; @@ -455,12 +485,16 @@ fn fix_interpolated( kept_arg_idents.push(arg_idents.remove(index)); Ok(format!("{{{}}}", kept_arg_idents.len())) } else { - Ok(format!("{{{{{ident}}}}}")) + Err(syn::Error::new( + Span::call_site(), + // TODO: better error message + format!("cannot find `{ident}` in this scope."), + )) } } fn parse_interpolated( - fmt_iter: &mut std::str::Chars<'_>, + fmt_iter: &mut Peekable, arg_idents: &mut Vec, kept_arg_idents: &mut Vec, ) -> Result { @@ -486,7 +520,7 @@ fn parse_interpolated( } fn skip_whitespace_and_check( - fmt_iter: &mut std::str::Chars<'_>, + fmt_iter: &mut Peekable, last_char: &mut char, ident_char: char, ) -> Result<(), syn::Error> { @@ -502,7 +536,7 @@ fn skip_whitespace_and_check( _ => { return Err(syn::Error::new( Span::call_site(), - format!("expected `}}`, but found `{blank_char}."), + format!("invalid format string: expected `'}}'`, found `'{blank_char}'`\nif you intended to print `{{`, you can escape it using `{{`."), )) } } From 4207a0a0189bb690c9c289ba2176111c5209d302 Mon Sep 17 00:00:00 2001 From: Gulshan Singh Date: Tue, 4 Jul 2023 10:12:18 -0700 Subject: [PATCH 12/12] Apply suggestions from code review --- src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 11ff083..3716e8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// - `logging` - Use `log::trace!` from the `log` crate instead of `println`. Disabled by default. /// -/// - `format_enter` - The format (anything after the prefix) of of `println!` statements when a function +/// - `format_enter` - The format (anything after the prefix) of `println!` statements when a function /// is entered. Allows parameter interpolation like: /// ```rust /// #[trace(format_enter = "i is {i}")] @@ -152,8 +152,9 @@ pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// ``` /// Interpolation follows the same rules as `format!()` besides for the fact that there is no pretty printing, /// that is anything interpolated will be debug formatted. Disabled by default. -///- `format_exit` - The format (anything after the prefix) of of `println!` statements when a function -/// is exited. To interpolate the return value use `{r}` + +/// - `format_exit` - The format (anything after the prefix) of `println!` statements when a function +/// is exited. To interpolate the return value use `{r}`: /// ```rust /// #[trace(format_exit = "returning {r}")] /// fn foo() -> i32 {