use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, visit_mut::VisitMut, Expr, Item, Path};
struct AutoContext;
impl VisitMut for AutoContext {
fn visit_expr_mut(&mut self, expr: &mut Expr) {
if let Expr::Try(expr_try) = expr {
let context = anyhow_context(&expr_try.expr);
let inner = &expr_try.expr;
let span = expr_try.question_token.spans[0];
*expr = syn::parse_quote_spanned! { span=>
(#inner).context(format!("{} @ {}::{}", #context, file!(), line!()))?
};
}
syn::visit_mut::visit_expr_mut(self, expr);
}
}
fn path_to_string(path: &Path) -> String {
path
.segments
.iter()
.map(|s| format!("{}", s.ident))
.collect::<Vec<String>>()
.join("::")
}
fn anyhow_context(expr: &Expr) -> String {
match expr {
Expr::MethodCall(method_call) => {
let name = method_call.method.to_string();
let args = if method_call.args.is_empty() { "" } else { ".." };
format!(".{name}({args})")
}
Expr::Call(call) => {
if let Expr::Path(path) = &*call.func {
let path = path_to_string(&path.path);
let args = if call.args.is_empty() { "" } else { ".." };
format!("{path}({args})")
} else {
"(.. some expression ..)".to_string()
}
}
Expr::Path(path) => path_to_string(&path.path),
_ => "(.. some expr ..)".to_string(),
}
}
#[proc_macro_attribute]
pub fn auto_context(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut item = parse_macro_input!(item as Item);
AutoContext.visit_item_mut(&mut item);
TokenStream::from(quote! { #item })
}