use crate::parse_attr;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use proc_macro_error2::abort;
use quote::quote;
use std::collections::HashSet;
use syn::{self, ext::IdentExt, spanned::Spanned, token::Const, Field, Visibility};
use self::GenMode::{GetCopy, GetMut, GetRef, Set, SetWith};
#[derive(Clone)]
pub struct GenParams {
pub mode: GenMode,
pub vis: Option<Visibility>,
pub is_const: Option<bool>,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub enum GenMode {
GetRef,
GetCopy,
GetMut,
Set,
SetWith,
}
impl GenMode {
pub fn list() -> [GenMode; 5] {
[
GenMode::GetRef,
GenMode::GetCopy,
GenMode::GetMut,
GenMode::Set,
GenMode::SetWith,
]
}
pub fn prefix(self) -> &'static str {
match self {
GetRef | GetCopy | GetMut => "",
Set => "set_",
SetWith => "with_",
}
}
pub fn suffix(self) -> &'static str {
match self {
GetRef | GetCopy | Set | SetWith => "",
GetMut => "_mut",
}
}
}
pub fn implement(field: &Field, global_params: &[GenParams]) -> TokenStream2 {
let mut ts = TokenStream2::new();
let (mut params_list, skip_list) = if let Some(attr) = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("getset2"))
{
parse_attr(attr)
} else {
(global_params.to_vec(), HashSet::new())
};
let had_ref_copy = params_list
.iter()
.any(|p| matches!(p.mode, GenMode::GetRef | GenMode::GetCopy));
for params in global_params {
if (!skip_list.contains(¶ms.mode) && params_list.iter().all(|p| p.mode != params.mode))
&& (!had_ref_copy || !matches!(params.mode, GenMode::GetRef | GenMode::GetCopy))
{
params_list.push(params.clone());
}
}
for mut params in params_list {
params.vis = params.vis.or_else(|| Some(field.vis.clone()));
ts.extend(gen_method(field, params));
}
ts
}
pub fn gen_method(field: &Field, params: GenParams) -> TokenStream2 {
let field_name = field
.ident
.clone()
.unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name"));
let fn_name = if params.mode.prefix().is_empty() && params.mode.suffix().is_empty() {
field_name.clone()
} else {
Ident::new(
&format!(
"{}{}{}",
params.mode.prefix(),
field_name.unraw(),
params.mode.suffix()
),
Span::call_site(),
)
};
let ty = field.ty.clone();
let doc = field.attrs.iter().filter(|v| v.meta.path().is_ident("doc"));
let visibility = params.vis;
let const_kw: Option<Const> = params.is_const.and_then(|is_const| {
if is_const {
Some(Const::default())
} else {
None
}
});
match params.mode {
GenMode::GetRef => {
quote! {
#(#doc)*
#[inline(always)]
#visibility #const_kw fn #fn_name(&self) -> &#ty {
&self.#field_name
}
}
}
GenMode::GetCopy => {
quote! {
#(#doc)*
#[inline(always)]
#visibility #const_kw fn #fn_name(&self) -> #ty {
self.#field_name
}
}
}
GenMode::GetMut => {
quote! {
#(#doc)*
#[inline(always)]
#visibility #const_kw fn #fn_name(&mut self) -> &mut #ty {
&mut self.#field_name
}
}
}
GenMode::Set => {
quote! {
#(#doc)*
#[inline(always)]
#visibility #const_kw fn #fn_name(&mut self, val: #ty) -> &mut Self {
self.#field_name = val;
self
}
}
}
GenMode::SetWith => {
quote! {
#(#doc)*
#[inline(always)]
#visibility #const_kw fn #fn_name(mut self, val: #ty) -> Self {
self.#field_name = val;
self
}
}
}
}
}