auto-delegate-impl 0.1.2

Auto delegate allows you that automatic impl of traits and delegate their handling to child members.
Documentation
use proc_macro::TokenStream;

use proc_macro2::Span;
use quote::quote;
use syn::{Attribute, GenericParam, ItemTrait, LifetimeParam, TraitItem, TypeParam, TypeParamBound};
use syn::__private::TokenStream2;
use syn::punctuated::Punctuated;
use syn::token::Plus;

use crate::async_trait::AsyncTraitArgs;
use crate::delegatable::delegatable_ident_with_generics;
use crate::intersperse;
use crate::syn::syn_generics::{
    expand_generic_param_without_bound,
    expand_where_bound_without_where_token,
};
use crate::trait_item::functions::TraitFunctions;

pub fn expand_delegate_trait(_attr: TokenStream, input: TokenStream) -> TokenStream2 {
    match try_expand_delegate_trait(input) {
        Ok(output) => output,
        Err(error) => error.to_compile_error(),
    }
}


fn try_expand_delegate_trait(input: TokenStream) -> syn::Result<TokenStream2> {
    let mut item = syn::parse::<syn::ItemTrait>(input)?;
    expand_impl_trait(&mut item)
}


fn expand_impl_trait(item: &mut ItemTrait) -> syn::Result<TokenStream2> {
    let trait_name = expand_trait_name(item);
    let delegatable = delegatable_ident_with_generics(item.ident.clone());

    let impl_generic = proc_macro2::Ident::new("DelegateImpl", Span::call_site());
    let trait_functions = trait_functions(item.clone())?;

    let mut generics = item.generics.clone();
    generics.params.push(GenericParam::Type(TypeParam::from(impl_generic.clone())));

    let lifetime_bound = expand_lifetimes_bound(item);
    let super_traits = super_traits_bound(&item.supertraits);
    let where_generics = expand_where_bound_without_where_token(&item.generics);
    let async_trait_attr = async_trait_attr(item);
    let send_and_sync = send_and_sync_bound(item, &async_trait_attr);

    Ok(quote::quote! {
         #item

         #async_trait_attr
         impl #generics #trait_name for #impl_generic
             where #impl_generic: #delegatable #super_traits #send_and_sync,
                <#impl_generic as #delegatable>::A :  #lifetime_bound,
                <#impl_generic as #delegatable>::B :  #lifetime_bound,
                <#impl_generic as #delegatable>::C :  #lifetime_bound,
                <#impl_generic as #delegatable>::D :  #lifetime_bound,
                <#impl_generic as #delegatable>::D :  #lifetime_bound,
                <#impl_generic as #delegatable>::E :  #lifetime_bound,
                <#impl_generic as #delegatable>::F :  #lifetime_bound,
                <#impl_generic as #delegatable>::G :  #lifetime_bound,
                <#impl_generic as #delegatable>::H :  #lifetime_bound,
                <#impl_generic as #delegatable>::I :  #lifetime_bound,
                <#impl_generic as #delegatable>::J :  #lifetime_bound,
                <#impl_generic as #delegatable>::K :  #lifetime_bound,
                <#impl_generic as #delegatable>::L :  #lifetime_bound,
                #where_generics
            {
                #(#trait_functions)*
            }
    })
}


fn async_trait_attr(item: &ItemTrait) -> Option<&Attribute> {
    item.attrs.iter().find(|attr| {
        attr
            .path()
            .segments
            .first()
            .map_or(false, |segment| segment.ident == "async_trait")
    })
}

fn send_and_sync_bound(item: &ItemTrait, async_trait_attr: &Option<&Attribute>) -> Option<TokenStream2> {
    let attr = async_trait_attr.as_ref()?;
    if let Ok(list) = attr.meta.require_list() {
        let args = syn::parse::<AsyncTraitArgs>(list.tokens.clone().into()).unwrap();
        if args.local {
            None
        } else {
            Some(quote!(+ Send))
        }
    }else if has_immutable_self_receiver(item.items.clone()) {
        Some(quote ! ( + Send + Sync))
    }else {
        Some(quote!(+ Send))
    }
}

fn super_traits_bound(super_traits: &Punctuated<TypeParamBound, Plus>) -> Option<TokenStream2> {
    if super_traits.is_empty() {
        None
    } else {
        Some(quote::quote!(+ #super_traits))
    }
}


fn has_immutable_self_receiver(items: Vec<TraitItem>) -> bool {
    TraitFunctions::new(items).any(|meta| meta.has_immutable_self_ref_receiver())
}


fn trait_functions(item: ItemTrait) -> syn::Result<Vec<TokenStream2>> {
    let mut trait_fn: Vec<TokenStream2> = Vec::new();
    let delegatable_ident = delegatable_ident_with_generics(item.ident);
    for fn_token in TraitFunctions::new(item.items).map(|mut meta| meta.expand_fn(&delegatable_ident)) {
        trait_fn.push(fn_token?);
    }

    Ok(trait_fn)
}


fn expand_trait_name(item: &ItemTrait) -> TokenStream2 {
    let generics: Vec<TokenStream2> = intersperse(
        quote::quote!(,),
        item
            .generics
            .params
            .iter()
            .map(expand_generic_param_without_bound),
    );

    let generics_brackets = if generics.is_empty() {
        None
    } else {
        Some(quote::quote! {
            <#(#generics)*>
        })
    };

    let trait_ident = &item.ident;

    quote::quote! {
        #trait_ident #generics_brackets
    }
}


fn expand_lifetimes_bound(item_trait: &ItemTrait) -> TokenStream2 {
    let trait_name = expand_trait_name(item_trait);

    let lifetime_params: Vec<&LifetimeParam> = item_trait
        .generics
        .lifetimes()
        .collect();

    if lifetime_params.is_empty() {
        quote::quote! {
            #trait_name
        }
    } else {
        let lifetimes_bound: Vec<TokenStream2> = intersperse(
            quote::quote!(+),
            lifetime_params
                .iter()
                .map(|lifetime| quote::quote!(#lifetime)),
        );

        quote::quote! {
            #trait_name  +  #(#lifetimes_bound)*
        }
    }
}