use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident, ItemFn, Token};
#[derive(Default)]
struct InstrumentArgs {
method_calls: Vec<Ident>,
format_args: Option<proc_macro2::TokenStream>,
boxed: bool,
}
impl syn::parse::Parse for InstrumentArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut method_calls = Vec::new();
let mut format_args = None;
let mut boxed = false;
while input.peek(Ident) {
let fork = input.fork();
let ident: Ident = fork.parse()?;
if fork.peek(Token![,]) || fork.is_empty() {
input.parse::<Ident>()?;
if ident == "boxed" {
boxed = true;
} else {
method_calls.push(ident);
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
} else {
break;
}
}
if !input.is_empty() {
let remaining: proc_macro2::TokenStream = input.parse()?;
format_args = Some(remaining);
}
Ok(InstrumentArgs {
method_calls,
format_args,
boxed,
})
}
}
#[proc_macro_attribute]
pub fn instrument(args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as ItemFn);
if input_fn.sig.asyncness.is_none() {
return syn::Error::new_spanned(
&input_fn.sig.fn_token,
"the `instrument` attribute can only be applied to async functions",
)
.to_compile_error()
.into();
}
let parsed_args = if args.is_empty() {
InstrumentArgs::default()
} else {
match syn::parse::<InstrumentArgs>(args) {
Ok(args) => args,
Err(e) => return e.to_compile_error().into(),
}
};
let span_args = if let Some(format_args) = parsed_args.format_args {
quote! { #format_args }
} else {
let fn_name = &input_fn.sig.ident;
quote! { stringify!(#fn_name) }
};
let mut span_creation = quote! { ::await_tree::span!(#span_args) };
for method_name in parsed_args.method_calls {
span_creation = quote! { #span_creation.#method_name() };
}
let fn_vis = &input_fn.vis;
let fn_sig = &input_fn.sig;
let fn_block = &input_fn.block;
let fn_attrs = &input_fn.attrs;
let boxed =
(parsed_args.boxed).then(|| quote! { let __at_fut = ::std::boxed::Box::pin(__at_fut); });
let result = quote! {
#(#fn_attrs)*
#fn_vis #fn_sig {
use ::await_tree::SpanExt as _;
let __at_span: ::await_tree::Span = #span_creation;
let __at_fut = async move #fn_block;
#boxed
::await_tree::InstrumentAwait::instrument_await(__at_fut, __at_span).await
}
};
result.into()
}