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

macros 1.1: Allow proc_macro functions to declare attributes to be mark as used #37614

Merged
merged 3 commits into from
Nov 9, 2016

Conversation

keeperofdakeys
Copy link
Contributor

@keeperofdakeys keeperofdakeys commented Nov 6, 2016

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 #37563.

@rust-highfive
Copy link
Collaborator

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.

Copy link
Member

@alexcrichton alexcrichton left a 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) {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Member

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(|| {
Copy link
Member

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?

@keeperofdakeys
Copy link
Contributor Author

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

@jseyfried jseyfried Nov 6, 2016

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))
Copy link
Contributor

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>),
Copy link
Contributor

Choose a reason for hiding this comment

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

This should stay MultiItemModifier.

Copy link
Member

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))),
Copy link
Contributor

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

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.

Copy link
Contributor

@jseyfried jseyfried Nov 7, 2016

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

@jseyfried jseyfried Nov 6, 2016

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),
Copy link
Contributor

@jseyfried jseyfried Nov 6, 2016

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)
Copy link
Contributor

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.

@nrc
Copy link
Member

nrc commented Nov 7, 2016

Currently this uses a separate attribute named #[proc_macro_attributes(..)], is this the best choice?

What is the motivation for that? I'd prefer a single attribute as proposed by @eddyb.

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 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.

Is switching to MultiItemDecorator the right thing here?

Yes.

@keeperofdakeys
Copy link
Contributor Author

So a few things I've done:

  • I've renamed KNOWN_ATTRIBUTES to BUILTIN_ATTRIBUTES, and made KNOWN_ATTRIBUTES similar in scope to USED_ATTRIBUTES. CustomDerive then uses attr::mark_known so that the attribute won't trigger a custom attributes error.
  • After discussing this on IRC, I've changed back to MultiItemModifier. It has a better call-signature, and CustomDerive may end up having its own copy as a separate trait.
  • I've updated the tests a bit, but I still need to write some failing tests for custom attributes errors.

@nrc
Copy link
Member

nrc commented Nov 7, 2016

r? @jseyfried

Copy link
Contributor

@jseyfried jseyfried left a 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?

}


Copy link
Contributor

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

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;
Copy link
Contributor

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
Copy link
Contributor

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");
Copy link
Contributor

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.
@keeperofdakeys keeperofdakeys force-pushed the proc_macro branch 3 times, most recently from 784aa0d to 96eca8b Compare November 8, 2016 10:48
@keeperofdakeys
Copy link
Contributor Author

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.

@keeperofdakeys
Copy link
Contributor Author

What changes would need to be made to fix #37614? Is it as simple as reverting the changes made in 3784067? I've checked the example item errors, and they are successfully reported on the correct item span thanks to this PR.

`attributes(foo, bar)`");
&[]
}).into_iter()
.filter_map(|attr| {
Copy link
Contributor

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| {.

@jseyfried
Copy link
Contributor

What changes would need to be made to fix #37614?

Reverting 3784067, adding a regression test, and editing this PR's initial comment to include "fixes #37614".

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.
@keeperofdakeys
Copy link
Contributor Author

I've submitted the required changes to fix #37563. This should be ready to merge now.

@jseyfried
Copy link
Contributor

jseyfried commented Nov 8, 2016

Excellent, thanks @keeperofdakeys!
@bors r+

@bors
Copy link
Contributor

bors commented Nov 8, 2016

📌 Commit 134ef4f has been approved by jseyfried

eddyb added a commit to eddyb/rust that referenced this pull request Nov 9, 2016
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.
bors added a commit that referenced this pull request Nov 9, 2016
Rollup of 15 pull requests

- Successful merges: #36868, #37134, #37229, #37250, #37370, #37428, #37432, #37472, #37524, #37614, #37622, #37627, #37636, #37644, #37654
- Failed merges: #37463, #37542, #37645
@bors bors merged commit 134ef4f into rust-lang:master Nov 9, 2016
@nikomatsakis
Copy link
Contributor

Just saw this; thanks for jumping on this!

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.

Warnings in code generated by proc_macro_derive should be on derive() attr
8 participants