diff --git a/derive_builder/CHANGELOG.md b/derive_builder/CHANGELOG.md index 973196d..89007e0 100644 --- a/derive_builder/CHANGELOG.md +++ b/derive_builder/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +- Allow default values that access `self` in combination with the "owned" pattern. #298 + ## [0.20.0] - 2024-02-14 - Bump `syn` to version 2 #308 - Bump `darling` to version 0.20.6 #308 diff --git a/derive_builder/tests/compile-fail/crate_root.stderr b/derive_builder/tests/compile-fail/crate_root.stderr index 8bb4f18..126b13e 100644 --- a/derive_builder/tests/compile-fail/crate_root.stderr +++ b/derive_builder/tests/compile-fail/crate_root.stderr @@ -24,24 +24,6 @@ help: consider importing one of these items 5 | use std::option::Option; | -error[E0433]: failed to resolve: could not find `export` in `empty` - --> tests/compile-fail/crate_root.rs:7:10 - | -7 | #[derive(Builder)] - | ^^^^^^^ not found in `empty::export::core::clone` - | - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -5 | use core::clone::Clone; - | -5 | use derive_builder::export::core::clone::Clone; - | -5 | use serde::__private::Clone; - | -5 | use std::clone::Clone; - | - error[E0433]: failed to resolve: could not find `export` in `empty` --> tests/compile-fail/crate_root.rs:7:10 | @@ -91,6 +73,24 @@ help: consider importing this struct 5 | use derive_builder::UninitializedFieldError; | +error[E0433]: failed to resolve: could not find `export` in `empty` + --> tests/compile-fail/crate_root.rs:7:10 + | +7 | #[derive(Builder)] + | ^^^^^^^ not found in `empty::export::core::clone` + | + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider importing one of these items + | +5 | use core::clone::Clone; + | +5 | use derive_builder::export::core::clone::Clone; + | +5 | use serde::__private::Clone; + | +5 | use std::clone::Clone; + | + error[E0433]: failed to resolve: could not find `export` in `empty` --> tests/compile-fail/crate_root.rs:7:10 | diff --git a/derive_builder/tests/custom_default.rs b/derive_builder/tests/custom_default.rs index 8fd6d21..0229774 100644 --- a/derive_builder/tests/custom_default.rs +++ b/derive_builder/tests/custom_default.rs @@ -136,3 +136,53 @@ mod struct_level { assert_eq!(ipsum.not_type_default, None); } } + +mod owned_field { + + #[derive(Debug, Clone, PartialEq, Eq, Builder)] + #[builder(pattern = "owned")] + struct Lorem { + #[builder(default = "self.ipsum_default()")] + ipsum: String, + + #[builder(setter(skip), default = "self.dolor_default()")] + dolor: String, + } + + impl LoremBuilder { + fn ipsum_default(&self) -> String { + "ipsum".to_string() + } + fn dolor_default(&self) -> String { + "dolor".to_string() + } + } + + #[test] + fn builder_test() { + let x = LoremBuilder::create_empty() + .ipsum("Ipsum".to_string()) + .build() + .unwrap(); + + assert_eq!( + x, + Lorem { + ipsum: "Ipsum".to_string(), + dolor: "dolor".to_string(), + } + ); + } + #[test] + fn defaults_test() { + let x = LoremBuilder::create_empty().build().unwrap(); + + assert_eq!( + x, + Lorem { + ipsum: "ipsum".to_string(), + dolor: "dolor".to_string(), + } + ); + } +} diff --git a/derive_builder_core/src/build_method.rs b/derive_builder_core/src/build_method.rs index 55150b4..4ce1383 100644 --- a/derive_builder_core/src/build_method.rs +++ b/derive_builder_core/src/build_method.rs @@ -5,7 +5,8 @@ use quote::{ToTokens, TokenStreamExt}; use syn::spanned::Spanned; use crate::{ - doc_comment_from, BuilderPattern, DefaultExpression, Initializer, DEFAULT_STRUCT_NAME, + doc_comment_from, BuilderPattern, DefaultExpression, FieldDefaultValue, Initializer, + DEFAULT_STRUCT_NAME, }; /// Initializer for the struct fields in the build method, implementing @@ -57,6 +58,8 @@ pub struct BuildMethod<'a> { pub error_ty: syn::Path, /// Field initializers for the target type. pub initializers: Vec, + /// Default values for the target type + pub defaults: Vec, /// Doc-comment of the builder struct. pub doc_comment: Option, /// Default value for the whole struct. @@ -74,6 +77,7 @@ impl<'a> ToTokens for BuildMethod<'a> { let vis = &self.visibility; let target_ty = &self.target_ty; let target_ty_generics = &self.target_ty_generics; + let defaults = &self.defaults; let initializers = &self.initializers; let self_param = match self.pattern { BuilderPattern::Owned => quote!(self), @@ -100,6 +104,7 @@ impl<'a> ToTokens for BuildMethod<'a> { { #validate_fn #default_struct + #(#defaults)* Ok(#target_ty { #(#initializers)* }) @@ -125,6 +130,16 @@ impl<'a> BuildMethod<'a> { self.initializers.push(quote!(#init)); self } + + /// Populate the `BuildMethod` with appropiate default values of + /// the underlying struct. + /// + /// For each struct field this must be called with the appropriate + /// default value. + pub fn push_default(&mut self, default: FieldDefaultValue) -> &mut Self { + self.defaults.push(quote!(#default)); + self + } } // pub struct BuildMethodError { @@ -150,6 +165,7 @@ macro_rules! default_build_method { target_ty_generics: None, error_ty: syn::parse_quote!(FooBuilderError), initializers: vec![quote!(foo: self.foo,)], + defaults: vec![quote!()], doc_comment: None, default_struct: None, validate_fn: None, diff --git a/derive_builder_core/src/field_default_value.rs b/derive_builder_core/src/field_default_value.rs new file mode 100644 index 0000000..60cb0ea --- /dev/null +++ b/derive_builder_core/src/field_default_value.rs @@ -0,0 +1,298 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; +use syn::Type; + +use crate::{change_span, DefaultExpression, DEFAULT_FIELD_NAME_PREFIX, DEFAULT_STRUCT_NAME}; + +/// Calculates the default value or error for fields, implementing `quote::ToTokens +/// +/// Lives in the body of `BuildMethod`. +/// +/// # Examples +/// +/// Will expand to something like the following (depending on settings): +/// +/// ```rust,ignore +/// # extern crate proc_macro2; +/// # #[macro_use] +/// # extern crate quote; +/// # extern crate syn; +/// # #[macro_use] +/// # extern crate derive_builder_core; +/// # use derive_builder_core::{DeprecationNotes, Initializer, BuilderPattern}; +/// # fn main() { +/// # let mut default = default_field_default_value!(); +/// # let default_value = DefaultExpression::explicit::(parse_quote!(42)); +/// # default.default_value = Some(&default_value); +/// # assert_eq!(quote!(#default).to_string(), quote!( +/// let __default_foo: Option = match self.foo.as_ref() { +/// Some(_) => None, +/// None => Some({ 42 }), +/// }; +/// # ).to_string()); +/// # } +/// ``` +/// In case there is no default value +/// (`default_value == None && use_default_struct == false`) +/// the `None` case will return an error. +#[derive(Debug, Clone)] +pub struct FieldDefaultValue<'a> { + /// Path to the root of the derive_builder crate. + pub crate_root: &'a syn::Path, + /// Name of the target field. + pub field_ident: &'a syn::Ident, + /// Type of the builder field. + pub field_type: &'a Type, + /// Whether the builder implements a setter for this field. + pub field_enabled: bool, + /// Whether the builder uses the default value. + pub enabled: bool, + /// Default value for the target field. + /// + /// This takes precedence over a default struct identifier. + pub default_value: Option<&'a DefaultExpression>, + /// Whether the build_method defines a default struct. + pub use_default_struct: bool, + /// Span where the macro was told to use a preexisting error type, instead of creating one, + /// to represent failures of the `build` method. + /// + /// An initializer can force early-return if a field has no set value and no default is + /// defined. In these cases, it will convert from `derive_builder::UninitializedFieldError` + /// into the return type of its enclosing `build` method. That conversion is guaranteed to + /// work fr generated error types, but if the caller specified an error type to use instead + /// they may have forgotten the conversion from `UninitializedFieldError` into their specified + /// error type. + pub custom_error_type_span: Option, +} + +impl<'a> ToTokens for FieldDefaultValue<'a> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + if !self.enabled { + return; + } + + let struct_field = &self.field_ident; + let builder_field = struct_field; + + let field_type = &self.field_type; + + let default_value = Ident::new( + &format!("{}{}", DEFAULT_FIELD_NAME_PREFIX, struct_field), + Span::call_site(), + ); + + if self.field_enabled { + let default_calculation = self.default_value_calculation(); + tokens.append_all(quote!( + let #default_value: Option<#field_type> = match self.#builder_field.as_ref() { + Some(_) => None, + None => #default_calculation, + }; + )); + } else { + let default_calculation = self.default_value_for_disabled(); + tokens.append_all(quote!( + let #default_value: #field_type = #default_calculation; + )); + } + } +} + +impl<'a> FieldDefaultValue<'a> { + fn default_value_for_disabled(&'a self) -> TokenStream { + let crate_root = self.crate_root; + match self.default_value { + Some(expr) => expr.with_crate_root(crate_root).into_token_stream(), + None if self.use_default_struct => { + let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site()); + let field_ident = self.field_ident; + quote!(#struct_ident.#field_ident) + } + None => { + quote!(#crate_root::export::core::default::Default::default()) + } + } + } + + fn default_value_calculation(&'a self) -> DefaultValue<'a> { + match self.default_value { + Some(expr) => DefaultValue::DefaultTo { + expr, + crate_root: self.crate_root, + }, + None => { + if self.use_default_struct { + DefaultValue::UseDefaultStructField(self.field_ident) + } else { + DefaultValue::ReturnError { + crate_root: self.crate_root, + field_name: self.field_ident.to_string(), + span: self.custom_error_type_span, + } + } + } + } + } +} + +enum DefaultValue<'a> { + /// Inner value must be a valid Rust expression + DefaultTo { + expr: &'a DefaultExpression, + crate_root: &'a syn::Path, + }, + /// Inner value must be the field identifier + /// + /// The default struct must be in scope in the build_method. + UseDefaultStructField(&'a syn::Ident), + /// Inner value must be the field name + ReturnError { + crate_root: &'a syn::Path, + field_name: String, + span: Option, + }, +} + +impl<'a> ToTokens for DefaultValue<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + match *self { + DefaultValue::DefaultTo { expr, crate_root } => { + let expr = expr.with_crate_root(crate_root); + tokens.append_all(quote!(Some(#expr))); + } + DefaultValue::UseDefaultStructField(field_ident) => { + let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site()); + tokens.append_all(quote!( + Some(#struct_ident.#field_ident) + )) + } + DefaultValue::ReturnError { + ref field_name, + ref span, + crate_root, + } => { + let conv_span = span.unwrap_or_else(Span::call_site); + // If the conversion fails, the compiler error should point to the error declaration + // rather than the crate root declaration, but the compiler will see the span of #crate_root + // and produce an undesired behavior (possibly because that's the first span in the bad expression?). + // Creating a copy with deeply-rewritten spans preserves the desired error behavior. + let crate_root = change_span(crate_root.into_token_stream(), conv_span); + let err_conv = quote_spanned!(conv_span => #crate_root::export::core::convert::Into::into( + #crate_root::UninitializedFieldError::from(#field_name) + )); + tokens.append_all(quote!( + return #crate_root::export::core::result::Result::Err(#err_conv) + )); + } + } + } +} + +/// Helper macro for unit tests. This is _only_ public in order to be accessible +/// from doc-tests too. +#[doc(hidden)] +#[macro_export] +macro_rules! default_field_default_value { + () => { + FieldDefaultValue { + // Deliberately don't use the default value here - make sure + // that all test cases are passing crate_root through properly. + crate_root: &parse_quote!(::db), + field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()), + field_type: &Type::Verbatim(proc_macro2::TokenStream::from_str("usize").unwrap()), + field_enabled: true, + enabled: true, + default_value: None, + use_default_struct: false, + custom_error_type_span: None, + } + }; +} + +#[cfg(test)] +mod tests { + + #[allow(unused_imports)] + use super::*; + + use std::str::FromStr; + + #[test] + fn disabled() { + let mut default = default_field_default_value!(); + default.enabled = false; + + assert_eq!(quote!(#default).to_string(), quote!().to_string()); + } + + #[test] + fn disabled_field() { + let mut default = default_field_default_value!(); + default.field_enabled = false; + let default_value = DefaultExpression::explicit::(parse_quote!(42)); + default.default_value = Some(&default_value); + + assert_eq!( + quote!(#default).to_string(), + quote!( + let __default_foo: usize = { 42 }; + ) + .to_string() + ); + } + + #[test] + fn default_value() { + let mut default = default_field_default_value!(); + let default_value = DefaultExpression::explicit::(parse_quote!(42)); + default.default_value = Some(&default_value); + + assert_eq!( + quote!(#default).to_string(), + quote!( + let __default_foo: Option = match self.foo.as_ref() { + Some(_) => None, + None => Some({ 42 }), + }; + ) + .to_string() + ); + } + + #[test] + fn default_struct() { + let mut default = default_field_default_value!(); + default.use_default_struct = true; + + assert_eq!( + quote!(#default).to_string(), + quote!( + let __default_foo: Option = match self.foo.as_ref() { + Some(_) => None, + None => Some(__default.foo), + }; + ) + .to_string() + ); + } + + #[test] + fn no_default() { + let default = default_field_default_value!(); + + assert_eq!( + quote!(#default).to_string(), + quote!( + let __default_foo: Option = match self.foo.as_ref() { + Some(_) => None, + None => return ::db::export::core::result::Result::Err( + ::db::export::core::convert::Into::into( + ::db::UninitializedFieldError::from("foo") + ) + ), + }; + ) + .to_string() + ); + } +} diff --git a/derive_builder_core/src/initializer.rs b/derive_builder_core/src/initializer.rs index d609c15..e78beb3 100644 --- a/derive_builder_core/src/initializer.rs +++ b/derive_builder_core/src/initializer.rs @@ -1,12 +1,13 @@ -use proc_macro2::{Span, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use crate::{change_span, BlockContents, BuilderPattern, DefaultExpression, DEFAULT_STRUCT_NAME}; +use crate::{BlockContents, BuilderPattern, DEFAULT_FIELD_NAME_PREFIX}; /// Initializer for the target struct fields, implementing `quote::ToTokens`. /// /// Lives in the body of `BuildMethod`. /// +/// /// # Examples /// /// Will expand to something like the following (depending on settings): @@ -21,14 +22,10 @@ use crate::{change_span, BlockContents, BuilderPattern, DefaultExpression, DEFAU /// # use derive_builder_core::{DeprecationNotes, Initializer, BuilderPattern}; /// # fn main() { /// # let mut initializer = default_initializer!(); -/// # initializer.default_value = Some("42".parse().unwrap()); /// # initializer.builder_pattern = BuilderPattern::Owned; /// # /// # assert_eq!(quote!(#initializer).to_string(), quote!( -/// foo: match self.foo { -/// Some(value) => value, -/// None => { 42 }, -/// }, +/// foo: self.foo.or(__default_foo).unwrap(), /// # ).to_string()); /// # } /// ``` @@ -42,25 +39,13 @@ pub struct Initializer<'a> { pub field_enabled: bool, /// How the build method takes and returns `self` (e.g. mutably). pub builder_pattern: BuilderPattern, - /// Default value for the target field. - /// - /// This takes precedence over a default struct identifier. - pub default_value: Option<&'a DefaultExpression>, - /// Whether the build_method defines a default struct. - pub use_default_struct: bool, - /// Span where the macro was told to use a preexisting error type, instead of creating one, - /// to represent failures of the `build` method. - /// - /// An initializer can force early-return if a field has no set value and no default is - /// defined. In these cases, it will convert from `derive_builder::UninitializedFieldError` - /// into the return type of its enclosing `build` method. That conversion is guaranteed to - /// work for generated error types, but if the caller specified an error type to use instead - /// 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) + /// If the `conversion` is `FieldConversion::OptionOrDefault` this will + /// use the default value calculated in `FieldDefaultValue`. Otherwise + /// the default value is calculated based on `default_value` and + /// `use_default_struct`. pub conversion: FieldConversion<'a>, } @@ -68,30 +53,24 @@ impl<'a> ToTokens for Initializer<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { let struct_field = &self.field_ident; let builder_field = struct_field; + let default_value = Ident::new( + &format!("{}{}", DEFAULT_FIELD_NAME_PREFIX, struct_field), + Span::call_site(), + ); // 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 - )); + tokens.append(default_value); } else { match &self.conversion { - FieldConversion::Block(conv) => { - conv.to_tokens(tokens); - } - FieldConversion::Move => tokens.append_all(quote!( self.#builder_field )), + 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, - } - )); + let moved_or_cloned = + self.move_or_clone_option(quote!(self.#builder_field)); + tokens.append_all(quote!( #moved_or_cloned.or(#default_value).unwrap())) } + FieldConversion::Block(content) => content.to_tokens(tokens), } } }; @@ -103,48 +82,16 @@ impl<'a> ToTokens for Initializer<'a> { } impl<'a> Initializer<'a> { - /// To be used inside of `#struct_field: match self.#builder_field { ... }` - fn match_some(&'a self) -> MatchSome { - match self.builder_pattern { - BuilderPattern::Owned => MatchSome::Move, - BuilderPattern::Mutable | BuilderPattern::Immutable => MatchSome::Clone { - crate_root: self.crate_root, - }, - } - } - - /// To be used inside of `#struct_field: match self.#builder_field { ... }` - fn match_none(&'a self) -> MatchNone<'a> { - match self.default_value { - Some(expr) => MatchNone::DefaultTo { - expr, - crate_root: self.crate_root, - }, - None => { - if self.use_default_struct { - MatchNone::UseDefaultStructField(self.field_ident) - } else { - MatchNone::ReturnError { - crate_root: self.crate_root, - field_name: self.field_ident.to_string(), - span: self.custom_error_type_span, - } - } - } - } - } - - fn default(&'a self) -> TokenStream { + fn move_or_clone_option(&'a self, value_in_option: TokenStream) -> TokenStream { let crate_root = self.crate_root; - match self.default_value { - Some(expr) => expr.with_crate_root(crate_root).into_token_stream(), - None if self.use_default_struct => { - let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site()); - let field_ident = self.field_ident; - quote!(#struct_ident.#field_ident) - } - None => { - quote!(#crate_root::export::core::default::Default::default()) + + match self.builder_pattern { + BuilderPattern::Owned => value_in_option, + BuilderPattern::Mutable | BuilderPattern::Immutable => { + quote!( + #value_in_option.as_ref() + .map(|value| #crate_root::export::core::clone::Clone::clone(value)) + ) } } } @@ -160,79 +107,6 @@ pub enum FieldConversion<'a> { Move, } -/// To be used inside of `#struct_field: match self.#builder_field { ... }` -enum MatchNone<'a> { - /// Inner value must be a valid Rust expression - DefaultTo { - expr: &'a DefaultExpression, - crate_root: &'a syn::Path, - }, - /// Inner value must be the field identifier - /// - /// The default struct must be in scope in the build_method. - UseDefaultStructField(&'a syn::Ident), - /// Inner value must be the field name - ReturnError { - crate_root: &'a syn::Path, - field_name: String, - span: Option, - }, -} - -impl<'a> ToTokens for MatchNone<'a> { - fn to_tokens(&self, tokens: &mut TokenStream) { - match *self { - MatchNone::DefaultTo { expr, crate_root } => { - let expr = expr.with_crate_root(crate_root); - tokens.append_all(quote!(None => #expr)); - } - MatchNone::UseDefaultStructField(field_ident) => { - let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site()); - tokens.append_all(quote!( - None => #struct_ident.#field_ident - )) - } - MatchNone::ReturnError { - ref field_name, - ref span, - crate_root, - } => { - let conv_span = span.unwrap_or_else(Span::call_site); - // If the conversion fails, the compiler error should point to the error declaration - // rather than the crate root declaration, but the compiler will see the span of #crate_root - // and produce an undesired behavior (possibly because that's the first span in the bad expression?). - // Creating a copy with deeply-rewritten spans preserves the desired error behavior. - let crate_root = change_span(crate_root.into_token_stream(), conv_span); - let err_conv = quote_spanned!(conv_span => #crate_root::export::core::convert::Into::into( - #crate_root::UninitializedFieldError::from(#field_name) - )); - tokens.append_all(quote!( - None => return #crate_root::export::core::result::Result::Err(#err_conv) - )); - } - } - } -} - -/// To be used inside of `#struct_field: match self.#builder_field { ... }` -enum MatchSome<'a> { - Move, - Clone { crate_root: &'a syn::Path }, -} - -impl ToTokens for MatchSome<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - match *self { - Self::Move => tokens.append_all(quote!( - Some(value) => value - )), - Self::Clone { crate_root } => tokens.append_all(quote!( - Some(ref value) => #crate_root::export::core::clone::Clone::clone(value) - )), - } - } -} - /// Helper macro for unit tests. This is _only_ public in order to be accessible /// from doc-tests too. #[doc(hidden)] @@ -246,10 +120,7 @@ macro_rules! default_initializer { field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()), field_enabled: true, builder_pattern: BuilderPattern::Mutable, - default_value: None, - use_default_struct: false, conversion: FieldConversion::OptionOrDefault, - custom_error_type_span: None, } }; } @@ -267,12 +138,9 @@ mod tests { assert_eq!( quote!(#initializer).to_string(), quote!( - foo: match self.foo { - Some(ref value) => ::db::export::core::clone::Clone::clone(value), - None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( - ::db::UninitializedFieldError::from("foo") - )), - }, + foo: self.foo.as_ref() + .map(|value| ::db::export::core::clone::Clone::clone(value)) + .or(__default_foo).unwrap(), ) .to_string() ); @@ -286,12 +154,9 @@ mod tests { assert_eq!( quote!(#initializer).to_string(), quote!( - foo: match self.foo { - Some(ref value) => ::db::export::core::clone::Clone::clone(value), - None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( - ::db::UninitializedFieldError::from("foo") - )), - }, + foo: self.foo.as_ref() + .map(|value| ::db::export::core::clone::Clone::clone(value)) + .or(__default_foo).unwrap(), ) .to_string() ); @@ -305,63 +170,26 @@ mod tests { assert_eq!( quote!(#initializer).to_string(), quote!( - foo: match self.foo { - Some(value) => value, - None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( - ::db::UninitializedFieldError::from("foo") - )), - }, - ) - .to_string() - ); - } - - #[test] - fn default_value() { - let mut initializer = default_initializer!(); - let default_value = DefaultExpression::explicit::(parse_quote!(42)); - initializer.default_value = Some(&default_value); - - assert_eq!( - quote!(#initializer).to_string(), - quote!( - foo: match self.foo { - Some(ref value) => ::db::export::core::clone::Clone::clone(value), - None => { 42 }, - }, + foo: self.foo.or(__default_foo).unwrap(), ) .to_string() ); } #[test] - fn default_struct() { + fn setter_disabled() { let mut initializer = default_initializer!(); - initializer.use_default_struct = true; + initializer.field_enabled = false; assert_eq!( quote!(#initializer).to_string(), quote!( - foo: match self.foo { - Some(ref value) => ::db::export::core::clone::Clone::clone(value), - None => __default.foo, - }, + foo: __default_foo, ) .to_string() ); } - #[test] - fn setter_disabled() { - let mut initializer = default_initializer!(); - initializer.field_enabled = false; - - assert_eq!( - quote!(#initializer).to_string(), - quote!(foo: ::db::export::core::default::Default::default(),).to_string() - ); - } - #[test] fn no_std() { let initializer = default_initializer!(); @@ -369,12 +197,9 @@ mod tests { assert_eq!( quote!(#initializer).to_string(), quote!( - foo: match self.foo { - Some(ref value) => ::db::export::core::clone::Clone::clone(value), - None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( - ::db::UninitializedFieldError::from("foo") - )), - }, + foo: self.foo.as_ref() + .map(|value| ::db::export::core::clone::Clone::clone(value)) + .or(__default_foo).unwrap(), ) .to_string() ); diff --git a/derive_builder_core/src/lib.rs b/derive_builder_core/src/lib.rs index da5737d..1ac88fb 100644 --- a/derive_builder_core/src/lib.rs +++ b/derive_builder_core/src/lib.rs @@ -39,6 +39,7 @@ mod change_span; mod default_expression; mod deprecation_notes; mod doc_comment; +mod field_default_value; mod initializer; mod macro_options; mod options; @@ -53,11 +54,13 @@ 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 field_default_value::FieldDefaultValue; pub(crate) use initializer::{FieldConversion, Initializer}; pub(crate) use options::{BuilderPattern, Each}; pub(crate) use setter::Setter; const DEFAULT_STRUCT_NAME: &str = "__default"; +const DEFAULT_FIELD_NAME_PREFIX: &str = "__default_"; /// Derive a builder for a struct pub fn builder_for_struct(ast: syn::DeriveInput) -> proc_macro2::TokenStream { @@ -83,6 +86,7 @@ pub fn builder_for_struct(ast: syn::DeriveInput) -> proc_macro2::TokenStream { for field in opts.fields() { builder.push_field(field.as_builder_field()); builder.push_setter_fn(field.as_setter()); + build_fn.push_default(field.as_default_value()); build_fn.push_initializer(field.as_initializer()); } diff --git a/derive_builder_core/src/macro_options/darling_opts.rs b/derive_builder_core/src/macro_options/darling_opts.rs index aa0f650..eeeeb4a 100644 --- a/derive_builder_core/src/macro_options/darling_opts.rs +++ b/derive_builder_core/src/macro_options/darling_opts.rs @@ -10,7 +10,7 @@ use syn::{spanned::Spanned, Attribute, Generics, Ident, Meta, Path}; use crate::{ BlockContents, Builder, BuilderField, BuilderFieldType, BuilderPattern, DefaultExpression, - DeprecationNotes, Each, FieldConversion, Initializer, Setter, + DeprecationNotes, Each, FieldConversion, FieldDefaultValue, Initializer, Setter, }; #[derive(Debug, Clone)] @@ -706,6 +706,7 @@ impl Options { target_ty_generics: Some(ty_generics), error_ty: self.builder_error_ident(), initializers: Vec::with_capacity(self.field_count()), + defaults: Vec::with_capacity(self.field_count()), doc_comment: None, default_struct: self.default.as_ref(), validate_fn: self.build_fn.validate.as_ref(), @@ -893,9 +894,23 @@ impl<'a> FieldWithDefaults<'a> { field_enabled: self.field_enabled(), field_ident: self.field_ident(), builder_pattern: self.pattern(), + conversion: self.conversion(), + } + } + + pub fn as_default_value(&'a self) -> FieldDefaultValue<'a> { + let enabled = match self.conversion() { + FieldConversion::OptionOrDefault => true, + FieldConversion::Block(_) | FieldConversion::Move => false, + }; + FieldDefaultValue { + crate_root: &self.parent.crate_root, + field_ident: self.field_ident(), + field_enabled: self.field_enabled(), + enabled, + field_type: &self.field.ty, 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.error.as_ref().and_then(|err_ty| { match err_ty { BuildFnError::Existing(p) => Some(p.span()),