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;
+}