use proc_macro::TokenStream;
use quote::quote;
use syn::{ImplItem, ItemImpl, parse_macro_input};
#[proc_macro_attribute]
pub fn instrumented_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
let impl_block = parse_macro_input!(item as ItemImpl);
#[cfg(kani)]
{
return TokenStream::from(quote! { #impl_block });
}
#[cfg(not(kani))]
{
let mut impl_block = impl_block;
for item in &mut impl_block.items {
if let ImplItem::Fn(method) = item {
if matches!(method.vis, syn::Visibility::Public(_)) {
let method_name = method.sig.ident.to_string();
let has_generics = !method.sig.generics.params.is_empty();
let instrument_attr = if is_constructor(&method_name) {
if has_generics {
let param_names: Vec<_> = method
.sig
.inputs
.iter()
.filter_map(|arg| {
if let syn::FnArg::Typed(pat_type) = arg
&& let syn::Pat::Ident(ident) = &*pat_type.pat
{
return Some(ident.ident.clone());
}
None
})
.collect();
quote! {
#[tracing::instrument(skip(#(#param_names),*), err)]
}
} else {
quote! {
#[tracing::instrument(err)]
}
}
} else if is_accessor(&method_name) {
quote! {
#[tracing::instrument(level = "trace", ret)]
}
} else {
quote! {
#[tracing::instrument(skip(self))]
}
};
let attr: syn::Attribute = syn::parse_quote! { #instrument_attr };
method.attrs.insert(0, attr);
}
}
}
TokenStream::from(quote! { #impl_block })
}
}
fn is_constructor(name: &str) -> bool {
name == "new" || name.starts_with("from_") || name.starts_with("try_") || name == "default"
}
fn is_accessor(name: &str) -> bool {
name == "get"
|| name == "into_inner"
|| name.starts_with("as_")
|| name.starts_with("to_")
|| name.starts_with("get_")
}