use proc_macro2::TokenStream;
use quote::quote;
use syn::{Generics, Type, WherePredicate};
use crate::{
field::Fields,
format::FormatTrait,
semantics::{Display, ErrorSource},
};
pub struct Context<'context> {
fields: &'context Fields,
root: &'context TokenStream,
}
impl<'context> Context<'context> {
#[inline]
#[must_use]
pub const fn new(fields: &'context Fields, root: &'context TokenStream) -> Self {
Self { fields, root }
}
#[inline]
#[must_use]
pub const fn fields(&self) -> &Fields {
let Self { fields, .. } = self;
fields
}
#[inline]
#[must_use]
pub const fn root(&self) -> &TokenStream {
let Self { root, .. } = self;
root
}
}
pub trait Contribute<ContextType: ?Sized> {
fn contribute(&self, generics: &mut Generics, context: &ContextType);
}
impl Contribute<Context<'_>> for Display {
#[inline]
fn contribute(&self, generics: &mut Generics, context: &Context<'_>) {
let fields = context.fields();
let root = context.root();
match self {
Self::Format(format) => {
for format_use in format.uses() {
let ty = fields.ty(format_use.field());
if let Some(trait_name) = TraitName::format(format_use.format_trait()) {
Predicate::push(generics, quote! { #ty: #root::fmt::#trait_name });
}
}
}
Self::Transparent(field) => {
let ty = fields.ty(*field);
Predicate::push(generics, quote! { #ty: #root::fmt::Display });
}
Self::Custom(..) => {}
}
}
}
impl Contribute<Context<'_>> for ErrorSource {
#[inline]
fn contribute(&self, generics: &mut Generics, context: &Context<'_>) {
let root = context.root();
let source = match self {
Self::Field(source) | Self::Transparent(source) => source,
Self::None => return,
};
let ty = source.error_type();
if !matches!(ty, Type::TraitObject(..)) {
Predicate::push(generics, quote! { #ty: #root::error::Error + 'static });
}
}
}
struct TraitName;
impl TraitName {
fn format(format_trait: FormatTrait) -> Option<syn::Ident> {
let name = match format_trait {
FormatTrait::Display => "Display",
FormatTrait::Debug => "Debug",
FormatTrait::LowerHex => "LowerHex",
FormatTrait::UpperHex => "UpperHex",
FormatTrait::Octal => "Octal",
FormatTrait::Binary => "Binary",
FormatTrait::LowerExp => "LowerExp",
FormatTrait::UpperExp => "UpperExp",
FormatTrait::Pointer => return None,
};
Some(syn::Ident::new(name, proc_macro2::Span::call_site()))
}
}
struct Predicate;
impl Predicate {
fn push(generics: &mut Generics, tokens: TokenStream) {
let predicate = syn::parse2::<WherePredicate>(tokens).expect("fack generates only syntactically valid where predicates");
generics.make_where_clause().predicates.push(predicate);
}
pub fn self_error(generics: &mut Generics, root: &TokenStream) {
Self::push(generics, quote! { Self: #root::fmt::Debug + #root::fmt::Display });
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ErrorSelf;
impl Contribute<TokenStream> for ErrorSelf {
#[inline]
fn contribute(&self, generics: &mut Generics, root: &TokenStream) {
Predicate::self_error(generics, root);
}
}