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

Add try_setter option and generate appropriate methods #72

Merged
merged 17 commits into from
Apr 12, 2017
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions derive_builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ proc-macro = true
logging = [ "log", "env_logger", "derive_builder_core/logging" ]
struct_default = []
skeptic_tests = ["skeptic"]
try_setter = []
Copy link
Owner

Choose a reason for hiding this comment

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

The user should not need to set a feature flag to use generate try-setters.

Instead we only need some feature flag to activate the integration tests. The situation with having multiple crates in a workspace and running tests on travis is a bit complicated. When you look at travis.yml:57, you'll see that tests are started in the derive_builder_test folder with the arguments --all --no-fail-fast --features "$CARGO_FEATURES". My experience is that the features are only passed to the derive_builder_test crate, but not derive_builder or derive_builder_core unless derive_builder_test/Cargo.toml specifies to propagate the features (which is does not right now). So the easiest solution is to place all integration tests that require nightly into derive_builder_test/tests and use a feature flag in the derive_builder_test crate. That also has the advantage that the public derive_builder does not expose any feature flag that's useless for users.

You can use the compiletests feature flag and rename it to nightlytests. Please also change all occurences of feature = "compiletests", but don't change filenames compiletests.rs or other occurences of the word. ;-)

Let me know if you need any further advice.


