diff --git a/Cargo.toml b/Cargo.toml index 3da36c5..20a1c5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ proc-macro = true quote = "1" syn = "1" proc-macro2 = { version = "1", default-features = false } +proc-macro-error = "0.4" diff --git a/src/generate.rs b/src/generate.rs index e5da6da..44a6f4d 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,15 +1,17 @@ use proc_macro2::TokenStream as TokenStream2; use proc_macro2::{Ident, Span}; -use syn::{self, Field, Lit, Meta, MetaNameValue, Visibility}; +use proc_macro_error::{abort, ResultExt}; +use syn::{self, spanned::Spanned, Field, Lit, Meta, MetaNameValue, Visibility}; + +use self::GenMode::*; +use super::parse_attr; pub struct GenParams { - pub attribute_name: &'static str, - pub fn_name_prefix: &'static str, - pub fn_name_suffix: &'static str, + pub mode: GenMode, pub global_attr: Option, } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Copy, Clone)] pub enum GenMode { Get, GetCopy, @@ -18,7 +20,30 @@ pub enum GenMode { } impl GenMode { - fn is_get(&self) -> bool { + pub fn name(self) -> &'static str { + match self { + Get => "get", + GetCopy => "get_copy", + Set => "set", + GetMut => "get_mut", + } + } + + pub fn prefix(self) -> &'static str { + match self { + Get | GetCopy | GetMut => "", + Set => "set_", + } + } + + pub fn suffix(self) -> &'static str { + match self { + Get | GetCopy | Set => "", + GetMut => "_mut", + } + } + + fn is_get(self) -> bool { match self { GenMode::Get | GenMode::GetCopy | GenMode::GetMut => true, GenMode::Set => false, @@ -35,10 +60,11 @@ pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option { if path.is_ident(meta_name) { - s.value() - .split(' ') - .find(|v| *v != "with_prefix") - .map(|v| syn::parse(v.parse().unwrap()).expect("invalid visibility found")) + 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 } @@ -49,20 +75,15 @@ pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option bool { +fn has_prefix_attr(f: &Field, mode: GenMode) -> bool { let inner = f .attrs .iter() - .filter_map(|v| { - let meta = v.parse_meta().expect("Could not get attribute"); - if ["get", "get_copy"] + .filter_map(|v| parse_attr(v, mode)) + .filter(|meta| { + ["get", "get_copy"] .iter() .any(|ident| meta.path().is_ident(ident)) - { - Some(meta) - } else { - None - } }) .last(); match inner { @@ -78,49 +99,44 @@ fn has_prefix_attr(f: &Field) -> bool { } } -pub fn implement(field: &Field, mode: &GenMode, params: &GenParams) -> TokenStream2 { +pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { let field_name = field .clone() .ident - .expect("Expected the field to have a name"); + .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name")); let fn_name = Ident::new( &format!( "{}{}{}{}", - if has_prefix_attr(field) && (mode.is_get()) { + if has_prefix_attr(field, params.mode) && (params.mode.is_get()) { "get_" } else { "" }, - params.fn_name_prefix, + params.mode.prefix(), field_name, - params.fn_name_suffix + params.mode.suffix() ), Span::call_site(), ); let ty = field.ty.clone(); - let mut doc = Vec::new(); + let doc = field.attrs.iter().filter(|v| { + v.parse_meta() + .map(|meta| meta.path().is_ident("doc")) + .unwrap_or(false) + }); + let attr = field .attrs .iter() - .filter_map(|v| { - let meta = v.parse_meta().expect("attribute"); - if meta.path().is_ident("doc") { - doc.push(v); - None - } else if meta.path().is_ident(params.attribute_name) { - Some(meta) - } else { - None - } - }) + .filter_map(|v| parse_attr(v, params.mode)) .last() .or_else(|| params.global_attr.clone()); - let visibility = parse_visibility(attr.as_ref(), params.attribute_name); + let visibility = parse_visibility(attr.as_ref(), params.mode.name()); match attr { - Some(_) => match mode { + Some(_) => match params.mode { GenMode::Get => { quote! { #(#doc)* diff --git a/src/lib.rs b/src/lib.rs index eca7881..0c5e47e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,107 +148,119 @@ extern crate proc_macro2; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use syn::{DataStruct, DeriveInput, Meta}; +use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt}; +use syn::{spanned::Spanned, DataStruct, DeriveInput, Meta}; mod generate; use crate::generate::{GenMode, GenParams}; -#[proc_macro_derive(Getters, attributes(get, with_prefix))] +#[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("Couldn't parse for getters"); + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); let params = GenParams { - attribute_name: "get", - fn_name_prefix: "", - fn_name_suffix: "", - global_attr: parse_global_attr(&ast.attrs, "get"), + mode: GenMode::Get, + global_attr: parse_global_attr(&ast.attrs, GenMode::Get), }; // Build the impl - let gen = produce(&ast, &GenMode::Get, ¶ms); + let gen = produce(&ast, ¶ms); // Return the generated impl gen.into() } -#[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix))] +#[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("Couldn't parse for getters"); + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); let params = GenParams { - attribute_name: "get_copy", - fn_name_prefix: "", - fn_name_suffix: "", - global_attr: parse_global_attr(&ast.attrs, "get_copy"), + mode: GenMode::GetCopy, + global_attr: parse_global_attr(&ast.attrs, GenMode::GetCopy), }; // Build the impl - let gen = produce(&ast, &GenMode::GetCopy, ¶ms); + let gen = produce(&ast, ¶ms); // Return the generated impl gen.into() } -#[proc_macro_derive(MutGetters, attributes(get_mut))] +#[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("Couldn't parse for getters"); + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); let params = GenParams { - attribute_name: "get_mut", - fn_name_prefix: "", - fn_name_suffix: "_mut", - global_attr: parse_global_attr(&ast.attrs, "get_mut"), + mode: GenMode::GetMut, + global_attr: parse_global_attr(&ast.attrs, GenMode::GetMut), }; // Build the impl - let gen = produce(&ast, &GenMode::GetMut, ¶ms); + let gen = produce(&ast, ¶ms); // Return the generated impl gen.into() } -#[proc_macro_derive(Setters, attributes(set))] +#[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("Couldn't parse for setters"); + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for setters"); let params = GenParams { - attribute_name: "set", - fn_name_prefix: "set_", - fn_name_suffix: "", - global_attr: parse_global_attr(&ast.attrs, "set"), + mode: GenMode::Set, + global_attr: parse_global_attr(&ast.attrs, GenMode::Set), }; // Build the impl - let gen = produce(&ast, &GenMode::Set, ¶ms); + let gen = produce(&ast, ¶ms); // Return the generated impl gen.into() } -fn parse_global_attr(attrs: &[syn::Attribute], attribute_name: &str) -> Option { +fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option { attrs .iter() - .filter_map(|v| { - let meta = v.parse_meta().expect("attribute"); - if meta.path().is_ident(attribute_name) { - Some(meta) - } else { - None - } - }) + .filter_map(|v| parse_attr(v, mode)) // non "meta" attributes are not our concern .last() } -fn produce(ast: &DeriveInput, mode: &GenMode, params: &GenParams) -> TokenStream2 { +fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { + use syn::{punctuated::Punctuated, Token}; + + if attr.path.is_ident("getset") { + attr.parse_args_with(Punctuated::::parse_terminated) + .unwrap_or_abort() + .into_iter() + .inspect(|meta| { + if !(meta.path().is_ident("get") + || meta.path().is_ident("get_copy") + || meta.path().is_ident("get_mut") + || meta.path().is_ident("set")) + { + abort!(meta.path().span(), "unknown setter or getter") + } + }) + .filter(|meta| meta.path().is_ident(mode.name())) + .last() + } else { + attr.parse_meta() + .ok() + .filter(|meta| meta.path().is_ident(mode.name())) + } +} + +fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { let name = &ast.ident; let generics = &ast.generics; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); // Is it a struct? if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data { - let generated = fields - .iter() - .map(|f| generate::implement(f, mode, params)) - .collect::>(); + let generated = fields.iter().map(|f| generate::implement(f, params)); quote! { impl #impl_generics #name #ty_generics #where_clause { @@ -257,6 +269,6 @@ fn produce(ast: &DeriveInput, mode: &GenMode, params: &GenParams) -> TokenStream } } else { // Nope. This is an Enum. We cannot handle these! - panic!("#[derive(Getters)] is only defined for structs, not for enums!"); + abort_call_site!("#[derive(Getters)] is only defined for structs, not for enums!"); } } diff --git a/tests/getset.rs b/tests/getset.rs new file mode 100644 index 0000000..9985078 --- /dev/null +++ b/tests/getset.rs @@ -0,0 +1,121 @@ +#[macro_use] +extern crate getset; + +use crate::submodule::other::{Generic, Plain, Where}; + +// For testing `pub(super)` +mod submodule { + // For testing `pub(in super::other)` + pub mod other { + #[derive(MutGetters, Getters, Default)] + #[getset(get_mut)] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[getset(get = "pub", get_mut = "pub")] + public_accessible: usize, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(MutGetters, Getters, Default)] + #[getset(get_mut)] + pub struct Generic { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[getset(get = "pub", get_mut = "pub")] + public_accessible: T, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(MutGetters, Getters, Default)] + #[getset(get_mut)] + pub struct Where + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[getset(get = "pub", get_mut = "pub")] + public_accessible: T, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let mut val = Plain::default(); + (*val.private_accessible_mut()) += 1; + } + + #[test] + fn test_generic() { + let mut val = Generic::::default(); + (*val.private_accessible_mut()) += 1; + } + + #[test] + fn test_where() { + let mut val = Where::::default(); + (*val.private_accessible_mut()) += 1; + } + } +} + +#[test] +fn test_plain() { + let mut val = Plain::default(); + let _ = val.public_accessible(); + (*val.public_accessible_mut()) += 1; +} + +#[test] +fn test_generic() { + let mut val = Generic::::default(); + let _ = val.public_accessible(); + (*val.public_accessible_mut()) += 1; +} + +#[test] +fn test_where() { + let mut val = Where::::default(); + let _ = val.public_accessible(); + (*val.public_accessible_mut()) += 1; +}