ferment-sys 0.2.14

Syntax tree morphing of FFI-compatible stuff
Documentation
use syn::{Generics, Type};
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use indexmap::IndexMap;
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use crate::ast::CommaPunctuated;
use crate::composable::{TypeModel, TypeModeled};
use crate::composer::CommaPunctuatedNestedArguments;
use crate::kind::ObjectKind;
use crate::ext::{AsType, MaybeLambdaArgs, ToType};
use crate::formatter::format_generic_scope_chain;
use crate::lang::Specification;
use crate::presentable::{Expression, ScopeContextPresentable};

#[derive(Clone)]
pub struct GenericBoundsModel {
    pub type_model: TypeModel,
    pub chain: IndexMap<ObjectKind, Vec<ObjectKind>>,
    pub nested_arguments: CommaPunctuatedNestedArguments,
}

impl<'a> AsType<'a> for GenericBoundsModel {
    fn as_type(&'a self) -> &'a Type {
        self.type_model.as_type()
    }
}
impl ToType for GenericBoundsModel {
    fn to_type(&self) -> Type {
        self.type_model.to_type()
    }
}
impl TypeModeled for GenericBoundsModel {
    fn type_model_mut(&mut self) -> &mut TypeModel {
        &mut self.type_model
    }

    fn type_model_ref(&self) -> &TypeModel {
        &self.type_model
    }
}

impl Debug for GenericBoundsModel {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let Self { type_model, chain, nested_arguments, .. } = self;
        f.write_fmt(format_args!("GenericBoundsModel(ty: {type_model}, chain: {}, nested_args: {})", format_generic_scope_chain(chain), nested_arguments.to_token_stream()))
    }
}

impl Display for GenericBoundsModel {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}
impl PartialEq for GenericBoundsModel {
    fn eq(&self, other: &Self) -> bool {
        let self_bounds = self.chain.iter().map(|(bounded_ty, bounds)| quote!(#bounded_ty: #(#bounds)*+));
        let other_bounds = other.chain.iter().map(|(bounded_ty, bounds)| quote!(#bounded_ty: #(#bounds)*+));
        let self_tokens = [self.as_type().to_token_stream(), quote!(#(#self_bounds),*)];
        let other_tokens = [other.as_type().to_token_stream(), quote!(#(#other_bounds),*)];
        self_tokens.iter()
            .map(|t| t.to_string())
            .zip(other_tokens.iter().map(ToString::to_string))
            .all(|(a, b)| a == b)
    }
}

impl Eq for GenericBoundsModel {}

impl Hash for GenericBoundsModel {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_type().to_token_stream().to_string().hash(state);
        self.chain.iter().for_each(|(bounded_ty, bounds)| quote!(#bounded_ty: #(#bounds)*+).to_string().hash(state));
    }
}

impl GenericBoundsModel {
    pub fn new(ident: &Ident, chain: IndexMap<ObjectKind, Vec<ObjectKind>>, generics: Generics, nested_arguments: CommaPunctuatedNestedArguments) -> Self {
        Self {
            type_model: TypeModel::new_generic(ident.to_type(), generics, nested_arguments.clone()),
            chain,
            nested_arguments,
        }
    }
}

impl<SPEC> MaybeLambdaArgs<SPEC> for GenericBoundsModel
    where SPEC: Specification {
    fn maybe_lambda_arg_names(&self) -> Option<CommaPunctuated<SPEC::Name>> {
        if self.is_lambda() {
            self.chain.first().and_then(|(_, bounds)| bounds.first().map(MaybeLambdaArgs::<SPEC>::maybe_lambda_arg_names))?
        } else {
            None
        }
    }
}
impl GenericBoundsModel {
    pub fn is_lambda(&self) -> bool {
        self.chain.values().any(|bounds| bounds.iter().any(|b| b.is_lambda()))
    }

    pub fn first_bound(&self) -> Option<&ObjectKind> {
        self.chain.first().and_then(|(_, bounds)| bounds.first())
    }

    pub fn expr_from<SPEC>(&self, field_path: SPEC::Expr) -> SPEC::Expr
        where SPEC: Specification<Expr=Expression<SPEC>>,
              SPEC::Expr: ScopeContextPresentable {
        if self.chain.is_empty() {
            Expression::from_primitive(field_path)
        } else if let Some(lambda_args) = MaybeLambdaArgs::<SPEC>::maybe_lambda_arg_names(self) {
            Expression::from_lambda(field_path, lambda_args)
        } else {
            Expression::from_complex(field_path)
        }
    }

}