[dependencies]
syn = "0.11"
Expand Down
2 changes: 2 additions & 0 deletions derive_builder/src/options/field_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl OptionsBuilder<FieldMode> {
setter_vis: f!(setter_vis),
default_expression: f!(default_expression),
setter_into: f!(setter_into),
try_setter_enabled: f!(try_setter_enabled),
no_std: f!(no_std),
mode: mode,
}
Expand Down Expand Up @@ -133,6 +134,7 @@ impl From<OptionsBuilder<FieldMode>> for FieldOptions {
field_ident: field_ident,
field_type: field_type,
setter_into: b.setter_into.unwrap_or(false),
try_setter_enabled: b.try_setter_enabled.unwrap_or(false),
deprecation_notes: b.mode.deprecation_notes.clone(),
default_expression: b.default_expression.clone(),
use_default_struct: b.mode.use_default_struct,
Expand Down
3 changes: 3 additions & 0 deletions derive_builder/src/options/field_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct FieldOptions {
pub attrs: Vec<syn::Attribute>,
/// Bindings to libstd or libcore.
pub bindings: Bindings,
/// Enables code generation for the TryInto setter.
pub try_setter_enabled: bool,
Copy link
Owner

Choose a reason for hiding this comment

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

please change this to try_setter

}

impl DefaultExpression {
Expand Down Expand Up @@ -58,6 +60,7 @@ impl FieldOptions {
pub fn as_setter<'a>(&'a self) -> Setter<'a> {
Setter {
enabled: self.setter_enabled,
try_enabled: cfg!(feature = "try_setter") && self.try_setter_enabled,
visibility: &self.setter_visibility,
pattern: self.builder_pattern,
attrs: &self.attrs,
Expand Down
11 changes: 11 additions & 0 deletions derive_builder/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct OptionsBuilder<Mode> {
setter_vis: Option<syn::Visibility>,
default_expression: Option<DefaultExpression>,
setter_into: Option<bool>,
try_setter_enabled: Option<bool>,
Copy link
Owner

Choose a reason for hiding this comment

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

please change this to try_setter

no_std: Option<bool>,
mode: Mode,
}
Expand All @@ -66,6 +67,7 @@ impl<Mode> From<Mode> for OptionsBuilder<Mode> {
setter_prefix: None,
setter_name: None,
setter_vis: None,
try_setter_enabled: None,
default_expression: None,
setter_into: None,
no_std: None,
Expand Down Expand Up @@ -100,6 +102,12 @@ impl<Mode> OptionsBuilder<Mode> where
desc: "setter type conversion",
map: |x: bool| { x },
}

impl_setter!{
ident: try_setter_enabled,
desc: "try_setter activation",
map: |x: bool| { x },
}

impl_setter!{
ident: default_expression,
Expand Down Expand Up @@ -198,6 +206,9 @@ impl<Mode> OptionsBuilder<Mode> where
// setter implicitly enabled
self.setter_enabled(true)
},
"try_setter" => {
self.try_setter_enabled(true)
Copy link
Owner

Choose a reason for hiding this comment

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

please set setter_enabled implicitly if the user specifies #[builder(try_setter)]

"try_setter" => {
    /* ... */
    // setter implicitly enabled
    self.setter_enabled(true)
}

Copy link
Owner

Choose a reason for hiding this comment

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

Or wait - let me think about it again. Initially I thought you were implementing an API like #[builder(setter(try))] as in this comment.

Copy link
Collaborator Author

@TedDriggs TedDriggs Apr 11, 2017

Choose a reason for hiding this comment

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

I think the #[builder(setter(try))] is the better API in the long run, as having a try_{field} without the regular setter seems bad.

Copy link
Owner

Choose a reason for hiding this comment

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

ok, please forget that comment about implicitly enabling the standard setter - as we decided not to piggy back. :-)

}
"default" => {
if !cfg!(feature = "struct_default") && self.mode.struct_mode() {
let where_info = self.where_diagnostics();
Expand Down
1 change: 1 addition & 0 deletions derive_builder/src/options/struct_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl From<OptionsBuilder<StructMode>> for (StructOptions, OptionsBuilder<FieldMo
setter_prefix: b.setter_prefix,
setter_vis: b.setter_vis,
setter_into: b.setter_into,
try_setter_enabled: b.try_setter_enabled,
default_expression: field_default_expression,
no_std: b.no_std,
mode: {
Expand Down
79 changes: 79 additions & 0 deletions derive_builder/tests/try_setter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#![cfg_attr(feature = "try_setter", feature(try_from))]

#[macro_use]
extern crate derive_builder;

#[allow(unused_imports)]
mod struct_level {
#[cfg(feature = "try_setter")]
use std::convert::TryFrom;

use std::net::{IpAddr, AddrParseError};
use std::str::FromStr;
use std::string::ToString;

#[derive(Debug, Clone, PartialEq)]
pub struct MyAddr(IpAddr);

impl From<IpAddr> for MyAddr {
fn from(v: IpAddr) -> Self {
MyAddr(v)
}
}

#[cfg(feature = "try_setter")]
impl<'a> TryFrom<&'a str> for MyAddr {
type Err = AddrParseError;

fn try_from(v: &str) -> Result<Self, Self::Err> {
Ok(MyAddr(v.parse()?))
}
}

#[derive(Debug, PartialEq, Builder)]
#[builder(try_setter, setter(into))]
struct Lorem {
pub source: MyAddr,
pub dest: MyAddr,
}

#[test]
fn infallible_set() {
let _ = LoremBuilder::default().source(IpAddr::from_str("1.2.3.4").unwrap()).build();
}

#[test]
#[cfg(feature = "try_setter")]
fn fallible_set() {
let mut builder = LoremBuilder::default();
let try_result = builder.try_source("1.2.3.4");
let built = try_result.expect("Passed well-formed address")
.dest(IpAddr::from_str("0.0.0.0").unwrap())
.build()
.unwrap();
assert_eq!(built, exact_helper().unwrap());
}

// Allow dead code here since the test that uses this depends on the try_setter feature.
#[cfg_attr(not(feature = "try_setter"), allow(dead_code))]
fn exact_helper() -> Result<Lorem, String> {
LoremBuilder::default()
.source(IpAddr::from_str("1.2.3.4").unwrap())
.dest(IpAddr::from_str("0.0.0.0").unwrap())
.build()
}

#[cfg(feature = "try_setter")]
fn try_helper() -> Result<Lorem, String> {
LoremBuilder::default()
.try_source("1.2.3.4").map_err(|e| e.to_string())?
.try_dest("0.0.0.0").map_err(|e| e.to_string())?
.build()
}

#[test]
#[cfg(feature = "try_setter")]
fn with_helper() {
assert_eq!(exact_helper().unwrap(), try_helper().unwrap());
}
}
9 changes: 9 additions & 0 deletions derive_builder_core/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ impl Bindings {
":: std :: convert :: Into"
})
}

/// TryInto trait.
pub fn try_into_trait(&self) -> RawTokens<&'static str> {
RawTokens(if self.no_std {
":: core :: convert :: TryInto"
} else {
":: std :: convert :: TryInto"
})
}
}

#[test]
Expand Down
26 changes: 25 additions & 1 deletion derive_builder_core/src/setter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ use Bindings;
pub struct Setter<'a> {
/// Enables code generation for this setter fn.
pub enabled: bool,
/// Enables code generation for the `try_` variant of this setter fn.
pub try_enabled: bool,
Copy link
Owner

Choose a reason for hiding this comment

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

please change this to try_setter

/// Visibility of the setter, e.g. `syn::Visibility::Public`.
pub visibility: &'a syn::Visibility,
/// How the setter method takes and returns `self` (e.g. mutably).
Expand All @@ -58,7 +60,7 @@ pub struct Setter<'a> {

impl<'a> ToTokens for Setter<'a> {
fn to_tokens(&self, tokens: &mut Tokens) {
if self.enabled {
if self.enabled || self.try_enabled {
Copy link
Owner

Choose a reason for hiding this comment

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

please remove the || self.try_enabled. If we piggy back the try-variant onto the namespace and code generation of the standard setter, there should be no try-setter without the standard setter.

Copy link
Owner

Choose a reason for hiding this comment

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

PS: You can either pull all shared variables out of the if self.enabled { statement, or just emit two Setter objects per field (as suggested in another comment).

trace!("Deriving setter for `{}`.", self.field_ident);
let ty = self.field_type;
let pattern = self.pattern;
Expand Down Expand Up @@ -117,6 +119,27 @@ impl<'a> ToTokens for Setter<'a> {
new.#field_ident = #option::Some(#into_value);
new
}));

if self.try_enabled {
let try_into = self.bindings.try_into_trait();
let try_ty_params = quote!(<VALUE: #try_into<#ty>>);
let try_ident = syn::Ident::new(format!("try_{}", ident));
let result = self.bindings.result_ty();

tokens.append(quote!(
#(#attrs)*
#vis fn #try_ident #try_ty_params (#self_param, value: VALUE)
-> #result<#return_ty, VALUE::Err>
{
#deprecation_notes
Copy link
Owner

Choose a reason for hiding this comment

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

Please remove this line, because we want to emit this only once per setter combination.
Since we piggy back the try-variant onto the standard setter, it will already be emitted in the standard setter.

let value : #ty = value.try_into()?;
let mut new = #self_into_return_ty;
new.#field_ident = #option::Some(value);
Ok(new)
}));
} else {
trace!("Skipping try_setter for `{}`.", self.field_ident);
}
} else {
trace!("Skipping setter for `{}`.", self.field_ident);
}
Expand All @@ -131,6 +154,7 @@ macro_rules! default_setter {
() => {
Setter {
enabled: true,
try_enabled: false,
visibility: &syn::Visibility::Public,
pattern: BuilderPattern::Mutable,
attrs: &vec![],
Expand Down