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";
#[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();
let mut skip_new = false;
for attr in &input.attrs {
if attr.path().is_ident(SKIP_NEW) {
skip_new = true;
break;
}
}
if let Data::Struct(data_struct) = &input.data {
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;
let attrs = parse_field_attributes(&f.attrs);
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);
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);
}
}
}
}
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); let getter = quote! {
pub fn #getter_name(&self) -> &#field_ty {
&self.#index
}
};
getters.push(getter);
}
}
}
let new_fn = if !skip_new {
generate_new_fn(&input.data)
} else {
quote! {}
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let expanded = quote! {
impl #impl_generics #name #ty_generics #where_clause {
#new_fn
#(#getters)*
#(#mut_getters)*
}
};
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! {},
}
}
#[derive(Default)]
struct FieldAttributes {
use_deref: bool,
use_as_ref: bool,
generate_mut: bool,
skip_getter: bool,
custom_logic: Option<LitStr>,
}
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
})
}