From b9fcefa043746608e943ae01634332d2a8ee1f13 Mon Sep 17 00:00:00 2001 From: Andrii Kuteiko Date: Fri, 22 Jan 2021 23:55:58 +0100 Subject: [PATCH] Add an option to extend collection-like fields --- derive_builder/tests/setter_extend.rs | 43 +++++++++++++++++++ .../src/macro_options/darling_opts.rs | 8 ++++ derive_builder_core/src/setter.rs | 29 ++++++++++++- 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 derive_builder/tests/setter_extend.rs diff --git a/derive_builder/tests/setter_extend.rs b/derive_builder/tests/setter_extend.rs new file mode 100644 index 00000000..c9e0ea20 --- /dev/null +++ b/derive_builder/tests/setter_extend.rs @@ -0,0 +1,43 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate derive_builder; + +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Default, Builder, Clone)] +struct Lorem { + #[builder(extend)] + foo: String, + #[builder(extend)] + bar: Vec, + #[builder(extend)] + baz: HashMap, +} + +#[test] +fn generic_field() { + let x = LoremBuilder::default() + .foo("foo".into()) + .bar_extend_one("bar".into()) + .bar_extend_one("bar bar".into()) + .bar_extend_one("bar bar bar".into()) + .foo_extend_one('-') + .baz_extend_one(("baz".into(), 1)) + .baz_extend_one(("bazz".into(), 2)) + .baz_extend_one(("bazzz".into(), 3)) + .foo_extend_one("foo") + .build() + .unwrap(); + + assert_eq!( + x, + Lorem { + foo: "foo-foo".into(), + bar: vec!["bar".into(), "bar bar".into(), "bar bar bar".into()], + baz: vec![("baz".into(), 1), ("bazz".into(), 2), ("bazzz".into(), 3)] + .into_iter() + .collect(), + } + ); +} diff --git a/derive_builder_core/src/macro_options/darling_opts.rs b/derive_builder_core/src/macro_options/darling_opts.rs index 5c59a729..8ab85816 100644 --- a/derive_builder_core/src/macro_options/darling_opts.rs +++ b/derive_builder_core/src/macro_options/darling_opts.rs @@ -238,6 +238,8 @@ pub struct Field { try_setter: Flag, #[darling(default)] field: FieldMeta, + #[darling(default)] + extend: Flag, } impl FlagVisibility for Field { @@ -442,6 +444,11 @@ impl<'a> FieldWithDefaults<'a> { .unwrap_or(true) } + /// Check if this field should emit a setter for extend. + pub fn setter_extend_enabled(&self) -> bool { + self.field.extend.into() + } + pub fn field_enabled(&self) -> bool { self.field .setter @@ -555,6 +562,7 @@ impl<'a> FieldWithDefaults<'a> { generic_into: self.setter_into(), strip_option: self.setter_strip_option(), deprecation_notes: self.deprecation_notes(), + extend: self.setter_extend_enabled(), } } diff --git a/derive_builder_core/src/setter.rs b/derive_builder_core/src/setter.rs index 3f8fcb04..48c22f25 100644 --- a/derive_builder_core/src/setter.rs +++ b/derive_builder_core/src/setter.rs @@ -62,6 +62,8 @@ pub struct Setter<'a> { pub strip_option: bool, /// Emit deprecation notes to the user. pub deprecation_notes: &'a DeprecationNotes, + /// Emit extend + pub extend: bool, } impl<'a> ToTokens for Setter<'a> { @@ -135,7 +137,8 @@ impl<'a> ToTokens for Setter<'a> { let mut new = #self_into_return_ty; new.#field_ident = ::derive_builder::export::core::option::Option::Some(#into_value); new - })); + } + )); if self.try_setter { let try_ty_params = @@ -151,7 +154,28 @@ impl<'a> ToTokens for Setter<'a> { let mut new = #self_into_return_ty; new.#field_ident = ::derive_builder::export::core::option::Option::Some(converted); Ok(new) - })); + } + )); + } + + if self.extend { + let ident_extend_one = format_ident!("{}_extend_one", self.ident); + + tokens.append_all(quote!( + #(#attrs)* + #[allow(unused_mut)] + #vis fn #ident_extend_one (#self_param, item: ITEM) -> #return_ty + where + #ty: ::derive_builder::export::core::default::Default + ::derive_builder::export::core::iter::Extend, + { + #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)); + new + } + )); } } } @@ -222,6 +246,7 @@ macro_rules! default_setter { generic_into: false, strip_option: false, deprecation_notes: &Default::default(), + extend: false, }; }; }