diff --git a/Cargo.toml b/Cargo.toml index 62dd360..4da61a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,7 @@ appveyor = { repository = "https://github.com/Hoverbear/getset", service = "gith proc-macro = true [dependencies] -quote = "0.3.15" -syn = "0.11.11" +quote = "0.5.2" +syn = "0.13.10" +proc-macro2 = { version = "0.3", default-features = false } +lazy_static = "1.0.0" \ No newline at end of file diff --git a/src/generate.rs b/src/generate.rs new file mode 100644 index 0000000..805dc6c --- /dev/null +++ b/src/generate.rs @@ -0,0 +1,186 @@ +use proc_macro2::{Span, Term}; +use quote::Tokens; +use std::collections::HashSet; +use syn::{Attribute, Field, Ident, Lit, Meta, MetaNameValue, PathSegment, Type, TypePath}; + +pub struct GenParams { + pub attribute_name: &'static str, + pub fn_name_prefix: &'static str, + pub fn_name_suffix: &'static str, +} + +pub enum GenMode { + Get, + Set, + GetMut, +} + +fn attr_name(attr: &Attribute) -> Option { + attr.interpret_meta().map(|v| v.name()) +} + +lazy_static! { + static ref PRIMITIVE_TYPENAMES: HashSet<&'static str> = { + let mut m = HashSet::new(); + macro_rules! add_types { + ($m:ident, types: [$( $Type:ident ),*]) => { + $( + $m.insert(stringify!($Type)); + )* + } + } + add_types!(m, types: [i8, u8, i16, u16, i32, u32, i64, u64, usize, isize, bool, f32, f64]); + m + }; +} + +fn is_type_primitive(ty: &Type) -> bool { + if let Type::Path(TypePath { + qself: ref _qself, + path, + }) = ty + { + if path.segments.len() == 1 { + let seg: &PathSegment = path.segments.first().unwrap().value(); + if PRIMITIVE_TYPENAMES.contains(seg.ident.as_ref()) { + true + } else { + false + } + } else { + false + } + } else { + false + } +} + +pub fn implement(field: &Field, mode: GenMode, params: GenParams) -> Tokens { + let field_name = field + .clone() + .ident + .expect("Expected the field to have a name"); + let type_primitive = is_type_primitive(&field.ty); + let fn_name = Term::new( + &format!( + "{}{}{}", + params.fn_name_prefix, field_name, params.fn_name_suffix + ), + Span::call_site(), + ); + let ty = field.ty.clone(); + + let attr = field + .attrs + .iter() + .filter(|v| attr_name(v).expect("attribute") == params.attribute_name) + .last(); + + let doc = field + .attrs + .iter() + .filter(|v| attr_name(v).expect("attribute") == "doc") + .collect::>(); + + match attr { + Some(attr) => { + match attr.interpret_meta() { + // `#[get]` or `#[set]` + Some(Meta::Word(_)) => match mode { + GenMode::Get => { + quote! { + #(#doc)* + #[inline(always)] + fn #fn_name(&self) -> &#ty { + &self.#field_name + } + } + } + GenMode::Set => { + quote! { + #(#doc)* + #[inline(always)] + fn #fn_name(&mut self, val: #ty) -> &mut Self { + self.#field_name = val; + self + } + } + } + GenMode::GetMut => { + quote! { + #(#doc)* + fn #fn_name(&mut self) -> &mut #ty { + &mut self.#field_name + } + } + } + }, + // `#[get = "pub"]` or `#[set = "pub"]` + Some(Meta::NameValue(MetaNameValue { + lit: Lit::Str(ref s), + .. + })) => { + let visibility = Term::new(&s.value(), s.span()); + match mode { + GenMode::Get => { + if type_primitive { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&self) -> #ty { + self.#field_name + } + } + } else { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&self) -> &#ty { + &self.#field_name + } + } + } + } + GenMode::Set => { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self { + self.#field_name = val; + self + } + } + } + GenMode::GetMut => { + quote! { + #(#doc)* + #visibility fn #fn_name(&mut self) -> &mut #ty { + &mut self.#field_name + } + } + } + } + } + // This currently doesn't work, but it might in the future. + // + // // `#[get(pub)]` + // MetaItem::List(_, ref vec) => { + // let s = vec.iter().last().expect("No item found in attribute list."); + // let visibility = match s { + // &NestedMetaItem::MetaItem(MetaItem::Word(ref i)) => Ident::new(format!("{}", i)), + // &NestedMetaItem::Literal(Lit::Str(ref l, _)) => Ident::from(l.clone()), + // _ => panic!("Unexpected attribute parameters."), + // }; + // quote! { + // #visibility fn #fn_name(&self) -> &#ty { + // &self.#field_name + // } + // } + // }, + _ => panic!("Unexpected attribute parameters."), + } + } + // Don't need to do anything. + None => quote!{}, + } +} diff --git a/src/getters.rs b/src/getters.rs deleted file mode 100644 index b48a722..0000000 --- a/src/getters.rs +++ /dev/null @@ -1,67 +0,0 @@ -use syn::{MetaItem, Lit, Field}; -use quote::{Ident, Tokens}; - -const ATTRIBUTE_NAME: &'static str = "get"; -const FN_NAME_PREFIX: &'static str = ""; -const FN_NAME_SUFFIX: &'static str = ""; - -pub fn implement(field: &Field) -> Tokens { - let field_name = field.clone().ident.expect("Expected the field to have a name"); - let fn_name = Ident::from(format!("{}{}{}", FN_NAME_PREFIX, field_name, FN_NAME_SUFFIX)); - let ty = field.ty.clone(); - - let attr = field.attrs.iter() - .filter(|v| v.name() == ATTRIBUTE_NAME) - .last(); - - let doc = field.attrs.iter() - .filter(|v| v.name() == "doc") - .collect::>(); - - match attr { - Some(attr) => { - match attr.value { - // `#[get]` - MetaItem::Word(_) => { - quote! { - #(#doc)* - #[inline(always)] - fn #fn_name(&self) -> &#ty { - &self.#field_name - } - } - }, - // `#[get = "pub"]` - MetaItem::NameValue(_, Lit::Str(ref s, _)) => { - let visibility = Ident::from(s.clone()); - quote! { - #(#doc)* - #[inline(always)] - #visibility fn #fn_name(&self) -> &#ty { - &self.#field_name - } - } - }, - // This currently doesn't work, but it might in the future. - /// --- - // // `#[get(pub)]` - // MetaItem::List(_, ref vec) => { - // let s = vec.iter().last().expect("No item found in attribute list."); - // let visibility = match s { - // &NestedMetaItem::MetaItem(MetaItem::Word(ref i)) => Ident::new(format!("{}", i)), - // &NestedMetaItem::Literal(Lit::Str(ref l, _)) => Ident::from(l.clone()), - // _ => panic!("Unexpected attribute parameters."), - // }; - // quote! { - // #visibility fn #fn_name(&self) -> &#ty { - // &self.#field_name - // } - // } - // }, - _ => panic!("Unexpected attribute parameters."), - } - }, - // Don't need to do anything. - None => quote! { } - } -} diff --git a/src/lib.rs b/src/lib.rs index f78bca0..3b28562 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,63 +33,83 @@ fn main() { ``` */ - extern crate proc_macro; extern crate syn; #[macro_use] extern crate quote; +extern crate proc_macro2; +#[macro_use] +extern crate lazy_static; use proc_macro::TokenStream; -use syn::{Field, DeriveInput}; use quote::Tokens; +use syn::{DataStruct, DeriveInput, Field}; -mod mut_getters; -mod getters; -mod setters; +mod generate; +use generate::{GenMode, GenParams}; #[proc_macro_derive(Getters, attributes(get))] pub fn getters(input: TokenStream) -> TokenStream { - // Construct a string representation of the type definition - let s = input.to_string(); - // Parse the string representation - let ast = syn::parse_derive_input(&s).expect("Couldn't parse for getters"); + let ast = syn::parse(input).expect("Couldn't parse for getters"); // Build the impl - let gen = produce(&ast, getters::implement); + let gen = produce(&ast, |f| { + generate::implement( + f, + GenMode::Get, + GenParams { + attribute_name: "get", + fn_name_prefix: "", + fn_name_suffix: "", + }, + ) + }); // Return the generated impl - gen.parse().unwrap() + gen.into() } #[proc_macro_derive(MutGetters, attributes(get_mut))] pub fn mut_getters(input: TokenStream) -> TokenStream { - // Construct a string representation of the type definition - let s = input.to_string(); - // Parse the string representation - let ast = syn::parse_derive_input(&s).expect("Couldn't parse for getters"); - + let ast = syn::parse(input).expect("Couldn't parse for getters"); // Build the impl - let gen = produce(&ast, mut_getters::implement); - + let gen = produce(&ast, |f| { + generate::implement( + f, + GenMode::GetMut, + GenParams { + attribute_name: "get_mut", + fn_name_prefix: "", + fn_name_suffix: "_mut", + }, + ) + }); // Return the generated impl - gen.parse().unwrap() + gen.into() } #[proc_macro_derive(Setters, attributes(set))] pub fn setters(input: TokenStream) -> TokenStream { - // Construct a string representation of the type definition - let s = input.to_string(); - // Parse the string representation - let ast = syn::parse_derive_input(&s).expect("Couldn't parse for setters"); + let ast = syn::parse(input).expect("Couldn't parse for setters"); // Build the impl - let gen = produce(&ast, setters::implement); - + let gen = produce(&ast, |f| { + generate::implement( + f, + GenMode::Set, + GenParams { + attribute_name: "set", + fn_name_prefix: "set_", + fn_name_suffix: "", + }, + ) + }); + // Return the generated impl - gen.parse().unwrap() + gen.into() } fn produce(ast: &DeriveInput, worker: fn(&Field) -> Tokens) -> Tokens { @@ -98,8 +118,7 @@ fn produce(ast: &DeriveInput, worker: fn(&Field) -> Tokens) -> Tokens { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); // Is it a struct? - if let syn::Body::Struct(syn::VariantData::Struct(ref fields)) = ast.body { - + if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data { let generated = fields.iter().map(worker).collect::>(); quote! { @@ -111,4 +130,4 @@ fn produce(ast: &DeriveInput, worker: fn(&Field) -> Tokens) -> Tokens { // Nope. This is an Enum. We cannot handle these! panic!("#[derive(Getters)] is only defined for structs, not for enums!"); } -} \ No newline at end of file +} diff --git a/src/mut_getters.rs b/src/mut_getters.rs deleted file mode 100644 index 4ba7371..0000000 --- a/src/mut_getters.rs +++ /dev/null @@ -1,65 +0,0 @@ -use syn::{MetaItem, Lit, Field}; -use quote::{Ident, Tokens}; - -const ATTRIBUTE_NAME: &'static str = "get_mut"; -const FN_NAME_PREFIX: &'static str = ""; -const FN_NAME_SUFFIX: &'static str = "_mut"; - -pub fn implement(field: &Field) -> Tokens { - let field_name = field.clone().ident.expect("Expected the field to have a name"); - let fn_name = Ident::from(format!("{}{}{}", FN_NAME_PREFIX, field_name, FN_NAME_SUFFIX)); - let ty = field.ty.clone(); - - let attr = field.attrs.iter() - .filter(|v| v.name() == ATTRIBUTE_NAME) - .last(); - - let doc = field.attrs.iter() - .filter(|v| v.name() == "doc") - .collect::>(); - - match attr { - Some(attr) => { - match attr.value { - // `#[get]` - MetaItem::Word(_) => { - quote! { - #(#doc)* - fn #fn_name(&mut self) -> &mut #ty { - &mut self.#field_name - } - } - }, - // `#[get = "pub"]` - MetaItem::NameValue(_, Lit::Str(ref s, _)) => { - let visibility = Ident::from(s.clone()); - quote! { - #(#doc)* - #visibility fn #fn_name(&mut self) -> &mut #ty { - &mut self.#field_name - } - } - }, - // This currently doesn't work, but it might in the future. - /// --- - // // `#[get(pub)]` - // MetaItem::List(_, ref vec) => { - // let s = vec.iter().last().expect("No item found in attribute list."); - // let visibility = match s { - // &NestedMetaItem::MetaItem(MetaItem::Word(ref i)) => Ident::new(format!("{}", i)), - // &NestedMetaItem::Literal(Lit::Str(ref l, _)) => Ident::from(l.clone()), - // _ => panic!("Unexpected attribute parameters."), - // }; - // quote! { - // #visibility fn #fn_name(&mut self) -> &mut #ty { - // &mut self.#field_name - // } - // } - // }, - _ => panic!("Unexpected attribute parameters."), - } - }, - // Don't need to do anything. - None => quote! { } - } -} \ No newline at end of file diff --git a/src/setters.rs b/src/setters.rs deleted file mode 100644 index 757b4fc..0000000 --- a/src/setters.rs +++ /dev/null @@ -1,68 +0,0 @@ -use syn::{MetaItem, Lit, Field}; -use quote::{Ident, Tokens}; - -const ATTRIBUTE_NAME: &'static str = "set"; -const FN_NAME_PREFIX: &'static str = "set_"; -const FN_NAME_SUFFIX: &'static str = ""; - -pub fn implement(field: &Field) -> Tokens { - let field_name = field.clone().ident.expect("Expected the field to have a name"); - let fn_name = Ident::from(format!("{}{}{}", FN_NAME_PREFIX, field_name, FN_NAME_SUFFIX)); - let ty = field.ty.clone(); - let attr = field.attrs.iter() - .filter(|v| v.name() == ATTRIBUTE_NAME) - .last(); - - let doc = field.attrs.iter() - .filter(|v| v.name() == "doc") - .collect::>(); - - match attr { - Some(attr) => { - match attr.value { - // `#[set]` - MetaItem::Word(_) => { - quote! { - #(#doc)* - #[inline(always)] - fn #fn_name(&mut self, val: #ty) -> &mut Self { - self.#field_name = val; - self - } - } - }, - // `#[set = "pub"]` - MetaItem::NameValue(_, Lit::Str(ref s, _)) => { - let visibility = Ident::from(s.clone()); - quote! { - #(#doc)* - #[inline(always)] - #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self { - self.#field_name = val; - self - } - } - }, - // This currently doesn't work, but it might in the future. - /// --- - // // `#[set(pub)]` - // MetaItem::List(_, ref vec) => { - // let s = vec.iter().last().expect("No item found in attribute list."); - // let visibility = match s { - // &NestedMetaItem::MetaItem(MetaItem::Word(ref i)) => Ident::new(format!("{}", i)), - // &NestedMetaItem::Literal(Lit::Str(ref l, _)) => Ident::from(l.clone()), - // _ => panic!("Unexpected attribute parameters."), - // }; - // quote! { - // #visibility fn #fn_name(&self) -> &#ty { - // &self.#field_name - // } - // } - // }, - _ => panic!("Unexpected attribute parameters."), - } - }, - // Don't need to do anything. - None => quote! { } - } -} diff --git a/tests/getters.rs b/tests/getters.rs index a4a4ee0..99a6035 100644 --- a/tests/getters.rs +++ b/tests/getters.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate getset; -use submodule::other::{Plain, Generic, Where}; +use submodule::other::{Generic, Plain, Where}; // For testing `pub(super)` mod submodule { @@ -17,7 +17,6 @@ mod submodule { /// A doc comment. #[get = "pub"] public_accessible: usize, - // /// A doc comment. // #[get = "pub(crate)"] // crate_accessible: usize, @@ -41,7 +40,6 @@ mod submodule { /// A doc comment. #[get = "pub"] public_accessible: T, - // /// A doc comment. // #[get = "pub(crate)"] // crate_accessible: usize, @@ -56,7 +54,10 @@ mod submodule { } #[derive(Getters, Default)] - pub struct Where where T: Copy + Clone + Default { + pub struct Where + where + T: Copy + Clone + Default, + { /// A doc comment. /// Multiple lines, even. #[get] @@ -65,7 +66,6 @@ mod submodule { /// A doc comment. #[get = "pub"] public_accessible: T, - // /// A doc comment. // #[get = "pub(crate)"] // crate_accessible: usize, @@ -102,13 +102,13 @@ mod submodule { #[test] fn test_plain() { let val = Plain::default(); - val.public_accessible(); + assert_eq!(usize::default(), val.public_accessible()); } #[test] fn test_generic() { let val = Generic::::default(); - val.public_accessible(); + assert_eq!(usize::default(), *val.public_accessible()); } #[test] diff --git a/tests/mut_getters.rs b/tests/mut_getters.rs index fb59c9f..cc6bea0 100644 --- a/tests/mut_getters.rs +++ b/tests/mut_getters.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate getset; -use submodule::other::{Plain, Generic, Where}; +use submodule::other::{Generic, Plain, Where}; // For testing `pub(super)` mod submodule { @@ -13,11 +13,10 @@ mod submodule { /// Multiple lines, even. #[get_mut] private_accessible: usize, - + /// A doc comment. #[get_mut = "pub"] public_accessible: usize, - // /// A doc comment. // #[get_mut = "pub(crate)"] // crate_accessible: usize, @@ -37,11 +36,10 @@ mod submodule { /// Multiple lines, even. #[get_mut] private_accessible: T, - + /// A doc comment. #[get_mut = "pub"] public_accessible: T, - // /// A doc comment. // #[get_mut = "pub(crate)"] // crate_accessible: usize, @@ -56,16 +54,18 @@ mod submodule { } #[derive(MutGetters, Default)] - pub struct Where where T: Copy + Clone + Default { + pub struct Where + where + T: Copy + Clone + Default, + { /// A doc comment. /// Multiple lines, even. #[get_mut] private_accessible: T, - + /// A doc comment. #[get_mut = "pub"] public_accessible: T, - // /// A doc comment. // #[get_mut = "pub(crate)"] // crate_accessible: usize, @@ -115,4 +115,4 @@ fn test_generic() { fn test_where() { let mut val = Where::::default(); (*val.public_accessible_mut()) += 1; -} \ No newline at end of file +} diff --git a/tests/setters.rs b/tests/setters.rs index 19ba1c9..cd55608 100644 --- a/tests/setters.rs +++ b/tests/setters.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate getset; -use submodule::other::{Plain, Generic, Where}; +use submodule::other::{Generic, Plain, Where}; // For testing `pub(super)` mod submodule { @@ -21,8 +21,6 @@ mod submodule { /// This field is used for testing chaining. #[set = "pub"] second_public_accessible: bool, - - // /// A doc comment. // #[set = "pub(crate)"] // crate_accessible: usize, @@ -46,7 +44,6 @@ mod submodule { /// A doc comment. #[set = "pub"] public_accessible: T, - // /// A doc comment. // #[set = "pub(crate)"] // crate_accessible: usize, @@ -61,7 +58,10 @@ mod submodule { } #[derive(Setters, Default)] - pub struct Where where T: Copy + Clone + Default { + pub struct Where + where + T: Copy + Clone + Default, + { /// A doc comment. /// Multiple lines, even. #[set] @@ -70,7 +70,6 @@ mod submodule { /// A doc comment. #[set = "pub"] public_accessible: T, - // /// A doc comment. // #[set = "pub(crate)"] // crate_accessible: usize, @@ -125,7 +124,6 @@ fn test_where() { #[test] fn test_chaining() { let mut val = Plain::default(); - val - .set_public_accessible(1) + val.set_public_accessible(1) .set_second_public_accessible(true); }