use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, parse_quote, parse_str, punctuated::Punctuated, token::Comma, Attribute,
GenericArgument, GenericParam, Ident, Item, ItemStruct, Meta, Type, Visibility, WhereClause,
};
const HOLDER_SUFFIX: &'static str = "Holder";
struct ItemEnumOrStruct {
ident: Ident,
generic_params: Punctuated<GenericParam, Comma>,
where_clause: Option<WhereClause>,
vis: Visibility,
}
#[proc_macro_derive(Holdable)]
pub fn holder_derive(input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as Item);
let item = match item {
Item::Enum(value) => ItemEnumOrStruct {
ident: value.ident,
generic_params: value.generics.params,
where_clause: value.generics.where_clause,
vis: value.vis,
},
Item::Struct(value) => ItemEnumOrStruct {
ident: value.ident,
generic_params: value.generics.params,
where_clause: value.generics.where_clause,
vis: value.vis,
},
_ => panic!("unimplemented item type"),
};
let struct_name = &item.ident;
let struct_generic = item.generic_params;
let struct_where_clause = item.where_clause;
let struct_visibility = item.vis;
let mut struct_generic_without_bounds = struct_generic.clone();
remove_bounds_from_generic(&mut struct_generic_without_bounds);
let holder_trait_name = format_ident!("{}{HOLDER_SUFFIX}", struct_name);
let fn_name = struct_name.to_string().clone().to_case(Case::Snake);
let mut_fn_name = format!("{}_mut", fn_name);
let fn_name: Ident = parse_str(fn_name.as_str()).unwrap();
let mut_fn_name: Ident = parse_str(mut_fn_name.as_str()).unwrap();
#[cfg(feature = "fast_delegate")]
let attr: Attribute = parse_quote!(#[fast_delegate::delegate]);
#[cfg(not(feature = "fast_delegate"))]
let attr: Option<Attribute> = None;
quote! {
#attr
#struct_visibility trait #holder_trait_name<#struct_generic> #struct_where_clause {
fn #fn_name<'__a: '__b, '__b>(&'__a self) -> &'__b #struct_name<#struct_generic_without_bounds>;
fn #mut_fn_name<'__a: '__b, '__b>(&'__a mut self) -> &'__b mut #struct_name<#struct_generic_without_bounds>;
}
}
.into()
}
#[proc_macro_derive(Holder, attributes(hold))]
pub fn holder(input: TokenStream) -> TokenStream {
let item_struct = parse_macro_input!(input as ItemStruct);
let struct_name = &item_struct.ident;
let struct_generic = &item_struct.generics.params;
let mut struct_generic_without_bounds = struct_generic.clone();
remove_bounds_from_generic(&mut struct_generic_without_bounds);
let struct_where_clause = &item_struct.generics.where_clause;
let quotes: Vec<_> = item_struct
.fields
.iter()
.filter_map(|field| {
let mut holder_trait_name_in_attr: Option<Ident> = None;
let is_holdable_field = field.attrs.iter().any(|attr| match &attr.meta {
Meta::List(list) => {
if list.path.is_ident("hold") {
holder_trait_name_in_attr = attr.parse_args().unwrap();
true
} else {
false
}
},
Meta::Path(path) => path.is_ident("hold"),
_ => panic!("unimplemented attr meta type"),
});
if !is_holdable_field {
return Option::<proc_macro2::TokenStream>::None;
}
let field_name = field
.ident
.clone()
.expect("unimplemented non field name case");
let field_type_ident = get_ident_by_type(&field.ty);
let field_type = &field.ty;
let holder_trait_name = holder_trait_name_in_attr
.or_else(|| Some(parse_str(format!("{}{HOLDER_SUFFIX}", field_type_ident).as_str()).unwrap())).unwrap();
let mut type_name = holder_trait_name.clone().to_string();
type_name.truncate(holder_trait_name.to_string().len() - HOLDER_SUFFIX.len());
let fn_name = type_name.to_case(Case::Snake);
let field_type_name: Ident = parse_str(type_name.as_str()).unwrap();
let fn_name: Ident = parse_str(fn_name.as_str()).unwrap();
let mut_fn_name = format!("{}_mut", fn_name.to_string());
let mut_fn_name: Ident = parse_str(mut_fn_name.as_str()).unwrap();
let field_bounds = get_generic_by_type(field_type);
Some(
quote! {
impl<#struct_generic>
#holder_trait_name<#field_bounds> for #struct_name<#struct_generic_without_bounds> #struct_where_clause {
fn #fn_name<'__a: '__b, '__b>(&'__a self) -> &'__b #field_type_name<#field_bounds> {
&self.#field_name
}
fn #mut_fn_name<'__a: '__b, '__b>(&'__a mut self) -> &'__b mut #field_type_name<#field_bounds> {
&mut self.#field_name
}
}
}
.into(),
)
})
.collect();
quote! {#(#quotes)*}.into()
}
fn get_ident_by_type(ty: &Type) -> Ident {
match ty {
Type::Path(value) => value.path.segments.last().unwrap().ident.clone(),
Type::Reference(value) => get_ident_by_type(&*value.elem),
_ => panic!("unimplemented field type"),
}
}
fn get_generic_by_type(ty: &Type) -> Option<Punctuated<GenericArgument, Comma>> {
match ty {
Type::Path(value) => match &value.path.segments.last().unwrap().arguments {
syn::PathArguments::None => None,
syn::PathArguments::AngleBracketed(value) => Some(value.args.clone()),
syn::PathArguments::Parenthesized(_) => None,
},
Type::Reference(value) => get_generic_by_type(&*value.elem),
_ => panic!("unimplemented field type"),
}
}
fn remove_bounds_from_generic(generic: &mut Punctuated<GenericParam, Comma>) {
for struct_generic in generic.iter_mut() {
match struct_generic {
syn::GenericParam::Lifetime(lifetime) => {
lifetime.bounds.clear();
}
syn::GenericParam::Type(lifetime) => {
lifetime.bounds.clear();
}
_ => {}
}
}
}