Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1, #2. Partially implements #3, #4. #5

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ proc-macro = true
test = false

[dependencies]
log = "0.3.6"
syn = "0.9.0"
quote = "0.3.3"
syn = "^0.10.0"
quote = "^0.3.3"
27 changes: 23 additions & 4 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,30 @@ extern crate accessors;
#[derive(getters, setters)]
#[setters(into)]
struct Simple {
field: String,
normal_field: String,

#[getter(ignore)]
ignored_field: String,

#[getter(return_type = "&str")]
custom_return_type_field: String,
}

impl Simple {
fn ignored_field(&self) -> &str {
&self.ignored_field
}
}

fn main() {
let mut s = Simple { field: "hello".to_owned() };
println!("{}", s.field());
s.set_field("there");
let mut s = Simple {
normal_field: "hello".to_owned(),
ignored_field: "".to_string(),
custom_return_type_field: "World".to_string()
};

println!("{}", s.normal_field());
s.set_normal_field("there");

let _: &str = s.custom_return_type_field();
}
165 changes: 92 additions & 73 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
// good refactoring once I figure out the basic ideas. Do not use use this
// as an example of good style.

#[macro_use]
extern crate log;
extern crate proc_macro;
#[macro_use]
extern crate quote;
Expand All @@ -19,133 +17,154 @@ extern crate syn;
use proc_macro::TokenStream;
use std::collections::BTreeMap;

#[proc_macro_derive(getters)]
#[proc_macro_derive(getters, attributes(getter, getters))]
pub fn derive_getters(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input(&input.to_string()).unwrap();
let expanded = expand_getters(ast);
expanded.to_string().parse().unwrap()
}

fn expand_getters(mut ast: syn::MacroInput) -> quote::Tokens {
fn expand_getters(ast: syn::MacroInput) -> quote::Tokens {
// println!("Defining getters for: {:#?}", ast);

extract_attrs(&mut ast.attrs, "getters");
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics
.split_for_impl();

let fields: Vec<_> = match ast.body {
let getters = match ast.body {
syn::Body::Struct(syn::VariantData::Struct(ref fields)) => {
fields.iter().map(|f| (f.ident.as_ref().unwrap(), &f.ty)).collect()
fields.iter().filter_map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_ty = &f.ty;

let mut field_attrs = f.attrs.iter().filter(|a| a.name() == "getter");
let config = config_from(&mut field_attrs, &["ignore", "return_type"]);

let ignore_default = syn::Lit::Bool(false);
let ignore = match *config.get("ignore").unwrap_or(&ignore_default) {
syn::Lit::Bool(b) => b,
ref val => panic!("'ignore' must be a boolean value, not {:?}", val),
};
if ignore {
return None;
}

let return_type = match config.get("return_type") {
Some(&syn::Lit::Str(ref v, _)) => syn::parse_type(v).unwrap(),
None => syn::parse_type(&quote!(&#field_ty).to_string()).unwrap(),
ref val => panic!("'return_type' must be a string value, not {:?}", val),
};

Some(quote! {
pub fn #field_name(&self) -> #return_type {
&self.#field_name
}
})
})
}
_ => panic!("#[derive(getters)] can only be used with braced structs"),
};

let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics
.split_for_impl();
let getter: Vec<_> = fields.iter().map(|f| f.0).collect();
let field: Vec<_> = fields.iter().map(|f| f.0).collect();
let ty: Vec<_> = fields.iter().map(|f| f.1).collect();

quote! {
#ast

impl #impl_generics #name #ty_generics #where_clause {
#(
pub fn #getter(&self) -> &#ty {
&self.#field
}
)*
#(#getters)*
}
}
}

#[proc_macro_derive(setters)]
#[proc_macro_derive(setters, attributes(setter, setters))]
pub fn derive_setters(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input(&input.to_string()).unwrap();
let expanded = expand_setters(ast);
// println!("Expanded: {}", expanded.to_string());
expanded.to_string().parse().unwrap()
}

