-
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
Added #[builder(derive(...)] for #43 #84
Conversation
Cool, will have a look the next days (hopefully soon)! :-) |
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.
Looks good.
However, I'd like to discuss, how we want to treat the auto-derivation of Debug
and Clone
. I feel like we can improve the story a little here. :-)
derive_builder/README.md
Outdated
@@ -93,6 +93,7 @@ with [cargo-edit](https://github.com/killercup/cargo-edit): | |||
* **Setter type conversions**: With `#[builder(setter(into))]`, setter methods will be generic over the input types – you can then supply every argument that implements the [`Into`][into] trait for the field type. | |||
* **Generic structs**: Are also supported, but you **must not** use a type parameter named `VALUE`, if you also activate setter type conversions. | |||
* **Default values**: You can use `#[builder(default)]` to delegate to the `Default` implementation or any explicit value via `=".."`. This works both on the struct and field level. | |||
* **Builder derivations**: You can have the builder derive additonal traits using `#[builder(derive(Trait1, Trait2, ...))]`. |
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.
in README.md
I prefer example style, e.g.
You can use `#[builder(derive(Copy, PartialEq, ...))]` to derive additional traits on the builder struct.
struct NotPartialEq(String); | ||
|
||
#[derive(Debug, Clone, Builder)] | ||
#[builder(derive(Debug, PartialEq, Eq))] |
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 tests that (one of these) is actually implemented.
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 actually have that; the defaults
test down below will fail if LoremBuilder
doesn't implement PartialEq
and Debug
. I've made this more explicit with a comment in the test.
derive_builder/src/lib.rs
Outdated
//! } | ||
//! ``` | ||
//! | ||
//! Attributes declared for those crates are _not_ forwarded to the fields on the builder. |
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.
What do you mean by crates here? Do you mean struct instead?
Please add another note, that we derive Default
and Clone
on the builder unconditionally. Well, unless we change that ^^Alternatively this would be our chance to say Default
and Clone
are only derived automatically if the user didn't specify #[builder(derive)]
. This would allow to opt-out of these derivations - but I feel like they don't do any harm. I lean towards unconditionally deriving them. What do you think?
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.
That should be "traits", not "crates" - I'll fix that. I agree with unconditional derivation for the Debug
and Clone
traits.
@@ -80,7 +83,7 @@ impl<'a> ToTokens for Builder<'a> { | |||
impl_generics); | |||
|
|||
tokens.append(quote!( | |||
#[derive(Default, Clone)] | |||
#[derive(Default, Clone #( , #derives)* )] |
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 it would be cleaner, if we move Default
and Clone
into the derive options - even, if we set it unconditionally.
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: In that case also remove Default
and Clone
from the unit tests - since we already have the testcase add_derives
.
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 thought about this, but as long as these are going to be unconditionally present it felt strange to hide them far from the actual rendering of the builder.
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.
TL;DR: Ok,let's just keep it as is :-)
My line of thinking was this:
derive_builder_core
is just a toolkit for code generation and should make minimal assumptionsderive_builder
populates the settings and sets all defaults - i.e. most decisions should be here
But I just realized that derive_builder_core
assumes the builder to implement Clone
for immutable/mutable setters. So we already know in the scope of derive_builder_core
that we need Clone
for consistency. With this minimal reasoning rule of thumb the decision about deriving Clone
should in fact stay in derive_builder_core
. Now if only had Default
, we could move at least that to derive_builder
. But since we have both - let's just keep it as is. :-)
@@ -97,6 +124,7 @@ impl From<OptionsBuilder<StructMode>> for (StructOptions, OptionsBuilder<FieldMo | |||
builder_visibility: m.builder_vis.unwrap_or(m.build_target_vis), | |||
builder_pattern: b.builder_pattern.unwrap_or_default(), | |||
build_target_ident: syn::Ident::new(m.build_target_name), | |||
derives: m.derive_traits.unwrap_or_default(), |
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.
Depending on which behaviour we want, I'd suggest either
- unconditionally push
Debug
andClone
onto the derives vector - only push
Debug
andClone
if they are not yet in the vector - or default to
[`Debug`, `Clone`]
, i.e.unwrap_or(vec![syn::Ident::new("Debug"), syn::Ident::new("Clone")])
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: I lean towards 1 or 2.
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.
If we pick 2, we could emit a (deprecation) warning which informs the user that Debug
and Clone
derives are already implicit and that deriving them explicitly is redundant? (deprecations and panics are the only ways we can currently communicate with the user).
// We don't allow name-value pairs or further nesting here, so | ||
// only look for words. | ||
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref tr)) => { | ||
traits.push(tr.clone()) |
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.
question is, whether we want to do any checks like tr == "Debug"
| "Clone"
at this place. But I guess it would feel a bit hacky to have the information about Debug
and Clone
being automatically derived scattered about multiple places. It's probably ok, to not have any checks here.
Note: It's worth looking at how the Rust compiler complains about this
IN
#[derive(Builder)]
#[builder(derive(Default))]
struct Lorem {
OUT
error[E0119]: conflicting implementations of trait `std::default::Default` for type `LoremBuilder`:
--> derive_builder/tests/derive_trait.rs:7:10
|
7 | #[derive(Builder)]
| -------
| |
| conflicting implementation for `LoremBuilder`
| first implementation here
So the error message is ok-ish, but not perfect. I think it would be nicer, if this would either just work without error, or if the builder would panic with a custom message.
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've added a warning at the location in the code review. At the moment it also suppresses forwarding the trait, but I can remove that so we push the warning and then let compilation fail.
* Reword README.md * Filter Default and Clone * Clarify how test works
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.
Looks good - going to merge. :-)
Thank you! 🎉
derive_builder/tests/derive_trait.rs
Outdated
@@ -17,5 +17,7 @@ struct Lorem { | |||
|
|||
#[test] | |||
fn defaults() { | |||
// This macro requires that the two sides implement `PartialEq` AND `Debug`, | |||
// so this one line is testing that the requested traits were in fact generated. |
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.
cool :-)
format!("The `Default` and `Clone` traits are automatically added to all \ | ||
builders; explicitly deriving them is unnecessary ({})", where_diag)); | ||
}, | ||
_ => traits.push(tr.clone()) |
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
@colin-kiegel you're right, I tried to rebase to clean up the commit history. Thanks for the merge! |
Addresses #43; enables builder to have its own additional derive statements. This PR does not include attribute forwarding on fields; there are still good questions about the future of that in #79. This does add immediate value for debugging and testing, as it allows generation of equality tests and debug output.