getters0 0.1.0

Rust Getters Derive Macro: Effortlessly auto-generate customizable getter methods for Rust structs
Documentation
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;
use syn::{
    parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Fields, Ident, LitStr,
};

const USE_DEREF: &str = "use_deref";
const USE_AS_REF: &str = "use_as_ref";
const GET_MUT: &str = "get_mut";
const SKIP_NEW: &str = "skip_new";
const GETTER_LOGIC: &str = "getter_logic";
const SKIP_GETTER: &str = "skip_getter";

/// A procedural macro to automatically derive getter methods for struct fields.
///
/// Attributes:
/// - `use_deref`: Generate a getter method that dereferences the field.
/// - `use_as_ref`: Generate a getter method using `AsRef` trait.
/// - `get_mut`: Generate a mutable getter method for the field.
/// - `skip_new`: Skip generating a `new` method for the struct.
/// - `getter_logic`: Specify custom logic for a getter method. (MUST be a function path)
/// - `skip_getter`: Do not generate a getter method for this field.
#[proc_macro_derive(
    Getters,
    attributes(use_deref, use_as_ref, get_mut, skip_new, getter_logic, skip_getter)
)]
pub fn derive_getters_fn(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let generics = &input.generics;
    let mut getters = Vec::new();
    let mut mut_getters = Vec::new();

    // Check if `skip_new` attribute is present.
    let mut skip_new = false;
    for attr in &input.attrs {
        if attr.path().is_ident(SKIP_NEW) {
            skip_new = true;
            break;
        }
    }

    // Generate getters based on struct fields and attributes.
    if let Data::Struct(data_struct) = &input.data {
        // Handle named fields.
        if let Fields::Named(fields_named) = &data_struct.fields {
            for f in fields_named.named.iter() {
                let field_name = f.ident.as_ref().unwrap();
                let field_ty = &f.ty;

                // Parse and process attributes for each field.
                let attrs = parse_field_attributes(&f.attrs);

                // Generate getters based on parsed attributes.
                if !attrs.skip_getter {
                    let getter = if let Some(logic_str) = attrs.custom_logic {
                        let logic: proc_macro2::TokenStream =
                            logic_str.parse().unwrap_or_else(|_| quote! {});
                        quote! {
                            pub fn #field_name(&self) -> u32 {
                                #logic(self.#field_name)
                            }
                        }
                    } else if attrs.use_deref {
                        quote! {
                            pub fn #field_name(&self) -> &<#field_ty as std::ops::Deref>::Target {
                                &*self.#field_name
                            }
                        }
                    } else if attrs.use_as_ref {
                        quote! {
                            pub fn #field_name(&self) -> &<#field_ty as std::convert::AsRef<#field_ty>>::Target {
                                self.#field_name.as_ref()
                            }
                        }
                    } else {
                        quote! {
                            pub fn #field_name(&self) -> &#field_ty {
                                &self.#field_name
                            }
                        }
                    };

                    getters.push(getter);

                    // Generate mutable getters if needed.
                    if attrs.generate_mut {
                        let getter_mut_name =
                            Ident::new(&format!("{}_mut", field_name), field_name.span());
                        let getter_mut = quote! {
                            pub fn #getter_mut_name(&mut self) -> &mut #field_ty {
                                &mut self.#field_name
                            }
                        };
                        mut_getters.push(getter_mut);
                    }
                }
            }
        }
        // Handle unnamed fields (tuples).
        if let Fields::Unnamed(fields_unnamed) = &data_struct.fields {
            for (i, f) in fields_unnamed.unnamed.iter().enumerate() {
                let field_ty = &f.ty;
                let getter_name = Ident::new(&format!("get_{}", i), f.span());
                let index = syn::Index::from(i); // Using syn::Index::from
                let getter = quote! {
                    pub fn #getter_name(&self) -> &#field_ty {
                        &self.#index
                    }
                };
                getters.push(getter);
            }
        }
    }

    // Generate a `new` function if not skipped.
    let new_fn = if !skip_new {
        generate_new_fn(&input.data)
    } else {
        quote! {}
    };

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    // Combine getters, mutable getters, and the `new` function into the impl block..
    let expanded = quote! {
        impl #impl_generics #name #ty_generics #where_clause {
            #new_fn

            #(#getters)*
            #(#mut_getters)*
        }
    };

    // Convert to a TokenStream and return.
    TokenStream::from(expanded)
}

fn generate_new_fn(data: &Data) -> proc_macro2::TokenStream {
    match data {
        Data::Struct(data_struct) => match &data_struct.fields {
            Fields::Named(fields_named) => {
                let args = fields_named.named.iter().map(|f| {
                    let field_name = f.ident.as_ref().unwrap();
                    let field_ty = &f.ty;
                    quote! { #field_name: #field_ty }
                });
                let assignments = fields_named.named.iter().map(|f| {
                    let field_name = f.ident.as_ref().unwrap();
                    quote! { #field_name: #field_name }
                });
                quote! {
                    pub fn new(#(#args),*) -> Self {
                        Self {
                            #(#assignments),*
                        }
                    }
                }
            }
            Fields::Unnamed(fields_unnamed) => {
                let args = fields_unnamed.unnamed.iter().enumerate().map(|(i, f)| {
                    let field_ty = &f.ty;
                    let ident = Ident::new(&format!("field_{}", i), f.span());
                    quote! { #ident: #field_ty }
                });
                let assignments = fields_unnamed.unnamed.iter().enumerate().map(|(i, _)| {
                    let ident = Ident::new(&format!("field_{}", i), proc_macro2::Span::call_site());
                    quote! { #ident }
                });
                quote! {
                    pub fn new(#(#args),*) -> Self {
                        Self(#(#assignments),*)
                    }
                }
            }
            Fields::Unit => quote! {},
        },
        Data::Enum(_) => quote! {},
        Data::Union(_) => quote! {},
    }
}

/// Represents parsed field attributes for getter generation.
#[derive(Default)]
struct FieldAttributes {
    use_deref: bool,
    use_as_ref: bool,
    generate_mut: bool,
    skip_getter: bool,
    custom_logic: Option<LitStr>,
}

/// Parses attributes applied to struct fields and returns a `FieldAttributes` instance.
///
/// This function reads through the provided attributes and sets flags in `FieldAttributes`
/// based on the attributes found.
fn parse_field_attributes(attrs: &[Attribute]) -> FieldAttributes {
    attrs
        .iter()
        .fold(FieldAttributes::default(), |mut acc, attr| {
            match attr
                .path()
                .get_ident()
                .map(|ident| ident.to_string())
                .as_deref()
            {
                Some(USE_DEREF) => acc.use_deref = true,
                Some(USE_AS_REF) => acc.use_as_ref = true,
                Some(GET_MUT) => acc.generate_mut = true,
                Some(SKIP_GETTER) => acc.skip_getter = true,
                Some(GETTER_LOGIC) => {
                    acc.custom_logic = {
                        if let syn::Meta::NameValue(meta_name_value) = &attr.meta {
                            if let syn::Expr::Lit(lit_str) = &meta_name_value.value {
                                match &lit_str.lit {
                                    syn::Lit::Str(lit) => Some(lit.clone()),
                                    _ => todo!(),
                                }
                            } else {
                                None
                            }
                        } else {
                            None
                        }
                    }
                }
                _ => (),
            };
            acc
        })
}