moxy-derive 0.0.4

derive macros for moxy crate
Documentation
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};

use crate::core::pascal_to_snake;

struct VariantEntry {
    name: syn::Ident,
    ty: syn::Type,
}

impl Parse for VariantEntry {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name: syn::Ident = input.parse()?;
        input.parse::<syn::Token![=>]>()?;
        let ty: syn::Type = input.parse()?;
        Ok(Self { name, ty })
    }
}

struct UnionizeInput {
    variants: Vec<VariantEntry>,
    vis: syn::Visibility,
    name: syn::Ident,
}

impl Parse for UnionizeInput {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        syn::braced!(content in input);
        let entries = content.parse_terminated(VariantEntry::parse, syn::Token![,])?;
        input.parse::<syn::Token![as]>()?;
        let vis: syn::Visibility = input.parse()?;
        let name: syn::Ident = input.parse()?;
        Ok(Self {
            variants: entries.into_iter().collect(),
            vis,
            name,
        })
    }
}

pub fn render(tokens: proc_macro::TokenStream) -> syn::Result<TokenStream> {
    let input: UnionizeInput = syn::parse(tokens)?;
    let vis = &input.vis;
    let name = &input.name;

    let enum_variants: Vec<_> = input
        .variants
        .iter()
        .map(|v| {
            let vname = &v.name;
            let ty = &v.ty;
            quote! { #vname(#ty) }
        })
        .collect();

    let mut methods = Vec::new();
    let mut from_impls = Vec::new();

    for v in &input.variants {
        let vname = &v.name;
        let ty = &v.ty;
        let snake = pascal_to_snake(&vname.to_string());
        let is_name = format_ident!("is_{}", snake);
        let as_name = format_ident!("as_{}", snake);
        let to_name = format_ident!("to_{}", snake);

        methods.push(quote! {
            pub fn #is_name(&self) -> bool {
                matches!(self, Self::#vname(..))
            }

            pub fn #as_name(&self) -> ::std::option::Option<&#ty> {
                match self {
                    Self::#vname(__v) => ::std::option::Option::Some(__v),
                    _ => ::std::option::Option::None,
                }
            }

            pub fn #to_name(&self) -> ::std::option::Option<#ty>
            where #ty: ::std::clone::Clone
            {
                match self {
                    Self::#vname(__v) => ::std::option::Option::Some(__v.clone()),
                    _ => ::std::option::Option::None,
                }
            }
        });

        from_impls.push(quote! {
            impl ::std::convert::From<#ty> for #name {
                fn from(value: #ty) -> Self {
                    Self::#vname(value)
                }
            }
        });
    }

    Ok(quote! {
        #vis enum #name {
            #(#enum_variants,)*
        }

        impl #name {
            #(#methods)*
        }

        #(#from_impls)*
    })
}