waiter_codegen 1.6.6

Macro for Waiter DI
Documentation
use proc_macro::TokenStream;
use std::ops::Deref;

use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{Error, GenericParam, ItemFn, ItemImpl, ItemStruct, Path, ReturnType, Type};
use syn::spanned::Spanned;

use crate::attr_parser::ProvidesAttr;
use crate::component::{generate_dependencies_create_code, generate_inject_dependencies_tuple};
use crate::component::type_to_inject::TypeToInject;

pub(crate) fn generate_component_provider_impl_struct(component: ItemStruct) -> TokenStream {
    let comp_name = component.ident;
    let comp_generics = component.generics.clone();

    let create_component_code = quote::quote! {
        #comp_name::__waiter_create(self)
    };
    let inject_deferred_code = quote::quote! {
        #comp_name::__waiter_inject_deferred(self, &component);
    };

    generate_component_provider_impl(
        quote::quote! { #comp_name #comp_generics },
        component.generics.params.iter().collect(),
        vec!(),
        create_component_code,
        inject_deferred_code,
    )
}

pub(crate) fn generate_component_provider_impl_fn(
    provides: ProvidesAttr,
    factory: ItemFn,
    force_type: TokenStream2,
) -> Result<TokenStream, Error> {
    let comp_name = if force_type.is_empty() {
        let ret_value = if let ReturnType::Type(_, type_) = &factory.sig.output {
            if let Type::Path(type_path) = type_.deref() {
                type_path.path.segments.to_token_stream()
            } else {
                return Err(Error::new(
                    factory.span(),
                    "Unsupported return type for factory function",
                ));
            }
        } else {
            return Err(Error::new(
                factory.span(),
                "Return type must be specified for factory function",
            ));
        };
        ret_value
    } else {
        force_type.clone()
    };

    let fn_name = factory.sig.ident.to_token_stream();
    let fn_name_prefix = if force_type.is_empty() {
        force_type
    } else {
        quote::quote! { #force_type :: }
    };

    let dependencies_code = generate_dependencies_create_code(
        factory.sig.inputs.iter()
            .map(|arg| TypeToInject::from_fn_arg(arg.clone()))
            .collect::<Result<Vec<_>, _>>()?
    );
    let factory_code = generate_inject_dependencies_tuple(factory.sig.inputs.len());

    let create_component_code = quote::quote! {
        {
            let container = &mut *self;
            #dependencies_code
            #fn_name_prefix #fn_name #factory_code
        }
    };
    let inject_deferred_code = quote::quote! {};

    Ok(generate_component_provider_impl(
        comp_name,
        factory.sig.generics.params.iter()
            .filter(|p| matches!(p, GenericParam::Lifetime(_)))
            .collect(),
        provides.profiles,
        create_component_code,
        inject_deferred_code,
    ))
}

pub fn generate_component_provider_impl(
    comp_name: TokenStream2,
    comp_generics: Vec<&GenericParam>,
    profiles: Vec<Path>,
    create_component_code: TokenStream2,
    inject_deferred_code: TokenStream2,
) -> TokenStream {
    let (profiles, provider_generics) = if profiles.is_empty() {
        let generic_profile = quote::quote! { PROFILE };

        let provider_generics = if comp_generics.is_empty() {
            quote::quote! { <PROFILE> }
        } else {
            quote::quote! { <#(#comp_generics),*, PROFILE> }
        };

        (vec!(generic_profile), provider_generics)
    } else {
        let profiles = profiles.iter()
            .map(|p| p.to_token_stream())
            .collect();
        (profiles, quote::quote! { <#(#comp_generics),*> })
    };

    let result = quote::quote! {#(
        impl #provider_generics waiter_di::Provider<#comp_name> for waiter_di::Container<#profiles> {
            type Impl = #comp_name;
            fn get(&mut self) -> waiter_di::Wrc<Self::Impl> {
                let type_id = std::any::TypeId::of::<#comp_name>();
                if !self.components.contains_key(&type_id) {
                    let component = waiter_di::Wrc::new(#create_component_code);
                    self.components.insert(type_id, component.clone());
                    #inject_deferred_code
                }
                let any = self.components.get(&type_id)
                    .unwrap();

                return any.clone()
                    .downcast::<#comp_name>()
                    .unwrap();
            }
            fn create(&mut self) -> Self::Impl {
                let component = #create_component_code;
                #inject_deferred_code
                return component;
            }
        }
    )*};

    TokenStream::from(result)
}

pub(crate) fn generate_interface_provider_impl(provides: ProvidesAttr, impl_block: ItemImpl) -> TokenStream {
    let interface = match impl_block.trait_ {
        Some((_, interface, _)) => interface,
        None => return TokenStream::from(Error::new(
            impl_block.span(),
            "#[provides] can be used only on impl blocks for traits",
        ).to_compile_error())
    };

    let comp_name = if let Type::Path(comp_path) = *impl_block.self_ty {
        comp_path.path.segments.first().unwrap().ident.clone()
    } else {
        return TokenStream::from(Error::new(impl_block.self_ty.span(), "Failed to create provider").to_compile_error());
    };

    let provider_body = quote::quote! {{
        type Impl = #comp_name;
        fn get(&mut self) -> waiter_di::Wrc<Self::Impl> {
            waiter_di::Provider::<#comp_name>::get(self)
        }
        fn create(&mut self) -> Self::Impl {
            waiter_di::Provider::<#comp_name>::create(self)
        }
    }};

    let profiles = provides.profiles;
    let result = if profiles.is_empty() {
        quote::quote! {
            impl<P> waiter_di::Provider<dyn #interface> for waiter_di::Container<P> #provider_body
        }
    } else {
        quote::quote! {
            #(impl waiter_di::Provider<dyn #interface> for waiter_di::Container<#profiles> #provider_body)*
        }
    };

    TokenStream::from(result)
}