-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Conversation
ec443c5
to
64cdec0
Compare
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; |
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
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. |
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. |
r? @nrc |
@dtolnay an example might be an attribute on an enum that wants to do something specific with one field:
or maybe specifying a precondition:
|
Concretely, my libhoare crate does the precondition thing, but hacks it with a string atm. |
Neat, the libhoare use case is compelling. Thanks! |
@dtolnay |
e05b383
to
f7b2fca
Compare
☔ The latest upstream changes (presumably #40432) made this pull request unmergeable. Please resolve the merge conflicts. |
062c4a7
to
e672b5e
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.
r=me with the naming and refactoring addressed
src/librustc/middle/stability.rs
Outdated
@@ -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 }; |
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.
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
src/librustc_resolve/macros.rs
Outdated
@@ -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 }; |
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.
Ha, 4 uses, definitely needs a 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.
Done.
src/libsyntax/attr.rs
Outdated
} | ||
|
||
pub fn is_word(&self) -> bool { self.meta().is_word() } | ||
pub fn is_word(&self) -> bool { | ||
self.tokens.is_empty() |
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 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.
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.
Good point -- I added a check that the path has length 1.
e672b5e
to
81d8269
Compare
`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
☀️ Test successful - status-appveyor, status-travis |
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.
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.
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.
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.
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.
…rochenkov macros: fix bug parsing `#[derive]` invocations Fixes rust-lang#40962 (introduced in rust-lang#40346). r? @nrc
…rochenkov macros: fix bug parsing `#[derive]` invocations Fixes rust-lang#40962 (introduced in rust-lang#40346). r? @nrc
Hm, should arbitrary token trees in attributes require an RFC? At least, there was an RFC for the current I would feel slightly more confident if attributes were restricted to, say expressions. The problem that I see is that currently, I don't think I necessary disagree with allowing arbitrary token trees, but it does make me feel uneasy :) |
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 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 :-) |
This PR
Attribute
to usePath
andTokenStream
instead ofMetaItem
.#[::foo::attr_macro] struct S;
,#[cfg_attr(all(), foo::attr_macro)] struct S;
#[derive(foo::Bar, super::Baz)] struct S;
#[foo::attr_macro arbitrary + tokens] struct S;
#[derive(Foo)] struct S(#[inert arbitrary + tokens] i32);
where
#[proc_macro_derive(Foo, attributes(inert))]
r? @nrc