-
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
Allow specifying arbitrary attributes on the builder struct #237
Conversation
I am very conflicted about this PR. I agree that this is powerful, and that #221 - and probably others - would be able to benefit from it. At the same time, I have two specific fears:
Can you elaborate a bit on why you need to deserialize into a builder? That might help me get over fear 2. |
Hi. Thanks for taking a look. I agree that this is powerful and I can see why you want to be careful. I guess my primary response is that I think whether this obscures things too much, or is useful, is a tradeoff that ought to be considered primarily by the project who choose to use this feature (or, indeed, choose not to use it).
Of course. We want this in Arti for the configuration. Making the builder Currently we attempt to do this by having the built struct be
I think you are right and this is wrong. It ought to be capable of handling anything that fits into the content of a |
Barring a major change in When That leaves us with three options:
Of those three, I'm inclined to pursue the third one; it works with any attribute, doesn't require raw strings, and should (I think) be able to preserve span information better for the macro that receives the attributes. Here's a skeleton of what that would look like: /// Data extracted from the fields of the input struct.
#[derive(Debug, Clone, FromField)]
#[darling(
attributes(builder),
forward_attrs(doc, cfg, allow, builder_field_attr),
and_then = "Self::unnest_field_attrs"
)]
pub struct Field {
// ... fields elided ...
}
impl Field {
/// Remove the `builder_field_attr(...)` packaging around attributes meant for fields
/// in the builder.
fn unnest_field_attrs(mut self) -> darling::Result<Self> {
let mut errors = vec![];
for attr in self
.attrs
.iter_mut()
.filter(|attr| attr.path.is_ident("builder_field_attr"))
{
match unnest_attribute(attr) {
Ok(new_attr) => *attr = new_attr,
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
return Err(darling::Error::multiple(errors));
}
Ok(self)
}
}
fn unnest_attribute(_attr: &syn::Attribute) -> darling::Result<Attribute> {
todo!()
} I took a brief stab at pulling the attribute apart and reassembling it, but couldn't figure out how to do so without a panic if the contents of As an aside, I've called this |
@TedDriggs Thanks for the further review and the very helpful comments. I agree that 3 is the best approach. I will rework this MR accordingly. |
I tried the Having played about, I think the best way to do this is to pass an error vec to |
I have a strong preference for doing all validation upfront, so that when |
Well, yes, but
where if the nested attribute feature isn't used, TBH, looking at the existing code, my first thought of where to add the necessary additional processing was in the construction of The alternatives would seem to be:
Given what you've just said, I think possibly Anyway, I'm happy to do it whatever way you prefer so please let me know. Thanks for your attention. |
Or ossibly
and then in the rest, use |
Ah, I see the issue now. I think a little excess cloning is an acceptable tradeoff for keeping this change more self-contained. If it turns out to be a major efficiency hit later, there are ways we could optimize it, such as keeping a separate field that marks which attribute indices are field, setter, or both. |
dccbbb8
to
6ef0a6e
Compare
OK. I have now done this, and force pushed. (It didn't seem to make much sense to read it as an incremental change.) While I was there I added
I haven't done any profiling. I'm not sure how we would discover the marginal effect of this. Personally , having slept on it,I would have thought that the borrowing-capable argument processing (along the lines of my suggestion in #237 (comment)) could be made self-contaned enough, . That's still concentrating the argument processing right at the start. But obviously I will defer to your opinion on this question of taste and I didn't want to get this MR blocked on that. If you like, I could try that out in a separate MR for you to see how it turns 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.
I hadn't anticipated putting multiple attributes inside the outer container like this, but as long as rustfmt can handle it then I'm content with the approach. Thanks for digging so deeply into this.
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.
Per comment thread, let's switch to #[builder_field_attr(serde(...))]
.
I briefly considered the following alternate syntax:
#[builder_attr(field, serde(...))]
However, I think the single ident for the attr is sufficient, and I didn't like the risk of rustfmt putting field
on its own line in that case.
OK, thanks. Will get back to you today. |
Right now we just clone and drain Field::attrs. But these values are going to be distinct in a moment.
This is hard to demonstrate without importing some other crate whose attributes can be used. Choose serde.
This makes the example better and the test more thorough.
I couldn't think of a good example.
6ef0a6e
to
f207dba
Compare
OK, made the syntax change. I chose to put it on as a commit on top, rather than squash it in. I have rebased the branch onto current master. |
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.
Getting closer - I don't think we want to allow multiple attributes in a single #[builder_field_attr(...)]
attribute under this syntax. Limiting it to one makes it more like cfg_attr
, and I'm skeptical it adds much verbosity - hopefully people aren't adding that many distinct proc-macros to their builders.
You are completely right. In fact, the code can only accept one attribute because it strips a |
They were plural. Renaming only in this commit.
git-rebase applied this hunk to the wrong part of the file.
Pushed. Again, I didn't squash anything, not even fixup for the botched merge which I just spotted. Every commit ought to pass the tests still so it shouldn't break bisectability... |
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.
Beautiful work.
Between this and the other changes you've put in, we have enough to merit a publication. Version 0.11.0 will be available once CI passes. |
Thanks :-). |
In arti, we would like to be able to make the builder
Deserialize
. That means that we need to specify our custom serde attributes. There may be other use cases.