use alloc::vec::Vec;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Generics, Ident};
use crate::{
binding::Bind as _,
bounds::{self, Context as BoundContext, Contribute as _},
field::Fields,
semantics::{ErrorSource, Variant},
};
use crate::source::ExpandContext as SourceContext;
use super::{Context, Expand};
pub struct ErrorImpl<'target> {
name: &'target Ident,
generics: &'target Generics,
fields: &'target Fields,
source: &'target ErrorSource,
}
impl<'target> ErrorImpl<'target> {
pub const fn new(name: &'target Ident, generics: &'target Generics, fields: &'target Fields, source: &'target ErrorSource) -> Self {
Self {
name,
generics,
fields,
source,
}
}
}
impl Expand for ErrorImpl<'_> {
type Context = Context;
fn expand_with(self, context: Self::Context) -> syn::Result<TokenStream> {
let Self {
name,
generics,
fields,
source,
} = self;
let root = context.root();
let mut generics = generics.clone();
let bound_context = BoundContext::new(fields, root);
bounds::ErrorSelf.contribute(&mut generics, root);
source.contribute(&mut generics, &bound_context);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let method = SourceMethod { fields, source }.expand_with(context.clone())?;
Ok(quote! {
#[automatically_derived]
impl #impl_generics #root::error::Error for #name #ty_generics #where_clause {
#method
}
})
}
}
struct SourceMethod<'target> {
fields: &'target Fields,
source: &'target ErrorSource,
}
impl Expand for SourceMethod<'_> {
type Context = Context;
fn expand_with(self, context: Self::Context) -> syn::Result<TokenStream> {
let Self { fields, source } = self;
let root = context.root();
let inline = context.inline();
let (source, transparent) = match source {
ErrorSource::None => return Ok(TokenStream::new()),
ErrorSource::Field(source) => (source, false),
ErrorSource::Transparent(source) => (source, true),
};
let bindings = fields.bind(&[source.field()]);
let pattern = bindings.pattern();
let binding = bindings.ident(source.field()).clone();
let source_context = SourceContext::new(binding, root.clone(), transparent);
let value = source.expand_with(source_context)?;
Ok(quote! {
#inline
fn source(&self) -> Option<&(dyn #root::error::Error + 'static)> {
let &Self #pattern = self;
#value
}
})
}
}
pub struct EnumErrorImpl<'target> {
name: &'target Ident,
generics: &'target Generics,
variants: &'target [Variant],
}
impl<'target> EnumErrorImpl<'target> {
pub const fn new(name: &'target Ident, generics: &'target Generics, variants: &'target [Variant]) -> Self {
Self { name, generics, variants }
}
}
impl Expand for EnumErrorImpl<'_> {
type Context = Context;
fn expand_with(self, context: Self::Context) -> syn::Result<TokenStream> {
let Self { name, generics, variants } = self;
let root = context.root();
let inline = context.inline();
let mut generics = generics.clone();
bounds::ErrorSelf.contribute(&mut generics, root);
for variant in variants {
let bound_context = BoundContext::new(variant.fields(), root);
variant.source().contribute(&mut generics, &bound_context);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let has_source = variants.iter().any(|variant| !matches!(variant.source(), ErrorSource::None));
let method = if has_source {
let arms = variants
.iter()
.map(|variant| VariantSourceArm { variant }.expand_with(context.clone()))
.collect::<syn::Result<Vec<_>>>()?;
quote! {
#inline
fn source(&self) -> Option<&(dyn #root::error::Error + 'static)> {
match self {
#(#arms),*
}
}
}
} else {
TokenStream::new()
};
Ok(quote! {
#[automatically_derived]
impl #impl_generics #root::error::Error for #name #ty_generics #where_clause {
#method
}
})
}
}
struct VariantSourceArm<'target> {
variant: &'target Variant,
}
impl Expand for VariantSourceArm<'_> {
type Context = Context;
fn expand_with(self, context: Self::Context) -> syn::Result<TokenStream> {
let Self { variant } = self;
let name = variant.name();
let fields = variant.fields();
let root = context.root();
let (source, transparent) = match variant.source() {
ErrorSource::None => {
let bindings = fields.bind(&[]);
let pattern = bindings.pattern();
return Ok(quote! { &Self::#name #pattern => None });
}
ErrorSource::Field(source) => (source, false),
ErrorSource::Transparent(source) => (source, true),
};
let bindings = fields.bind(&[source.field()]);
let pattern = bindings.pattern();
let binding = bindings.ident(source.field()).clone();
let source_context = SourceContext::new(binding, root.clone(), transparent);
let value = source.expand_with(source_context)?;
Ok(quote! { &Self::#name #pattern => #value })
}
}