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: fix inert attributes from proc_macro_derives with #![feature(proc_macro)] #39572

Merged
merged 2 commits into from Feb 13, 2017

Conversation

jseyfried
Copy link
Contributor

This PR refactors collection of proc_macro_derive invocations to fix #39347.

After this PR, the input to a #[proc_macro_derive] function no longer sees #[derive]s on the underlying item. For example, consider:

extern crate my_derives;
use my_derives::{Trait, Trait2};

#[derive(Copy, Clone)]
#[derive(Trait)]
#[derive(Trait2)]
struct S;

Today, the input to the Trait derive is #[derive(Copy, Clone, Trait2)] struct S;, and the input to the Trait2 derive is #[derive(Copy, Clone)] struct S;. More generally, a proc_macro_derive sees all builtin derives, as well as all proc_macro_derives listed after the one being invoked.

After this PR, both Trait and Trait2 will see struct S;.
This is a [breaking-change], but I believe it is highly unlikely to cause breakage in practice.

r? @nrc

@jseyfried
Copy link
Contributor Author

jseyfried commented Feb 5, 2017

An alternative would be for proc_macro_derives to see all builtin derives and proc_macro_derives applied to the item, e.g. both Trait and Trait2 would see
#[derive(Copy, Clone)] #[derive(Trait)] #[derive(Trait2)] struct S;.

@jseyfried
Copy link
Contributor Author

jseyfried commented Feb 5, 2017

cc #38356
cc @keeper @abonander

@jseyfried jseyfried changed the title macros: Fix inert attributes from proc_macro_derives behind #![feature(proc_macro)] macros: fix inert attributes from proc_macro_derives with #![feature(proc_macro)] Feb 6, 2017
@jseyfried jseyfried force-pushed the fix_inert_attributes branch 5 times, most recently from 1094f9f to b260146 Compare February 6, 2017 05:34
);
let attrs = attributes.iter().cloned().map(Symbol::intern).collect::<Vec<_>>();
let derive = ProcMacroDerive::new(expand, attrs.clone());
let derive = SyntaxExtension::ProcMacroDerive(Box::new(derive), attrs);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason you can't just store the ProcMacroDerive struct without boxing? Seems a waste to have two separate Vecs for the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could, but it would require moving more code from libsyntax_ext into libsyntax. Still might be worth it, but probably best for another PR (perf. impact is negligible).

@@ -172,7 +176,8 @@ impl<'a> base::Resolver for Resolver<'a> {
ImportResolver { resolver: self }.resolve_imports()
}

