use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, parse_macro_input};
#[proc_macro_attribute]
pub fn stack_trace_debug(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match generate_stack_trace_impl(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn generate_stack_trace_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let _data = match &input.data {
Data::Enum(data) => data,
_ => {
return Err(syn::Error::new_spanned(
input,
"stack_trace_debug can only be applied to enums",
));
}
};
let stack_trace_impl =
generate_virtual_stack_trace_impl(name, &impl_generics, &ty_generics, where_clause)?;
Ok(quote! {
#input
#stack_trace_impl
})
}
fn generate_virtual_stack_trace_impl(
name: &syn::Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
) -> syn::Result<proc_macro2::TokenStream> {
Ok(quote! {
impl #impl_generics snafu_virtstack::VirtualStackTrace for #name #ty_generics #where_clause {
#[track_caller]
fn virtual_stack(&self) -> Vec<snafu_virtstack::StackFrame> {
let mut stack = vec![snafu_virtstack::StackFrame::new(
std::panic::Location::caller(),
self.to_string(),
)];
let mut current_error = self as &dyn std::error::Error;
while let Some(source) = current_error.source() {
stack.push(snafu_virtstack::StackFrame::new(
std::panic::Location::caller(),
source.to_string(),
));
current_error = source;
}
stack
}
}
impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use snafu_virtstack::VirtualStackTrace;
writeln!(f, "Error: {}", self)?;
writeln!(f, "Virtual Stack Trace:")?;
let stack = self.virtual_stack();
for (i, frame) in stack.iter().enumerate() {
writeln!(f, " {}: {}", i, frame)?;
}
Ok(())
}
}
})
}