error-stack-macros2 0.2.1

Community-made procedural macros for error-stack.
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use syn::{
    Attribute, DeriveInput, Generics, Ident,
    parse::{Parse, ParseStream},
};

mod fmt;
use fmt::TypeData;

mod util;
use util::ReducedGenerics;

pub(crate) struct ErrorStackDeriveInput {
    other_attrs: Vec<Attribute>,
    ident: Ident,
    generics: Generics,
    display_data: TypeData,
}

impl Parse for ErrorStackDeriveInput {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let derive_input: DeriveInput = input.parse()?;

        drop(derive_input.vis);

        let mut attrs = derive_input.attrs;

        let display_data = TypeData::new(
            derive_input.data,
            &mut attrs,
            derive_input.ident.span(),
        )?;

        let ident = derive_input.ident;

        let mut generics = derive_input.generics;
        generics
            .params
            .iter_mut()
            .for_each(util::remove_generic_default);

        Ok(Self {
            other_attrs: attrs,
            ident,
            generics,
            display_data,
        })
    }
}

impl ToTokens for ErrorStackDeriveInput {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let Self {
            ref other_attrs,
            ref ident,
            ref generics,
            ref display_data,
        } = *self;

        let where_clause = &generics.where_clause;

        let mut error_trait_generics = generics.clone();
        error_trait_generics
            .params
            .iter_mut()
            .for_each(util::add_debug_trait_bound);

        let type_generics: ReducedGenerics = generics
            .params
            .iter()
            .cloned()
            .map(util::generic_reduced_to_ident)
            .collect();

        tokens.extend(quote! {
            #[allow(single_use_lifetimes)]
            #(#other_attrs)*
            impl #generics ::core::fmt::Display for #ident #type_generics
            #where_clause
            {
                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                    #display_data
                }
            }

            #[allow(single_use_lifetimes)]
            #(#other_attrs)*
            impl #error_trait_generics ::core::error::Error for #ident #type_generics
            #where_clause
            {
            }
        });
    }
}

#[cfg(test)]
#[expect(
    clippy::expect_used,
    reason = "this is a test module with calls to `.expect()`"
)]
mod tests {
    use quote::quote;

    use crate::ErrorStackDeriveInput;

    #[test]
    fn input_works_with_other_attrs() {
        let input: ErrorStackDeriveInput = syn::parse2(quote! {
            #[test_attribute]
            #[display("custom type")]
            #[test_attribute_2]
            struct CustomType;
        })
        .expect("malformed test stream");

        let output = quote! { #input };
        assert_eq!(
            output.to_string(),
            "# [allow (single_use_lifetimes)] # [test_attribute] # [test_attribute_2] impl :: core :: fmt :: Display for CustomType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { :: core :: write ! (f , \"custom type\" ,) } } # [allow (single_use_lifetimes)] # [test_attribute] # [test_attribute_2] impl :: core :: error :: Error for CustomType { }"
        );
    }

    #[test]
    fn generics_work_with_attrs() {
        let derive_input: ErrorStackDeriveInput = syn::parse2(quote! {
            #[display("custom type")]
            struct CustomType<#[cfg(true)] T> {
                _data: PhantomData<T>
            }
        })
        .expect("malformed test stream");

        let output = quote! { #derive_input };
        assert_eq!(
            output.to_string(),
            "# [allow (single_use_lifetimes)] impl < # [cfg (true)] T > :: core :: fmt :: Display for CustomType < T > { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { :: core :: write ! (f , \"custom type\" ,) } } # [allow (single_use_lifetimes)] impl < # [cfg (true)] T : :: core :: fmt :: Debug > :: core :: error :: Error for CustomType < T > { }"
        );
    }

    #[test]
    fn output_impl_has_attr_allow_single_use_lifetimes() {
        let input: ErrorStackDeriveInput = syn::parse2(quote! {
            #[display("custom type")]
            struct CustomType;
        })
        .expect("malformed test stream");

        let output = quote! { #input };
        assert_eq!(
            output.to_string(),
            "# [allow (single_use_lifetimes)] impl :: core :: fmt :: Display for CustomType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { :: core :: write ! (f , \"custom type\" ,) } } # [allow (single_use_lifetimes)] impl :: core :: error :: Error for CustomType { }"
        );
    }
}