snafu_virtstack_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, parse_macro_input};
4
5/// Proc macro attribute to automatically generate virtual stack traces for SNAFU errors.
6///
7/// This attribute automatically implements the [`VirtualStackTrace`] trait and provides
8/// a custom [`Debug`] implementation that displays a formatted virtual stack trace.
9///
10/// See the main [`snafu_virtstack`] crate documentation for comprehensive usage examples
11/// and detailed information about virtual stack traces.
12///
13/// [`VirtualStackTrace`]: snafu_virtstack::VirtualStackTrace
14/// [`snafu_virtstack`]: https://docs.rs/snafu_virtstack
15#[proc_macro_attribute]
16pub fn stack_trace_debug(_args: TokenStream, input: TokenStream) -> TokenStream {
17    let input = parse_macro_input!(input as DeriveInput);
18
19    // Generate the enhanced version with virtual stack trace implementation
20    match generate_stack_trace_impl(&input) {
21        Ok(tokens) => tokens.into(),
22        Err(err) => err.to_compile_error().into(),
23    }
24}
25
26fn generate_stack_trace_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
27    let name = &input.ident;
28    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
29
30    // Parse the enum to understand its structure
31    let _data = match &input.data {
32        Data::Enum(data) => data,
33        _ => {
34            return Err(syn::Error::new_spanned(
35                input,
36                "stack_trace_debug can only be applied to enums",
37            ));
38        }
39    };
40
41    // Generate VirtualStackTrace implementation
42    let stack_trace_impl =
43        generate_virtual_stack_trace_impl(name, &impl_generics, &ty_generics, where_clause)?;
44
45    Ok(quote! {
46        // First, emit the original item unchanged
47        #input
48
49        // Finally, add the VirtualStackTrace implementation
50        #stack_trace_impl
51    })
52}
53
54fn generate_virtual_stack_trace_impl(
55    name: &syn::Ident,
56    impl_generics: &syn::ImplGenerics,
57    ty_generics: &syn::TypeGenerics,
58    where_clause: Option<&syn::WhereClause>,
59) -> syn::Result<proc_macro2::TokenStream> {
60    Ok(quote! {
61        impl #impl_generics snafu_virtstack::VirtualStackTrace for #name #ty_generics #where_clause {
62            #[track_caller]
63            fn virtual_stack(&self) -> Vec<snafu_virtstack::StackFrame> {
64                let mut stack = vec![snafu_virtstack::StackFrame::new(
65                    std::panic::Location::caller(),
66                    self.to_string(),
67                )];
68
69                // Walk the error source chain
70                let mut current_error = self as &dyn std::error::Error;
71                while let Some(source) = current_error.source() {
72                    // Add a simple frame for this source
73                    stack.push(snafu_virtstack::StackFrame::new(
74                        std::panic::Location::caller(),
75                        source.to_string(),
76                    ));
77                    current_error = source;
78                }
79
80                stack
81            }
82        }
83
84        impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
85            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86                use snafu_virtstack::VirtualStackTrace;
87
88                writeln!(f, "Error: {}", self)?;
89                writeln!(f, "Virtual Stack Trace:")?;
90
91                let stack = self.virtual_stack();
92                for (i, frame) in stack.iter().enumerate() {
93                    writeln!(f, "  {}: {}", i, frame)?;
94                }
95
96                Ok(())
97            }
98        }
99    })
100}