hephae-utils-derive 0.7.2

Hephae's generic utility module proc-macro derive.
Documentation
use std::iter::repeat_n;

use hephae_macros::{
    Manifest,
    proc_macro2::{Ident, TokenStream},
    quote::{ToTokens, quote, quote_spanned},
    syn,
    syn::{
        Attribute, ConstParam, GenericParam, Generics, ImplItemFn, LifetimeParam, Meta, Token, TypeParam, Visibility,
        parse::{Parse, ParseStream},
        spanned::Spanned,
    },
};

struct Syntax {
    attrs: Vec<Attribute>,
    vis: Visibility,
    name: Ident,
    generics: Generics,
    impls: Vec<ImplItemFn>,
}

impl Parse for Syntax {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let attrs = input.call(Attribute::parse_outer)?;

        let vis = input.parse()?;
        input.parse::<Token![struct]>()?;
        let name = input.parse()?;
        let generics = input.parse()?;
        input.parse::<Token![;]>()?;

        let mut impls = Vec::new();
        while !input.is_empty() {
            impls.push(input.parse()?)
        }

        Ok(Self {
            attrs,
            vis,
            name,
            generics,
            impls,
        })
    }
}

pub fn parse(input: TokenStream) -> syn::Result<TokenStream> {
    let bevy_app = Manifest::resolve_bevy("app", &input)?;
    let Syntax {
        attrs,
        vis,
        name,
        generics,
        impls,
    } = syn::parse2(input)?;

    let mut new_attrs = Vec::with_capacity(attrs.len());
    let mut group = false;

    for attr in attrs {
        if let Meta::Path(ref path) = attr.meta {
            if path.is_ident("plugin_group") {
                group = true;
                continue;
            }
        }

        new_attrs.push(attr);
    }

    let (phantom, args) = generics
        .params
        .iter()
        .fold((Vec::new(), Vec::new()), |(mut phantom, mut args), param| {
            match param {
                GenericParam::Lifetime(LifetimeParam { lifetime, .. }) => {
                    phantom.push(quote_spanned!(param.span()=> fn() -> #lifetime ()))
                }
                GenericParam::Type(TypeParam { ident, .. }) => {
                    phantom.push(quote_spanned!(param.span()=> fn() -> #ident ));
                    args.push(quote_spanned!(param.span()=> ::core::any::type_name::<#ident>()))
                }
                GenericParam::Const(ConstParam { ident, .. }) => args.push(ident.into_token_stream()),
            }
            (phantom, args)
        });

    let phantom = match &*phantom {
        [] => quote!(::core::marker::PhantomData::<()>),
        [data] => quote!(::core::marker::PhantomData::<#data>),
        data => quote!(::core::marker::PhantomData::<(#(#data),*)>),
    };

    let fmt = match &*args {
        [] => quote!(::core::stringify!(#name)),
        args => {
            let braces = repeat_n(quote!({}), args.len());
            quote!(::core::stringify!(#name<#(#braces) *>))
        }
    };

    let (params, where_clause) = (&generics.params, &generics.where_clause);
    let this = quote!(#(#new_attrs)* #vis struct #name <#params> (#phantom) #where_clause; );

    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
    let impl_plugin = if group {
        quote! {
            impl #impl_generics #bevy_app::PluginGroup for #name #type_generics #where_clause {
                #(#impls)*
            }
        }
    } else {
        quote! {
            impl #impl_generics #bevy_app::Plugin for #name #type_generics #where_clause {
                #(#impls)*
            }
        }
    };

    Ok(quote! {
        #this

        impl #impl_generics ::core::fmt::Debug for #name #type_generics #where_clause {
            #[inline]
            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                write!(f, #fmt, #(#args),*)
            }
        }

        impl #impl_generics ::core::clone::Clone for #name #type_generics #where_clause {
            #[inline]
            fn clone(&self) -> Self {
                *self
            }
        }

        impl #impl_generics ::core::marker::Copy for #name #type_generics #where_clause {}

        impl #impl_generics ::core::default::Default for #name #type_generics #where_clause {
            #[inline]
            fn default() -> Self {
                Self(::core::marker::PhantomData)
            }
        }

        #impl_plugin
    })
}