blob: 864188488331c9d7d57a1091e8c673147e010dac [file] [log] [blame]
#![allow(clippy::useless_let_if_seq)]
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn;
use BuilderPattern;
use DeprecationNotes;
use Each;
/// Setter for the struct fields in the build method, implementing
/// `quote::ToTokens`.
///
/// # Examples
///
/// Will expand to something like the following (depending on settings):
///
/// ```rust,ignore
/// # extern crate proc_macro2;
/// # #[macro_use]
/// # extern crate quote;
/// # extern crate syn;
/// # #[macro_use]
/// # extern crate derive_builder_core;
/// # use derive_builder_core::{Setter, BuilderPattern};
/// # fn main() {
/// # 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 = ::derive_builder::export::core::option::Option::Some(value);
/// new
/// }
/// # ).to_string());
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Setter<'a> {
/// Enables code generation for this setter fn.
pub setter_enabled: bool,
/// Enables code generation for the `try_` variant of this setter fn.
pub try_setter: bool,
/// Visibility of the setter, e.g. `syn::Visibility::Public`.
pub visibility: syn::Visibility,
/// How the setter method takes and returns `self` (e.g. mutably).
pub pattern: BuilderPattern,
/// Attributes which will be attached to this setter fn.
pub attrs: &'a [syn::Attribute],
/// Name of this setter fn.
pub ident: syn::Ident,
/// Name of the target field.
pub field_ident: &'a syn::Ident,
/// Type of the target field.
///
/// The corresonding builder field will be `Option<field_type>`.
pub field_type: &'a syn::Type,
/// Make the setter generic over `Into<T>`, where `T` is the field type.
pub generic_into: bool,
/// Make the setter remove the Option wrapper from the setter, remove the need to call Some(...).
/// when combined with into, the into is used on the content Type of the Option.
pub strip_option: bool,
/// Emit deprecation notes to the user.
pub deprecation_notes: &'a DeprecationNotes,
/// Emit extend method.
pub each: Option<&'a Each>,
}
impl<'a> ToTokens for Setter<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.setter_enabled {
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 (ty, stripped_option) = {
if self.strip_option {
match extract_type_from_option(field_type) {
Some(ty) => (ty, true),
None => (field_type, false),
}
} else {
(field_type, false)
}
};
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!(::derive_builder::export::core::clone::Clone::clone(self));
}
};
let ty_params: TokenStream;
let param_ty: TokenStream;
let mut into_value: TokenStream;
if self.generic_into {
ty_params = quote!(<VALUE: ::derive_builder::export::core::convert::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!(::derive_builder::export::core::option::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 = ::derive_builder::export::core::option::Option::Some(#into_value);
new
}
));
if self.try_setter {
let try_ty_params =
quote!(<VALUE: ::derive_builder::export::core::convert::TryInto<#ty>>);
let try_ident = syn::Ident::new(&format!("try_{}", ident), Span::call_site());
tokens.append_all(quote!(
#(#attrs)*
#vis fn #try_ident #try_ty_params (#self_param, value: VALUE)
-> ::derive_builder::export::core::result::Result<#return_ty, VALUE::Error>
{
let converted : #ty = value.try_into()?;
let mut new = #self_into_return_ty;
new.#field_ident = ::derive_builder::export::core::option::Option::Some(converted);
Ok(new)
}
));
}
if let Some(each) = self.each {
let ident_each = &each.name;
// Access the collection to extend, initialising with default value if necessary.
let get_initialized_collection = if stripped_option {
// Outer (builder) Option -> Inner (field) Option -> collection.
quote!(get_or_insert_with(|| Some(
::derive_builder::export::core::default::Default::default()
))
.get_or_insert_with(::derive_builder::export::core::default::Default::default))
} else {
// Outer (builder) Option -> collection.
quote!(get_or_insert_with(
::derive_builder::export::core::default::Default::default
))
};
let ty_params: TokenStream;
let param_ty: TokenStream;
let into_item: TokenStream;
if each.into {
ty_params = quote!(<VALUE, FROM_VALUE: ::derive_builder::export::core::convert::Into<VALUE>>);
param_ty = quote!(FROM_VALUE);
into_item = quote!(::derive_builder::export::core::convert::Into::into(item));
} else {
ty_params = quote!(<VALUE>);
param_ty = quote!(VALUE);
into_item = quote!(item);
}
tokens.append_all(quote!(
#(#attrs)*
#[allow(unused_mut)]
#vis fn #ident_each #ty_params(#self_param, item: #param_ty) -> #return_ty
where
#ty: ::derive_builder::export::core::default::Default + ::derive_builder::export::core::iter::Extend<VALUE>,
{
#deprecation_notes
let mut new = #self_into_return_ty;
new.#field_ident
.#get_initialized_collection
.extend(::derive_builder::export::core::option::Option::Some(#into_item));
new
}
));
}
}
}
}
// adapted from https://stackoverflow.com/a/55277337/469066
// Note that since syn is a parser, it works with tokens.
// We cannot know for sure that this is an Option.
// The user could, for example, `type MaybeString = std::option::Option<String>`
// We cannot handle those arbitrary names.
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,
}
}
// TODO store (with lazy static) precomputed parsing of Option when support of rust 1.18 will be removed (incompatible with lazy_static)
// TODO maybe optimization, reverse the order of segments
fn extract_option_segment(path: &Path) -> Option<Pair<&PathSegment, &Colon2>> {
let idents_of_path = path.segments.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().map(Pair::End))
}
extract_type_path(ty)
.and_then(extract_option_segment)
.and_then(|pair_path_segment| {
let type_params = &pair_path_segment.into_value().arguments;
// It should have only on angle-bracketed param ("<String>"):
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
}
/// Helper macro for unit tests. This is _only_ public in order to be accessible
/// from doc-tests too.
#[doc(hidden)]
#[macro_export]
macro_rules! default_setter {
() => {
Setter {
setter_enabled: true,
try_setter: false,
visibility: parse_quote!(pub),
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: &parse_quote!(Foo),
generic_into: false,
strip_option: false,
deprecation_notes: &Default::default(),
each: None,
}
};
}
#[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 = ::derive_builder::export::core::clone::Clone::clone(self);
new.foo = ::derive_builder::export::core::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 = ::derive_builder::export::core::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 = ::derive_builder::export::core::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 = ::derive_builder::export::core::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn generic() {
let mut setter = default_setter!();
setter.generic_into = true;
#[rustfmt::skip]
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo<VALUE: ::derive_builder::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::derive_builder::export::core::option::Option::Some(value.into());
new
}
)
.to_string()
);
}
#[test]
fn strip_option() {
let ty = parse_quote!(Option<Foo>);
let mut setter = default_setter!();
setter.strip_option = true;
setter.field_type = &ty;
#[rustfmt::skip]
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 = ::derive_builder::export::core::option::Option::Some(
::derive_builder::export::core::option::Option::Some(value)
);
new
}
)
.to_string()
);
}
#[test]
fn strip_option_into() {
let ty = parse_quote!(Option<Foo>);
let mut setter = default_setter!();
setter.strip_option = true;
setter.generic_into = true;
setter.field_type = &ty;
#[rustfmt::skip]
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo<VALUE: ::derive_builder::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::derive_builder::export::core::option::Option::Some(
::derive_builder::export::core::option::Option::Some(value.into())
);
new
}
)
.to_string()
);
}
// including try_setter
#[test]
fn full() {
//named!(outer_attrs -> Vec<syn::Attribute>, many0!(syn::Attribute::parse_outer));
//let attrs = outer_attrs.parse_str("#[some_attr]").unwrap();
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: ::derive_builder::export::core::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self {
#deprecated
let mut new = self;
new.foo = ::derive_builder::export::core::option::Option::Some(value.into());
new
}
#[some_attr]
pub fn try_foo<VALUE: ::derive_builder::export::core::convert::TryInto<Foo>>(&mut self, value: VALUE)
-> ::derive_builder::export::core::result::Result<&mut Self, VALUE::Error> {
let converted : Foo = value.try_into()?;
let mut new = self;
new.foo = ::derive_builder::export::core::option::Option::Some(converted);
Ok(new)
}
).to_string()
);
}
#[test]
fn no_std() {
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 = ::derive_builder::export::core::clone::Clone::clone(self);
new.foo = ::derive_builder::export::core::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn no_std_generic() {
let mut setter = default_setter!();
setter.generic_into = true;
#[rustfmt::skip]
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo<VALUE: ::derive_builder::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::derive_builder::export::core::option::Option::Some(value.into());
new
}
)
.to_string()
);
}
#[test]
fn setter_disabled() {
let mut setter = default_setter!();
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;
#[rustfmt::skip]
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 = ::derive_builder::export::core::option::Option::Some(value);
new
}
pub fn try_foo<VALUE: ::derive_builder::export::core::convert::TryInto<Foo>>(
&mut self,
value: VALUE
) -> ::derive_builder::export::core::result::Result<&mut Self, VALUE::Error> {
let converted: Foo = value.try_into()?;
let mut new = self;
new.foo = ::derive_builder::export::core::option::Option::Some(converted);
Ok(new)
}
)
.to_string()
);
}
#[test]
fn extract_type_from_option_on_simple_type() {
let ty_foo = parse_quote!(Foo);
assert_eq!(extract_type_from_option(&ty_foo), None);
for s in vec![
parse_quote!(Option<Foo>),
parse_quote!(std::option::Option<Foo>),
parse_quote!(::std::option::Option<Foo>),
parse_quote!(core::option::Option<Foo>),
parse_quote!(::core::option::Option<Foo>),
] {
assert_eq!(extract_type_from_option(&s), Some(&ty_foo));
}
}
}