Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow collection setters to be generic over Into #214

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions derive_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
- Change `each` syntax from `#[builder(setter(each = "bar"))]` to
`#[builder(setter(each(name = "bar")))]`.
- Allow collection setters to be generic over `Into`.

## [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
58 changes: 48 additions & 10 deletions derive_builder/tests/setter_extend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,33 @@ use std::collections::HashMap;

#[derive(Debug, PartialEq, Default, Builder, Clone)]
struct Lorem {
#[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>,
}

#[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>,
}

#[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>,
}

Expand Down Expand Up @@ -53,6 +64,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 @@ -82,13 +120,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 @@ -50,7 +50,7 @@ use darling::FromDeriveInput;
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
4 changes: 2 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,7 @@ use proc_macro2::Span;
use syn::{self, spanned::Spanned, Attribute, Generics, Ident, Path, Visibility};

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

/// `derive_builder` uses separate sibling keywords to represent
/// mutually-exclusive visibility states. This trait requires implementers to
Expand Down Expand Up @@ -130,7 +130,7 @@ pub struct FieldLevelSetter {
strip_option: Option<bool>,
skip: Option<bool>,
custom: Option<bool>,
each: Option<Ident>,
each: Option<Each>,
}

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

#[derive(Debug, Clone, FromMeta)]
pub struct Each {
pub name: syn::Ident,
#[darling(default)]
pub into: bool,
}
25 changes: 21 additions & 4 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,19 +159,35 @@ impl<'a> ToTokens for Setter<'a> {
));
}

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

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!(item.into());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
.get_or_insert_with(::derive_builder::export::core::default::Default::default)
.extend(::derive_builder::export::core::option::Option::Some(item));
.extend(::derive_builder::export::core::option::Option::Some(#into_item));
new
}
));
Expand Down