fn find_attr_invoc(&mut self, attrs: &mut Vec<ast::Attribute>) -> Option<ast::Attribute> {
fn find_legacy_attr_invoc(&mut self, attrs: &mut Vec<ast::Attribute>)
Copy link
Contributor

Choose a reason for hiding this comment

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

What makes this legacy, will it only resolve non-path attributes?

Copy link
Contributor Author

@jseyfried jseyfried Feb 6, 2017

Choose a reason for hiding this comment

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

It only resolves legacy attribute macro invocations and legacy derive invocations (i.e. attributes and derives from #![plugin(...)]). I'll add a comment.

} else {
attrs[i].value = ast::MetaItem {
name: attrs[i].name(),
span: span,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be the span for the old derive attribute?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good point -- will do.

let span = traits.remove(j).span;
self.gate_legacy_custom_derive(legacy_name, span);
if traits.is_empty() {
attrs.remove(i);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this attribute be marked as used, or does that happen elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It gets marked as used during expansion (here).

let expansion = match ext {
Some(ext) => self.expand_invoc(invoc, ext),
None => invoc.expansion_kind.dummy(invoc.span()),
let (expansion, new_invocations) = if let Some(ext) = ext {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems a bit complex, should there be a separate expand_derives function? (Two Some(ext)'s?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably -- I was thinking maybe a separate collect_derives. I was thinking once we implement inert attribute vs macro attribute ambiguity errors (i.e. scope (b) from #39347 (comment)) and expanding items before derives, we'll have a better idea of how best to abstract / refactor out.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, makes sense. I think it still deserves a comment for now at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

return attrs;
}

traits = collect_derives(&mut self.cx, &attrs);
Copy link
Contributor

Choose a reason for hiding this comment

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

If there are multiple inert attributes, will collect_derives be called multiple times? If so, multiple derive warnings may occur.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No -- we don't call classify_item again after we collect a potential proc_macro_attribute invocation.
If the potential proc_macro_attribute invocation ends up being an inert attribute, we just mutate the invocation and re-resolve it (separate from this code-path).

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think I quite understand. What's the final for loop needed for?

_ => {}
}

for &(name, span) in traits {
Copy link
Contributor

@keeperofdakeys keeperofdakeys Feb 6, 2017

Choose a reason for hiding this comment

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

I don't quite understand what's happening here. For each InvocKind::Attr, we have a possible instance of an inert attribute, then we loop through the traits seeing if it belongs to any invocation? And if it does, we simply add it to a dummy item, and mark it as used?

Copy link
Contributor Author

@jseyfried jseyfried Feb 6, 2017

Choose a reason for hiding this comment

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

Yeah -- if it is an inert attribute, we mark it as known (so that it won't get collected as a proc_macro_attribute invocation again), add it back to underlying item (the dummy item is just a workaround for lack of easy mutable access to an Expansion's attributes), and put the invocation back on the work list.

@nrc
Copy link
Member

nrc commented Feb 9, 2017

cc @alexcrichton I feel like this discussion has come up before in the macros 1.1 process.

@jseyfried
Copy link
Contributor Author

Yeah, c.f. #35900 (comment) and #37067.

Copy link
Member

@nrc nrc left a comment

Choose a reason for hiding this comment

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

r=me with the added comment, but I'd like to give Alex a chance to see this before merging

@@ -514,7 +514,7 @@ pub enum SyntaxExtension {
/// The input is the annotated item.
/// Allows generating code to implement a Trait for a given struct
/// or enum item.
ProcMacroDerive(Box<MultiItemModifier>),
ProcMacroDerive(Box<MultiItemModifier>, Vec<Symbol>),
Copy link
Member

Choose a reason for hiding this comment

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

Could you comment on the additional arg please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

@jseyfried
Copy link
Contributor Author

jseyfried commented Feb 9, 2017

IIRC, #37067 was written when proc_macro_derives could modify the underlying item, so neither the current solution in this PR nor the solution from #39572 (comment) was possible.

@alexcrichton
Copy link
Member

Yeah I think that the current behavior made more sense when custom derive could alter the item itself, but nowadays where it can't then this looks good to me!

(I can't think of any problems at least)

@jseyfried
Copy link
Contributor Author

@bors r=nrc

@bors
Copy link
Contributor

bors commented Feb 12, 2017

📌 Commit 1029ee0 has been approved by nrc

@keeperofdakeys
Copy link
Contributor

Did you fix the span issue in find_legacy_attr_invoc?

@bors
Copy link
Contributor

bors commented Feb 12, 2017

⌛ Testing commit 1029ee0 with merge c110a97...

@jseyfried
Copy link
Contributor Author

@keeperofdakeys Yeah.
@bors r=nrc

@bors
Copy link
Contributor

bors commented Feb 12, 2017

📌 Commit 2cc61ee has been approved by nrc

@jseyfried
Copy link
Contributor Author

@bors p=1

@bors
Copy link
Contributor

bors commented Feb 12, 2017

⌛ Testing commit 2cc61ee with merge 956e2bc...

bors added a commit that referenced this pull request Feb 12, 2017
macros: fix inert attributes from `proc_macro_derives` with `#![feature(proc_macro)]`

This PR refactors collection of `proc_macro_derive` invocations to fix #39347.

After this PR, the input to a `#[proc_macro_derive]` function no longer sees `#[derive]`s on the underlying item. For example, consider:
```rust
extern crate my_derives;
use my_derives::{Trait, Trait2};

#[derive(Copy, Clone)]
#[derive(Trait)]
#[derive(Trait2)]
struct S;
```

Today, the input to the `Trait` derive is `#[derive(Copy, Clone, Trait2)] struct S;`, and the input to the `Trait2` derive is `#[derive(Copy, Clone)] struct S;`. More generally, a `proc_macro_derive` sees all builtin derives, as well as all `proc_macro_derive`s listed *after* the one being invoked.

After this PR, both `Trait` and `Trait2` will see `struct S;`.
This is a [breaking-change], but I believe it is highly unlikely to cause breakage in practice.

r? @nrc
@bors
Copy link
Contributor

bors commented Feb 13, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nrc
Pushing 956e2bc to master...

@bors bors merged commit 2cc61ee into rust-lang:master Feb 13, 2017
@jseyfried jseyfried deleted the fix_inert_attributes branch February 18, 2017 04:42
@nikomatsakis nikomatsakis added the relnotes Marks issues that should be documented in the release notes of the next release. label Apr 5, 2017
@nikomatsakis
Copy link
Contributor

I'm tagging this as relnotes since the there is certainly a change in behavior here.

glandium added a commit to glandium/rust-derivative that referenced this pull request Jul 13, 2018
…opy`"

This reverts commit 4117fff.

As of rust 1.17, proc-macros don't get attributes other than their own
in the token stream they're fed.
See rust-lang/rust#39572
mcarton pushed a commit to glandium/rust-derivative that referenced this pull request Oct 20, 2018
…opy`"

This reverts commit 4117fff.

As of rust 1.17, proc-macros don't get attributes other than their own
in the token stream they're fed.
See rust-lang/rust#39572
kankri pushed a commit to kankri/rust-derivative that referenced this pull request Aug 23, 2019
…opy`"

This reverts commit 4117fff.

As of rust 1.17, proc-macros don't get attributes other than their own
in the token stream they're fed.
See rust-lang/rust#39572
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
relnotes Marks issues that should be documented in the release notes of the next release.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature(proc_macro) breaks attributes on custom derive
6 participants