#![allow(unused_variables)]
#![deny(missing_docs)]
use darling::{
ast::{Data, Fields},
util::Flag,
FromDeriveInput, FromField, FromVariant,
};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::abort;
use quote::{format_ident, quote, ToTokens};
use syn::{
parse_macro_input, Attribute, DeriveInput, Expr, Generics, Ident, Index, Member, Type,
Visibility,
};
#[derive(Debug, FromField)]
#[darling(attributes(getters))]
struct GettersField {
ident: Option<Ident>,
#[allow(unused)]
vis: Visibility,
ty: Type,
#[allow(unused)]
attrs: Vec<Attribute>,
mutable: Flag,
deref: Flag,
clone: Flag,
skip: Flag,
skip_mutable: Flag,
skip_deref: Flag,
skip_clone: Flag,
}
#[derive(Debug, FromVariant)]
#[darling(attributes(getters))]
struct GettersVariant {
ident: Ident,
discriminant: Option<Expr>,
fields: Fields<GettersField>,
#[allow(unused)]
attrs: Vec<Attribute>,
#[allow(unused)]
mutable: Flag,
#[allow(unused)]
deref: Flag,
#[allow(unused)]
clone: Flag,
skip: Flag,
skip_mutable: Flag,
skip_deref: Flag,
skip_clone: Flag,
}
#[derive(Debug, FromDeriveInput)]
#[darling(
attributes(getters),
supports(
struct_named,
struct_newtype,
struct_tuple,
enum_named,
enum_newtype,
enum_tuple,
enum_unit
),
// NOTE: https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
forward_attrs(
cfg,
derive,
allow,
warn,
deny,
forbid,
deprecated,
must_use,
doc,
non_exhaustive
)
)]
struct GettersInput {
ident: Ident,
#[allow(unused)]
vis: Visibility,
generics: Generics,
data: Data<GettersVariant, GettersField>,
#[allow(unused)]
attrs: Vec<Attribute>,
mutable: Flag,
clone: Flag,
deref: Flag,
}
impl GettersInput {
fn method_field(&self, field: &GettersField, index: usize, max: usize) -> TokenStream2 {
let ty = &field.ty;
let immutable = !field.skip.is_present();
let mutable = (field.mutable.is_present() || self.mutable.is_present())
&& !field.skip_mutable.is_present();
let clone =
(field.clone.is_present() || self.clone.is_present()) && !field.skip_clone.is_present();
let deref =
(field.deref.is_present() || self.deref.is_present()) && !field.skip_deref.is_present();
let (immutable, maybe_mutable, maybe_clone, maybe_deref) =
if let Some(ident) = field.ident.as_ref() {
let ident_ref = format_ident!("{}_ref", ident);
let ident_mut = format_ident!("{}_mut", ident);
let ident_clone = format_ident!("{}_clone", ident);
let ident_deref = format_ident!("{}_deref", ident);
(
immutable
.then_some(quote! {
#[inline(always)]
pub fn #ident_ref(&self) -> &#ty {
&self.#ident
}
})
.unwrap_or_default(),
mutable
.then_some(quote! {
#[inline(always)]
pub fn #ident_mut(&mut self) -> &mut #ty {
&mut self.#ident
}
})
.unwrap_or_default(),
clone
.then_some(quote! {
#[inline(always)]
pub fn #ident_clone(&self) -> #ty {
self.#ident.clone()
}
})
.unwrap_or_default(),
deref
.then_some(quote! {
#[inline(always)]
pub fn #ident_deref(&self) -> #ty {
self.#ident
}
})
.unwrap_or_default(),
)
} else {
let name = method_name(index, max);
let name_ref = format_ident!("{}_ref", name);
let name_mut = format_ident!("{}_mut", name);
let name_clone = format_ident!("{}_clone", name);
let name_deref = format_ident!("{}_deref", name);
let index = Member::Unnamed(Index {
index: index as u32,
span: Span::call_site(),
});
(
immutable
.then_some(quote! {
#[inline(always)]
pub fn #name_ref(&self) -> &#ty {
&self.#index
}
})
.unwrap_or_default(),
mutable
.then_some(quote! {
#[inline(always)]
pub fn #name_mut(&mut self) -> &mut #ty {
&mut self.#index
}
})
.unwrap_or_default(),
clone
.then_some(quote! {
#[inline(always)]
pub fn #name_clone(&self) -> #ty {
self.#index.clone()
}
})
.unwrap_or_default(),
deref
.then_some(quote! {
#[inline(always)]
pub fn #name_deref(&self) -> #ty {
self.#index
}
})
.unwrap_or_default(),
)
};
quote! {
#immutable
#maybe_mutable
#maybe_clone
#maybe_deref
}
}
#[allow(clippy::too_many_arguments)]
fn method_variant(
&self,
field: &GettersField,
index: usize,
max: usize,
enum_ident: &Ident,
variant_ident: &Ident,
skip: bool,
skip_mutable: bool,
skip_clone: bool,
skip_deref: bool,
) -> TokenStream2 {
let ty = &field.ty;
let immutable = !field.skip.is_present() && !skip;
let mutable = (field.mutable.is_present() || self.mutable.is_present())
&& !field.skip_mutable.is_present()
&& !skip_mutable;
let clone = (field.clone.is_present() || self.clone.is_present())
&& !field.skip_clone.is_present()
&& !skip_clone;
let deref = (field.deref.is_present() || self.deref.is_present())
&& !field.skip_deref.is_present()
&& !skip_deref;
let prefix = variant_ident.to_string().to_ascii_lowercase();
let (immutable, maybe_mutable, maybe_clone, maybe_deref) =
if let Some(ident) = field.ident.as_ref() {
let ident_ref = format_ident!("{}_{}_ref", prefix, ident);
let ident_mut = format_ident!("{}_{}_mut", prefix, ident);
let ident_clone = format_ident!("{}_{}_clone", prefix, ident);
let ident_deref = format_ident!("{}_{}_deref", prefix, ident);
(
immutable
.then_some(quote! {
#[inline(always)]
pub fn #ident_ref(&self) -> Option<&#ty> {
if let #enum_ident::#variant_ident { #ident, .. } = self {
Some(#ident)
} else {
None
}
}
})
.unwrap_or_default(),
mutable
.then_some(quote! {
#[inline(always)]
pub fn #ident_mut(&mut self) -> Option<&mut #ty> {
if let #enum_ident::#variant_ident { ref mut #ident, .. } = self {
Some(#ident)
} else {
None
}
}
})
.unwrap_or_default(),
clone
.then_some(quote! {
#[inline(always)]
pub fn #ident_clone(&self) -> Option<#ty> {
if let #enum_ident::#variant_ident { #ident, .. } = self {
Some(#ident.clone())
} else {
None
}
}
})
.unwrap_or_default(),
deref
.then_some(quote! {
#[inline(always)]
pub fn #ident_deref(&self) -> Option<#ty> {
if let #enum_ident::#variant_ident { #ident, .. } = self {
Some(*#ident)
} else {
None
}
}
})
.unwrap_or_default(),
)
} else {
let name = method_name(index, max);
let name_ref = format_ident!("{}_{}_ref", prefix, name);
let name_mut = format_ident!("{}_{}_mut", prefix, name);
let name_clone = format_ident!("{}_{}_clone", prefix, name);
let name_deref = format_ident!("{}_{}_deref", prefix, name);
let elements = tuple_elements(index, max);
let elements_mut = tuple_elements_mut(index, max);
let element = tuple_element_name(index);
(
immutable
.then_some(quote! {
#[inline(always)]
pub fn #name_ref(&self) -> Option<&#ty> {
if let #enum_ident::#variant_ident(#elements) = self {
Some(#element)
} else {
None
}
}
})
.unwrap_or_default(),
mutable
.then_some(quote! {
#[inline(always)]
pub fn #name_mut(&mut self) -> Option<&mut #ty> {
if let #enum_ident::#variant_ident(#elements_mut) = self {
Some(#element)
} else {
None
}
}
})
.unwrap_or_default(),
clone
.then_some(quote! {
#[inline(always)]
pub fn #name_clone(&self) -> Option<#ty> {
if let #enum_ident::#variant_ident(#elements) = self {
Some(#element.clone())
} else {
None
}
}
})
.unwrap_or_default(),
deref
.then_some(quote! {
#[inline(always)]
pub fn #name_deref(&self) -> Option<#ty> {
if let #enum_ident::#variant_ident(#elements) = self {
Some(*#element)
} else {
None
}
}
})
.unwrap_or_default(),
)
};
quote! {
#immutable
#maybe_mutable
#maybe_clone
#maybe_deref
}
}
fn methods_struct(&self, fields: &Fields<&GettersField>) -> TokenStream2 {
fields
.iter()
.enumerate()
.map(|(i, f)| self.method_field(f, i, fields.len()))
.collect::<TokenStream2>()
}
fn methods_enum(&self, variants: &[&GettersVariant]) -> TokenStream2 {
variants
.iter()
.map(|v| {
let variant_ident = &v.ident;
if let Some(discriminant) = v.discriminant.as_ref() {
abort!(
discriminant,
"Getters cannot be derived for enums with discriminants"
)
}
v.fields
.iter()
.enumerate()
.map(|(i, f)| {
self.method_variant(
f,
i,
v.fields.len(),
&self.ident,
variant_ident,
v.skip.is_present(),
v.skip_mutable.is_present(),
v.skip_clone.is_present(),
v.skip_deref.is_present(),
)
})
.collect::<TokenStream2>()
})
.collect::<TokenStream2>()
}
}
impl ToTokens for GettersInput {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ident = &self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let methods = if let Some(ref fields) = self.data.as_ref().take_struct() {
self.methods_struct(fields)
} else if let Some(ref variants) = self.data.as_ref().take_enum() {
self.methods_enum(variants)
} else {
abort!(
self.ident,
"Getters can only be derived for structs and enums"
)
};
tokens.extend(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
#methods
}
})
}
}
#[proc_macro_derive(Getters, attributes(getters))]
#[allow(non_snake_case)]
pub fn Getters(input: TokenStream) -> TokenStream {
let getters = match GettersInput::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(g) => g,
Err(e) => {
return TokenStream::from(e.write_errors());
}
};
let mut tokens = TokenStream2::new();
getters.to_tokens(&mut tokens);
tokens.into()
}
const NUMERAL_TO_ORDINAL: [&str; 20] = [
"first",
"second",
"third",
"fourth",
"fifth",
"sixth",
"seventh",
"eigth",
"ninth",
"tenth",
"eleventh",
"twelfth",
"thirteenth",
"fourteenth",
"fifteenth",
"sixteenth",
"seventeenth",
"eighteenth",
"nineteenth",
"twentieth",
];
const LAST: &str = "last";
fn method_name(i: usize, max: usize) -> Ident {
if i == max - 1 && max != 1 {
Ident::new(LAST, Span::call_site())
} else {
Ident::new(NUMERAL_TO_ORDINAL[i], Span::call_site())
}
}
const TUPLE_ELEMENTS: [&str; 20] = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t",
];
fn tuple_element_name(index: usize) -> Ident {
Ident::new(TUPLE_ELEMENTS[index], Span::call_site())
}
fn tuple_elements(index: usize, max: usize) -> TokenStream2 {
(0..max)
.map(tuple_element_name)
.enumerate()
.map(|(i, n)| {
let ident = if i == index {
format_ident!("{}", n)
} else {
format_ident!("_{}", n)
};
if i == max - 1 {
quote!(ref #ident)
} else {
quote!(ref #ident,)
}
})
.collect::<TokenStream2>()
}
fn tuple_elements_mut(index: usize, max: usize) -> TokenStream2 {
(0..max)
.map(tuple_element_name)
.enumerate()
.map(|(i, n)| {
let ident = if i == index {
format_ident!("{}", n)
} else {
format_ident!("_{}", n)
};
let maybe_mut = if i == index { quote!(mut) } else { quote!() };
if i == max - 1 {
quote!(ref #maybe_mut #ident)
} else {
quote!(ref #maybe_mut #ident,)
}
})
.collect::<TokenStream2>()
}