gdnative-derive 0.11.3

The Godot game engine's gdnative derive and procedural macros.
Documentation
use proc_macro2::Span;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;

use syn::visit::Visit;
use syn::Fields;
use syn::{spanned::Spanned, Data, DeriveInput, Ident};

use crate::utils::extend_bounds::with_visitor;

pub(crate) fn derive_from_varargs(input: DeriveInput) -> Result<TokenStream2, syn::Error> {
    let derived = crate::automatically_derived();

    if let Data::Struct(struct_data) = input.data {
        let ident = input.ident;

        let mut generics = with_visitor(
            input.generics,
            Some(&syn::parse_quote! { ::gdnative::core_types::FromVariant }),
            None,
            |visitor| {
                visitor.visit_data_struct(&struct_data);
            },
        );

        for param in generics.type_params_mut() {
            param.default = None;
        }

        let input_ident = Ident::new("__args", Span::call_site());
        let where_clause = &generics.where_clause;

        let fields = match &struct_data.fields {
            Fields::Named(fields) => &fields.named,
            Fields::Unnamed(fields) => &fields.unnamed,
            Fields::Unit => {
                return Ok(quote! {
                    #derived
                    impl #generics ::gdnative::export::FromVarargs for #ident #generics #where_clause {
                        fn read<'a>(
                            #input_ident: &mut ::gdnative::export::Varargs<'a>,
                        ) -> std::result::Result<Self, std::vec::Vec<::gdnative::export::ArgumentError<'a>>> {
                            std::result::Result::Ok(#ident)
                        }
                    }
                })
            }
        };

        let mut required = Vec::new();
        let mut optional = Vec::new();
        let mut skipped = Vec::new();
        for field in fields {
            if field.attrs.iter().any(|attr| attr.path.is_ident("skip")) {
                skipped.push(field);
                continue;
            }

            let is_optional = field.attrs.iter().any(|attr| attr.path.is_ident("opt"));
            if !is_optional && !optional.is_empty() {
                return Err(syn::Error::new(
                    field.ident.span(),
                    "cannot add required arguments after optional ones",
                ));
            }
            if is_optional {
                optional.push(field);
            } else {
                required.push(field);
            }
        }

        let req_var_idents = required
            .iter()
            .enumerate()
            .map(|(n, field)| {
                field
                    .ident
                    .clone()
                    .unwrap_or_else(|| Ident::new(&format!("__req_arg_{n}"), Span::call_site()))
            })
            .collect::<Vec<_>>();
        let req_var_names = required
            .iter()
            .map(|field| {
                field.ident.as_ref().map(|id| {
                    let s = id.to_string();
                    quote!(.with_name(#s))
                })
            })
            .collect::<Vec<_>>();
        let req_var_tys = required
            .iter()
            .map(|field| format!("{}", field.ty.to_token_stream()))
            .collect::<Vec<_>>();

        let opt_var_idents = optional
            .iter()
            .enumerate()
            .map(|(n, field)| {
                field
                    .ident
                    .clone()
                    .unwrap_or_else(|| Ident::new(&format!("__opt_arg_{n}"), Span::call_site()))
            })
            .collect::<Vec<_>>();
        let opt_var_names = optional
            .iter()
            .map(|field| {
                field.ident.as_ref().map(|id| {
                    let s = id.to_string();
                    quote!(.with_name(#s))
                })
            })
            .collect::<Vec<_>>();
        let opt_var_tys = optional
            .iter()
            .map(|field| format!("{}", field.ty.to_token_stream()))
            .collect::<Vec<_>>();

        let skipped_var_idents = skipped
            .iter()
            .enumerate()
            .map(|(n, field)| {
                field
                    .ident
                    .clone()
                    .unwrap_or_else(|| Ident::new(&format!("__skipped_arg_{n}"), Span::call_site()))
            })
            .collect::<Vec<_>>();

        Ok(quote! {
            #derived
            impl #generics ::gdnative::export::FromVarargs for #ident #generics #where_clause {
                fn read<'a>(
                    #input_ident: &mut ::gdnative::export::Varargs<'a>,
                ) -> std::result::Result<Self,std::vec:: Vec<::gdnative::export::ArgumentError<'a>>> {
                    let mut __errors = std::vec::Vec::new();

                    #(
                        let #req_var_idents = #input_ident.read()
                            #req_var_names
                            .with_type_name(stringify!(#req_var_tys))
                            .get()
                            .map_err(|err| __errors.push(err))
                            .ok();
                    )*

                    #(
                        let #opt_var_idents = #input_ident.read()
                            #opt_var_names
                            .with_type_name(stringify!(#opt_var_tys))
                            .get_optional()
                            .map_err(|err| __errors.push(err))
                            .ok()
                            .flatten()
                            .unwrap_or_default();
                    )*

                    if !__errors.is_empty() {
                        return std::result::Result::Err(__errors);
                    }

                    #(
                        let #req_var_idents = #req_var_idents.unwrap();
                    )*

                    #(
                        let #skipped_var_idents = core::default::Default::default();
                    )*

                    std::result::Result::Ok(#ident {
                        #(#req_var_idents,)*
                        #(#opt_var_idents,)*
                        #(#skipped_var_idents,)*
                    })
                }
            }
        })
    } else {
        Err(syn::Error::new(
            input.span(),
            "`FromVarargs` can only be derived for structs",
        ))
    }
}