diff --git a/.gitignore b/.gitignore index 6aa1064..4b8ad7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target/ **/*.rs.bk Cargo.lock +*.swp diff --git a/Cargo.toml b/Cargo.toml index 38285e6..bedec54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ Getset, we're ready to go! A procedural macro for generating the most basic getters and setters on fields. """ -version = "0.1.2" +version = "0.1.3" authors = ["Ana Hobden ", "John Baublitz , meta_name: &str) -> Option { - match attr { - // `#[get = "pub"]` or `#[set = "pub"]` - Some(Meta::NameValue(MetaNameValue { - lit: Lit::Str(ref s), - path, - .. - })) => { - if path.is_ident(meta_name) { - s.value().split(' ').find(|v| *v != "with_prefix").map(|v| { - syn::parse_str(v) - .map_err(|e| syn::Error::new(s.span(), e)) - .expect_or_abort("invalid visibility found") - }) - } else { - None - } +// Helper function to extract string from Expr +fn expr_to_string(expr: &Expr) -> Option { + if let Expr::Lit(expr_lit) = expr { + if let Lit::Str(s) = &expr_lit.lit { + Some(s.value()) + } else { + None } - _ => None, + } else { + None + } +} + +// Helper function to parse visibility +fn parse_vis_str(s: &str, span: proc_macro2::Span) -> Visibility { + match syn::parse_str(s) { + Ok(vis) => vis, + Err(e) => abort!(span, "Invalid visibility found: {}", e), + } +} + +// Helper function to parse visibility attribute +pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option { + let meta = attr?; + let Meta::NameValue(MetaNameValue { value, path, .. }) = meta else { + return None; + }; + + if !path.is_ident(meta_name) { + return None; } + + let value_str = expr_to_string(value)?; + let vis_str = value_str.split(' ').find(|v| *v != "with_prefix")?; + + Some(parse_vis_str(vis_str, value.span())) } -/// Some users want legacy/compatability. +/// Some users want legacy/compatibility. /// (Getters are often prefixed with `get_`) fn has_prefix_attr(f: &Field, params: &GenParams) -> bool { - let inner = f - .attrs - .iter() - .filter_map(|v| parse_attr(v, params.mode)) - .filter(|meta| { - ["get", "get_copy"] - .iter() - .any(|ident| meta.path().is_ident(ident)) - }) - .last(); - - // Check it the attr includes `with_prefix` - let wants_prefix = |possible_meta: &Option| -> bool { - match possible_meta { - Some(Meta::NameValue(meta)) => { - if let Lit::Str(lit_str) = &meta.lit { - // Naive tokenization to avoid a possible visibility mod named `with_prefix`. - lit_str.value().split(' ').any(|v| v == "with_prefix") - } else { - false - } + // helper function to check if meta has `with_prefix` attribute + let meta_has_prefix = |meta: &Meta| -> bool { + if let Meta::NameValue(name_value) = meta { + if let Some(s) = expr_to_string(&name_value.value) { + return s.split(" ").any(|v| v == "with_prefix"); } - _ => false, } + false }; - // `with_prefix` can either be on the local or global attr - wants_prefix(&inner) || wants_prefix(¶ms.global_attr) + let field_attr_has_prefix = f + .attrs + .iter() + .filter_map(|attr| parse_attr(attr, params.mode)) + .find(|meta| meta.path().is_ident("get") || meta.path().is_ident("get_copy")) + .as_ref() + .map_or(false, meta_has_prefix); + + let global_attr_has_prefix = params.global_attr.as_ref().map_or(false, meta_has_prefix); + + field_attr_has_prefix || global_attr_has_prefix } pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { let field_name = field - .clone() .ident + .clone() .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name")); let fn_name = if !has_prefix_attr(field, params) @@ -136,11 +146,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { }; let ty = field.ty.clone(); - let doc = field.attrs.iter().filter(|v| { - v.parse_meta() - .map(|meta| meta.path().is_ident("doc")) - .unwrap_or(false) - }); + let doc = field.attrs.iter().filter(|v| v.meta.path().is_ident("doc")); let attr = field .attrs @@ -151,7 +157,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { let visibility = parse_visibility(attr.as_ref(), params.mode.name()); match attr { - // Generate nothing for skipped field. + // Generate nothing for skipped field Some(meta) if meta.path().is_ident("skip") => quote! {}, Some(_) => match params.mode { GenMode::Get => { @@ -192,7 +198,6 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { } } }, - // Don't need to do anything. None => quote! {}, } } diff --git a/src/lib.rs b/src/lib.rs index faf8c08..d090868 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,101 +175,81 @@ impl Foo { ``` */ -extern crate proc_macro; -extern crate syn; #[macro_use] extern crate quote; -extern crate proc_macro2; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt}; -use syn::{spanned::Spanned, DataStruct, DeriveInput, Meta}; +use proc_macro_error2::{abort, abort_call_site, proc_macro_error}; +use syn::{parse_macro_input, spanned::Spanned, DataStruct, DeriveInput, Meta}; -mod generate; use crate::generate::{GenMode, GenParams}; +mod generate; + #[proc_macro_derive(Getters, attributes(get, with_prefix, getset))] #[proc_macro_error] pub fn getters(input: TokenStream) -> TokenStream { - // Parse the string representation - let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); + let ast = parse_macro_input!(input as DeriveInput); let params = GenParams { mode: GenMode::Get, global_attr: parse_global_attr(&ast.attrs, GenMode::Get), }; - // Build the impl - let gen = produce(&ast, ¶ms); - - // Return the generated impl - gen.into() + produce(&ast, ¶ms).into() } #[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix, getset))] #[proc_macro_error] pub fn copy_getters(input: TokenStream) -> TokenStream { - // Parse the string representation - let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); + let ast = parse_macro_input!(input as DeriveInput); let params = GenParams { mode: GenMode::GetCopy, global_attr: parse_global_attr(&ast.attrs, GenMode::GetCopy), }; - // Build the impl - let gen = produce(&ast, ¶ms); - - // Return the generated impl - gen.into() + produce(&ast, ¶ms).into() } #[proc_macro_derive(MutGetters, attributes(get_mut, getset))] #[proc_macro_error] pub fn mut_getters(input: TokenStream) -> TokenStream { - // Parse the string representation - let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); + let ast = parse_macro_input!(input as DeriveInput); let params = GenParams { mode: GenMode::GetMut, global_attr: parse_global_attr(&ast.attrs, GenMode::GetMut), }; - // Build the impl - let gen = produce(&ast, ¶ms); - // Return the generated impl - gen.into() + produce(&ast, ¶ms).into() } #[proc_macro_derive(Setters, attributes(set, getset))] #[proc_macro_error] pub fn setters(input: TokenStream) -> TokenStream { - // Parse the string representation - let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for setters"); + let ast = parse_macro_input!(input as DeriveInput); let params = GenParams { mode: GenMode::Set, global_attr: parse_global_attr(&ast.attrs, GenMode::Set), }; - // Build the impl - let gen = produce(&ast, ¶ms); - - // Return the generated impl - gen.into() + produce(&ast, ¶ms).into() } fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option { - attrs - .iter() - .filter_map(|v| parse_attr(v, mode)) // non "meta" attributes are not our concern - .last() + attrs.iter().filter_map(|v| parse_attr(v, mode)).last() } -fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { +fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { use syn::{punctuated::Punctuated, Token}; - if attr.path.is_ident("getset") { - let (last, skip, mut collected) = attr - .parse_args_with(Punctuated::::parse_terminated) - .unwrap_or_abort() + if attr.path().is_ident("getset") { + let meta_list = + match attr.parse_args_with(Punctuated::::parse_terminated) { + Ok(list) => list, + Err(e) => abort!(attr.span(), "Failed to parse getset attribute: {}", e), + }; + + let (last, skip, mut collected) = meta_list .into_iter() .inspect(|meta| { if !(meta.path().is_ident("get") @@ -289,8 +269,6 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { } else if meta.path().is_ident("skip") { (last, Some(meta), collected) } else { - // Store inapplicable item for potential error message - // if used with skip. collected.push(meta); (last, skip, collected) } @@ -309,14 +287,14 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { ); } } else { - // If skip is not used, return the last occurrence of matching - // setter/getter, if there is any. last } + } else if attr.path().is_ident(mode.name()) { + // If skip is not used, return the last occurrence of matching + // setter/getter, if there is any. + attr.meta.clone().into() } else { - attr.parse_meta() - .ok() - .filter(|meta| meta.path().is_ident(mode.name())) + None } }