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

TokenStream-based attributes, paths in attribute and derive macro invocations #40346

Merged
merged 4 commits into from
Mar 19, 2017

Conversation

jseyfried
Copy link
Contributor

@jseyfried jseyfried commented Mar 8, 2017

This PR

  • refactors Attribute to use Path and TokenStream instead of MetaItem.
  • supports macro invocation paths for attribute procedural macros.
    • e.g. #[::foo::attr_macro] struct S;, #[cfg_attr(all(), foo::attr_macro)] struct S;
  • supports macro invocation paths for derive procedural macros.
    • e.g. #[derive(foo::Bar, super::Baz)] struct S;
  • supports arbitrary tokens as arguments to attribute procedural macros.
    • e.g. #[foo::attr_macro arbitrary + tokens] struct S;
  • supports using arbitrary tokens in "inert attributes" with derive procedural macros.
    • e.g. #[derive(Foo)] struct S(#[inert arbitrary + tokens] i32);
      where #[proc_macro_derive(Foo, attributes(inert))]

r? @nrc

@jseyfried
Copy link
Contributor Author

jseyfried commented Mar 8, 2017

@dtolnay
Copy link
Member

dtolnay commented Mar 8, 2017

What is the use case that motivates arbitrary tokens in attributes?

The test case that has the following is not a real thing that we expect people to write, correct?

#[::attr_args::identity
  fn main() { assert_eq!(foo(), "Hello, world!"); }]
struct Dummy;

@jseyfried
Copy link
Contributor Author

@dtolnay

What is the use case that motivates arbitrary tokens in attributes?

We want to give attribute procedural macro authors the power/flexibility to parse the attributes however they want. Also, we pass the attribute's arguments to the procedural macro as a TokenStream, so it makes sense to support any TokenStream.

The test case that has the following is not a real thing that we expect people to write, correct?

Well, it's a not a useful procedural macro, but people can put whatever they want in macro attributes. Items in macro attributes is technically fine but looks ugly so I wouldn't expect it in the wild. However, it seems reasonable to for people to use other AST nodes (e.g. expressions, patterns, paths, etc.) in attribute macros.

@dtolnay
Copy link
Member

dtolnay commented Mar 8, 2017

I guess I would strongly prefer to have a concrete use case in mind to motivate this change. Are there any existing procedural macro crates whose API you think would be improved by using "unconventional" attributes? Are there any procedural macro crates that you wish could exist but require this feature?

I agree that we can come up with hypothetical ways to use this but so far I disagree that this functionality is the best way to address any of those use cases, and consequently my instinct would be to discourage libraries from using this.

I don't find "we pass the attribute's arguments to the procedural macro as a TokenStream" to be a compelling justification. The only thing that means is that it is technically possible to implement this change, not that it is a good idea.

@nrc nrc removed their assignment Mar 8, 2017
@nrc
Copy link
Member

nrc commented Mar 8, 2017

r? @nrc

@nrc
Copy link
Member

nrc commented Mar 8, 2017

@dtolnay an example might be an attribute on an enum that wants to do something specific with one field:

#[foo::macro(Var2::field2)]
enum Bar {
    Var1,
    Var2 { field1: i32, field2: i32 }
}

or maybe specifying a precondition:

#[precondition x < 100]
fn foo(x: i32) { ... }

@nrc
Copy link
Member

nrc commented Mar 8, 2017

Concretely, my libhoare crate does the precondition thing, but hacks it with a string atm.

@dtolnay
Copy link
Member

dtolnay commented Mar 8, 2017

Neat, the libhoare use case is compelling. Thanks!

@jseyfried
Copy link
Contributor Author

jseyfried commented Mar 8, 2017

@dtolnay
Also, the "unconventional" attributes this PR allows are gated behind #![feature(proc_macro)], so we can always decide to change course and add restrictions later.

@jseyfried jseyfried force-pushed the path_and_tokenstream_attr branch 9 times, most recently from e05b383 to f7b2fca Compare March 8, 2017 23:13
@bors
Copy link
Contributor

bors commented Mar 11, 2017

