-
Notifications
You must be signed in to change notification settings - Fork 89
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
Conversation
NOT YET DONE * Generate appropriate documentation for `try_` methods * Confirm core::convert::TryInto exists * Finalize declaration (`try_setter` vs. `setter(include_try)` CAVEATS * An error on a try_ with the owned builder pattern is irrecoverable because the builder was consumed and not returned * Without error_chain or the like, error handling can be verbose
Hey, that was fast. Hm, good questions - especially the second one.
I'll look at the code tomorrow. :-) |
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.
Ok, looks good so far. I left some feedback, which I'll also summarize here. :-)
Please incorporate these changes
- rename the
FieldOptions
,OptionsBuilder
andSetter
field totry_setter
. - remove the
|| self.try_enabled
and pull out the shared variables - remove the redundant
#deprecation_notes
And don't forget to
- update changelog
- update documentation in
lib.rs
(optional: Readme) - add unit tests to
derive_builder_core/src/setter.rs
(two variants (a) vanilla settings (b) everything customized, custom name, custom builder pattern, custom privacy etc.) - add integration tests to
derive_builder_test/tests/
(two variants as with the unit tests please). Also see this coment about the neccessary feature gating of these tests.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
please change this to try_setter
derive_builder/src/options/mod.rs
Outdated
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
please change this to try_setter
derive_builder_core/src/setter.rs
Outdated
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
please change this to try_setter
derive_builder_core/src/setter.rs
Outdated
@@ -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 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.
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.
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).
derive_builder_core/src/setter.rs
Outdated
#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 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.
derive_builder/Cargo.toml
Outdated
@@ -25,6 +25,7 @@ proc-macro = true | |||
logging = [ "log", "env_logger", "derive_builder_core/logging" ] | |||
struct_default = [] | |||
skeptic_tests = ["skeptic"] | |||
try_setter = [] |
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 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.
derive_builder/src/options/mod.rs
Outdated
@@ -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 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)
}
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.
Or wait - let me think about it again. Initially I thought you were implementing an API like #[builder(setter(try))]
as in this comment.
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.
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.
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.
ok, please forget that comment about implicitly enabling the standard setter - as we decided not to piggy back. :-)
@TedDriggs before we continue with this PR let's first discuss what behaviour want to have in issue #25 |
* Renamed try_setter_enabled and try_enabled to `try_setter` * Removed excess #deprecation_notes * Removed `|| self.try_enabled`
I was thinking, if we go with the approach (c) discussed here, it might make sense to abstract over I'm absolutely ok with deferring that refactoring to the point where we need it. But if you feel like doing it now, I'd welcome that of course. You decide. ;-) |
There's one more issue I need to address, which is controlling the generation of these based on the presence of the
I think approach 1 is the simplest, but I'm not sure if that makes the determination about exposing these be part of the exporting crate, or the consuming crate. Ideally, the consuming crate's language features would control this. |
} | ||
|
||
fn create(name: &str, addr: &str) -> Result<Lorem, String> { | ||
// Fallible and infallible setters can be mixed freely when using |
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.
@colin-kiegel can you please try running cargo expand --example try_setter
on this and watch what happens to the comment? Something weird is happening here, but I'm not sure what.
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.
Let me guess, does the comment appear at random places in the generated output? :-)
I noticed very strange distributions of comments before. I could imagine that this is caused by the syn crate, which we use to parse the input into an ast. But didn't investigate it, because it didn't cause any trouble.
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.
It seems to consistently get moved into the build
method just before the uninitialized field error string for "name", even when I move it around in the raw source.
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.
I noticed that you shortened the test in derive_builder_test/tests/run-pass/try_setter.rs. I know that I put some testcases in the run-pass
folder where that doesn't add any value.
I think you can just move this whole test case up one level. This will give you all the usual test features. compiletests in the run-pass
folder are really only needed, if you want to assert certain compiler outputs. Like if you want to check that it compiles and runs, but emits a deprecation warning or some other lint. We don't need that here and in that case using compile-tests only makes it more complicated. ;-)
derive_builder_core/src/setter.rs
Outdated
setter.try_setter = true; | ||
|
||
// Had to hoist these out to avoid a recursion limit in the quote! | ||
// macro invocation below. |
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.
no, problem. You can just add #![cfg_attr(test, recursion_limit="100")]
to the crate root. That should increase the recursion limit in test-mode only. Just put in a number that's roughly twice as high as the current minimum requirement. :-)
derive_builder_core/src/setter.rs
Outdated
} | ||
); | ||
|
||
// hoisting necessary to avoid recursion limit. |
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.
same here
|
||
#[macro_use] | ||
extern crate derive_builder; | ||
|
||
#[allow(unused_imports)] | ||
mod struct_level { | ||
#[cfg(feature = "try_setter")] | ||
#[cfg(feature = "try_from")] |
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.
ok, gating on try_from
instead of nightlytests
is actually a good idea here. Please do it consistently in the rest of the test then - that'd be easier to understand. :-)
Alternatively also gate on nightlytests
here.
derive_builder/CHANGELOG.md
Outdated
conversions to the needed type through `TryInto`. This attribute can be used on any | ||
Rust channel, but will only emit setters 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. |
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.
nice summary! 👍
@@ -366,6 +391,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. |
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.
nice catch :-)
derive_builder/src/lib.rs
Outdated
//! } | ||
//! ``` | ||
//! | ||
//! A complete example of using fallible setters is available in `examples/try_setter.rs`. |
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.
please add a note that the name and visibility are inherited from the setter
settings.
You could change the example into something like this, to better explain it?
//! # #[macro_use]
//! # extern crate derive_builder;
//! #
//! #[derive(Builder, Debug, PartialEq)]
//! struct Lorem {
//! #[builder(try_setter, setter(name="foo"))]
//! pub ipsum: u8,
//! }
I think all three variants don't work, because I ran into exactly the same problem with |
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.
can you revive the tests you had in the old version of derive_builder_test/tests/try_setter.rs
?
PS: Are you developing on Linux? I can commit some local shell scripts that I use to emulate what travis is doing. I find them really useful to test all the different combinations nightly with features, stable / beta without..
derive_builder/CHANGELOG.md
Outdated
declared in the consuming crate's root; this will change when Rust issue | ||
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 until `#![feature(try_from)]` is declared |
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.
when until
-> when
Done, and added a more complete
OS X; I can try the scripts to see if they'll work for me. |
ok, I pushed a devtools branch. There is a |
PS: Note running the tests might take a little while, because this will call
|
@colin-kiegel I've pulled down that branch, and found one an issue. It appears OS X doesn't support function base_dir {
if [ uname -s != "Darwin" ] -a hash readlink 2>/dev/null; then
# use readlink, if installed, to follow symlinks
local __DIR="$(dirname "$(readlink -f "$0")")"
else
local __DIR="$(dirname "$0")"
fi
echo ${__DIR}
} OS X appears to reliably return |
That example seems to be causing many/all of the failures.
The basedir() function is supposed to get the directory of the script independently of how it was called. It may seem like overkill to follow symlinks, but actually that's what went wrong, because To workaround this, you can remove the symlink in the #!/bin/bash
../../dev/githook.sh Or search for a way how to follow symlinks on OS X. |
It's great that this was the only issue! :-) Note the skeptic and compile tests sometimes have linking problems, when switching branches, updating dependencies etc. In that case just run |
Okay, so this is interesting... the one that fails says Update: Got bitten by nightly's instability; looks like the associated type got changed to |
I'd |
Or once I make that change everything will work :) |
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.
This implements the method-level proposal described in #25. The initial PR is not ready for merging; it's meant to start a discussion.
Open Questions
TryFrom
andTryInto
implementations will be provided when the feature stabilizes? Right now, the lack of support among built-in types makes this only narrowly useful. It looks like there will be a blanket impl forTryFrom<&'a str>
for things that currently implementFromStr
(source comment)try_from
feature?