Skip to content

Commit

Permalink
Accept e.g. strip_collection(from_first = |first| vec![first]) inst…
Browse files Browse the repository at this point in the history
…ead of `strip_collection = vec![…_first]`
  • Loading branch information
Tamschi committed Jan 23, 2021
1 parent 4376566 commit 8e4b301
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 39 deletions.
2 changes: 1 addition & 1 deletion examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct Foo {

// No `default`: This field must be set at least once.
// You can explicitly create the collection from the first item (but this is not required even without `default`).
#[builder(setter(strip_collection = vec![v1_first]))]
#[builder(setter(strip_collection(from_first = |first| vec![first])))]
v1: Vec<i32>,

// Other `Extend` types are also supported.
Expand Down
61 changes: 52 additions & 9 deletions src/field_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::parse::Error;
use syn::spanned::Spanned;
use syn::{self, Token};

use crate::util::ident_to_type;
use crate::util::{expr_to_single_string, path_to_single_string};
Expand Down Expand Up @@ -95,7 +94,13 @@ pub struct SetterSettings {
#[derive(Debug, Clone)]
pub struct StripCollection {
pub keyword_span: Span,
pub custom_initializer: Option<(Token![=], syn::Expr)>,
pub from_first: Option<FromFirst>,
}

#[derive(Debug, Clone)]
pub struct FromFirst {
pub keyword_span: Span,
pub closure: syn::ExprClosure,
}

impl FieldBuilderAttr {
Expand Down Expand Up @@ -237,21 +242,59 @@ impl SetterSettings {
self.doc = Some(*assign.right);
Ok(())
}
_ => Err(Error::new_spanned(&assign, format!("Unknown parameter {:?}", name))),
}
}
syn::Expr::Call(call) => {
let name =
expr_to_single_string(&call.func).ok_or_else(|| Error::new_spanned(&call.func, "Expected identifier"))?;
match name.as_str() {
"strip_collection" => {
if self.strip_collection.is_some() {
Err(Error::new(
assign.span(),
call.span(),
"Illegal setting - field is already calling extend(...) with the argument",
))
} else if let Some(attr) = call.attrs.first() {
Err(Error::new_spanned(attr, "Unexpected attribute"))
} else {
self.strip_collection = Some(StripCollection {
let mut strip_collection = StripCollection {
keyword_span: name.span(),
custom_initializer: Some((assign.eq_token, *assign.right)),
});
from_first: None,
};
for arg in call.args {
match arg {
syn::Expr::Assign(assign) => {
let name = expr_to_single_string(&assign.left)
.ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
match name.as_str() {
"from_first" => match *assign.right {
syn::Expr::Closure(closure) => {
strip_collection.from_first = Some(FromFirst {
keyword_span: assign.left.span(),
closure,
})
}
other => {
return Err(Error::new_spanned(other, "Expected closure (|first| <...>)"))
}
},
_ => {
return Err(Error::new_spanned(
&assign.left,
format!("Unknown parameter {:?}", name),
))
}
}
}
_ => return Err(Error::new_spanned(arg, "Expected (<...>=<...>)")),
}
}
self.strip_collection = Some(strip_collection);
Ok(())
}
}
_ => Err(Error::new_spanned(&assign, format!("Unknown parameter {:?}", name))),
_ => Err(Error::new_spanned(&call.func, format!("Unknown parameter {:?}", name))),
}
}
syn::Expr::Path(path) => {
Expand All @@ -275,7 +318,7 @@ impl SetterSettings {
} else {
self.strip_collection = Some(StripCollection{
keyword_span: name.span(),
custom_initializer: None
from_first: None
});
Ok(())
}
Expand Down Expand Up @@ -328,7 +371,7 @@ impl SetterSettings {
Err(Error::new_spanned(expr, "Expected simple identifier".to_owned()))
}
}
_ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
_ => Err(Error::new_spanned(expr, "Expected (<...>=<...>) or (<...>(<...>))")),
}
}
}
39 changes: 10 additions & 29 deletions src/struct_info.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::parse::Error;
use syn::spanned::Spanned;
use syn::{self, Ident};

use crate::field_info::{FieldBuilderAttr, FieldInfo, StripCollection};
use crate::field_info::{FieldBuilderAttr, FieldInfo, FromFirst, StripCollection};
use crate::util::{
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single, modify_types_generics_hack,
path_to_single_string, type_tuple,
Expand Down Expand Up @@ -281,20 +280,22 @@ impl<'a> StructInfo<'a> {
field_type
};
let (arg_type, arg_expr) = if let Some(StripCollection {
keyword_span,
ref custom_initializer,
ref keyword_span,
ref from_first,
}) = field.builder_attr.setter.strip_collection
{
let arg_expr = if let Some((_, init)) = custom_initializer {
init.to_token_stream()
let arg_expr = if let Some(FromFirst { keyword_span, closure }) = from_first {
// `Span::mixed_site()`-resolution suppresses `clippy::redundant_closure_call` on
// the generated code only.
quote_spanned!(keyword_span.resolved_at(Span::mixed_site())=> (#closure)(#field_name))
} else if let Some(default) = &field.builder_attr.default {
quote_spanned!(keyword_span=> {
quote_spanned!(*keyword_span=> {
let mut collection: #arg_type = #default;
::core::iter::Extend::extend(&mut collection, ::core::iter::once(#field_name));
collection
})
} else {
quote_spanned!(keyword_span=> ::<#arg_type as ::core::iter::FromIterator>::from_iter(::core::iter::once(#field_name)))
quote_spanned!(*keyword_span=> ::<#arg_type as ::core::iter::FromIterator>::from_iter(::core::iter::once(#field_name)))
};
(quote!(<#arg_type as ::core::iter::IntoIterator>::Item), arg_expr)
} else {
Expand Down Expand Up @@ -361,31 +362,11 @@ impl<'a> StructInfo<'a> {
}
};

let (arg_name, forbid_unused_first) = if let Some(StripCollection {
custom_initializer: Some((ref eq, ref init)),
..
}) = field.builder_attr.setter.strip_collection
{
(
Ident::new(
&format!("{}_first", field_name),
// This places the `unused_variables` error caused by faulty collection seeds on the expression after `strip_collection =`
// (more or less - we might only get the first token, but this should eventually improve with proc macro improvements),
// which is much less confusing than having it on the field name.
init.span(),
),
// Place "the lint level is defined here" on `strip_collection =`'s `=`.
Some(quote_spanned!(eq.span=> #[forbid(unused_variables)])),
)
} else {
(field_name.clone(), None)
};

Ok(quote! {
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
#doc
pub fn #field_name (self, #forbid_unused_first #arg_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
let #field_name = (#arg_expr,);
let ( #(#descructuring,)* ) = self.fields;
#builder_name {
Expand Down

0 comments on commit 8e4b301

Please sign in to comment.