☔ The latest upstream changes (presumably #40432) made this pull request unmergeable. Please resolve the merge conflicts.

@jseyfried jseyfried force-pushed the path_and_tokenstream_attr branch 2 times, most recently from 062c4a7 to e672b5e Compare March 12, 2017 08:46
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 naming and refactoring addressed

@@ -402,7 +402,8 @@ impl<'a, 'tcx> Index<'tcx> {

let mut is_staged_api = false;
for attr in &krate.attrs {
if attr.name() == "stable" || attr.name() == "unstable" {
let name = match attr.name() { Some(name) => name, _ => continue };
Copy link
Member

Choose a reason for hiding this comment

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

worth factoring this idiom out into a macro? Maybe doesn't get used enough, but I'm not really a fan of match one-liners

@@ -194,9 +197,11 @@ impl<'a> base::Resolver for Resolver<'a> {

// Check for legacy derives
for i in 0..attrs.len() {
if attrs[i].name() == "derive" {
let name = match attrs[i].name() { Some(name) => name, None => continue };
Copy link
Member

Choose a reason for hiding this comment

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

Ha, 4 uses, definitely needs a macro :-)

Copy link
Contributor Author

@jseyfried jseyfried Mar 13, 2017

Choose a reason for hiding this comment

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

Done.

}

pub fn is_word(&self) -> bool { self.meta().is_word() }
pub fn is_word(&self) -> bool {
self.tokens.is_empty()
Copy link
Member

Choose a reason for hiding this comment

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

This seems a bit odd to me - this basically means that the attribute is just a path with no other tokens? But that means is_word is a bad name - either there should be a check that the path has length 1 or the name should change.

Copy link
Contributor Author

@jseyfried jseyfried Mar 13, 2017

Choose a reason for hiding this comment

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

Good point -- I added a check that the path has length 1.

@bors
Copy link
Contributor

bors commented Mar 19, 2017

⌛ Testing commit 85e02bd with merge 9c15de4...

bors added a commit that referenced this pull request Mar 19, 2017
`TokenStream`-based attributes, paths in attribute and derive macro invocations

This PR
 - refactors `Attribute` to use  `Path` and `TokenStream` instead of `MetaItem`.
 - supports macro invocation paths for attribute procedural macros.
   - e.g. `#[::foo::attr_macro] struct S;`, `#[cfg_attr(all(), foo::attr_macro)] struct S;`
 - supports macro invocation paths for derive procedural macros.
   - e.g. `#[derive(foo::Bar, super::Baz)] struct S;`
 - supports arbitrary tokens as arguments to attribute procedural macros.
   - e.g. `#[foo::attr_macro arbitrary + tokens] struct S;`
 - supports using arbitrary tokens in "inert attributes" with derive procedural macros.
   - e.g. `#[derive(Foo)] struct S(#[inert arbitrary + tokens] i32);`
where `#[proc_macro_derive(Foo, attributes(inert))]`

r? @nrc
@bors
Copy link
Contributor

bors commented Mar 19, 2017

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

@bors bors merged commit 85e02bd into rust-lang:master Mar 19, 2017
@jseyfried jseyfried deleted the path_and_tokenstream_attr branch March 19, 2017 17:25
jonhoo added a commit to jonhoo/tarpc that referenced this pull request Mar 20, 2017
Since rust-lang/rust#40346 has now been merged, Attribute no longer has
a .value field. Instead, we must follow the token stream and modify the
tokens directly. For Docstring attributes, there should only be one
token, the docstring value.
jonhoo added a commit to jonhoo/tarpc that referenced this pull request Mar 21, 2017
Since rust-lang/rust#40346 has now been merged, Attribute no longer has
a .value field. Instead, we must follow the token stream and modify the
tokens directly. For Docstring attributes, there should only be one
token, the docstring value.
jonhoo added a commit to jonhoo/tarpc that referenced this pull request Mar 21, 2017
Since rust-lang/rust#40346 has now been merged, Attribute no longer has
a .value field. Instead, we must follow the token stream and modify the
tokens directly. For Docstring attributes, there should only be one
token, the docstring value.
jonhoo added a commit to jonhoo/tarpc that referenced this pull request Mar 21, 2017
Since rust-lang/rust#40346 has now been merged, Attribute no longer has
a .value field. Instead, we must follow the token stream and modify the
tokens directly. For Docstring attributes, there should only be one
token, the docstring value.
tikue pushed a commit to google/tarpc that referenced this pull request Mar 21, 2017
Since rust-lang/rust#40346 has now been merged, Attribute no longer has
a .value field. Instead, we must follow the token stream and modify the
tokens directly. For Docstring attributes, there should only be one
token, the docstring value.
frewsxcv added a commit to frewsxcv/rust that referenced this pull request Apr 6, 2017
…rochenkov

macros: fix bug parsing `#[derive]` invocations

Fixes rust-lang#40962 (introduced in rust-lang#40346).
r? @nrc
frewsxcv added a commit to frewsxcv/rust that referenced this pull request Apr 6, 2017
…rochenkov

macros: fix bug parsing `#[derive]` invocations

Fixes rust-lang#40962 (introduced in rust-lang#40346).
r? @nrc
@matklad
Copy link
Member

matklad commented Jan 11, 2018

Hm, should arbitrary token trees in attributes require an RFC? At least, there was an RFC for the current meta_item stuff and arbitrary literals.

I would feel slightly more confident if attributes were restricted to, say expressions. The problem that I see is that currently, #[foo(bar, baz quuz spam)] is a syntax error, which is easily detected during parsing. With token trees, this is a totally valid syntax though, and you'd need semantic knowledge just to parse this thing, which hurts IDEs.

I don't think I necessary disagree with allowing arbitrary token trees, but it does make me feel uneasy :)

@matklad
Copy link
Member

matklad commented Jan 20, 2018

Found one more interesting problem with arbitrary token trees in attributes: they make good error recovery very challenging.

Consider, for example, this code:

#[cfg(foo,
fn foo() {
}

mod m {
}

With the previous restricted grammar, the parser, after consuming foo, and waiting for the next meta item, would see a fn token, which can not start a meta item, but belongs to the FIRST(item). So the natural and easy thing to do would be to report an error, stop parsing the attributes, and then proceed with parsing the following function and module.

Now, when arbitrary token trees are allowed in attributes, the parser has to parse all of the following code as a part of an attribute, and so it would not recognize than a function and a module are, in fact, a function and a module.

Granted, the similar effect is also present with Rust macros, but it is expected that macros are annoying to work with :-)

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.

6 participants