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

Improved type param bound expressions (#91) #92

Merged
merged 4 commits into from
Apr 27, 2017

Conversation

TedDriggs
Copy link
Collaborator

This addresses #91 for the Clone trait using the same principles as the built-in #[derive(Clone)] implementation. It's sitting on top of #85, so a diff against that branch may be more useful for seeing the scope of this change.

@colin-kiegel, I'm not 100% happy with the split between derive_builder and derive_builder_core at the moment. The StructOptions has to pass some of the generics twice or else the generated code won't compile. At the moment that's easy to uphold, but I'm wondering if it would be better to pass additional_bounds to derive_builder_core::Builder and have it assemble the correct generics internally.

I deliberately didn't put anything about this in README.md - I think that the behavior after the PR is more "normal" than the current behavior, so trying to explain it would be confusing.

* Builder impl block is now bounded for when generics implement Clone, rather than requiring that declaration on base type
* Updated unit tests in core
* Added integration test in derive_builder/tests
@@ -233,7 +230,7 @@ impl From<OptionsBuilder<StructMode>> for (StructOptions, OptionsBuilder<FieldMo
`field(<vis>)` to the builder attribute on the struct. (Found {})",
where_diagnostics));
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My editor keeps insisting on doing this, and I'm not sure why. I'll see if I can find a setting that will make it stop.

@TedDriggs TedDriggs changed the title Improved bound expressions for #91 Improved type param bound expressions (#91) Apr 26, 2017
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.

Nice work. 👍

Regarding your question about passing the impl_generics vs. additional_generics: I think derive_builder_core::Builder should instead know about BuilderPattern and generate the impl_generics in a private helper method.

I also left some comments.

for mut typ in generics.ty_params.iter_mut() {
typ.bounds.push(syn::TyParamBound::Trait(
syn::PolyTraitRef {
trait_ref: syn::parse_path("::std::clone::Clone").unwrap(),
Copy link
Owner

Choose a reason for hiding this comment

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

Can you cache this calculation? Ideally behind an early return (if loop will be empty). :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


#[test]
fn dolor_lorems() {
assert_eq!(make_default_lorem(), make_dolor_lorem(Dolor::default()));
Copy link
Owner

Choose a reason for hiding this comment

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

Hm, in a test summary dolor_lorems will not be very informative. :-)

Please try to describe the test property in the function name.

If I understand this correctly, it's actually a compile-test in the sense that hey this compiles now, but it didn't before. This test is actually only asserting something (almost) arbitrary in order to show up in the test summary, right?

In that case I would prefer to turn it into something like this

#[test]
fn generic_field_without_clone_compiles() {
    // This is actually a compile test, if we can _name_ `LoremBuilder<Dolor>`, we are already fine.
    // But we will not be able to call any setters or build method on it, because that would
    // require the field to implement `Clone`.
    let _: LoremBuilder<Dolor>;
}

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 I'd also like another test - like this:

#[test]
fn generic_field_without_clone_has_owned_builder() {
    assert_eq!(OwnedLoremBuilder::default()
                   .ipsum(Dolor::default())
                   .build()
                   .unwrap(),
               OwnedLorem {
                   ipsum: Dolor::default()
               });
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tests have been cleaned up and expanded. As far as your second comment, you got a test that looks exactly like that 😛


#[test]
fn u16_lorems() {
assert_eq!(make_u16_lorem(10), build_u16_lorem(10));
Copy link
Owner

Choose a reason for hiding this comment

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

please use a more informative title (see below). Also since most methods are used only once, I think it's easier to read inlined, no?

E.g.

#[test]
fn generic_field_with_clone_has_builder_impl() {
    assert_eq!(LoremBuilder::default()
                   .ipsum(10)
                   .build()
                   .unwrap(),
               Lorem {
                   ipsum: 10
               });
}

#[test]
fn type_inference() {
assert_eq!(LoremBuilder::<String>::default().ipsum("".to_string()).build().unwrap(),
make_default_lorem());
Copy link
Owner

Choose a reason for hiding this comment

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

title is fine, but please put the thing you want to test on the left side and try to keep the other side simple. E.g. something like this perhaps?

#[test]
fn type_inference() {
    assert_eq!(make_default_lorem(),
               Lorem { ipsum: "".to_string() });
}

I prefer a pattern where the left side does all fragile computations and the right side is as robust/straight-forward as possible. If there is an error this will make it easier to trace it down, because you can concentrate on one arm of the equality. :-)

/// Notice that this type derives Builder without disallowing
/// `Lorem<Dolor>`.
#[derive(Debug, Clone, Builder, PartialEq, Eq)]
#[builder(field(private), setter(into))]
Copy link
Owner

Choose a reason for hiding this comment

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

please remove the attributes you don't need. Both field(private) and setter(into) are not necessary IMO - right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

field(private) was needed to squelch the warnings about public fields. You're right that I don't need setter(into); I'll remove that.

Copy link
Owner

Choose a reason for hiding this comment

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

I know, we have deprecations all over the place. :-)

But we also run all tests a second time with feature = "private_fields". So I think it's best not to add #[builder(fields(private))] on every test, but rather live with the deprecations for one cycle.

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 ended up keeping it, because I added a test that uses it.

@colin-kiegel
Copy link
Owner

colin-kiegel commented Apr 26, 2017

PS: Yeah, I agree that README is only to showcase important stuff. But this sentence is not true anymore:

README.md:99:127 All builders derive Default and Clone, so you should not declare those in this attribute.

In lib.rs:491 this can should be removed: :-)

//! - Generic structs need the boundary where T: std::clone::Clone if
//! used in combination with the immutable/mutable pattern

@TedDriggs
Copy link
Collaborator Author

I'm partway through moving this logic over; turns out it also requires the derive_builder_core::Builder struct to know about the bindings. Not a big deal, but wanted to flag it for you.

README.md:99:127 is true; the builder derives Default and Clone, so you shouldn't declare those in the derive(...) attribute or we'll emit a warning. The built-in derives for those types already apply bounds checks like the ones I'm adding for the builder's inherent impl.

I'll remove the line in lib.rs.

* Moved trait bounding logic to derive_builder_core
* Cache trait bound for use across all type params
* Fixed tests
* Removed "gotcha" from lib.rs doc comment
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.

@TedDriggs Thank you for this great work!

FYI - I tweaked the changelog message just a little bit. :-)

} else {
Default::default()
}
}
Copy link
Owner

@colin-kiegel colin-kiegel Apr 27, 2017

Choose a reason for hiding this comment

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

very nice! 👍

I like how Builder::compute_impl_bounds() turned out. :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks!

@colin-kiegel colin-kiegel merged commit aa7e6e0 into colin-kiegel:master Apr 27, 2017
@TedDriggs TedDriggs deleted the generic_bounds branch April 27, 2017 15:11
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