fn expand_setters(mut ast: syn::MacroInput) -> quote::Tokens {
fn expand_setters(ast: syn::MacroInput) -> quote::Tokens {
// println!("Defining setters for: {:#?}", ast);

let setters_attrs = extract_attrs(&mut ast.attrs, "setters");
let config = config_from(&setters_attrs, &["into"]);
let mut setters_attrs = ast.attrs.iter().filter(|a| a.name() == "setters");
let config = config_from(&mut setters_attrs, &["into"]);
// println!("Config: {:#?}", &config);
let into_default = syn::Lit::Bool(false);
let into = match *config.get("into").unwrap_or(&into_default) {
syn::Lit::Bool(b) => b,
ref val => panic!("'into' must be a boolean value, not {:?}", val),
};

let fields: Vec<_> = match ast.body {
syn::Body::Struct(syn::VariantData::Struct(ref fields)) => {
fields.iter().map(|f| (f.ident.as_ref().unwrap(), &f.ty)).collect()
}
_ => panic!("#[derive(setters)] can only be used with braced structs"),
};

let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics
.split_for_impl();
let setters: Vec<_> = fields.iter()
.map(|&(ref field_name, ref ty)| {
let set_fn_name: syn::Ident = format!("set_{}", field_name).into();
if into {
quote! {
pub fn #set_fn_name<T>(&mut self, value: T)
where T: Into<#ty>
{
self.#field_name = value.into();
}

let setters = match ast.body {
syn::Body::Struct(syn::VariantData::Struct(ref fields)) => {
fields.iter().filter_map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_ty = &f.ty;

let mut field_attrs = f.attrs.iter().filter(|a| a.name() == "setter");
let config = config_from(&mut field_attrs, &["ignore"]);

let ignore_default = syn::Lit::Bool(false);
let ignore = match *config.get("ignore").unwrap_or(&ignore_default) {
syn::Lit::Bool(b) => b,
ref val => panic!("'ignore' must be a boolean value, not {:?}", val),
};
if ignore {
return None;
}
} else {
quote! {
pub fn #set_fn_name(&mut self, value: #ty) {
self.#field_name = value;
}

let set_fn_name: syn::Ident = format!("set_{}", field_name).into();
if into {
Some(quote! {
pub fn #set_fn_name<T>(&mut self, value: T)
where T: Into<#field_ty>
{
self.#field_name = value.into();
}
})
} else {
Some(quote! {
pub fn #set_fn_name(&mut self, value: #field_ty) {
self.#field_name = value;
}
})
}
}
})
.collect();
})
}
_ => panic!("#[derive(setters)] can only be used with braced structs"),
};

quote! {
#ast

impl #impl_generics #name #ty_generics #where_clause {
#(#setters)*
}
}
}

fn extract_attrs(attrs: &mut Vec<syn::Attribute>,
name: &str)
-> Vec<syn::Attribute> {
let extracted =
attrs.iter().filter(|a| a.name() == name).cloned().collect();
attrs.retain(|a| a.name() != name);
extracted
}

fn config_from(attrs: &[syn::Attribute],
fn config_from(attrs: &mut Iterator<Item = &syn::Attribute>,
keys: &[&str])
-> BTreeMap<String, syn::Lit> {
let mut result = BTreeMap::new();
for attr in attrs {
while let Some(attr) = attrs.next() {
if let syn::MetaItem::List(_, ref args) = attr.value {
for arg in args {
let name = arg.name();
if !keys.contains(&name) {
panic!("'{}' in {:?} is not a known attribute", name, attr);
}
match *arg {
syn::MetaItem::Word(_) => {
result.insert(name.to_owned(), syn::Lit::Bool(true));
if let syn::NestedMetaItem::MetaItem(ref meta_item) = *arg {
let name = meta_item.name();
if !keys.contains(&name) {
panic!("'{}' in {:?} is not a known attribute", name, attr);
}
syn::MetaItem::NameValue(_, ref value) => {
result.insert(name.to_owned(), value.to_owned());
match *meta_item {
syn::MetaItem::Word(_) => {
result.insert(name.to_owned(), syn::Lit::Bool(true));
}
syn::MetaItem::NameValue(_, ref value) => {
result.insert(name.to_owned(), value.to_owned());
}
_ => panic!("can't parse '{:?}'", &arg),
}
_ => panic!("can't parse '{:?}'", &arg),
} else {
panic!("'{:?}' in {:?} is not a known attribute", arg, attr);
}
}
} else {
Expand Down