Skip to content

Commit

Permalink
Merge pull request #72 from Teddriggs/try_setter
Browse files Browse the repository at this point in the history
Added
- try_setters, e.g. `#[builder(try_setter)]`. These setters are exposed alongside the
  normal field setters and allow callers to pass in values which have
  fallible conversions to the needed type through `TryInto`. This attribute
  can only be used on nightly when `#![feature(try_from)]` is declared
  in the consuming crate's root; this will change when Rust issue
  [#33417](rust-lang/rust#33417) is resolved.
  • Loading branch information
colin-kiegel committed Apr 12, 2017
2 parents 9260f96 + c629cbd commit 5a57ad6
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ matrix:
- rust: 1.15.0
env: JOB=build CARGO_FEATURES="struct_default"
- rust: nightly
env: JOB=build CARGO_FEATURES="compiletests logging"
env: JOB=build CARGO_FEATURES="nightlytests logging"
- rust: nightly
env: JOB=style_check
allow_failures:
Expand Down
10 changes: 10 additions & 0 deletions derive_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Added
- try_setters, e.g. `#[builder(try_setter)]`. These setters are exposed
alongside the normal field setters and allow callers to pass in values which
have fallible conversions to the needed type through `TryInto`. This
attribute can only be used on nightly when `#![feature(try_from)]` is
declared in the consuming crate's root; this will change when Rust issue
[#33417](https://github.com/rust-lang/rust/issues/33417) is resolved.

## [0.4.3] - 2017-04-11

### Fixed
Expand Down
47 changes: 47 additions & 0 deletions derive_builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,50 @@
//! }
//! ```
//!
//! ## Fallible Setters
//!
//! Alongside the normal setter methods, you can expose fallible setters which are generic over
//! the `TryInto` trait. TryInto is a not-yet-stable trait
//! (see rust-lang issue [#33417](https://github.com/rust-lang/rust/issues/33417)) similar to
//! `Into` with the key distinction that the conversion can fail, and therefore produces a
//! `Result`.
//!
//! You can only declare the `try_setter` attribute today if you're targeting nightly, and you have
//! to add `#![feature(try_from)]` to your crate to use it.
//!
//! ```rust,ignore
//! #![feature(try_from)]
//! # #[macro_use]
//! # extern crate derive_builder;
//! #
//! #[derive(Builder, Debug, PartialEq)]
//! #[builder(try_setter, setter(into))]
//! struct Lorem {
//! pub name: String,
//! pub ipsum: u8,
//! }
//!
//! #[derive(Builder, Debug, PartialEq)]
//! struct Ipsum {
//! #[builder(try_setter, setter(into, name = "foo"))]
//! pub dolor: u8,
//! }
//!
//! fn main() {
//! LoremBuilder::default()
//! .try_ipsum(1u16).unwrap()
//! .name("hello")
//! .build()
//! .expect("1 fits into a u8");
//!
//! IpsumBuilder::default()
//! .try_foo(1u16)
//! .unwrap()
//! .build()
//! .expect("1 fits into a u8");
//! }
//! ```
//!
//! ## Default Values
//!
//! You can define default values for each field via annotation by `#[builder(default="...")]`,
Expand Down Expand Up @@ -366,6 +410,9 @@
//! - If derive_builder depends on your crate, and vice versa, then a cyclic
//! dependency would occur. To break it you could try to depend on the
//! [`derive_builder_core`] crate instead.
//! - The `try_setter` attribute and `owned` builder pattern are not compatible in practice;
//! an error during building will consume the builder, making it impossible to continue
//! construction.
//!
//! ## Debugging Info
//!
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: f!(try_setter),
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: b.try_setter.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: bool,
}

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_setter: self.try_setter,
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: Option<bool>,
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: 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,
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(true)
}
"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: b.try_setter,
default_expression: field_default_expression,
no_std: b.no_std,
mode: {
Expand Down
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
1 change: 1 addition & 0 deletions derive_builder_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//! [`derive_builder_core`]: https://!crates.io/crates/derive_builder_core
#![deny(warnings, missing_docs)]
#![cfg_attr(test, recursion_limit = "100")]

extern crate proc_macro;
extern crate syn;
Expand Down
58 changes: 57 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_setter: bool,
/// 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 Down Expand Up @@ -117,6 +119,26 @@ impl<'a> ToTokens for Setter<'a> {
new.#field_ident = #option::Some(#into_value);
new
}));

