-
Notifications
You must be signed in to change notification settings - Fork 90
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
Changes from 5 commits
fe38226
f0772df
4cec554
85f4d5a
0b59e93
a56e870
6d497bc
24d7eb4
79491a5
fd7deeb
79e4474
0600c05
1510419
22f49f0
ec3cc06
12daa64
c629cbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please change this to |
||
} | ||
|
||
impl DefaultExpression { | ||
|
@@ -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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please change this to |
||
no_std: Option<bool>, | ||
mode: Mode, | ||
} | ||
|
@@ -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, | ||
|
@@ -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, | ||
|
@@ -198,6 +206,9 @@ impl<Mode> OptionsBuilder<Mode> where | |
// setter implicitly enabled | ||
self.setter_enabled(true) | ||
}, | ||
"try_setter" => { | ||
self.try_setter_enabled(true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please set "try_setter" => {
/* ... */
// setter implicitly enabled
self.setter_enabled(true)
} There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
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()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please change this to |
||
/// 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). | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PS: You can either pull all shared variables out of the |
||
trace!("Deriving setter for `{}`.", self.field_ident); | ||
let ty = self.field_type; | ||
let pattern = self.pattern; | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
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); | ||
} | ||
|
@@ -131,6 +154,7 @@ macro_rules! default_setter { | |
() => { | ||
Setter { | ||
enabled: true, | ||
try_enabled: false, | ||
visibility: &syn::Visibility::Public, | ||
pattern: BuilderPattern::Mutable, | ||
attrs: &vec![], | ||
|
There was a problem hiding this comment.
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 thederive_builder_test
folder with the arguments--all --no-fail-fast --features "$CARGO_FEATURES"
. My experience is that the features are only passed to thederive_builder_test
crate, but notderive_builder
orderive_builder_core
unlessderive_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 intoderive_builder_test/tests
and use a feature flag in thederive_builder_test
crate. That also has the advantage that the publicderive_builder
does not expose any feature flag that's useless for users.You can use the
compiletests
feature flag and rename it tonightlytests
. Please also change all occurences offeature = "compiletests"
, but don't change filenamescompiletests.rs
or other occurences of the word. ;-)Let me know if you need any further advice.