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 22, 2021
1 parent 7b64141 commit 0390267
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 40 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
81 changes: 72 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 @@ -103,7 +102,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 @@ -260,19 +265,74 @@ 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(), "Illegal setting - field is already calling extend(...) with the argument"))
Err(Error::new(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,
&call.func,
format!("Unknown parameter {:?}", name),
)),
}
Expand All @@ -299,7 +359,7 @@ impl SetterSettings {
} else {
self.strip_collection = Some(StripCollection{
keyword_span: name.span(),
custom_initializer: None
from_first: None
});
Ok(())
}
Expand Down Expand Up @@ -355,7 +415,10 @@ impl SetterSettings {
))
}
}
_ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
_ => Err(Error::new_spanned(
expr,
"Expected (<...>=<...>) or (<...>(<...>))",
)),
}
}
}
45 changes: 15 additions & 30 deletions src/struct_info.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use syn::{self, spanned::Spanned, Ident};

use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::parse::Error;
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 @@ -306,20 +305,26 @@ 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),
Expand Down Expand Up @@ -394,31 +399,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 0390267

Please sign in to comment.