#![cfg_attr(feature = "cargo-clippy", allow(useless_let_if_seq))]
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn;
use Bindings;
use BuilderPattern;
use DeprecationNotes;
#[derive(Debug, Clone)]
pub struct Setter<'a> {
pub enabled: bool,
pub try_setter: bool,
pub visibility: syn::Visibility,
pub pattern: BuilderPattern,
pub attrs: &'a [syn::Attribute],
pub ident: syn::Ident,
pub field_ident: &'a syn::Ident,
pub field_type: &'a syn::Type,
pub generic_into: bool,
pub strip_option: bool,
pub deprecation_notes: &'a DeprecationNotes,
pub bindings: Bindings,
}
impl<'a> ToTokens for Setter<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.enabled {
trace!("Deriving setter for `{}`.", self.field_ident);
let field_type = self.field_type;
let pattern = self.pattern;
let vis = &self.visibility;
let field_ident = self.field_ident;
let ident = &self.ident;
let attrs = self.attrs;
let deprecation_notes = self.deprecation_notes;
let clone = self.bindings.clone_trait();
let option = self.bindings.option_ty();
let into = self.bindings.into_trait();
let (ty, stripped_option) = match self.strip_option {
false => (field_type, false),
true => match extract_type_from_option(field_type) {
None => (field_type, false),
Some(ty) => (ty, true),
},
};
let self_param: TokenStream;
let return_ty: TokenStream;
let self_into_return_ty: TokenStream;
match pattern {
BuilderPattern::Owned => {
self_param = quote!(self);
return_ty = quote!(Self);
self_into_return_ty = quote!(self);
}
BuilderPattern::Mutable => {
self_param = quote!(&mut self);
return_ty = quote!(&mut Self);
self_into_return_ty = quote!(self);
}
BuilderPattern::Immutable => {
self_param = quote!(&self);
return_ty = quote!(Self);
self_into_return_ty = quote!(#clone::clone(self));
}
};
let ty_params: TokenStream;
let param_ty: TokenStream;
let mut into_value: TokenStream;
if self.generic_into {
ty_params = quote!(<VALUE: #into<#ty>>);
param_ty = quote!(VALUE);
into_value = quote!(value.into());
} else {
ty_params = quote!();
param_ty = quote!(#ty);
into_value = quote!(value);
}
if stripped_option {
into_value = quote!(#option::Some(#into_value));
}
tokens.append_all(quote!(
#(#attrs)*
#[allow(unused_mut)]
#vis fn #ident #ty_params (#self_param, value: #param_ty)
-> #return_ty
{
#deprecation_notes
let mut new = #self_into_return_ty;
new.#field_ident = #option::Some(#into_value);
new
}));
if self.try_setter {
let try_into = self.bindings.try_into_trait();
let try_ty_params = quote!(<VALUE: #try_into<#ty>>);
let try_ident = syn::Ident::new(&format!("try_{}", ident), Span::call_site());
let result = self.bindings.result_ty();
tokens.append_all(quote!(
#(#attrs)*
#vis fn #try_ident #try_ty_params (#self_param, value: VALUE)
-> #result<#return_ty, VALUE::Error>
{
let converted : #ty = value.try_into()?;
let mut new = #self_into_return_ty;
new.#field_ident = #option::Some(converted);
Ok(new)
}));
} else {
trace!("Skipping try_setter for `{}`.", self.field_ident);
}
} else {
trace!("Skipping setter for `{}`.", self.field_ident);
}
}
}
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::punctuated::Pair;
use syn::token::Colon2;
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
fn extract_option_segment(path: &Path) -> Option<Pair<&PathSegment, &Colon2>> {
let idents_of_path = path
.segments
.iter()
.into_iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
});
vec![
"Option|",
"std|option|Option|",
"core|option|Option|",
]
.into_iter()
.find(|s| &idents_of_path == *s)
.and_then(|_| path.segments.last())
}
extract_type_path(ty)
.and_then(|path| extract_option_segment(path))
.and_then(|pair_path_segment| {
let type_params = &pair_path_segment.into_value().arguments;
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg.into_value() {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
}
#[doc(hidden)]
#[macro_export]
macro_rules! default_setter {
() => {
Setter {
enabled: true,
try_setter: false,
visibility: syn::parse_str("pub").unwrap(),
pattern: BuilderPattern::Mutable,
attrs: &vec![],
ident: syn::Ident::new("foo", ::proc_macro2::Span::call_site()),
field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()),
field_type: &syn::parse_str("Foo").unwrap(),
generic_into: false,
strip_option: false,
deprecation_notes: &Default::default(),
bindings: Default::default(),
};
};
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn immutable() {
let mut setter = default_setter!();
setter.pattern = BuilderPattern::Immutable;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo(&self, value: Foo) -> Self {
let mut new = ::std::clone::Clone::clone(self);
new.foo = ::std::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn mutable() {
let mut setter = default_setter!();
setter.pattern = BuilderPattern::Mutable;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo(&mut self, value: Foo) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn owned() {
let mut setter = default_setter!();
setter.pattern = BuilderPattern::Owned;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo(self, value: Foo) -> Self {
let mut new = self;
new.foo = ::std::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn private() {
let vis = syn::Visibility::Inherited;
let mut setter = default_setter!();
setter.visibility = vis;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
fn foo(&mut self, value: Foo) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn generic() {
let mut setter = default_setter!();
setter.generic_into = true;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo <VALUE: ::std::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(value.into());
new
}
).to_string()
);
}
#[test]
fn strip_option() {
let ty = syn::parse_str("Option<Foo>").unwrap();
let mut setter = default_setter!();
setter.strip_option = true;
setter.field_type = &ty;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo(&mut self, value: Foo) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(::std::option::Option::Some(value));
new
}
)
.to_string()
);
}
#[test]
fn strip_option_into() {
let ty = syn::parse_str("Option<Foo>").unwrap();
let mut setter = default_setter!();
setter.strip_option = true;
setter.generic_into = true;
setter.field_type = &ty;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo<VALUE: ::std::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(::std::option::Option::Some(value.into()));
new
}
)
.to_string()
);
}
#[test]
fn full() {
let attrs: Vec<syn::Attribute> = vec![parse_quote!(#[some_attr])];
let mut deprecated = DeprecationNotes::default();
deprecated.push("Some example.".to_string());
let mut setter = default_setter!();
setter.attrs = attrs.as_slice();
setter.generic_into = true;
setter.deprecation_notes = &deprecated;
setter.try_setter = true;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[some_attr]
#[allow(unused_mut)]
pub fn foo <VALUE: ::std::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self {
#deprecated
let mut new = self;
new.foo = ::std::option::Option::Some(value.into());
new
}
#[some_attr]
pub fn try_foo<VALUE: ::std::convert::TryInto<Foo>>(&mut self, value: VALUE)
-> ::std::result::Result<&mut Self, VALUE::Error> {
let converted : Foo = value.try_into()?;
let mut new = self;
new.foo = ::std::option::Option::Some(converted);
Ok(new)
}
).to_string()
);
}
#[test]
fn no_std() {
let mut setter = default_setter!();
setter.bindings.no_std = true;
setter.pattern = BuilderPattern::Immutable;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo(&self, value: Foo) -> Self {
let mut new = ::core::clone::Clone::clone(self);
new.foo = ::core::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn no_std_generic() {
let mut setter = default_setter!();
setter.bindings.no_std = true;
setter.generic_into = true;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo <VALUE: ::core::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self {
let mut new = self;
new.foo = ::core::option::Option::Some(value.into());
new
}
).to_string()
);
}
#[test]
fn setter_disabled() {
let mut setter = default_setter!();
setter.enabled = false;
assert_eq!(quote!(#setter).to_string(), quote!().to_string());
}
#[test]
fn try_setter() {
let mut setter: Setter = default_setter!();
setter.pattern = BuilderPattern::Mutable;
setter.try_setter = true;
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo(&mut self, value: Foo) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(value);
new
}
pub fn try_foo<VALUE: ::std::convert::TryInto<Foo>>(&mut self, value: VALUE)
-> ::std::result::Result<&mut Self, VALUE::Error> {
let converted : Foo = value.try_into()?;
let mut new = self;
new.foo = ::std::option::Option::Some(converted);
Ok(new)
}
)
.to_string()
);
}
#[test]
fn extract_type_from_option_on_simple_type() {
let ty_foo = syn::parse_str("Foo").unwrap();
assert_eq!(extract_type_from_option(&ty_foo), None);
for s in vec![
"Option<Foo>",
"std::option::Option<Foo>",
"::std::option::Option<Foo>",
"core::option::Option<Foo>",
"::core::option::Option<Foo>",
] {
let ty_foo_opt = syn::parse_str(s).unwrap();
assert_eq!(extract_type_from_option(&ty_foo_opt), Some(&ty_foo));
}
}
}