rudi-macro-dev 0.1.3

Macros for Rudi.
Documentation
use from_attr::{AttrsValue, FromAttr, PathValue};
use proc_macro2::TokenStream;
use quote::quote;
use rudi_core::{Color, Scope};
use syn::{spanned::Spanned, ItemEnum};

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

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

    if item_enum.variants.is_empty() {
        return Err(syn::Error::new(item_enum.span(), "not support empty enum"));
    }

    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,
        &item_enum.generics,
        commons::ItemKind::Enum,
        scope,
    )?;

    let color = match async_ {
        Some(PathValue { value: true, .. }) => Color::Async,
        _ => Color::Sync,
    };

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

    let mut variant_spans = Vec::new();

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

    let matched = item_enum
        .variants
        .iter_mut()
        .filter_map(|variant| {
            variant_spans.push(variant.span());

            match ImplFnOrEnumVariantAttr::remove_attributes(&mut variant.attrs) {
                Ok(None) => None,
                Ok(Some(AttrsValue { attrs, .. })) => Some((variant, attrs)),
                Err(AttrsValue { attrs, value: e }) => {
                    parse_errors.push(e);
                    Some((variant, 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() {
        variant_spans.iter().for_each(|span| {
            no_matched_variant_errors.push(syn::Error::new(
                *span,
                "there must be a variant annotated by `#[resource]`",
            ));
        });
    }

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

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

    let FieldResolveStmts {
        ref_mut_cx_stmts,
        ref_cx_stmts,
        fields,
    } = commons::generate_field_resolve_stmts(&mut variant.fields, color)?;

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

    let enum_ident = &item_enum.ident;
    let variant_ident = &variant.ident;

    let (impl_generics, ty_generics, where_clause) = item_enum.generics.split_for_impl();

    let instance = match fields {
        ResolvedFields::Unit => quote! {
            #variant_ident
        },
        ResolvedFields::Named {
            field_names,
            field_values,
        } => {
            quote! {
                #variant_ident {
                    #(
                        #field_names: #field_values,
                    )*
                }
            }
        }
        ResolvedFields::Unnamed(field_values) => {
            quote! {
                #variant_ident(
                    #(
                        #field_values,
                    )*
                )
            }
        }
    };

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

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

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

    let expand = quote! {
        #item_enum

        impl #impl_generics #path::DefaultProvider for #enum_ident #ty_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)
}