traced_macro 0.1.0

Procedural macro support for traced_error
Documentation
use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::visit_mut::{self, VisitMut};
use syn::{Expr, ExprTry, ItemFn, parse_macro_input};

struct TryRewriter;

impl VisitMut for TryRewriter {
    fn visit_expr_mut(&mut self, expr: &mut Expr) {
        // 先递归处理子节点(嵌套的 ? 也要改写)
        visit_mut::visit_expr_mut(self, expr);

        if let Expr::Try(ExprTry { expr: inner, question_token, .. }) = expr {
            let inner = (**inner).clone();
            let q_span = question_token.span();
            // 给 Location::caller() 这一行打上 ? 的 span,这样运行时拿到的就是真实的源码位置
            let caller = quote_spanned! { q_span =>
                ::std::panic::Location::caller()
            };
            let replacement: Expr = syn::parse_quote! {
                match #inner {
                    ::core::result::Result::Ok(__v) => __v,
                    ::core::result::Result::Err(__e) => {
                        return ::core::result::Result::Err(
                            ::traced_error::__macro::WrapErr(__e).__resolve(#caller)
                        );
                    }
                }
            };
            *expr = replacement;
        }
    }

    // 不要进入嵌套的 fn / closure 体(闭包的 ? 语义不同,留给用户自己处理)
    fn visit_item_fn_mut(&mut self, _: &mut ItemFn) {}
}

#[proc_macro_attribute]
pub fn traced(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut item_fn = parse_macro_input!(item as ItemFn);
    TryRewriter.visit_block_mut(&mut item_fn.block);

    // 把 fallback trait 引入作用域,inherent 仍然优先;trait 仅在没有 inherent 时兜底
    let use_stmt: syn::Stmt = syn::parse_quote! {
        #[allow(unused_imports)]
        use ::traced_error::__macro::ResolveFallback as _;
    };
    item_fn.block.stmts.insert(0, use_stmt);

    quote!(#item_fn).into()
}