use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, Type};
use crate::{
expand::Expand,
field::{FieldId, Fields},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SourceShape {
Direct,
Boxed,
Optional,
OptionalBoxed,
}
#[derive(Clone, Debug)]
pub struct Source {
field: FieldId,
shape: SourceShape,
error_type: Type,
}
impl Source {
#[must_use]
#[inline]
pub fn new(fields: &Fields, field: FieldId) -> Self {
let field_type = fields.ty(field);
let (shape, error_type) = SourceShape::inspect(field_type);
Self { field, shape, error_type }
}
#[inline]
#[must_use]
pub const fn field(&self) -> FieldId {
let Self { field, .. } = self;
*field
}
#[inline]
#[must_use]
pub const fn shape(&self) -> SourceShape {
let Self { shape, .. } = self;
*shape
}
#[inline]
#[must_use]
pub const fn error_type(&self) -> &Type {
let Self { error_type, .. } = self;
error_type
}
}
impl SourceShape {
fn inspect(field_type: &Type) -> (Self, Type) {
if let Some(optional) = TypeContainer::single(field_type, "Option") {
if let Some(boxed) = TypeContainer::single(optional, "Box") {
return (Self::OptionalBoxed, boxed.clone());
}
return (Self::Optional, optional.clone());
}
if let Some(boxed) = TypeContainer::single(field_type, "Box") {
return (Self::Boxed, boxed.clone());
}
(Self::Direct, field_type.clone())
}
}
struct TypeContainer;
impl TypeContainer {
fn single<'a>(target: &'a Type, expected: &str) -> Option<&'a Type> {
let Type::Path(path) = target else {
return None;
};
let segment = path.path.segments.last()?;
if segment.ident != expected {
return None;
}
let syn::PathArguments::AngleBracketed(arguments) = &segment.arguments else {
return None;
};
let mut arguments = arguments.args.iter();
let syn::GenericArgument::Type(inner) = arguments.next()? else {
return None;
};
arguments.next().is_none().then_some(inner)
}
}
#[derive(Clone, Debug)]
pub struct ExpandContext {
binding: Ident,
root: TokenStream,
transparent: bool,
}
impl ExpandContext {
#[inline]
#[must_use]
pub const fn new(binding: Ident, root: TokenStream, transparent: bool) -> Self {
Self {
binding,
root,
transparent,
}
}
}
impl Expand for &Source {
type Context = ExpandContext;
#[inline]
fn expand_with(self, context: Self::Context) -> syn::Result<TokenStream> {
let ExpandContext {
binding,
root,
transparent,
} = context;
if transparent {
return match self.shape() {
SourceShape::Direct => Ok(quote! { #root::error::Error::source(#binding) }),
SourceShape::Boxed => Ok(quote! { #root::error::Error::source(&**#binding) }),
SourceShape::Optional | SourceShape::OptionalBoxed => Err(syn::Error::new(
proc_macro2::Span::call_site(),
"optional transparent source reached expansion",
)),
};
}
match self.shape() {
SourceShape::Direct => Ok(quote! { Some(#binding) }),
SourceShape::Boxed => Ok(quote! { Some(&**#binding) }),
SourceShape::Optional => Ok(quote! {
#binding.as_ref().map(|source| source as &(dyn #root::error::Error + 'static))
}),
SourceShape::OptionalBoxed => Ok(quote! {
#binding.as_ref().map(|source| &**source as &(dyn #root::error::Error + 'static))
}),
}
}
}