on_your_marks 0.1.1

Getters and Setters for Rust
Documentation
mod args;

use args::arg_types::GetArgs;
use proc_macro2::Ident;
use quote::quote;
use syn::{
    parse::ParseStream, parse_macro_input, punctuated::Punctuated, spanned::Spanned, Fields,
    ItemStruct, Meta, Token, Type,
};

#[proc_macro_derive(GetSet, attributes(get, set))]
pub fn get_set(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item_struct = parse_macro_input!(item as ItemStruct);

    let struct_name = item_struct.ident;
    let fields = item_struct.fields;

    let methods = process_fields(&fields);
    if methods.is_ok() {
        let methods = methods.unwrap();
        quote!(
            impl #struct_name {
                #(#methods)*
            }
        )
        .into()
    } else {
        let err = methods.unwrap_err();
        err.into_compile_error().into()
    }
}

fn validate_get_args(attrs: &Punctuated<GetArgs, Token![,]>) -> Option<&'static str> {
    let (copy, clone, im_ref, mut_ref) = attrs.iter().fold((0, 0, 0, 0), |mut curr, arg| {
        match arg {
            GetArgs::Copy => curr.0 += 1,
            GetArgs::Clone => curr.1 += 1,
            GetArgs::ImRef(_) => curr.2 += 1,
            GetArgs::MutRef(_) => curr.3 += 1,
            _ => {}
        }
        curr
    });

    match (copy, clone, im_ref, mut_ref) {
        (1, 1, _, _) => Some("`copy` and `clone` can not be used simultaneously"),
        (0 | 1, 0 | 1, 0 | 1, 0 | 1) => None, 
        _ => Some("only `funky` can be present more than once, rest all attribute helpers can be present at most once")
    }
}

fn transform_get(
    attrs: &Punctuated<GetArgs, Token![,]>,
    field_name: &Ident,
    ty: &Type,
) -> syn::Result<Vec<proc_macro2::TokenStream>> {
    if attrs.is_empty() {
        return Err(syn::Error::new(
            field_name.span(),
            "must specify a way to get the value from this field: 
            `#[get(copy)]`, `#[get(clone)]`, `#[get(im_ref(..))]`, `#[get(mut_ref(..))]`, `#[get(funky(..))]`",
        ));
    }

    Ok(attrs
        .iter()
        .map(|arg| match arg {
            GetArgs::Copy => {
                let method_name = Ident::new(&format!("get_{}", field_name), field_name.span());
                quote!(
                pub fn #method_name(&self) -> #ty {
                   self.#field_name
                   }
                )
            }
            GetArgs::Clone => {
                let method_name = Ident::new(&format!("get_{}", field_name), field_name.span());
                quote!(
                    pub fn #method_name(&self) -> #ty {
                        self.#field_name.clone()
                    }
                )
            }
            GetArgs::ImRef(ret_ty) => {
                let method_name = Ident::new(&format!("get_{}_ref", field_name), field_name.span());
                quote!(
                    pub fn #method_name(&self) -> #ret_ty {
                        self.#field_name.as_ref()
                    }
                )
            }
            GetArgs::MutRef(ret_ty) => {
                let method_name =
                    Ident::new(&format!("get_{}_ref_mut", field_name), field_name.span());
                quote!(
                    pub fn #method_name(&self) -> #ret_ty {
                        self.#field_name.as_mut()
                    }
                )
            }
            GetArgs::Funky(funky) => {
                let method_name =
                    Ident::new(&format!("get_{}", funky.method_name), field_name.span());
                let ret_ty = &funky.ret_type;
                let from = &funky.from;

                if !funky.mut_get {
                    quote!(
                    pub fn #method_name(&self) -> #ret_ty {
                        let #field_name = &self.#field_name;
                        let __res = { #from };
                        __res
                    })
                } else {
                    quote!(
                    pub fn #method_name(&mut self) -> #ret_ty {
                        let #field_name = &mut self.#field_name;
                        let __res = { #from };
                        __res
                    })
                }
            }
        })
        .collect())
}

fn process_fields(fields: &Fields) -> syn::Result<Vec<proc_macro2::TokenStream>> {
    fields
        .iter()
        .try_fold(vec![], |mut final_tokens, f| -> syn::Result<_> {
            let field_name = f.ident.as_ref().unwrap();
            let ty = &f.ty;

            let mut token_vec = vec![];

            if f.attrs.iter().filter(|a| a.path.is_ident("get")).count() > 1
                || f.attrs.iter().filter(|a| a.path.is_ident("set")).count() > 1
            {
                return Err(syn::Error::new(
                    f.span(),
                    "`#[get]` and `#[set]` are allowed atmost once",
                ));
            }

            if let Some(attr) = f.attrs.iter().find(|a| a.path.is_ident("get")) {
                let get_args = attr.parse_args_with(|input: ParseStream| -> syn::Result<_> {
                    Punctuated::<GetArgs, Token![,]>::parse_terminated(input)
                })?;

                if let Some(err) = validate_get_args(&get_args) {
                    return Err(syn::Error::new(f.span(), err));
                }

                token_vec.extend(transform_get(&get_args, field_name, ty)?);
            }

            if let Some(attr) = f.attrs.iter().find(|a| a.path.is_ident("set")) {
                match attr.parse_meta()? {
                    Meta::Path(_) => {}
                    _ => return Err(syn::Error::new(attr.span(), "only `#[set]` supported")),
                }
                let method_name = Ident::new(&format!("set_{}", field_name), attr.span());
                token_vec.push(quote!(
                    pub fn #method_name(&mut self, value: #ty) {
                        self.#field_name = value;
                    }
                ))
            }

            final_tokens.extend(token_vec);
            Ok(final_tokens)
        })
}