use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ Attribute, Field, Fields, FieldsNamed, Generics, Ident, ItemStruct, LitStr, Meta, }; #[derive(Default)] enum Visibility { #[default] Inherited, Pub, } impl ToTokens for Visibility { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Visibility::Pub => tokens.append_all(quote! { pub }), Visibility::Inherited => (), } } } #[derive(Default)] struct GetOptions { visibility: Visibility, rename: Option, } impl GetOptions { fn from_attr(attr: &Attribute) -> syn::Result { let mut out = Self::default(); attr.parse_nested_meta(|nm| { if nm.path.is_ident("pub") { out.visibility = Visibility::Pub; } if nm.path.is_ident("rename") { let name = nm.value()?; let name: LitStr = name.parse()?; out.rename = Some(name.value()); } Ok(()) })?; Ok(out) } } fn named_fields( name: Ident, generics: Generics, n: FieldsNamed, ) -> TokenStream { let (ipl, ty, _) = generics.split_for_impl(); n.named .into_iter() .flat_map(|field| { let field_name = field.ident.as_ref().expect("named should have ident"); let field_ty = &field.ty; fn options_for( attr_name: &str, field: &Field, ) -> Option> { field.attrs.iter().find_map(|a| match &a.meta { Meta::List(list) => list .path .get_ident() .filter(|i| *i == attr_name) .map(|_| GetOptions::from_attr(a)), Meta::Path(p) => p .get_ident() .filter(|i| *i == attr_name) .map(|_| Ok(GetOptions::default())), _ => None, }) } let get = options_for("get", &field); fn output_for( options: Option>, field_name: &Ident, f: impl FnOnce(Ident, Visibility) -> TokenStream, ) -> Option { match options { Some(Ok(get)) => { let GetOptions { visibility, rename, .. } = get; let fn_name = if let Some(rename) = rename { format_ident!("{}", rename) } else { field_name.clone() }; Some(f(fn_name, visibility)) }, Some(Err(e)) => Some(e.into_compile_error()), _ => None, } } let get_out = output_for(get, field_name, |fn_name, vis| { quote! { impl #ipl #name #ty { #vis fn #fn_name(&self) -> #field_ty { self.#field_name } } } }); let get_ref = options_for("get_ref", &field); let get_ref_out = output_for(get_ref, field_name, |fn_name, vis| { quote! { impl #ipl #name #ty { #vis fn #fn_name(&self) -> &#field_ty { &self.#field_name } } } }); let get_mut = options_for("get_mut", &field); let get_mut_renamed = get_mut .as_ref() .is_some_and(|r| r.as_ref().is_ok_and(|o| o.rename.is_some())); let get_mut_out = output_for(get_mut, field_name, |fn_name, vis| { let fn_name = if get_mut_renamed { fn_name } else { format_ident!("{}_mut", fn_name) }; quote! { impl #ipl #name #ty { #vis fn #fn_name(&mut self) -> &mut #field_ty { &mut self.#field_name } } } }); get_out.into_iter().chain(get_ref_out).chain(get_mut_out) }) .collect() } pub fn e_easy_get_core(input: TokenStream) -> TokenStream { let item: ItemStruct = match syn::parse2(input) { Ok(i) => i, Err(e) => return e.into_compile_error(), }; match item.fields { Fields::Named(n) => named_fields(item.ident, item.generics, n), _ => todo!("aaaaaa"), } } #[cfg(test)] mod test { use proc_macro2::TokenStream; use quote::quote; use crate::e_easy_get_core; fn expect_output(input: TokenStream, output: TokenStream) { assert_eq!(e_easy_get_core(input).to_string(), output.to_string()) } #[test] fn test_get_pub() { expect_output( quote! { #[derive(EasyGet)] pub struct Foo { #[get(pub)] bar: usize, } }, quote! { impl Foo { pub fn bar(&self) -> usize { self.bar } } }, ); } #[test] fn test_get_generic() { expect_output( quote! { #[derive(EasyGet)] pub struct Foo { #[get(pub)] bar: T, } }, quote! { impl Foo { pub fn bar(&self) -> T { self.bar } } }, ); } #[test] fn test_get_ref_generic_default() { expect_output( quote! { #[derive(EasyGet)] pub struct Foo { #[get_ref(pub)] bar: T, } }, quote! { impl Foo { pub fn bar(&self) -> &T { &self.bar } } }, ); } #[test] fn test_rename() { expect_output( quote! { #[derive(EasyGet)] pub struct Foo { #[get_ref(pub, rename = "baz")] bar: T, } }, quote! { impl Foo { pub fn baz(&self) -> &T { &self.bar } } }, ) } #[test] fn test_get_mut() { expect_output( quote! { #[derive(EasyGet)] pub struct Foo { #[get_mut(pub)] bar: T, } }, quote! { impl Foo { pub fn bar_mut(&mut self) -> &mut T { &mut self.bar } } }, ) } }