mutable_derive 0.3.3

Implementation for derive for Mutable and Softeq
Documentation
use convert_case::{Case, Casing};
use proc_macro::{Span, TokenStream};
use proc_macro_error::abort;
use quote::ToTokens;
use syn::{Item, Type};

fn generate_from_fields<'r, I>(fields: I) -> (Vec<String>, Vec<String>)
where
    I: Iterator<Item = (&'r Type, String, bool)>,
{
    fields.map(|(ty, name, convert_case)| {
        let pascal_name = if convert_case {name.to_case(Case::Pascal)} else {name.clone()};
        (format!(r#"updates.append(&mut <{field_type} as mutable::Mutable>::cmp(&self.{field_name}, &new_value.{field_name}).into_iter().map(Self::Mutation::{mutation_name}).collect())"#,
                field_type = ty.to_token_stream(),
                field_name = name,
                mutation_name = pascal_name),
        format!(r#"{}(<{} as mutable::Mutable>::Mutation)"#,
                pascal_name,
                ty.to_token_stream()
        ))
    }).collect::<Vec<(String, String)>>().into_iter().unzip()
}

pub(crate) fn try_(item: TokenStream) -> Result<TokenStream, ()> {
    let item =
        syn::parse::<Item>(item).map_err(|_| abort!(Span::call_site(), "Could not parse item"))?;

    match item {
        Item::Struct(struct_) => {
            let (mutation_checks, mutation_variants) = generate_from_fields(
                struct_
                    .fields
                    .iter()
                    .map(|f| (&f.ty, f.ident.to_token_stream().to_string(), true)),
            );

            let mutable_impl = format!(
                r#"
                #[derive(std::fmt::Debug, std::cmp::PartialEq, Clone)]
                {vis} enum {struct_name}Mutation{{
                    {mutation_variants}
                }}

                impl mutable::Mutable for {struct_name}{{
                    type Mutation={struct_name}Mutation;

                    fn cmp(&self, new_value: &Self) -> Vec<Self::Mutation>{{
                        let mut updates: Vec<Self::Mutation> = Vec::new();
                        {mutation_checks};
                        updates
                    }}
                }}"#,
                struct_name = struct_.ident,
                mutation_variants = mutation_variants.join(",\n"),
                mutation_checks = mutation_checks.join(";\n"),
                vis = struct_.vis.to_token_stream()
            );

            Ok(mutable_impl.parse().unwrap())
        }
        Item::Enum(enum_) => {
            let enum_name = enum_.ident;

            let (mutations, arms): (Vec<String>, Vec<String>) = {
                enum_
                    .variants
                    .iter()
                    .map(|v| {
                        let variant_name = v.ident.to_string();
                        if v.fields.is_empty() {
                            ("".into(), format!("({enum_name}::{variant_name}, {enum_name}::{variant_name}) => {{}}"))
                        } else {
                        let named = v.fields.iter().all(|f| f.ident.is_some());
                        let (checks, names, variants): (Vec<String>, Vec<String>, Vec<String>) = v.fields.iter().enumerate().map(|(i, f)| {
                        let (name, pascal_name) = if named {
                            (f.ident.as_ref().unwrap().to_string(), f.ident.as_ref().unwrap().to_string().to_case(Case::Pascal))
                        } else {
                            (format!("{i}"), format!("{i}"))
                        };

                        let mutation_name = format!("Variant{variant_name}Field{pascal_name}");
                            
                            (
                                format!(
                                    r#"updates.append(&mut <{field_type} as mutable::Mutable>::cmp(&old_{name}, &new_{name}).into_iter().map({enum_name}Mutation::{mutation_name}).collect())"#,
                                    field_type = f.ty.to_token_stream()
                                ),
                                name,
                                format!(r#"{mutation_name}(<{} as mutable::Mutable>::Mutation)"#, f.ty.to_token_stream())
                            )
                        }).collect::<Vec<(String, String, String)>>().into_iter().fold((vec![], vec![], vec![]), |acc, val| {
                            ([acc.0, vec![val.0]].concat(), [acc.1, vec![val.1]].concat(), [acc.2, vec![val.2]].concat())
                        });

                        (variants.join(",\n"),
                        format!("({enum_name}::{variant_name}{o}{}{c}, {enum_name}::{variant_name}{o}{}{c}) => {{
                            {checks}
                        }}",
                            names.iter().map(|n| if named {format!("{n}: old_{n}")} else {format!("old_{n}")}).collect::<Vec<_>>().join(","),
                            names.iter().map(|n| if named {format!("{n}: new_{n}")} else {format!("new_{n}")}).collect::<Vec<_>>().join(","),
                            o = if named { "{" }else {"("},
                            c = if named {"}"} else {")"},
                            checks = checks.join(";\n")
                        ))
                    }
                    })
                    .collect::<Vec<(String, String)>>().into_iter().unzip()
            };

            let mutable_impl = format!(
                r#"
                #[derive(std::fmt::Debug, std::cmp::PartialEq, Clone)]
                {vis} enum {enum_name}Mutation{{
                    NewVariant({enum_name}),
                    {mutations}
                }}

                impl mutable::Mutable for {enum_name}{{
                    type Mutation={enum_name}Mutation;

                    fn cmp(&self, new_value: &Self) -> Vec<Self::Mutation>{{
                        let mut updates: Vec<Self::Mutation> = Vec::new();
                        match (self, new_value) {{
                            {arms},
                            (_, n) => updates.push(Self::Mutation::NewVariant(n.clone())),
                        }}
                        updates
                    }}
                }}"#,
                mutations = mutations.into_iter().filter(|v| !v.is_empty()).collect::<Vec<_>>().join(",\n"),
                arms = arms.join(",\n"),
                vis = enum_.vis.to_token_stream()
            );

            Ok(mutable_impl.parse().unwrap())
        }
        _ => abort!(item, "mutable may only be derived on structures"),
    }
}