wherr_macro/
lib.rs

1//! `wherr-macro` crate provides a procedural macro to enhance Rust errors with file and line number information.
2//!
3//! When using the provided `wherr` macro attribute on a function, any error returned by the `?` operator within that function
4//! is automatically wrapped to include the file and line number where the error occurred.
5
6use proc_macro::TokenStream;
7use quote::{quote, quote_spanned};
8use syn::spanned::Spanned;
9use syn::{parse_macro_input, visit_mut::VisitMut, Expr};
10
11/// Procedural macro attribute that processes a function to automatically wrap errors using the `?` operator
12/// with file and line number details.
13#[proc_macro_attribute]
14pub fn wherr(_attrs: TokenStream, input: TokenStream) -> TokenStream {
15    let mut function = parse_macro_input!(input as syn::ItemFn);
16
17    let mut visitor = WherrVisitor;
18    visitor.visit_item_fn_mut(&mut function);
19
20    TokenStream::from(quote! { #function })
21}
22
23/// Visitor used by the `wherr` procedural macro to traverse and mutate the Abstract Syntax Tree (AST) of the function.
24///
25/// This visitor specifically looks for expressions using the `?` operator and wraps them with additional
26/// file and line information.
27struct WherrVisitor;
28
29impl VisitMut for WherrVisitor {
30    /// Visit expressions in the AST.
31    ///
32    /// Specifically, it targets the use of the `?` operator to wrap the error with file and line number details.
33    fn visit_expr_mut(&mut self, expr: &mut Expr) {
34        // Check if the expression is a `?` usage. If it is, wrap it with our `wherrapper` function.
35        if let Expr::Try(expr_try) = expr {
36            let span = expr_try.question_token.span();
37            let inner_expr = &expr_try.expr;
38            let new_expr = syn::parse2(quote_spanned! { span=>
39                wherr::wherrapper(#inner_expr, file!(), line!())?
40            })
41            .expect("Failed to create wherr wrapped expression");
42
43            *expr = new_expr;
44        } else {
45            // Only continue visiting child expressions if it's not a Try expression
46            syn::visit_mut::visit_expr_mut(self, expr);
47        }
48    }
49}