//! Derive support for static Kaptan runtime-log messages. use proc_macro::TokenStream; use quote::quote; use syn::{Data, DeriveInput, Error, Fields, parse_macro_input, parse_quote}; /// Implements [`kaptan_log::ErrorReport`], [`core::fmt::Display`], and /// [`core::error::Error`] for enums. /// /// Each enum variant receives one static message in the form /// `module::Type::Variant`. The derive deliberately accepts no attributes: /// text logging has no user-provided numeric catalog allocation. #[proc_macro_derive(KaptanError)] pub fn derive_kaptan_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); match expand(input) { Ok(tokens) => tokens.into(), Err(error) => error.into_compile_error().into(), } } fn expand(input: DeriveInput) -> Result { let DeriveInput { ident, generics, data, .. } = input; let Data::Enum(data) = data else { let kind = match data { Data::Struct(_) => "structs", Data::Union(_) => "unions", Data::Enum(_) => unreachable!("enum handled above"), }; return Err(Error::new_spanned( ident, format!("KaptanError can only be derived for enums, not {kind}"), )); }; let arms = data.variants.iter().map(|variant| { let variant_ident = &variant.ident; let pattern = match &variant.fields { Fields::Unit => quote!(Self::#variant_ident), Fields::Unnamed(_) => quote!(Self::#variant_ident(..)), Fields::Named(_) => quote!(Self::#variant_ident { .. }), }; quote! { #pattern => { static ERROR: ::kaptan_log::StaticError = ::kaptan_log::StaticError::new(concat!( module_path!(), "::", stringify!(#ident), "::", stringify!(#variant_ident), )); &ERROR } } }); let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); let mut error_generics = generics.clone(); error_generics .make_where_clause() .predicates .push(parse_quote!(#ident #type_generics: ::core::fmt::Debug)); let (error_impl_generics, error_type_generics, error_where_clause) = error_generics.split_for_impl(); Ok(quote! { impl #impl_generics ::kaptan_log::ErrorReport for #ident #type_generics #where_clause { fn static_error(&self) -> &'static ::kaptan_log::StaticError { match self { #(#arms),* } } } impl #impl_generics ::core::fmt::Display for #ident #type_generics #where_clause { fn fmt( &self, formatter: &mut ::core::fmt::Formatter<'_>, ) -> ::core::fmt::Result { formatter.write_str( ::static_error(self).path(), ) } } impl #error_impl_generics ::core::error::Error for #ident #error_type_generics #error_where_clause {} }) }