rudi-macro-dev 0.1.3

Macros for Rudi.
Documentation
use from_attr::{AttrsValue, FromAttr, PathValue};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use rudi_core::{Color, Scope};
use syn::{
    parse_quote, spanned::Spanned, Generics, ImplItem, ImplItemFn, ItemImpl, Path, ReturnType,
    Type, TypePath,
};

use crate::{
    commons::{self, ArgumentResolveStmts},
    impl_fn_or_enum_variant_attr::ImplFnOrEnumVariantAttr,
    resource_attr::ResourceAttr,
    struct_or_function_attr::{ClosureOrPath, StructOrFunctionAttr},
};

pub(crate) fn generate(
    attr: StructOrFunctionAttr,
    mut item_impl: ItemImpl,
    scope: Scope,
) -> syn::Result<TokenStream> {
    let ResourceAttr { path } = match ResourceAttr::remove_attributes(&mut item_impl.attrs) {
        Ok(Some(AttrsValue { value: attr, .. })) => attr,
        Ok(None) => ResourceAttr::default(),
        Err(AttrsValue { value: e, .. }) => return Err(e),
    };

    if let Some(PathValue { path, .. }) = attr.async_ {
        return Err(syn::Error::new(
            path,
            "`async` only support in struct and enum, please use async fn or sync fn instead",
        ));
    }

    let impl_span = item_impl.span();

    let ItemImpl {
        generics,
        self_ty,
        items,
        trait_,
        ..
    } = &mut item_impl;

    let trait_ = trait_.as_mut().map(|(_, path, _)| path);

    let mut parse_errors = Vec::new();
    let mut duplicate_errors = Vec::new();
    let mut no_matched_fn_errors = Vec::new();

    let matched = items
        .iter_mut()
        .filter_map(|impl_item| {
            let f = match impl_item {
                ImplItem::Fn(f) => f,
                _ => return None,
            };

            match ImplFnOrEnumVariantAttr::remove_attributes(&mut f.attrs) {
                Ok(None) => None,
                Ok(Some(AttrsValue { attrs, .. })) => Some((f, attrs)),
                Err(AttrsValue { attrs, value: e }) => {
                    parse_errors.push(e);
                    Some((f, attrs))
                }
            }
        })
        .reduce(|first, (_, attrs)| {
            attrs.into_iter().for_each(|attr| {
                let err = syn::Error::new(attr.span(), "duplicate `#[resource]` attribute");
                duplicate_errors.push(err);
            });

            first
        });

    if matched.is_none() {
        no_matched_fn_errors.push(syn::Error::new(
            impl_span.span(),
            "there must be an associated function annotated by `#[resource]`",
        ));
    }

    if let Some(e) = parse_errors
        .into_iter()
        .chain(duplicate_errors)
        .chain(no_matched_fn_errors)
        .reduce(|mut a, b| {
            a.combine(b);
            a
        })
    {
        return Err(e);
    }

    let (f, _) = matched.unwrap();

    let default_provider_impl =
        generate_default_provider_impl(f, &trait_, self_ty, generics, attr, scope, path)?;

    let expand = quote! {
        #item_impl

        #default_provider_impl
    };

    Ok(expand)
}

fn generate_default_provider_impl<'a>(
    impl_item_fn: &'a mut ImplItemFn,
    trait_: &'a Option<&'a mut Path>,
    type_with_generics: &'a Type,
    generics: &'a Generics,
    attr: StructOrFunctionAttr,
    scope: Scope,
    path: Path,
) -> syn::Result<TokenStream> {
    let StructOrFunctionAttr {
        name,
        eager_create,
        condition,
        binds,
        async_: _,
        #[cfg(feature = "auto-register")]
        auto_register,
        default,
    } = attr;

    #[cfg(feature = "auto-register")]
    commons::check_generics_when_enable_auto_register(
        auto_register,
        generics,
        commons::ItemKind::StructOrEnum,
        scope,
    )?;

    let return_type: Type = match &impl_item_fn.sig.output {
        ReturnType::Default => parse_quote!(()),
        ReturnType::Type(_, return_type) => *return_type.clone(),
    };

    let return_type_is_named = &return_type == type_with_generics;

    let return_type_is_self = if let Type::Path(TypePath {
        qself: None,
        path: Path {
            leading_colon: None,
            segments,
        },
    }) = &return_type
    {
        segments.len() == 1 && segments.first().unwrap().ident == "Self"
    } else {
        false
    };

    if !return_type_is_named && !return_type_is_self {
        return Err(syn::Error::new(
            impl_item_fn.sig.span(),
            format!(
                "return type must be `{}` or `Self`",
                type_with_generics.into_token_stream()
            ),
        ));
    }

    let color = match impl_item_fn.sig.asyncness {
        Some(_) => Color::Async,
        None => Color::Sync,
    };

    let condition = condition
        .map(|ClosureOrPath(expr)| quote!(Some(#expr)))
        .unwrap_or_else(|| quote!(None));

    let ArgumentResolveStmts {
        ref_mut_cx_stmts,
        ref_cx_stmts,
        args,
    } = commons::generate_argument_resolve_methods(&mut impl_item_fn.sig.inputs, color)?;

    let create_provider = commons::generate_create_provider(scope, color);

    let (impl_generics, _, where_clause) = generics.split_for_impl();

    let fn_ident = &impl_item_fn.sig.ident;

    let self_path = match trait_ {
        Some(trait_) => quote! { <Self as #trait_> },
        None => quote! { Self },
    };

    let constructor = match color {
        Color::Async => {
            quote! {
                #[allow(unused_variables)]
                |cx| ::std::boxed::Box::pin(async {
                    #(#ref_mut_cx_stmts)*
                    #(#ref_cx_stmts)*
                    #self_path::#fn_ident(#(#args,)*).await
                })
            }
        }
        Color::Sync => {
            quote! {
                #[allow(unused_variables)]
                |cx| {
                    #(#ref_mut_cx_stmts)*
                    #(#ref_cx_stmts)*
                    #self_path::#fn_ident(#(#args,)*)
                }
            }
        }
    };

    #[cfg(not(feature = "auto-register"))]
    let auto_register = quote! {};

    #[cfg(feature = "auto-register")]
    let auto_register = if auto_register {
        quote! {
            #path::register_provider!(<#type_with_generics as #path::DefaultProvider>::provider());
        }
    } else {
        quote! {}
    };

    let expand = quote! {
        impl #impl_generics #path::DefaultProvider for #type_with_generics #where_clause {
            type Type = Self;

            fn provider() -> #path::Provider<Self> {
                <#path::Provider<_> as ::core::convert::From<_>>::from(
                    #path::#create_provider(#constructor)
                        .name(#name)
                        .eager_create(#eager_create)
                        .condition(#condition)
                        #(
                            .bind(#binds)
                        )*
                )
            }
        }

        #auto_register
    };

    Ok(expand)
}