#![allow(clippy::useless_let_if_seq)]
use std::borrow::Cow;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use crate::{BuilderFieldType, BuilderPattern, Each};
#[derive(Debug, Clone)]
pub struct Setter<'a> {
pub crate_root: &'a syn::Path,
pub setter_enabled: bool,
pub try_setter: bool,
pub visibility: Cow<'a, syn::Visibility>,
pub pattern: BuilderPattern,
pub attrs: &'a [syn::Attribute],
pub ident: syn::Ident,
pub field_ident: &'a syn::Ident,
pub field_type: BuilderFieldType<'a>,
pub generic_into: bool,
pub strip_option: bool,
pub each: Option<&'a Each>,
}
impl<'a> ToTokens for Setter<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.setter_enabled {
let crate_root = self.crate_root;
let pattern = self.pattern;
let vis = &self.visibility;
let field_ident = self.field_ident;
let ident = &self.ident;
let attrs = self.attrs;
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!(#crate_root::export::core::clone::Clone::clone(self));
}
};
let ty_params: TokenStream;
let param_ty: TokenStream;
let mut into_value: TokenStream;
let (field_type, builder_field_is_option) = self.field_type.setter_type_info();
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)
}
};
if self.generic_into {
ty_params = quote!(<VALUE: #crate_root::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 = wrap_expression_in_some(crate_root, into_value);
}
if builder_field_is_option {
into_value = wrap_expression_in_some(crate_root, into_value);
}
tokens.append_all(quote!(
#(#attrs)*
#[allow(unused_mut)]
#vis fn #ident #ty_params (#self_param, value: #param_ty)
-> #return_ty
{
let mut new = #self_into_return_ty;
new.#field_ident = #into_value;
new
}
));
if self.try_setter {
let try_ty_params =
quote!(<VALUE: #crate_root::export::core::convert::TryInto<#ty>>);
let try_ident = syn::Ident::new(&format!("try_{}", ident), Span::call_site());
let mut converted = quote! {converted};
if builder_field_is_option {
converted = wrap_expression_in_some(crate_root, converted);
}
if stripped_option {
converted = wrap_expression_in_some(crate_root, converted);
}
tokens.append_all(quote!(
#(#attrs)*
#vis fn #try_ident #try_ty_params (#self_param, value: VALUE)
-> #crate_root::export::core::result::Result<#return_ty, VALUE::Error>
{
let converted : #ty = value.try_into()?;
let mut new = #self_into_return_ty;
new.#field_ident = #converted;
Ok(new)
}
));
}
if let Some(each) = self.each {
let ident_each = &each.name;
let get_initialized_collection = if stripped_option {
quote!(get_or_insert_with(|| Some(
#crate_root::export::core::default::Default::default()
))
.get_or_insert_with(#crate_root::export::core::default::Default::default))
} else {
quote!(get_or_insert_with(
#crate_root::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: #crate_root::export::core::convert::Into<VALUE>>);
param_ty = quote!(FROM_VALUE);
into_item = quote!(#crate_root::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: #crate_root::export::core::default::Default + #crate_root::export::core::iter::Extend<VALUE>,
{
let mut new = #self_into_return_ty;
new.#field_ident
.#get_initialized_collection
.extend(#crate_root::export::core::option::Option::Some(#into_item));
new
}
));
}
}
}
}
fn wrap_expression_in_some(crate_root: &syn::Path, bare_value: impl ToTokens) -> TokenStream {
quote!( #crate_root::export::core::option::Option::Some(#bare_value) )
}
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::punctuated::Pair;
use syn::token::PathSep;
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, &PathSep>> {
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;
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,
})
}
#[doc(hidden)]
#[macro_export]
macro_rules! default_setter {
() => {
Setter {
crate_root: &parse_quote!(::db),
setter_enabled: true,
try_setter: false,
visibility: ::std::borrow::Cow::Owned(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: BuilderFieldType::Optional(Box::leak(Box::new(parse_quote!(Foo)))),
generic_into: false,
strip_option: false,
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 = ::db::export::core::clone::Clone::clone(self);
new.foo = ::db::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 = ::db::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 = ::db::export::core::option::Option::Some(value);
new
}
)
.to_string()
);
}
#[test]
fn private() {
let vis = Cow::Owned(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 = ::db::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: ::db::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::db::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 = BuilderFieldType::Optional(&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 = ::db::export::core::option::Option::Some(
::db::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 = BuilderFieldType::Optional(&ty);
#[rustfmt::skip]
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo<VALUE: ::db::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::db::export::core::option::Option::Some(
::db::export::core::option::Option::Some(value.into())
);
new
}
)
.to_string()
);
}
#[test]
fn strip_option_try_setter() {
let ty = parse_quote!(Option<Foo>);
let mut setter = default_setter!();
setter.strip_option = true;
setter.try_setter = true;
setter.generic_into = true;
setter.field_type = BuilderFieldType::Optional(&ty);
#[rustfmt::skip]
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[allow(unused_mut)]
pub fn foo<VALUE: ::db::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::db::export::core::option::Option::Some(
::db::export::core::option::Option::Some(value.into())
);
new
}
pub fn try_foo<VALUE: ::db::export::core::convert::TryInto<Foo>>(
&mut self,
value: VALUE
) -> ::db::export::core::result::Result<&mut Self, VALUE::Error> {
let converted: Foo = value.try_into()?;
let mut new = self;
new.foo = ::db::export::core::option::Option::Some(
::db::export::core::option::Option::Some(converted)
);
Ok(new)
}
)
.to_string()
);
}
#[test]
fn full() {
let attrs: Vec<syn::Attribute> = vec![parse_quote!(#[some_attr])];
let mut setter = default_setter!();
setter.attrs = attrs.as_slice();
setter.generic_into = true;
setter.try_setter = true;
#[rustfmt::skip]
assert_eq!(
quote!(#setter).to_string(),
quote!(
#[some_attr]
#[allow(unused_mut)]
pub fn foo <VALUE: ::db::export::core::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self {
let mut new = self;
new.foo = ::db::export::core::option::Option::Some(value.into());
new
}
#[some_attr]
pub fn try_foo<VALUE: ::db::export::core::convert::TryInto<Foo>>(&mut self, value: VALUE)
-> ::db::export::core::result::Result<&mut Self, VALUE::Error> {
let converted : Foo = value.try_into()?;
let mut new = self;
new.foo = ::db::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 = ::db::export::core::clone::Clone::clone(self);
new.foo = ::db::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: ::db::export::core::convert::Into<Foo>>(
&mut self,
value: VALUE
) -> &mut Self {
let mut new = self;
new.foo = ::db::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 = ::db::export::core::option::Option::Some(value);
new
}
pub fn try_foo<VALUE: ::db::export::core::convert::TryInto<Foo>>(
&mut self,
value: VALUE
) -> ::db::export::core::result::Result<&mut Self, VALUE::Error> {
let converted: Foo = value.try_into()?;
let mut new = self;
new.foo = ::db::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));
}
}
}