use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::Ident;
use crate::attrs::{EnumCfg, FieldRole, VariantCfg, snake_of_pascal};
pub fn emit_display_and_error(cfg: &EnumCfg) -> TokenStream {
let enum_ident = &cfg.ident;
let display_arms = cfg.variants.iter().map(|v| display_arm(enum_ident, v));
let source_arms = cfg.variants.iter().map(source_arm);
let from_impls = cfg.variants.iter().filter_map(|v| from_impl(enum_ident, v));
quote! {
impl ::core::fmt::Display for #enum_ident {
fn fmt(&self, __f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#(#display_arms)*
}
}
}
impl ::std::error::Error for #enum_ident {
fn source(&self) -> ::core::option::Option<&(dyn ::std::error::Error + 'static)> {
match self {
#(#source_arms)*
}
}
}
#(#from_impls)*
}
}
fn display_arm(enum_ident: &Ident, v: &VariantCfg) -> TokenStream {
let variant = &v.ident;
let has_explicit_fmt = v.error_fmt.is_some();
let default_text = format!(
"{}.{}",
snake_of_pascal(&enum_ident.to_string()),
snake_of_pascal(&variant.to_string())
);
let fmt_string = v.error_fmt.clone().unwrap_or(default_text);
if v.fields.is_empty() {
return quote! {
Self::#variant => ::core::write!(__f, #fmt_string),
};
}
if v.is_tuple {
let pat_idents: Vec<Ident> = v
.fields
.iter()
.enumerate()
.map(|(i, _)| format_ident!("__f{}", i))
.collect();
let pat = quote! { ( #(#pat_idents),* ) };
if has_explicit_fmt {
quote! {
Self::#variant #pat => ::core::write!(__f, #fmt_string, #(#pat_idents),*),
}
} else {
let _suppress = pat_idents.iter().map(|i| quote! { let _ = #i; });
quote! {
Self::#variant #pat => {
#(#_suppress)*
::core::write!(__f, #fmt_string)
}
}
}
} else {
let names: Vec<&Ident> = v.fields.iter().filter_map(|f| f.ident.as_ref()).collect();
let pat = quote! { { #(#names),* } };
if has_explicit_fmt {
let kwargs = names.iter().map(|n| quote! { #n = #n });
quote! {
Self::#variant #pat => ::core::write!(__f, #fmt_string #(, #kwargs)*),
}
} else {
quote! {
Self::#variant { .. } => ::core::write!(__f, #fmt_string),
}
}
}
}
fn source_arm(v: &VariantCfg) -> TokenStream {
let variant = &v.ident;
if v.fields.is_empty() {
return quote! { Self::#variant => ::core::option::Option::None, };
}
let src_idx = v
.fields
.iter()
.position(|f| matches!(f.role, FieldRole::Source | FieldRole::From));
if let Some(idx) = src_idx {
if v.is_tuple {
let pat_idents: Vec<Ident> = v
.fields
.iter()
.enumerate()
.map(|(i, _)| {
if i == idx {
format_ident!("__src")
} else {
format_ident!("_")
}
})
.collect();
quote! {
Self::#variant ( #(#pat_idents),* ) => ::core::option::Option::Some(
__src as &(dyn ::std::error::Error + 'static)
),
}
} else {
let mut pat = TokenStream::new();
for (i, f) in v.fields.iter().enumerate() {
let name = f.ident.as_ref().unwrap();
if i == idx {
pat.extend(quote! { #name: __src, });
} else {
pat.extend(quote! { #name: _, });
}
}
quote! {
Self::#variant { #pat } => ::core::option::Option::Some(
__src as &(dyn ::std::error::Error + 'static)
),
}
}
} else {
if v.is_tuple {
let wild = v.fields.iter().map(|_| quote! { _ });
quote! { Self::#variant ( #(#wild),* ) => ::core::option::Option::None, }
} else {
quote! { Self::#variant { .. } => ::core::option::Option::None, }
}
}
}
fn from_impl(enum_ident: &Ident, v: &VariantCfg) -> Option<TokenStream> {
if !v.from_catchall {
return None;
}
let variant = &v.ident;
let from_field = v.fields.iter().find(|f| f.role == FieldRole::From)?;
let ty = &from_field.ty;
let ctor = if v.is_tuple {
quote! { #enum_ident::#variant(__from) }
} else if let Some(name) = &from_field.ident {
quote! { #enum_ident::#variant { #name: __from } }
} else {
return None;
};
Some(quote! {
impl ::core::convert::From<#ty> for #enum_ident {
fn from(__from: #ty) -> Self {
#ctor
}
}
})
}
#[allow(dead_code)]
fn _span_anchor() -> Span {
Span::call_site()
}