use std::str::FromStr;
use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{
Data, DataEnum, DataStruct, DataUnion, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed,
Generics, Ident, Path, Token, Type, Variant, Visibility, parse_quote, punctuated::Punctuated,
};
use crate::{
generics::{InferredBounds, ParamsInScope},
messages::{
AT_LEAST_ONE_FIELD, AT_LEAST_ONE_VARIANT, DISPLAY_TOKENS_NOT_ON_ENUM,
MISSING_DISPLAY_ON_VARIANT, SUPPORTED_TYPES, incorrect_def,
},
parser::{parse_type_attr, parse_variant_attr},
types::{ContextKind, ErrorKind, MyVariant, Trait, TypeAttr, TypeDisplayAttr, VariantAttr},
};
fn crate_path() -> TokenStream {
quote! { ::error2 }
}
pub(crate) fn generate(input: DeriveInput) -> syn::Result<TokenStream> {
let crate_path = crate_path();
let DeriveInput {
attrs,
vis: _,
ident,
generics,
data,
} = input;
let scope = ParamsInScope::new(&generics);
let mut error_inferred_bounds = InferredBounds::with_capacity(3);
if generics.type_params().next().is_some() {
let (_, ty_generics, _) = generics.split_for_impl();
let ty = quote! {
#ident #ty_generics
};
error_inferred_bounds.insert(ty.clone(), Trait::Debug);
error_inferred_bounds.insert(ty, Trait::Display);
}
let type_attr = parse_type_attr(&attrs)?;
match data {
Data::Union(DataUnion { union_token, .. }) => {
Err(syn::Error::new(union_token.span, SUPPORTED_TYPES))
}
Data::Struct(DataStruct { fields, .. }) => match fields {
Fields::Unit => Err(syn::Error::new(ident.span(), SUPPORTED_TYPES)),
Fields::Unnamed(FieldsUnnamed { paren_token, .. }) => {
Err(syn::Error::new(paren_token.span.join(), SUPPORTED_TYPES))
}
Fields::Named(FieldsNamed { brace_token, named }) => {
if named.is_empty() {
Err(syn::Error::new(brace_token.span.join(), AT_LEAST_ONE_FIELD))
} else {
generate_struct(
&crate_path,
type_attr,
ident,
&generics,
named,
&scope,
error_inferred_bounds,
)
}
}
},
syn::Data::Enum(DataEnum {
brace_token,
variants,
..
}) => {
if variants.is_empty() {
return Err(syn::Error::new(
brace_token.span.join(),
AT_LEAST_ONE_VARIANT,
));
}
let mut errors = Vec::new();
let variants = variants
.into_iter()
.filter_map(|vartiant| {
let Variant {
attrs,
ident,
fields,
..
} = vartiant;
match fields {
Fields::Named(FieldsNamed { brace_token, named }) => {
if named.is_empty() {
errors.push(syn::Error::new(
brace_token.span.join(),
AT_LEAST_ONE_FIELD,
));
None
} else {
Some(MyVariant {
attrs,
ident,
named_fields: named,
})
}
}
Fields::Unnamed(FieldsUnnamed { paren_token, .. }) => {
errors.push(syn::Error::new(paren_token.span.join(), SUPPORTED_TYPES));
None
}
Fields::Unit => {
errors.push(syn::Error::new(ident.span(), SUPPORTED_TYPES));
None
}
}
})
.collect::<Vec<_>>();
if let Some(e) = errors.into_iter().reduce(|mut a, b| {
a.combine(b);
a
}) {
return Err(e);
}
generate_enum(
&crate_path,
type_attr,
ident,
&generics,
variants,
&scope,
error_inferred_bounds,
)
}
}
}
fn generate_struct(
crate_path: &TokenStream,
type_attr: TypeAttr,
struct_ident: Ident,
generics: &Generics,
fields: Punctuated<Field, Token![,]>,
scope: &ParamsInScope,
mut error_inferred_bounds: InferredBounds,
) -> syn::Result<TokenStream> {
let TypeAttr {
display: type_display,
context_vis,
mod_vis,
} = type_attr;
let display_tokens = match type_display {
TypeDisplayAttr::None => None,
TypeDisplayAttr::Enabled { tokens, .. } => Some(tokens),
};
let mut all_field_idents: Vec<&Ident> = Vec::with_capacity(fields.len());
let mut no_source_no_backtrace_field_idents: Vec<&Ident> = Vec::with_capacity(fields.len());
let mut no_source_no_backtrace_field_generics: Vec<Ident> = Vec::with_capacity(fields.len());
let mut no_source_no_backtrace_inferred_bounds = InferredBounds::with_capacity(fields.len());
let mut source_field: Option<&Field> = None;
let mut backtrace_field: Option<&Field> = None;
for (i, field) in fields.iter().enumerate() {
let ident = field.ident.as_ref().unwrap();
all_field_idents.push(ident);
if ident == "source" {
source_field = Some(field);
} else if ident == "backtrace" {
backtrace_field = Some(field);
} else {
no_source_no_backtrace_field_idents.push(ident);
let generic = format_ident!("__T{}", i);
no_source_no_backtrace_field_generics.push(generic.clone());
let ty = &field.ty;
no_source_no_backtrace_inferred_bounds
.insert(generic, quote! { ::core::convert::Into<#ty> });
}
}
let error_kind: ErrorKind;
let middle_type: Type;
let backtrace_field_tokens: TokenStream;
match (source_field, backtrace_field) {
(None, None) => {
return Err(syn::Error::new(
struct_ident.span(),
incorrect_def(ContextKind::Struct),
));
}
(None, Some(_)) => {
error_kind = ErrorKind::Root;
middle_type = parse_quote! { () };
backtrace_field_tokens = quote! {
backtrace: #crate_path::Backtrace::new(),
};
}
(Some(source_field), None) => {
let ty = &source_field.ty;
error_kind = ErrorKind::Err2;
middle_type = ty.clone();
backtrace_field_tokens = quote! {};
if scope.intersects(ty) {
error_inferred_bounds.insert(ty, quote! { #crate_path::Error2 + 'static });
}
}
(Some(source_field), Some(_backtrace_field)) => {
let ty = &source_field.ty;
error_kind = ErrorKind::Std;
middle_type = ty.clone();
backtrace_field_tokens = quote! {
backtrace: #crate_path::Backtrace::with_head(&middle),
};
if scope.intersects(ty) {
error_inferred_bounds.insert(ty, quote! { ::core::error::Error + 'static });
}
}
}
let error_source_body = if error_kind.is_root() {
quote! {
::core::option::Option::None
}
} else {
quote! {
::core::option::Option::Some(&self.source)
}
};
let backtrace_body = match error_kind {
ErrorKind::Root | ErrorKind::Std => quote! {
&self.backtrace
},
ErrorKind::Err2 => quote! {
#crate_path::Error2::backtrace(&self.source)
},
};
let backtrace_mut_body = match error_kind {
ErrorKind::Root | ErrorKind::Std => quote! {
&mut self.backtrace
},
ErrorKind::Err2 => quote! {
#crate_path::Error2::backtrace_mut(&mut self.source)
},
};
let context_def = generate_context_def(
crate_path,
&struct_ident,
parse_quote! { #struct_ident },
&struct_ident,
&context_vis,
error_kind,
no_source_no_backtrace_field_idents,
no_source_no_backtrace_field_generics,
no_source_no_backtrace_inferred_bounds.merge(&error_inferred_bounds),
generics,
middle_type,
backtrace_field_tokens,
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let display_impl = match display_tokens {
None => quote! {},
Some(tokens) => quote! {
impl #impl_generics ::core::fmt::Display for #struct_ident #ty_generics #where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#[allow(unused_variables)]
#[allow(unused_assignments)]
let Self { #(#all_field_idents,)* } = self;
write!(f, #tokens)
}
}
},
};
let error_where_clause = error_inferred_bounds.augment_where_clause(where_clause.cloned());
let expand = quote! {
#context_def
#display_impl
impl #impl_generics ::core::error::Error for #struct_ident #ty_generics #error_where_clause {
fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
#error_source_body
}
}
impl #impl_generics #crate_path::Error2 for #struct_ident #ty_generics #error_where_clause {
#[inline]
fn backtrace(&self) -> &#crate_path::Backtrace {
#backtrace_body
}
#[inline]
fn backtrace_mut(&mut self) -> &mut #crate_path::Backtrace {
#backtrace_mut_body
}
}
};
let expand = if let Some(mod_vis) = mod_vis {
let mod_name = struct_ident.to_string().to_snake_case();
let mod_ident = Ident::new(&mod_name, struct_ident.span());
quote! {
#mod_vis mod #mod_ident {
use super::*;
#expand
}
}
} else {
expand
};
Ok(expand)
}
fn generate_enum(
crate_path: &TokenStream,
type_attr: TypeAttr,
enum_ident: Ident,
generics: &Generics,
variants: Vec<MyVariant>,
scope: &ParamsInScope,
mut error_inferred_bounds: InferredBounds,
) -> syn::Result<TokenStream> {
let TypeAttr {
display: type_display,
context_vis,
mod_vis,
} = type_attr;
if let TypeDisplayAttr::Enabled { meta_span, .. } = type_display {
return Err(syn::Error::new(meta_span, DISPLAY_TOKENS_NOT_ON_ENUM));
};
let mut errors = Vec::new();
let mut inputs: Vec<VariantInput> = Vec::with_capacity(variants.len());
let mut exist_display_on_variant = false;
for variant in &variants {
let MyVariant {
attrs: variant_attrs,
ident: variant_ident,
named_fields,
} = variant;
let VariantAttr {
display: variant_display,
} = match parse_variant_attr(variant_attrs) {
Ok(v) => v,
Err(e) => {
errors.push(e);
continue;
}
};
if variant_display.is_some() {
exist_display_on_variant = true;
}
let mut all_field_idents: Vec<&Ident> = Vec::with_capacity(named_fields.len());
let mut no_source_no_backtrace_field_idents: Vec<&Ident> =
Vec::with_capacity(named_fields.len());
let mut no_source_no_backtrace_field_generics: Vec<Ident> =
Vec::with_capacity(named_fields.len());
let mut no_source_no_backtrace_inferred_bounds =
InferredBounds::with_capacity(named_fields.len());
let mut source_field: Option<&Field> = None;
let mut backtrace_field: Option<&Field> = None;
for (i, field) in named_fields.iter().enumerate() {
let ident = field.ident.as_ref().unwrap();
all_field_idents.push(ident);
if ident == "source" {
source_field = Some(field);
} else if ident == "backtrace" {
backtrace_field = Some(field);
} else {
no_source_no_backtrace_field_idents.push(ident);
let generic = format_ident!("__T{}", i);
no_source_no_backtrace_field_generics.push(generic.clone());
let ty = &field.ty;
no_source_no_backtrace_inferred_bounds
.insert(generic, quote! { ::core::convert::Into<#ty> });
}
}
let error_kind: ErrorKind;
let middle_type: Type;
let backtrace_field_tokens: TokenStream;
match (source_field, backtrace_field) {
(None, None) => {
errors.push(syn::Error::new(
variant_ident.span(),
incorrect_def(ContextKind::Variant),
));
continue;
}
(None, Some(_)) => {
error_kind = ErrorKind::Root;
middle_type = parse_quote! { () };
backtrace_field_tokens = quote! {
backtrace: #crate_path::Backtrace::new(),
};
}
(Some(source_field), None) => {
let ty = &source_field.ty;
error_kind = ErrorKind::Err2;
middle_type = ty.clone();
backtrace_field_tokens = quote! {};
if scope.intersects(ty) {
error_inferred_bounds.insert(ty, quote! { #crate_path::Error2 + 'static });
}
}
(Some(source_field), Some(_backtrace_field)) => {
let ty = &source_field.ty;
error_kind = ErrorKind::Std;
middle_type = ty.clone();
backtrace_field_tokens = quote! {
backtrace: #crate_path::Backtrace::with_head(&middle),
};
if scope.intersects(ty) {
error_inferred_bounds.insert(ty, quote! { ::core::error::Error + 'static });
}
}
}
inputs.push(VariantInput {
variant_ident,
error_kind,
all_field_idents,
no_source_no_backtrace_field_idents,
no_source_no_backtrace_field_generics,
no_source_no_backtrace_inferred_bounds,
middle_type,
backtrace_field_tokens,
variant_display,
});
}
let mut context_defs = Vec::with_capacity(variants.len());
let mut display_arms = Vec::with_capacity(variants.len());
let mut error_source_arms = Vec::with_capacity(variants.len());
let mut backtrace_arms = Vec::with_capacity(variants.len());
let mut backtrace_mut_arms = Vec::with_capacity(variants.len());
for input in inputs {
let VariantInput {
variant_ident,
error_kind,
all_field_idents,
no_source_no_backtrace_field_idents,
no_source_no_backtrace_field_generics,
no_source_no_backtrace_inferred_bounds,
middle_type,
backtrace_field_tokens,
variant_display,
} = input;
if variant_display.is_none() && exist_display_on_variant {
errors.push(syn::Error::new(
variant_ident.span(),
MISSING_DISPLAY_ON_VARIANT,
));
continue;
}
let VariantOutput {
context_def,
display_arm,
error_source_arm,
backtrace_arm,
backtrace_mut_arm,
} = generate_variant(
crate_path,
&enum_ident,
variant_ident,
&context_vis,
error_kind,
all_field_idents,
no_source_no_backtrace_field_idents,
no_source_no_backtrace_field_generics,
no_source_no_backtrace_inferred_bounds.merge(&error_inferred_bounds),
generics,
middle_type,
backtrace_field_tokens,
variant_display,
);
context_defs.push(context_def);
display_arms.push(display_arm);
error_source_arms.push(error_source_arm);
backtrace_arms.push(backtrace_arm);
backtrace_mut_arms.push(backtrace_mut_arm);
}
if let Some(e) = errors.into_iter().reduce(|mut a, b| {
a.combine(b);
a
}) {
return Err(e);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let display_impl = if !exist_display_on_variant {
quote! {}
} else {
quote! {
impl #impl_generics ::core::fmt::Display for #enum_ident #ty_generics #where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#[allow(unused_variables)]
#[allow(unused_assignments)]
match self {
#(#display_arms)*
}
}
}
}
};
let error_where_clause = error_inferred_bounds.augment_where_clause(where_clause.cloned());
let expand = quote! {
#(#context_defs)*
#display_impl
impl #impl_generics ::core::error::Error for #enum_ident #ty_generics #error_where_clause {
fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
match self {
#(#error_source_arms)*
}
}
}
impl #impl_generics #crate_path::Error2 for #enum_ident #ty_generics #error_where_clause {
#[inline]
fn backtrace(&self) -> &#crate_path::Backtrace {
match self {
#(#backtrace_arms)*
}
}
#[inline]
fn backtrace_mut(&mut self) -> &mut #crate_path::Backtrace {
match self {
#(#backtrace_mut_arms)*
}
}
}
};
let expand = if let Some(mod_vis) = mod_vis {
let mod_name = enum_ident.to_string().to_snake_case();
let mod_ident = Ident::new(&mod_name, enum_ident.span());
quote! {
#mod_vis mod #mod_ident {
use super::*;
#expand
}
}
} else {
expand
};
Ok(expand)
}
#[expect(clippy::too_many_arguments)]
fn generate_variant(
crate_path: &TokenStream,
enum_ident: &Ident,
variant_ident: &Ident,
vis: &Visibility,
error_kind: ErrorKind,
all_field_idents: Vec<&Ident>,
no_source_no_backtrace_field_idents: Vec<&Ident>,
no_source_no_backtrace_field_generics: Vec<Ident>,
all_inferred_bounds: InferredBounds,
generics: &Generics,
middle_type: Type,
backtrace_field_tokens: TokenStream,
display_tokens: Option<TokenStream>,
) -> VariantOutput {
let context_def = generate_context_def(
crate_path,
enum_ident,
parse_quote! { #enum_ident::#variant_ident },
variant_ident,
vis,
error_kind,
no_source_no_backtrace_field_idents,
no_source_no_backtrace_field_generics,
all_inferred_bounds,
generics,
middle_type,
backtrace_field_tokens,
);
let display_arm = match display_tokens {
None => quote! {},
Some(tokens) => quote! {
Self::#variant_ident { #(#all_field_idents,)* } => {
write!(f, #tokens)
}
},
};
let error_source_arm = if error_kind.is_root() {
quote! {
Self::#variant_ident { .. } => ::core::option::Option::None,
}
} else {
quote! {
Self::#variant_ident { source, .. } => ::core::option::Option::Some(source),
}
};
let backtrace_arm = match error_kind {
ErrorKind::Root | ErrorKind::Std => quote! {
Self::#variant_ident { backtrace, .. } => backtrace,
},
ErrorKind::Err2 => quote! {
Self::#variant_ident { source, .. } => #crate_path::Error2::backtrace(source),
},
};
let backtrace_mut_arm = match error_kind {
ErrorKind::Root | ErrorKind::Std => quote! {
Self::#variant_ident { backtrace, .. } => backtrace,
},
ErrorKind::Err2 => quote! {
Self::#variant_ident { source, .. } => #crate_path::Error2::backtrace_mut(source),
},
};
VariantOutput {
context_def,
display_arm,
error_source_arm,
backtrace_arm,
backtrace_mut_arm,
}
}
struct VariantInput<'a> {
variant_ident: &'a Ident,
error_kind: ErrorKind,
all_field_idents: Vec<&'a Ident>,
no_source_no_backtrace_field_idents: Vec<&'a Ident>,
no_source_no_backtrace_field_generics: Vec<Ident>,
no_source_no_backtrace_inferred_bounds: InferredBounds,
middle_type: Type,
backtrace_field_tokens: TokenStream,
variant_display: Option<TokenStream>,
}
struct VariantOutput {
context_def: TokenStream,
display_arm: TokenStream,
error_source_arm: TokenStream,
backtrace_arm: TokenStream,
backtrace_mut_arm: TokenStream,
}
#[expect(clippy::too_many_arguments)]
fn generate_context_def(
crate_path: &TokenStream,
type_ident: &Ident,
type_path: Path,
context_ident_prefix: &Ident,
context_vis: &Visibility,
error_kind: ErrorKind,
no_source_no_backtrace_field_idents: Vec<&Ident>,
no_source_no_backtrace_field_generics: Vec<Ident>,
all_inferred_bounds: InferredBounds,
generics: &Generics,
middle_type: Type,
backtrace_field_tokens: TokenStream,
) -> TokenStream {
let context_ident = format_ident!("{}2", context_ident_prefix);
let context_struct_body = if no_source_no_backtrace_field_idents.is_empty() {
quote! { ; }
} else {
quote! {
{
#(
#[allow(missing_docs)]
#context_vis #no_source_no_backtrace_field_idents : #no_source_no_backtrace_field_generics,
)*
}
}
};
let context_generics = if no_source_no_backtrace_field_idents.is_empty() {
quote! {}
} else {
quote! { < #(#no_source_no_backtrace_field_generics,)* > }
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let additional_impl_generics = if no_source_no_backtrace_field_idents.is_empty() {
quote! { #impl_generics }
} else {
let mut impl_generics = impl_generics.to_token_stream().to_string();
match impl_generics.find('<') {
Some(pos) => {
let mut buf = String::new();
for g in &no_source_no_backtrace_field_generics {
buf.push_str(&g.to_string());
buf.push_str(", ");
}
impl_generics.insert_str(pos + 1, &buf);
TokenStream::from_str(&impl_generics).unwrap()
}
_ => quote! { < #(#no_source_no_backtrace_field_generics,)* > },
}
};
let where_clause = all_inferred_bounds.augment_where_clause(where_clause.cloned());
let source_field = if error_kind.is_root() {
quote! {}
} else {
quote! {
source: middle,
}
};
quote! {
#[derive(Debug, Clone, Copy)]
#context_vis struct #context_ident #context_generics #context_struct_body
impl #additional_impl_generics #crate_path::transform::MiddleToTarget < #middle_type, #type_ident #ty_generics > for #context_ident #context_generics #where_clause {
#[allow(unused_variables)]
fn middle_to_target(self, middle: #middle_type, location: #crate_path::Location) -> #type_ident #ty_generics {
let mut error = #type_path {
#(
#no_source_no_backtrace_field_idents : ::core::convert::Into::into(self.#no_source_no_backtrace_field_idents),
)*
#backtrace_field_tokens
#source_field
};
#crate_path::push_error(&mut error, location);
error
}
}
}
}