-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
macros 1.1: Allow proc_macro functions to declare attributes to be mark as used #37614
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @arielb1 (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
955688e
to
3030be2
Compare
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.
Awesome, thanks! Could you also be sure to add a number of tests for this functionality as well? It'd also be good to see some feature-gate related tests to ensure this attribute is gated appropriately as well.
I believe you'll also need to update the existing tests as the semantics of whether the input item is expected in the output has changed.
@@ -820,7 +826,7 @@ impl<'a> Context<'a> { | |||
// feature gate checking. Macro gating runs | |||
// before the plugin attributes are registered | |||
// so we skip this then | |||
if !is_macro { | |||
if !is_macro && !attr::is_used(attr) { |
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.
How come this part was modified as well? Presumably this means that we're not gating unused attributes now?
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.
Using mark_used appears to only disable the unused_attribute lint, not the custom_attributes error. So without this change the compiler will still give the custom attribute error for used attributes. The only alternative I can see is registering the attribute name in plugin_attributes.
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.
@alexcrichton It means we're not custom_attribute
-checking used attributes, which should be fine because unknown/custom attributes should never be mark_used
.
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.
"because unknown/custom attributes should never be mark_used" - where is this enforced?
let proc_attrs: Vec<_> = item.attrs.iter() | ||
.filter(|a| a.check_name("proc_macro_attributes")) | ||
.filter_map(|a| | ||
a.meta_item_list().or_else(|| { |
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.
This spacing/indentation here is a bit difficult to read, could this be updated to follow the surrounding code?
Thanks for the feedback, I'll work on the tests and get back to you. |
let derive = SyntaxExtension::CustomDerive(Box::new(CustomDerive::new(expand))); | ||
expand: fn(TokenStream) -> TokenStream, | ||
attributes: &[&'static str]) { | ||
let attrs: Vec<_> = attributes.iter().map(|s| InternedString::new(s)).collect(); |
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.
nit: .map(|s| InternedString::new(s))
-> .map(InternedString::new)
attributes: &[&'static str]) { | ||
let attrs: Vec<_> = attributes.iter().map(|s| InternedString::new(s)).collect(); | ||
let derive = SyntaxExtension::CustomDerive( | ||
Box::new(CustomDerive::new(expand, &attrs)) |
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.
This should pass attrs
directly instead of by reference so that CustomDerive::new
doesn't need to clone.
@@ -509,7 +509,7 @@ pub enum SyntaxExtension { | |||
/// | |||
IdentTT(Box<IdentMacroExpander>, Option<Span>, bool), | |||
|
|||
CustomDerive(Box<MultiItemModifier>), | |||
CustomDerive(Box<MultiItemDecorator>), |
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.
This should stay MultiItemModifier
.
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.
Why? I'd have thought Decorator is more appropriate?
"proc_macro", | ||
"the `#[proc_macro_attributes]` attribute \ | ||
is an experimental feature", | ||
cfg_fn!(proc_macro))), |
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.
We don't need to add a new kind of attribute -- extending proc_macro_derive
is fine.
use syntax::print::pprust; | ||
use syntax::parse::token::InternedString; | ||
|
||
fn mark_attrs_used<I: Iterator<Item=Attribute>>(attrs: I, whitelist: &[InternedString]) { |
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.
This should be a Visitor
.
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.
e.g. something like
struct MarkAttrsUsed<'a>(&'a [InternedString]);
impl<'a> Visitor for MarkAttrsUsed<'a> {
fn visit_attribute(&mut self, attr: &Attribute) {
if self.0.contains(attr.name()) {
attr::mark_used(attr);
}
}
}
pub fn new(inner: fn(TokenStream) -> TokenStream) -> CustomDerive { | ||
CustomDerive { inner: inner } | ||
pub fn new(inner: fn(TokenStream) -> TokenStream, | ||
attrs: &[InternedString]) |
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.
This should take a Vec<InternedString>
.
&self.attrs), | ||
ItemKind::Enum(ref def, ..) => | ||
mark_attrs_used(def.variants.iter().flat_map(|f| f.node.attrs.iter()).cloned(), | ||
&self.attrs), |
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 use a visitor, these calls should be able to be replaced with MarkAttrsUsed(&self.attrs).visit_item(item_ref)
.
// macros, everything is just parsed as a string. Reassign all spans to | ||
// the input `item` for better errors here. | ||
item.into_iter().flat_map(|item| { | ||
ChangeSpan { span: input_span }.fold_item(item) |
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.
We still want to ChangeSpan
the new impls generated by custom derives.
What is the motivation for that? I'd prefer a single attribute as proposed by @eddyb.
I would prefer not to do this. I think we should keep separate the concepts of used and allowed attributes. White-listing an attribute should make it allowed (scoped to the derive uses, so I guess the white-list has to be per-derive) and mark any uses as used. Other attributes could be marked used by a proc macro, but should still trigger the custom attribute lint.
Yes. |
So a few things I've done:
|
r? @jseyfried |
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 great! r=me modulo nits.
Could you squash some of the update commits and force-push?
} | ||
|
||
|
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.
nit: extra newline
@@ -133,7 +134,8 @@ impl<'a> Visitor for CollectCustomDerives<'a> { | |||
} | |||
|
|||
// Once we've located the `#[proc_macro_derive]` attribute, verify | |||
// that it's of the form `#[proc_macro_derive(Foo)]` | |||
// that it's of the form `#[proc_macro_derive(Foo)]` or | |||
// `#[proc_macro_derive(Foo attributes(A, ..))]` |
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.
nit: missing comma after Foo
.
self.handler.span_err(attr.span(), | ||
"attribute must only have one argument"); | ||
return | ||
let mut attributes_attr = None; |
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 more idiomatic to change the condition of the original if
to list.len() != 1 && list.len() != 2
and just add let attributes_attr = list.get(1);
instead of the match.
self.handler.span_err(attr.span(), | ||
"attribute must be of form: \ | ||
`attributes(foo, bar)`"); | ||
None |
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.
nit: None
-> Vec::new()
and or_else
-> unwrap_or_else
would avoid the flat_map
below.
2 => attributes_attr = Some(&list[1]), | ||
_ => { | ||
self.handler.span_err(attr.span(), | ||
"attribute must only have between one and two arguments"); |
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 "attribute must have either one or two arguments"
would be clearer.
KNOWN_ATTRIBUTES should really be named BUILT_ATTRIBUTES, while KNOWN_ATTRIBUTES should be used to mark attributes as known, similar to USED_ATTRIBUTES.
784aa0d
to
96eca8b
Compare
I've added some basic tests for the proc_macro_derive attribute form with an attributes list, and for custom attribute errors in deriving items. So unless there are more changes that you'd like me to make, this should be ready to merge. |
`attributes(foo, bar)`"); | ||
&[] | ||
}).into_iter() | ||
.filter_map(|attr| { |
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.
optional formatting nit:
It would be more idiomatic to either give })
its own line or have a single line }).into_iter().filter_map(|attr| {
.
By using a second attribute `attributes(Bar)` on proc_macro_derive, whitelist any attributes with the name `Bar` in the deriving item. This allows a proc_macro function to use custom attribtues without a custom attribute error or unused attribute lint.
This reverts commit 3784067. Any errors in the derived output now point at the derive attribute instead of the item.
96eca8b
to
134ef4f
Compare
I've submitted the required changes to fix #37563. This should be ready to merge now. |
Excellent, thanks @keeperofdakeys! |
📌 Commit 134ef4f has been approved by |
macros 1.1: Allow proc_macro functions to declare attributes to be mark as used This PR allows proc macro functions to declare attribute names that should be marked as used when attached to the deriving item. There are a few questions for this PR. - Currently this uses a separate attribute named `#[proc_macro_attributes(..)]`, is this the best choice? - In order to make this work, the `check_attribute` function had to be modified to not error on attributes marked as used. This is a pretty large change in semantics, is there a better way to do this? - I've got a few clones where I don't know if I need them (like turning `item` into a `TokenStream`), can these be avoided? - Is switching to `MultiItemDecorator` the right thing here? Also fixes rust-lang#37563.
Just saw this; thanks for jumping on this! |
This PR allows proc macro functions to declare attribute names that should be marked as used when attached to the deriving item. There are a few questions for this PR.
#[proc_macro_attributes(..)]
, is this the best choice?check_attribute
function had to be modified to not error on attributes marked as used. This is a pretty large change in semantics, is there a better way to do this?item
into aTokenStream
), can these be avoided?MultiItemDecorator
the right thing here?Also fixes #37563.