diff --git a/derive_builder/CHANGELOG.md b/derive_builder/CHANGELOG.md index 0afa7ccc..e122c2bd 100644 --- a/derive_builder/CHANGELOG.md +++ b/derive_builder/CHANGELOG.md @@ -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 diff --git a/derive_builder/README.md b/derive_builder/README.md index d6a4d306..5c675658 100644 --- a/derive_builder/README.md +++ b/derive_builder/README.md @@ -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`. -- **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` 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. diff --git a/derive_builder/tests/setter_extend.rs b/derive_builder/tests/setter_extend.rs index 1701dda6..91514ac1 100644 --- a/derive_builder/tests/setter_extend.rs +++ b/derive_builder/tests/setter_extend.rs @@ -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, - #[builder(setter(each = "baz"))] + #[builder(setter(each(name = "baz")))] bazes: HashMap, } #[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, - #[builder(setter(each = "baz"))] + #[builder(setter(each(name = "baz")))] + bazes: HashMap, +} + +#[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, + #[builder(setter(each(name = "baz")))] bazes: HashMap, } @@ -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() @@ -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, } #[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] }); } diff --git a/derive_builder_core/src/lib.rs b/derive_builder_core/src/lib.rs index 2970593b..63472823 100644 --- a/derive_builder_core/src/lib.rs +++ b/derive_builder_core/src/lib.rs @@ -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"; diff --git a/derive_builder_core/src/macro_options/darling_opts.rs b/derive_builder_core/src/macro_options/darling_opts.rs index 551d0f08..d56ffd8a 100644 --- a/derive_builder_core/src/macro_options/darling_opts.rs +++ b/derive_builder_core/src/macro_options/darling_opts.rs @@ -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 @@ -130,7 +130,7 @@ pub struct FieldLevelSetter { strip_option: Option, skip: Option, custom: Option, - each: Option, + each: Option, } impl FieldLevelSetter { diff --git a/derive_builder_core/src/options.rs b/derive_builder_core/src/options.rs index 3d6b97c3..93707388 100644 --- a/derive_builder_core/src/options.rs +++ b/derive_builder_core/src/options.rs @@ -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, +} diff --git a/derive_builder_core/src/setter.rs b/derive_builder_core/src/setter.rs index 67f99bbe..41e6f0aa 100644 --- a/derive_builder_core/src/setter.rs +++ b/derive_builder_core/src/setter.rs @@ -5,6 +5,7 @@ use syn; use BuilderPattern; use DeprecationNotes; +use Each; /// Setter for the struct fields in the build method, implementing /// `quote::ToTokens`. @@ -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> { @@ -158,11 +159,27 @@ 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!(>); + param_ty = quote!(FROM_VALUE); + into_item = quote!(::derive_builder::export::core::convert::Into::into(item)); + } else { + ty_params = quote!(); + param_ty = quote!(VALUE); + into_item = quote!(item); + } + tokens.append_all(quote!( #(#attrs)* #[allow(unused_mut)] - #vis fn #ident_each (#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, { @@ -170,7 +187,7 @@ impl<'a> ToTokens for Setter<'a> { 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 } ));