-
Notifications
You must be signed in to change notification settings - Fork 91
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
Conversation
* 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)); | |||
} | |||
|
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.
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.
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 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.
derive_builder_core/src/builder.rs
Outdated
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(), |
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 cache this calculation? Ideally behind an early return (if loop will be empty). :-)
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.
Done.
|
||
#[test] | ||
fn dolor_lorems() { | ||
assert_eq!(make_default_lorem(), make_dolor_lorem(Dolor::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.
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>;
}
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 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()
});
}
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.
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)); |
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 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()); |
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.
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))] |
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 attributes you don't need. Both field(private)
and setter(into)
are not necessary IMO - right?
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.
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.
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 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.
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 ended up keeping it, because I added a test that uses it.
PS: Yeah, I agree that
In
|
I'm partway through moving this logic over; turns out it also requires the
I'll remove the line in |
* 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
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.
@TedDriggs Thank you for this great work!
FYI - I tweaked the changelog message just a little bit. :-)
} else { | ||
Default::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.
very nice! 👍
I like how Builder::compute_impl_bounds()
turned out. :-)
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.
Thanks!
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
andderive_builder_core
at the moment. TheStructOptions
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 passadditional_bounds
toderive_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.