quote-data-codegen 1.0.0

Internal codegen for quote-it
Documentation
use crate::helper::Interpolated;
use crate::r#struct::StructField;
use heck::ToSnakeCase;
use helpers::get_wrapped_value;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{Token, Data, DeriveInput, Error, Fields, Ident, Variant as SynVariant, Generics, GenericParam};
use syn::punctuated::Punctuated;

pub struct EnumStructure {
    name: Ident,
    variants: Vec<Variant>,
    generics: Generics,
    mod_path: Option<TokenStream>,
}

impl EnumStructure {
    pub fn from_ast(input: &DeriveInput, mod_path: Option<TokenStream>) -> Result<Self, Error> {
        let name = input.ident.clone();

        let data_enum = match &input.data {
            Data::Enum(data) => data,
            _ => unreachable!(),
        };

        let variants = data_enum
            .variants
            .iter()
            .map(Variant::from_ast)
            .collect::<Result<Vec<Variant>, Error>>()?;

        let generics = input.generics.clone();

        Ok(EnumStructure {
            name,
            variants,
            generics,
            mod_path
        })
    }

    pub fn get_implement(&self) -> Result<TokenStream, Error> {
        let name = &self.name;
        let variants: Vec<TokenStream> = self
            .variants
            .iter()
            .map(|variant| variant.arm_token_stream(&self.name, &self.mod_path))
            .collect::<Result<Vec<_>, _>>()?;

        let generics = &self.generics.params;
        let generics_without_bounds: Punctuated<GenericParam, Token![,]> = self.generics.params.clone()
            .iter()
            .map(
                |item| {
                    if let GenericParam::Type(param) = item {
                        let mut new_param = param.clone();
                        new_param.attrs = vec![];
                        new_param.colon_token = None;
                        new_param.bounds = Punctuated::default();
                        new_param.eq_token = None;
                        new_param.default = None;

                        GenericParam::Type(new_param)
                    } else {
                        item.clone()
                    }
                }
            ).collect();
        let where_clause = &self.generics.where_clause;

        Ok(quote! {
            impl<#generics> quote::ToTokens for #name <#generics_without_bounds> #where_clause {
                fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
                    use quote_data::Tokenizable;
                    match self {
                            #(#variants),*
                    }.to_tokens(tokens);
                }
            }
        })
    }
}

struct Variant {
    name: Ident,
    named: bool,
    fields: Vec<StructField>,
}

impl Variant {
    pub fn from_ast(variant: &SynVariant) -> Result<Self, Error> {
        let name = variant.ident.clone();
        let named = matches!(variant.fields, Fields::Named(_));
        let fields = variant
            .fields
            .iter()
            .enumerate()
            .map(|(index, field)| StructField::from_ast(field, index))
            .collect::<Result<Vec<_>, _>>()?;

        Ok(Variant {
            name,
            named,
            fields,
        })
    }

    pub fn arm_token_stream(
        &self,
        enum_ident: &Ident,
        mod_path: &Option<TokenStream>,
    ) -> Result<TokenStream, Error> {
        let name = &self.name;

        let mut arguments = vec![];
        let mut temp_values = vec![];
        let mut construct_params = vec![];

        for field in self.fields.iter() {
            let argument = format_ident!(
                "{}_{}",
                enum_ident.to_string().to_snake_case(),
                field.name()
            );
            let temp_value_ident = format_ident!(
                "temp_{}_{}",
                enum_ident.to_string().to_snake_case(),
                field.name()
            );

            let value = get_wrapped_value(field.ty(), argument.to_token_stream(), false, false)?;

            let interpolated_param = Interpolated(temp_value_ident.to_string());

            arguments.push(if let Some(name) = field.ident() {
                quote::quote! {
                    #name: #argument
                }
            } else {
                argument.to_token_stream()
            });

            temp_values.push(quote::quote! {
                let #temp_value_ident = #value
            });
            construct_params.push(if let Some(name) = field.ident() {
                quote::quote! {
                    #name: #interpolated_param
                }
            } else {
                quote::quote! {
                    #interpolated_param
                }
            })
        }

        let arguments_tokens = if !self.fields.is_empty() {
            if self.named {
                quote! {
                    {#(#arguments),*}
                }
            } else {
                quote! {
                    (#(#arguments),*)
                }
            }
        } else {
            TokenStream::new()
        };

        let mod_path_token = mod_path
            .as_ref()
            .map(|path| quote! {#path::})
            .unwrap_or_default();

        let construct_token_stream = if self.named {
            quote::quote! {
                {#(#construct_params),*}
            }
        } else if self.fields.is_empty() {
            TokenStream::new()
        } else {
            quote::quote! {
                (#(#construct_params),*)
            }
        };

        Ok(quote! {
            #enum_ident::#name#arguments_tokens => {
                #(#temp_values;)*
                quote::quote! {
                    #mod_path_token#enum_ident::#name#construct_token_stream
                }
            }
        })
    }
}