diff --git a/derive_builder/src/lib.rs b/derive_builder/src/lib.rs index cc671fc8..bc3aaf4b 100644 --- a/derive_builder/src/lib.rs +++ b/derive_builder/src/lib.rs @@ -635,6 +635,46 @@ //! # } //! ``` //! +//! # Completely custom fields in the builder +//! +//! Instead of having an `Option`, you can have whatever type you like: +//! +//! ```rust +//! # #[macro_use] +//! # extern crate derive_builder; +//! #[derive(Debug, PartialEq, Default, Builder, Clone)] +//! #[builder(derive(Debug, PartialEq))] +//! struct Lorem { +//! #[builder(setter(into), field(type = "u32"))] +//! ipsum: u32, +//! +//! #[builder(field(type = "String", build = "()"))] +//! dolor: (), +//! +//! #[builder(field(type = "&'static str", build = "self.amet.parse()?"))] +//! amet: u32, +//! } +//! +//! impl From for LoremBuilderError { // ... +//! # fn from(e: std::num::ParseIntError) -> LoremBuilderError { +//! # e.to_string().into() +//! # } +//! # } +//! +//! # fn main() { +//! let mut builder = LoremBuilder::default(); +//! builder.ipsum(42u16).dolor("sit".into()).amet("12"); +//! assert_eq!(builder, LoremBuilder { ipsum: 42, dolor: "sit".into(), amet: "12" }); +//! let lorem = builder.build().unwrap(); +//! assert_eq!(lorem, Lorem { ipsum: 42, dolor: (), amet: 12 }); +//! # } +//! ``` +//! +//! The builder field type (`type =`) must implement `Default`. +//! +//! The argument to `build` must be a literal string containing Rust code for the contents of a block, which must evaluate to the type of the target field. +//! It may refer to the builder struct as `self`, use `?`, etc. +//! //! # **`#![no_std]`** Support (on Nightly) //! //! You can activate support for `#![no_std]` by adding `#[builder(no_std)]` to your struct diff --git a/derive_builder/tests/builder_field_custom.rs b/derive_builder/tests/builder_field_custom.rs new file mode 100644 index 00000000..35e5e8ec --- /dev/null +++ b/derive_builder/tests/builder_field_custom.rs @@ -0,0 +1,51 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate derive_builder; + +use std::num::ParseIntError; + +#[derive(Debug, PartialEq, Default, Builder, Clone)] +pub struct Lorem { + #[builder(field(type = "Option", build = "self.ipsum.unwrap_or(42) + 1"))] + ipsum: usize, + + #[builder(setter(into), field(type = "String", build = "self.dolor.parse()?"))] + dolor: u32, +} + +impl From for LoremBuilderError { + fn from(e: ParseIntError) -> LoremBuilderError { + LoremBuilderError::ValidationError(e.to_string()) + } +} + +#[test] +fn custom_fields() { + let x = LoremBuilder::default().dolor("7").build().unwrap(); + + assert_eq!( + x, + Lorem { + ipsum: 43, + dolor: 7, + } + ); + + let x = LoremBuilder::default() + .ipsum(Some(12)) + .dolor("66") + .build() + .unwrap(); + + assert_eq!( + x, + Lorem { + ipsum: 13, + dolor: 66, + } + ); + + let x = LoremBuilder::default().build().unwrap_err().to_string(); + assert_eq!(x, "cannot parse integer from empty string"); +} diff --git a/derive_builder/tests/compile-fail/builder_field_custom.rs b/derive_builder/tests/compile-fail/builder_field_custom.rs new file mode 100644 index 00000000..1d5c5ade --- /dev/null +++ b/derive_builder/tests/compile-fail/builder_field_custom.rs @@ -0,0 +1,10 @@ +#[macro_use] +extern crate derive_builder; + +#[derive(Debug, PartialEq, Default, Builder, Clone)] +pub struct Lorem { + #[builder(default = "88", field(type = "usize", build = "self.ipsum + 42"))] + ipsum: usize, +} + +fn main() {} diff --git a/derive_builder/tests/compile-fail/builder_field_custom.stderr b/derive_builder/tests/compile-fail/builder_field_custom.stderr new file mode 100644 index 00000000..9730ed5a --- /dev/null +++ b/derive_builder/tests/compile-fail/builder_field_custom.stderr @@ -0,0 +1,5 @@ +error: #[builder(default)] and #[builder(field(build="..."))] cannot be used together + --> tests/compile-fail/builder_field_custom.rs:6:25 + | +6 | #[builder(default = "88", field(type = "usize", build = "self.ipsum + 42"))] + | ^^^^ diff --git a/derive_builder_core/src/block.rs b/derive_builder_core/src/block.rs index 5a626f33..d412f901 100644 --- a/derive_builder_core/src/block.rs +++ b/derive_builder_core/src/block.rs @@ -44,6 +44,21 @@ impl From for BlockContents { } } +impl darling::FromMeta for BlockContents { + fn from_value(value: &syn::Lit) -> darling::Result { + if let syn::Lit::Str(s) = value { + let contents = BlockContents::try_from(s)?; + if contents.is_empty() { + Err(darling::Error::unknown_value("").with_span(s)) + } else { + Ok(contents) + } + } else { + Err(darling::Error::unexpected_lit_type(value)) + } + } +} + #[cfg(test)] mod test { use std::convert::TryInto; diff --git a/derive_builder_core/src/builder_field.rs b/derive_builder_core/src/builder_field.rs index 32a40475..bf336738 100644 --- a/derive_builder_core/src/builder_field.rs +++ b/derive_builder_core/src/builder_field.rs @@ -33,16 +33,8 @@ use syn; pub struct BuilderField<'a> { /// Name of the target field. pub field_ident: &'a syn::Ident, - /// Type of the target field. - /// - /// The corresonding builder field will be `Option`. - pub field_type: &'a syn::Type, - /// Whether the builder implements a setter for this field. - /// - /// Note: We will fallback to `PhantomData` if the setter is disabled - /// to hack around issues with unused generic type parameters - at - /// least for now. - pub field_enabled: bool, + /// Type of the builder field. + pub field_type: BuilderFieldType<'a>, /// Visibility of this builder field, e.g. `syn::Visibility::Public`. pub field_visibility: Cow<'a, syn::Visibility>, /// Attributes which will be attached to this builder field. @@ -51,24 +43,13 @@ pub struct BuilderField<'a> { impl<'a> ToTokens for BuilderField<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { - if self.field_enabled { - let vis = &self.field_visibility; - let ident = self.field_ident; - let ty = self.field_type; - let attrs = self.attrs; - - tokens.append_all(quote!( - #(#attrs)* #vis #ident: ::derive_builder::export::core::option::Option<#ty>, - )); - } else { - let ident = self.field_ident; - let ty = self.field_type; - let attrs = self.attrs; - - tokens.append_all(quote!( - #(#attrs)* #ident: ::derive_builder::export::core::marker::PhantomData<#ty>, - )); - } + let ident = self.field_ident; + let vis = &self.field_visibility; + let ty = &self.field_type; + let attrs = self.attrs; + tokens.append_all(quote!( + #(#attrs)* #vis #ident: #ty, + )); } } @@ -80,16 +61,65 @@ impl<'a> BuilderField<'a> { } } +/// The type of a field in the builder struct +#[derive(Debug, Clone)] +pub enum BuilderFieldType<'a> { + /// The corresonding builder field will be `Option`. + Optional(&'a syn::Type), + /// The corresponding builder field will be just this type + Precise(&'a syn::Type), + /// The corresponding builder field will be a PhantomData + /// + /// We do this if if the field is disabled. We mustn't just completely omit the field from the builder: + /// if we did that, the builder might have unused generic parameters (since we copy the generics from + /// the target struct). Using a PhantomData of the original field type provides the right generic usage + /// (and the right variance). The alternative would be to give the user a way to separately control + /// the generics of the builder struct, which would be very awkward to use and complex to document. + /// We could just include the field anyway, as `Option`, but this is wasteful of space, and it + /// seems good to explicitly suppress the existence of a variable that won't be set or read. + Phantom(&'a syn::Type), +} + +impl<'a> BuilderFieldType<'a> { + /// Obtain type information for the builder field setter + /// + /// Return value: + /// * `.0`: type of the argument to the setter function + /// (before application of `strip_option`, `into`) + /// * `.1`: whether the builder field is `Option` rather than just `type` + pub fn setter_type_info(&'a self) -> (&'a syn::Type, bool) { + match self { + BuilderFieldType::Optional(ty) => (ty, true), + BuilderFieldType::Precise(ty) => (ty, false), + BuilderFieldType::Phantom(_ty) => panic!("phantom fields should never have setters"), + } + } +} + +impl<'a> ToTokens for BuilderFieldType<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + BuilderFieldType::Optional(ty) => tokens.append_all(quote!( + ::derive_builder::export::core::option::Option<#ty> + )), + BuilderFieldType::Precise(ty) => ty.to_tokens(tokens), + BuilderFieldType::Phantom(ty) => tokens.append_all(quote!( + ::derive_builder::export::core::marker::PhantomData<#ty> + )), + } + } +} + /// Helper macro for unit tests. This is _only_ public in order to be accessible /// from doc-tests too. +#[cfg(test)] // This contains a Box::leak, so is suitable only for tests #[doc(hidden)] #[macro_export] macro_rules! default_builder_field { () => {{ BuilderField { field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()), - field_type: &parse_quote!(String), - field_enabled: true, + field_type: BuilderFieldType::Optional(Box::leak(Box::new(parse_quote!(String)))), field_visibility: ::std::borrow::Cow::Owned(parse_quote!(pub)), attrs: &[parse_quote!(#[some_attr])], } @@ -117,7 +147,11 @@ mod tests { #[test] fn setter_disabled() { let mut field = default_builder_field!(); - field.field_enabled = false; + field.field_visibility = Cow::Owned(syn::Visibility::Inherited); + field.field_type = match field.field_type { + BuilderFieldType::Optional(ty) => BuilderFieldType::Phantom(ty), + _ => panic!(), + }; assert_eq!( quote!(#field).to_string(), diff --git a/derive_builder_core/src/default_expression.rs b/derive_builder_core/src/default_expression.rs index ed950bb5..c45f8ca3 100644 --- a/derive_builder_core/src/default_expression.rs +++ b/derive_builder_core/src/default_expression.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use crate::BlockContents; use quote::{ToTokens, TokenStreamExt}; @@ -23,16 +21,7 @@ impl darling::FromMeta for DefaultExpression { } fn from_value(value: &syn::Lit) -> darling::Result { - if let syn::Lit::Str(s) = value { - let contents = BlockContents::try_from(s)?; - if contents.is_empty() { - Err(darling::Error::unknown_value("").with_span(s)) - } else { - Ok(Self::Explicit(contents)) - } - } else { - Err(darling::Error::unexpected_lit_type(value)) - } + Ok(Self::Explicit(BlockContents::from_value(value)?)) } } diff --git a/derive_builder_core/src/initializer.rs b/derive_builder_core/src/initializer.rs index 08cd8006..cf01053f 100644 --- a/derive_builder_core/src/initializer.rs +++ b/derive_builder_core/src/initializer.rs @@ -4,7 +4,7 @@ use syn; use BuilderPattern; use DEFAULT_STRUCT_NAME; -use crate::DefaultExpression; +use crate::{BlockContents, DefaultExpression}; /// Initializer for the target struct fields, implementing `quote::ToTokens`. /// @@ -59,28 +59,47 @@ pub struct Initializer<'a> { /// they may have forgotten the conversion from `UninitializedFieldError` into their specified /// error type. pub custom_error_type_span: Option, + /// Method to use to to convert the builder's field to the target field + /// + /// For sub-builder fields, this will be `build` (or similar) + pub conversion: FieldConversion<'a>, } impl<'a> ToTokens for Initializer<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { let struct_field = &self.field_ident; + let builder_field = struct_field; - if self.field_enabled { - let match_some = self.match_some(); - let match_none = self.match_none(); - let builder_field = &*struct_field; - tokens.append_all(quote!( - #struct_field: match self.#builder_field { - #match_some, - #match_none, - }, - )); - } else { - let default = self.default(); - tokens.append_all(quote!( - #struct_field: #default, - )); - } + // This structure prevents accidental failure to add the trailing `,` due to incautious `return` + let append_rhs = |tokens: &mut TokenStream| { + if !self.field_enabled { + let default = self.default(); + tokens.append_all(quote!( + #default + )); + } else { + match &self.conversion { + FieldConversion::Block(conv) => { + conv.to_tokens(tokens); + } + FieldConversion::Move => tokens.append_all(quote!( self.#builder_field )), + FieldConversion::OptionOrDefault => { + let match_some = self.match_some(); + let match_none = self.match_none(); + tokens.append_all(quote!( + match self.#builder_field { + #match_some, + #match_none, + } + )); + } + } + } + }; + + tokens.append_all(quote!(#struct_field:)); + append_rhs(tokens); + tokens.append_all(quote!(,)); } } @@ -123,6 +142,16 @@ impl<'a> Initializer<'a> { } } +#[derive(Debug, Clone)] +pub enum FieldConversion<'a> { + /// Usual conversion: unwrap the Option from the builder, or (hope to) use a default value + OptionOrDefault, + /// Custom conversion is a block contents expression + Block(&'a BlockContents), + /// Custom conversion is just to move the field from the builder + Move, +} + /// To be used inside of `#struct_field: match self.#builder_field { ... }` enum MatchNone<'a> { /// Inner value must be a valid Rust expression @@ -191,6 +220,7 @@ macro_rules! default_initializer { builder_pattern: BuilderPattern::Mutable, default_value: None, use_default_struct: false, + conversion: FieldConversion::OptionOrDefault, custom_error_type_span: None, } }; diff --git a/derive_builder_core/src/lib.rs b/derive_builder_core/src/lib.rs index 56670ba9..6d39dbf3 100644 --- a/derive_builder_core/src/lib.rs +++ b/derive_builder_core/src/lib.rs @@ -46,12 +46,12 @@ mod setter; pub(crate) use block::BlockContents; pub(crate) use build_method::BuildMethod; pub(crate) use builder::Builder; -pub(crate) use builder_field::BuilderField; +pub(crate) use builder_field::{BuilderField, BuilderFieldType}; use darling::FromDeriveInput; 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 initializer::{FieldConversion, Initializer}; pub(crate) use options::{BuilderPattern, Each}; pub(crate) use setter::Setter; diff --git a/derive_builder_core/src/macro_options/darling_opts.rs b/derive_builder_core/src/macro_options/darling_opts.rs index c8cce54d..b75f34cc 100644 --- a/derive_builder_core/src/macro_options/darling_opts.rs +++ b/derive_builder_core/src/macro_options/darling_opts.rs @@ -10,8 +10,8 @@ use syn::Meta; use syn::{self, spanned::Spanned, Attribute, Generics, Ident, Path}; use crate::{ - Builder, BuilderField, BuilderPattern, DefaultExpression, DeprecationNotes, Each, Initializer, - Setter, + BlockContents, Builder, BuilderField, BuilderFieldType, BuilderPattern, DefaultExpression, + DeprecationNotes, Each, FieldConversion, Initializer, Setter, }; /// `derive_builder` uses separate sibling keywords to represent @@ -125,15 +125,46 @@ impl Visibility for BuildFn { } } -/// Contents of the `field` meta in `builder` attributes. +/// Contents of the `field` meta in `builder` attributes at the struct level. #[derive(Debug, Clone, Default, FromMeta)] -pub struct FieldMeta { +pub struct StructLevelFieldMeta { public: Flag, private: Flag, vis: Option, } -impl Visibility for FieldMeta { +impl Visibility for StructLevelFieldMeta { + fn public(&self) -> &Flag { + &self.public + } + + fn private(&self) -> &Flag { + &self.private + } + + fn explicit(&self) -> Option<&syn::Visibility> { + self.vis.as_ref() + } +} + +/// Contents of the `field` meta in `builder` attributes at the field level. +// +// This is a superset of the attributes permitted in `field` at the struct level. +// Perhaps in the future we will be able to use `#[darling(flatten)]`, but +// that does not exist right now: https://github.com/TedDriggs/darling/issues/146 +#[derive(Debug, Clone, Default, FromMeta)] +pub struct FieldLevelFieldMeta { + public: Flag, + private: Flag, + vis: Option, + /// Custom builder field type + #[darling(rename = "type")] + builder_type: Option, + /// Custom builder field method, for making target struct field value + build: Option, +} + +impl Visibility for FieldLevelFieldMeta { fn public(&self) -> &Flag { &self.public } @@ -252,7 +283,7 @@ fn field_setter(meta: &Meta) -> darling::Result { #[darling( attributes(builder), forward_attrs(doc, cfg, allow, builder_field_attr, builder_setter_attr), - and_then = "Self::unnest_attrs" + and_then = "Self::resolve" )] pub struct Field { ident: Option, @@ -287,7 +318,7 @@ pub struct Field { default: Option, try_setter: Flag, #[darling(default)] - field: FieldMeta, + field: FieldLevelFieldMeta, #[darling(skip)] field_attrs: Vec, #[darling(skip)] @@ -302,17 +333,43 @@ impl Field { errors.finish() } - /// Populate `self.field_attrs` and `self.setter_attrs` by draining `self.attrs` - fn unnest_attrs(mut self) -> darling::Result { - distribute_and_unnest_attrs( + /// Resolve and check (post-parsing) options which come from multiple darling options + /// + /// * Check that we don't have a custom field builder *and* a default value + /// * Populate `self.field_attrs` and `self.setter_attrs` by draining `self.attrs` + fn resolve(mut self) -> darling::Result { + let mut errors = darling::Error::accumulator(); + + // `field.build` is stronger than `default`, as it contains both instructions on how to + // deal with a missing value and conversions to do on the value during target type + // construction. Because default will not be used, we disallow it. + if let Field { + default: Some(field_default), + field: + FieldLevelFieldMeta { + build: Some(_custom_build), + .. + }, + .. + } = &self + { + errors.push( + darling::Error::custom( + r#"#[builder(default)] and #[builder(field(build="..."))] cannot be used together"#, + ) + .with_span(field_default), + ); + }; + + errors.handle(distribute_and_unnest_attrs( &mut self.attrs, &mut [ ("builder_field_attr", &mut self.field_attrs), ("builder_setter_attr", &mut self.setter_attrs), ], - )?; + )); - Ok(self) + errors.finish_with(self) } } @@ -504,7 +561,7 @@ pub struct Options { try_setter: Flag, #[darling(default)] - field: FieldMeta, + field: StructLevelFieldMeta, #[darling(skip, default)] deprecation_notes: DeprecationNotes, @@ -753,10 +810,40 @@ impl<'a> FieldWithDefaults<'a> { self.field .field .as_expressed_vis() + .or_else( + // Disabled fields become a PhantomData in the builder. We make that field + // non-public, even if the rest of the builder is public, since this field is just + // there to make sure the struct's generics are properly handled. + || { + if self.field_enabled() { + None + } else { + Some(Cow::Owned(syn::Visibility::Inherited)) + } + }, + ) .or_else(|| self.parent.field.as_expressed_vis()) .unwrap_or(Cow::Owned(syn::Visibility::Inherited)) } + pub fn field_type(&'a self) -> BuilderFieldType<'a> { + if !self.field_enabled() { + BuilderFieldType::Phantom(&self.field.ty) + } else if let Some(custom_ty) = self.field.field.builder_type.as_ref() { + BuilderFieldType::Precise(custom_ty) + } else { + BuilderFieldType::Optional(&self.field.ty) + } + } + + pub fn conversion(&'a self) -> FieldConversion<'a> { + match (&self.field.field.builder_type, &self.field.field.build) { + (_, Some(block)) => FieldConversion::Block(block), + (Some(_), None) => FieldConversion::Move, + (None, None) => FieldConversion::OptionOrDefault, + } + } + pub fn pattern(&self) -> BuilderPattern { self.field.pattern.unwrap_or(self.parent.pattern) } @@ -782,7 +869,7 @@ impl<'a> FieldWithDefaults<'a> { attrs: &self.field.setter_attrs, ident: self.setter_ident(), field_ident: self.field_ident(), - field_type: &self.field.ty, + field_type: self.field_type(), generic_into: self.setter_into(), strip_option: self.setter_strip_option(), deprecation_notes: self.deprecation_notes(), @@ -802,6 +889,7 @@ impl<'a> FieldWithDefaults<'a> { builder_pattern: self.pattern(), default_value: self.field.default.as_ref(), use_default_struct: self.use_parent_default(), + conversion: self.conversion(), custom_error_type_span: self .parent .build_fn @@ -814,8 +902,7 @@ impl<'a> FieldWithDefaults<'a> { pub fn as_builder_field(&'a self) -> BuilderField<'a> { BuilderField { field_ident: self.field_ident(), - field_type: &self.field.ty, - field_enabled: self.field_enabled(), + field_type: self.field_type(), field_visibility: self.field_vis(), attrs: &self.field.field_attrs, } diff --git a/derive_builder_core/src/setter.rs b/derive_builder_core/src/setter.rs index 68b8d359..5728e659 100644 --- a/derive_builder_core/src/setter.rs +++ b/derive_builder_core/src/setter.rs @@ -5,6 +5,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; use syn; +use BuilderFieldType; use BuilderPattern; use DeprecationNotes; use Each; @@ -54,10 +55,10 @@ pub struct Setter<'a> { pub ident: syn::Ident, /// Name of the target field. pub field_ident: &'a syn::Ident, - /// Type of the target field. + /// Type of the builder field. /// /// The corresonding builder field will be `Option`. - pub field_type: &'a syn::Type, + pub field_type: BuilderFieldType<'a>, /// Make the setter generic over `Into`, where `T` is the field type. pub generic_into: bool, /// Make the setter remove the Option wrapper from the setter, remove the need to call Some(...). @@ -72,23 +73,12 @@ pub struct Setter<'a> { impl<'a> ToTokens for Setter<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { if self.setter_enabled { - let field_type = self.field_type; let pattern = self.pattern; let vis = &self.visibility; let field_ident = self.field_ident; let ident = &self.ident; let attrs = self.attrs; let deprecation_notes = self.deprecation_notes; - let (ty, stripped_option) = { - if self.strip_option { - match extract_type_from_option(field_type) { - Some(ty) => (ty, true), - None => (field_type, false), - } - } else { - (field_type, false) - } - }; let self_param: TokenStream; let return_ty: TokenStream; @@ -117,6 +107,19 @@ impl<'a> ToTokens for Setter<'a> { let param_ty: TokenStream; let mut into_value: TokenStream; + let (field_type, builder_field_is_option) = self.field_type.setter_type_info(); + + let (ty, stripped_option) = { + if self.strip_option { + match extract_type_from_option(field_type) { + Some(ty) => (ty, true), + None => (field_type, false), + } + } else { + (field_type, false) + } + }; + if self.generic_into { ty_params = quote!(>); param_ty = quote!(VALUE); @@ -126,10 +129,15 @@ impl<'a> ToTokens for Setter<'a> { param_ty = quote!(#ty); into_value = quote!(value); } + // If both `stripped_option` and `builder_field_is_option`, the target field is `Option`, + // the builder field is `Option>`, and the setter takes `file_type`, so we must wrap it twice. if stripped_option { - into_value = - quote!(::derive_builder::export::core::option::Option::Some(#into_value)); + into_value = wrap_expression_in_some(into_value); + } + if builder_field_is_option { + into_value = wrap_expression_in_some(into_value); } + tokens.append_all(quote!( #(#attrs)* #[allow(unused_mut)] @@ -138,7 +146,7 @@ impl<'a> ToTokens for Setter<'a> { { #deprecation_notes let mut new = #self_into_return_ty; - new.#field_ident = ::derive_builder::export::core::option::Option::Some(#into_value); + new.#field_ident = #into_value; new } )); @@ -148,6 +156,11 @@ impl<'a> ToTokens for Setter<'a> { quote!(>); let try_ident = syn::Ident::new(&format!("try_{}", ident), Span::call_site()); + let mut converted = quote! {converted}; + if builder_field_is_option { + converted = wrap_expression_in_some(converted); + } + tokens.append_all(quote!( #(#attrs)* #vis fn #try_ident #try_ty_params (#self_param, value: VALUE) @@ -155,7 +168,7 @@ impl<'a> ToTokens for Setter<'a> { { let converted : #ty = value.try_into()?; let mut new = #self_into_return_ty; - new.#field_ident = ::derive_builder::export::core::option::Option::Some(converted); + new.#field_ident = #converted; Ok(new) } )); @@ -212,6 +225,11 @@ impl<'a> ToTokens for Setter<'a> { } } +/// Returns expression wrapping `bare_value` in `Some` +fn wrap_expression_in_some(bare_value: impl ToTokens) -> TokenStream { + quote!( ::derive_builder::export::core::option::Option::Some(#bare_value) ) +} + // adapted from https://stackoverflow.com/a/55277337/469066 // Note that since syn is a parser, it works with tokens. // We cannot know for sure that this is an Option. @@ -273,7 +291,7 @@ macro_rules! default_setter { attrs: &vec![], ident: syn::Ident::new("foo", ::proc_macro2::Span::call_site()), field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()), - field_type: &parse_quote!(Foo), + field_type: BuilderFieldType::Optional(Box::leak(Box::new(parse_quote!(Foo)))), generic_into: false, strip_option: false, deprecation_notes: &Default::default(), @@ -393,7 +411,7 @@ mod tests { let ty = parse_quote!(Option); let mut setter = default_setter!(); setter.strip_option = true; - setter.field_type = &ty; + setter.field_type = BuilderFieldType::Optional(&ty); #[rustfmt::skip] assert_eq!( @@ -418,7 +436,7 @@ mod tests { let mut setter = default_setter!(); setter.strip_option = true; setter.generic_into = true; - setter.field_type = &ty; + setter.field_type = BuilderFieldType::Optional(&ty); #[rustfmt::skip] assert_eq!(