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

Added #[builder(derive(...)] for #43 #84

Merged
merged 6 commits into from
Apr 25, 2017

Conversation

TedDriggs
Copy link
Collaborator

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.

@colin-kiegel
Copy link
Owner

Cool, will have a look the next days (hopefully soon)! :-)

Copy link
Owner

@colin-kiegel colin-kiegel left a 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. :-)

@@ -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, ...))]`.
Copy link
Owner

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))]
Copy link
Owner

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.

Copy link
Collaborator Author

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.

//! }
//! ```
//!
//! Attributes declared for those crates are _not_ forwarded to the fields on the builder.
Copy link
Owner

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?

Copy link
Collaborator Author

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)* )]
Copy link
Owner

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.

Copy link
Owner

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.

Copy link
Collaborator Author

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.

Copy link
Owner

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 assumptions
  • derive_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(),
Copy link
Owner

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

  1. unconditionally push Debug and Clone onto the derives vector
  2. only push Debug and Clone if they are not yet in the vector
  3. or default to [`Debug`, `Clone`], i.e. unwrap_or(vec![syn::Ident::new("Debug"), syn::Ident::new("Clone")])

Copy link
Owner

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.

Copy link
Owner

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())
Copy link
Owner

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.

Copy link
Collaborator Author

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
@colin-kiegel
Copy link
Owner

Hm, there is again a duplicate commit.

This makes it harder to review.

According to git log, the commit 19523fb has a strange parent (e48b24d Colin Kiegel 2017-04-15 clippy). But in github it looks like the parent of 19523fb was 287877a Ted Driggs 2017-04-17 Merge remote-tracking branch 'upstream/master' into build_fn.
git_log

I'm no expert with github pull requests, but I would guess that you made a rebase at some point, either directly git rebase or maybe indirectly git pull --rebase? In that case I would prefer if you did rebases in pull requests only if the changes are so radical, that it's cleaner to start from scratch. If you didn't do a rebase, than I don't understand where this redundant comes from.

Copy link
Owner

@colin-kiegel colin-kiegel left a 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! 🎉

@@ -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.
Copy link
Owner

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())
Copy link
Owner

Choose a reason for hiding this comment

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

nice

@colin-kiegel colin-kiegel merged commit 5fe8c53 into colin-kiegel:master Apr 25, 2017
@TedDriggs TedDriggs deleted the derive branch April 25, 2017 15:06
@TedDriggs
Copy link
Collaborator Author

@colin-kiegel you're right, I tried to rebase to clean up the commit history. Thanks for the merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants