cgp-macro-lib 0.7.0

Context-generic programming core component macros implemented as a library.
Documentation
use proc_macro2::Span;
use quote::{ToTokens, quote};
use syn::token::{Eq, For, Impl, Semi};
use syn::{
    Error, Ident, ImplItem, ImplItemConst, ImplItemFn, ImplItemType, ItemImpl, ItemTrait, Path,
    TraitItem, Type, TypeParamBound, Visibility, WherePredicate, parse2,
};

use crate::blanket_trait::remove_self_path;

pub fn derive_blanket_trait(
    context_ident: &Ident,
    item_trait: &mut ItemTrait,
) -> syn::Result<ItemImpl> {
    let mut impl_items: Vec<ImplItem> = Vec::new();

    let mut assoc_idents: Vec<Ident> = Vec::new();
    let mut assoc_bounds: Vec<WherePredicate> = Vec::new();

    for trait_item in item_trait.items.iter() {
        if let TraitItem::Type(trait_item_type) = trait_item {
            let item_type_ident = &trait_item_type.ident;
            assoc_idents.push(item_type_ident.clone());
        }
    }

    for trait_item in item_trait.items.iter_mut() {
        match trait_item {
            TraitItem::Type(trait_item_type) => {
                trait_item_type.default.take();

                let item_type_ident = &trait_item_type.ident;

                let type_impl = parse2(quote! {
                    #item_type_ident
                })?;

                if !trait_item_type.bounds.is_empty() {
                    let mut current_assoc_bounds = trait_item_type.bounds.clone();

                    for bound in current_assoc_bounds.iter_mut() {
                        if let TypeParamBound::Trait(bound) = bound {
                            bound.path = parse2(remove_self_path(
                                bound.path.to_token_stream(),
                                &assoc_idents,
                            ))?;
                        }
                    }

                    assoc_bounds.push(parse2(quote! {
                        #item_type_ident : #current_assoc_bounds
                    })?);
                }

                let impl_item_type = ImplItemType {
                    attrs: trait_item_type.attrs.clone(),
                    vis: Visibility::Inherited,
                    defaultness: None,
                    type_token: trait_item_type.type_token,
                    ident: trait_item_type.ident.clone(),
                    generics: trait_item_type.generics.clone(),
                    eq_token: Eq(Span::call_site()),
                    ty: type_impl,
                    semi_token: Semi(Span::call_site()),
                };

                impl_items.push(ImplItem::Type(impl_item_type));
            }
            TraitItem::Fn(trait_item_fn) => {
                let fn_block = trait_item_fn
                    .default
                    .as_ref()
                    .ok_or_else(|| {
                        Error::new_spanned(
                            &trait_item_fn,
                            "function item require implementation block",
                        )
                    })?
                    .clone();

                trait_item_fn.default.take();

                let impl_item_fn = ImplItemFn {
                    attrs: trait_item_fn.attrs.clone(),
                    vis: Visibility::Inherited,
                    defaultness: None,
                    sig: trait_item_fn.sig.clone(),
                    block: fn_block,
                };

                impl_items.push(ImplItem::Fn(impl_item_fn));
            }
            TraitItem::Const(trait_item_const) => {
                let (eq_token, const_expr) = trait_item_const
                    .default
                    .as_ref()
                    .ok_or_else(|| {
                        Error::new_spanned(
                            &trait_item_const,
                            "const item require implementation expression",
                        )
                    })?
                    .clone();

                trait_item_const.default.take();

                let impl_item_const = ImplItemConst {
                    attrs: trait_item_const.attrs.clone(),
                    vis: Visibility::Inherited,
                    defaultness: None,
                    const_token: trait_item_const.const_token,
                    ident: trait_item_const.ident.clone(),
                    generics: trait_item_const.generics.clone(),
                    colon_token: trait_item_const.colon_token,
                    ty: trait_item_const.ty.clone(),
                    eq_token,
                    expr: const_expr,
                    semi_token: trait_item_const.semi_token,
                };

                impl_items.push(ImplItem::Const(impl_item_const));
            }
            _ => return Err(Error::new_spanned(&trait_item, "unsupported trait item")),
        }
    }

    let context_type: Type = parse2(quote! { #context_ident })?;

    let mut impl_generics = item_trait.generics.clone();

    impl_generics
        .params
        .push(parse2(context_type.to_token_stream())?);

    for assoc_ident in assoc_idents.iter() {
        impl_generics
            .params
            .push(parse2(assoc_ident.to_token_stream())?);
    }

    let mut supertraits = item_trait.supertraits.clone();

    for bound in supertraits.iter_mut() {
        if let TypeParamBound::Trait(trait_bound) = bound {
            trait_bound.path = parse2(remove_self_path(
                trait_bound.path.to_token_stream(),
                &assoc_idents,
            ))?;
        }
    }

    let where_clause = impl_generics.make_where_clause();
    where_clause.predicates.push(parse2(quote! {
        #context_type: #supertraits
    })?);

    where_clause.predicates.extend(assoc_bounds);

    let trait_name = &item_trait.ident;
    let (_, type_generics, _) = item_trait.generics.split_for_impl();

    let trait_path: Path = parse2(quote! { #trait_name #type_generics })?;

    let item_impl = ItemImpl {
        attrs: item_trait.attrs.clone(),
        defaultness: None,
        unsafety: item_trait.unsafety,
        impl_token: Impl(Span::call_site()),
        generics: impl_generics,
        trait_: Some((None, trait_path, For(Span::call_site()))),
        self_ty: Box::new(context_type),
        brace_token: item_trait.brace_token,
        items: impl_items,
    };

    Ok(item_impl)
}