Skip to content

Commit

Permalink
Support setter(each(into)) (#234)
Browse files Browse the repository at this point in the history
* Update `each` syntax to accept extra options
* Allow `each` setters to be generic over `Into`
* Allow both list and name forms of `setter(each)`
* Update CHANGELOG

Co-authored-by: Tomasz Kurcz <uint@lavabit.com>
  • Loading branch information
TedDriggs and uint committed Mar 3, 2022
1 parent fb90843 commit 1a61c39
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 17 deletions.
3 changes: 2 additions & 1 deletion derive_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
- Compatibility between `setter(strip_option`) and `setter(each = "name")` #213
- Support shorthand and long-form collection setters; `#[builder(setter(each = "..."))]` and `#[builder(setter(each(name = "...")))]` #234
- Allow collection setters to be generic over `Into` using `#[builder(setter(each(name = "...", into)))] #234 and #214

## [0.10.2] - 2021-04-21
- Don't reference `derive_builder_core` from `derive_builder` #206
Expand Down
2 changes: 1 addition & 1 deletion derive_builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ It's as simple as three steps:
- **Setter visibility**: You can opt into private setter by preceding your struct with `#[builder(private)]`.
- **Setter type conversions**: With `#[builder(setter(into))]`, setter methods will be generic over the input types – you can then supply every argument that implements the [`Into`][into] trait for the field type.
- **Setter strip option**: With `#[builder(setter(strip_option))]`, setter methods will take `T` as parameter'type for field of type `Option<T>`.
- **Collection setters**: Adding `#[builder(setter(each = "method_name"))]` to fields whose types implement `Default` and `Extend` will generate a setter which adds items to the builder collection for that field.
- **Collection setters**: Adding `#[builder(setter(each(name = "method_name")))]` to fields whose types implement `Default` and `Extend` will generate a setter which adds items to the builder collection for that field. It's possible for these setters to be generic over the `Into<T>` trait too, like so: `#[builder(setter(each(name = "foo", into)))]`.
- **Builder field visibility**: You can use `#[builder(field(private))]` or `..(public)`, to set field visibility of your builder.
- **Generic structs**: Are also supported, but you **must not** use a type parameter named `VALUE`, if you also activate setter type conversions.
- **Default values**: You can use `#[builder(default)]` to delegate to the `Default` implementation or any explicit value via ` = ".."`. This works both on the struct and field level.
Expand Down
52 changes: 45 additions & 7 deletions derive_builder/tests/setter_extend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,29 @@ struct Lorem {
#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[builder(pattern = "mutable")]
struct Ipsum {
#[builder(setter(each = "foo_append"))]
#[builder(setter(each(name = "foo_append")))]
foo: String,
#[builder(setter(each = "bar"))]
#[builder(setter(each(name = "bar")))]
bars: Vec<String>,
#[builder(setter(each = "baz"))]
#[builder(setter(each(name = "baz")))]
bazes: HashMap<String, i32>,
#[builder(setter(strip_option, each = "qux"))]
quxes: Option<Vec<String>>,
#[builder(setter(strip_option, each = "quux"))]
quuxes: Option<HashMap<String, i32>>,
}

#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[builder]
struct Dolor {
#[builder(setter(each(name = "foo_append")))]
foo: String,
#[builder(setter(each(name = "bar", into)))]
bars: Vec<String>,
#[builder(setter(each(name = "baz")))]
bazes: HashMap<String, i32>,
}

#[test]
fn extend_field() {
let x = LoremBuilder::default()
Expand Down Expand Up @@ -71,6 +82,33 @@ fn extend_field() {
);
}

#[test]
fn extend_field_into() {
let x = DolorBuilder::default()
.foo("foo".into())
.bar("bar")
.bar("bar bar")
.bar("bar bar bar")
.foo_append('-')
.baz(("baz".into(), 1))
.baz(("bazz".into(), 2))
.baz(("bazzz".into(), 3))
.foo_append("foo")
.build()
.unwrap();

assert_eq!(
x,
Dolor {
foo: "foo-foo".into(),
bars: vec!["bar".into(), "bar bar".into(), "bar bar bar".into()],
bazes: vec![("baz".into(), 1), ("bazz".into(), 2), ("bazzz".into(), 3)]
.into_iter()
.collect(),
}
);
}

#[test]
fn extend_field_mutable() {
let x = IpsumBuilder::default()
Expand Down Expand Up @@ -110,13 +148,13 @@ fn extend_field_mutable() {

#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[builder(setter(skip))]
struct Dolor {
#[builder(setter(each = "foo"))]
struct Sit {
#[builder(setter(each(name = "foo")))]
foos: Vec<i32>,
}

#[test]
fn extend_field_enabled() {
let x = DolorBuilder::default().foo(1).foo(2).build().unwrap();
assert_eq!(x, Dolor { foos: vec![1, 2] });
let x = SitBuilder::default().foo(1).foo(2).build().unwrap();
assert_eq!(x, Sit { foos: vec![1, 2] });
}
2 changes: 1 addition & 1 deletion derive_builder_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub(crate) use default_expression::DefaultExpression;
pub(crate) use deprecation_notes::DeprecationNotes;
pub(crate) use doc_comment::doc_comment_from;
pub(crate) use initializer::Initializer;
pub(crate) use options::BuilderPattern;
pub(crate) use options::{BuilderPattern, Each};
pub(crate) use setter::Setter;

const DEFAULT_STRUCT_NAME: &str = "__default";
Expand Down
40 changes: 38 additions & 2 deletions derive_builder_core/src/macro_options/darling_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use proc_macro2::Span;
use syn::{self, spanned::Spanned, Attribute, Generics, Ident, Path, Visibility};

use crate::{
Builder, BuilderField, BuilderPattern, DefaultExpression, DeprecationNotes, Initializer, Setter,
Builder, BuilderField, BuilderPattern, DefaultExpression, DeprecationNotes, Each, Initializer,
Setter,
};

/// `derive_builder` uses separate sibling keywords to represent
Expand Down Expand Up @@ -119,6 +120,40 @@ impl StructLevelSetter {
}
}

/// Adapter that enables:
///
/// 1. Use of a derived `FromMeta` on `Each`,
/// 2. Support for `each = "..."` and `each(name = "...")`
/// 3. The rest of the builder crate to directly access fields on `Each`
struct EachLongOrShort(Each);

/// Create `Each` from an attribute's `Meta`.
///
/// Two formats are supported:
///
/// * `each = "..."`, which provides the name of the `each` setter and otherwise uses default values
/// * `each(name = "...")`, which allows setting additional options on the `each` setter
impl FromMeta for EachLongOrShort {
fn from_value(value: &syn::Lit) -> darling::Result<Self> {
if let syn::Lit::Str(v) = value {
v.parse::<Ident>()
.map(Each::from)
.map(Self)
.map_err(|_| darling::Error::unknown_value(&v.value()).with_span(value))
} else {
Err(darling::Error::unexpected_lit_type(value))
}
}

fn from_list(items: &[syn::NestedMeta]) -> darling::Result<Self> {
Each::from_list(items).map(Self)
}
}

fn unpack_each_shorthand(input: Option<EachLongOrShort>) -> Option<Each> {
input.map(|v| v.0)
}

/// The `setter` meta item on fields in the input type.
/// Unlike the `setter` meta item at the struct level, this allows specific
/// name overrides.
Expand All @@ -131,7 +166,8 @@ pub struct FieldLevelSetter {
strip_option: Option<bool>,
skip: Option<bool>,
custom: Option<bool>,
each: Option<Ident>,
#[darling(map = "unpack_each_shorthand")]
each: Option<Each>,
}

impl FieldLevelSetter {
Expand Down
13 changes: 13 additions & 0 deletions derive_builder_core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,16 @@ impl Default for BuilderPattern {
Self::Mutable
}
}

#[derive(Debug, Clone, FromMeta)]
pub struct Each {
pub name: syn::Ident,
#[darling(default)]
pub into: bool,
}

impl From<syn::Ident> for Each {
fn from(name: syn::Ident) -> Self {
Self { name, into: false }
}
}
29 changes: 24 additions & 5 deletions derive_builder_core/src/setter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use syn;

use BuilderPattern;
use DeprecationNotes;
use Each;

/// Setter for the struct fields in the build method, implementing
/// `quote::ToTokens`.
Expand Down Expand Up @@ -63,7 +64,7 @@ pub struct Setter<'a> {
/// Emit deprecation notes to the user.
pub deprecation_notes: &'a DeprecationNotes,
/// Emit extend method.
pub each: Option<&'a syn::Ident>,
pub each: Option<&'a Each>,
}

impl<'a> ToTokens for Setter<'a> {
Expand Down Expand Up @@ -158,9 +159,11 @@ impl<'a> ToTokens for Setter<'a> {
));
}

if let Some(ref ident_each) = self.each {
if let Some(each) = self.each {
let ident_each = &each.name;

// Access the collection to extend, initialising with default value if necessary.
let field_inner = if stripped_option {
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()
Expand All @@ -173,16 +176,32 @@ impl<'a> ToTokens for Setter<'a> {
))
};

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 <VALUE>(#self_param, item: VALUE) -> #return_ty
#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.#field_inner.extend(::derive_builder::export::core::option::Option::Some(item));
new.#field_ident
.#get_initialized_collection
.extend(::derive_builder::export::core::option::Option::Some(#into_item));
new
}
));
Expand Down

0 comments on commit 1a61c39

Please sign in to comment.