if self.try_setter {
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::Error>
{
let converted : #ty = value.try_into()?;
let mut new = #self_into_return_ty;
new.#field_ident = #option::Some(converted);
Ok(new)
}));
} else {
trace!("Skipping try_setter for `{}`.", self.field_ident);
}
} else {
trace!("Skipping setter for `{}`.", self.field_ident);
}
Expand All @@ -131,6 +153,7 @@ macro_rules! default_setter {
() => {
Setter {
enabled: true,
try_setter: false,
visibility: &syn::Visibility::Public,
pattern: BuilderPattern::Mutable,
attrs: &vec![],
Expand Down Expand Up @@ -221,7 +244,7 @@ mod tests {
));
}

// including
// including try_setter
#[test]
fn full() {
let attrs = vec![syn::parse_outer_attr("#[some_attr]").unwrap()];
Expand All @@ -233,6 +256,7 @@ mod tests {
setter.attrs = attrs.as_slice();
setter.generic_into = true;
setter.deprecation_notes = &deprecated;
setter.try_setter = true;

assert_eq!(quote!(#setter), quote!(
#[some_attr]
Expand All @@ -242,6 +266,15 @@ mod tests {
new.foo = ::std::option::Option::Some(value.into());
new
}

#[some_attr]
pub fn try_foo<VALUE: ::std::convert::TryInto<Foo>>(&mut self, value: VALUE)
-> ::std::result::Result<&mut Self, VALUE::Error> {
let converted : Foo = value.try_into()?;
let mut new = self;
new.foo = ::std::option::Option::Some(converted);
Ok(new)
}
));
}

Expand Down Expand Up @@ -282,4 +315,27 @@ mod tests {

assert_eq!(quote!(#setter), quote!());
}

#[test]
fn try_setter() {
let mut setter: Setter = default_setter!();
setter.pattern = BuilderPattern::Mutable;
setter.try_setter = true;

assert_eq!(quote!(#setter), quote!(
pub fn foo(&mut self, value: Foo) -> &mut Self {
let mut new = self;
new.foo = ::std::option::Option::Some(value);
new
}

pub fn try_foo<VALUE: ::std::convert::TryInto<Foo>>(&mut self, value: VALUE)
-> ::std::result::Result<&mut Self, VALUE::Error> {
let converted : Foo = value.try_into()?;
let mut new = self;
new.foo = ::std::option::Option::Some(converted);
Ok(new)
}
));
}
}
2 changes: 1 addition & 1 deletion derive_builder_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ publish = false
path = "src/lib.rs"

[features]
compiletests = ["compiletest_rs"]
nightlytests = ["compiletest_rs"]
logging = [ "derive_builder/logging" ]
struct_default = [ "derive_builder/struct_default" ]

Expand Down
2 changes: 1 addition & 1 deletion derive_builder_test/tests/compiletests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![cfg(feature = "compiletests")]
#![cfg(feature = "nightlytests")]
extern crate compiletest_rs as compiletest;

// note:
Expand Down
2 changes: 1 addition & 1 deletion derive_builder_test/tests/run-pass/no_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct IgnoreEmptyStruct {}
struct Foo {
#[builder(default)]
defaulted: u32,
#[builder(setter(skip))]
#[builder(setter(skip), try_setter)]
skipped: u32,
}

Expand Down
Loading

0 comments on commit 5a57ad6

Please sign in to comment.