-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
New RFC: proc-macro-attribute-recursion #2628
Open
llogiq
wants to merge
1
commit into
rust-lang:master
Choose a base branch
from
llogiq:proc-macro-attribute-recursion
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
- Feature Name: `proc_macro_attribute_recursion` | ||
- Start Date: 2019-01-24 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Expand `proc_macro_attributes` recursively. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Currently, procedural macros have no way to expand macros at all. [RFC #2320](https://github.com/rust-lang/rfcs/pull/2320) aims to rectify this, but despite being reworked a lot still suffers from some complexity. | ||
|
||
This proc_macro author wants something workable now instead of waiting for that RFC while leaving the doors open for an eventual implementation. Also making this small part available allows us to collect experience with macro expansion in proc_macros at very modest cost. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
`proc_macro_attributes` can add other macro invocations (in the form of bang-macros or attributes) in their output. The expander expands all macros in the proc_macro output recursively in order of appearance. | ||
|
||
Here's an example from [flamer](https://crates.io/crates/flamer): | ||
|
||
```rust | ||
use flamer::flame; | ||
|
||
macro_rules! macro_fun { | ||
() => { | ||
fn this_is_fun(x: u64) -> u64 { | ||
x + 1 | ||
} | ||
} | ||
} | ||
|
||
#[flame] | ||
mod fun { | ||
macro_fun!(); | ||
} | ||
``` | ||
|
||
Flamer adds a clone of its own attribute to all `Macro` nodes it finds. In our example, it adds the attribute to `macro_fun!();` so we get `#[flame] macro_fun!();`. | ||
|
||
The expander checks the output and, because it is from the original code, first expands `macro_fun!()` yielding | ||
|
||
```rust | ||
mod fun { | ||
#[flame] | ||
fn this_is_fun(x: u64) -> u64 { | ||
x + 1 | ||
} | ||
} | ||
``` | ||
|
||
Because of the `#[flame]` attribute added during the first expansion of the outer `#[flame]`, this is fed back to flamer, which modifies the function resulting from the macro. Note that as in this example, the attribute needs not be placed at the same AST node (and in fact, flamer would place it only on macro invocation nodes). | ||
|
||
This way, `proc_macro_attribute`s can be deemed *recursive* like macros-by-example. Note that the macro recursion limit must also be observed by the `proc_macro_attribute` implementations. | ||
|
||
`proc_macro` writers can implement their macros in terms of `proc_macro_attributes` (which is a very roundabout way to deal with macros, but at least it would work at all) to gain the same benefits. The expansion logic could even be put into its own "expand" crate. | ||
|
||
For example, a `strlen!` proc macro to calculate the string length could expand `strlen!(concat!("foo", "bar"))` into: | ||
|
||
```rust | ||
#[expand_bang(strlen)] | ||
(concat!("foo", "bar"), ); | ||
``` | ||
|
||
Using the trick outlined above, this would then be expanded into: | ||
|
||
```rust | ||
#[expand_bang(strlen)] | ||
("foobar", ) | ||
``` | ||
|
||
Afterwards, the proc_macro_attribute can reconstruct the original macro call: | ||
|
||
```rust | ||
strlen!("foobar") | ||
``` | ||
|
||
Which will then be able to calculate the desired `6`. This RFC leaves the implementation of the `expand_bang` proc_macro_attribute as an exercise for the reader. | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
The expander is extended to search the expansion output of `proc_macro` and `proc_macro_attributes` for other macro invocations. Those are then expanded until there are no more attributes or macro invocations left or the macro expansion limit is reached, whichever comes first. | ||
|
||
Implementors will have to make sure to order the expansions within expanded output by their origin: macros which are in the `proc_macro_attribute`s' input need to be expanded before expanding macros that have been added by the `proc_macro_attribute`s expansion themselves. This can easily be done by examining the `Span`s of the expansion and ordering them by `SyntaxContext` number. | ||
|
||
This is necessary to avoid infinite loops, where a `proc_macro_attribute` calls itself without ever getting the expansion of its argument macro invocations. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
This is in theory a breaking change. However, the author deems it very unlikely that other `proc_macro_attribute` authors would introduce attrs into their expansions, except in the hope of triggering the expansion this RFC suggests, as those currently have zero functionality. In any event, a crater run shouldn't hurt. | ||
|
||
# Rationale and alternatives | ||
[alternatives]: #alternatives | ||
|
||
* leave things as they are, but this leaves proc_macro authors in the cold if they want to deal with macros in invocations | ||
* [RFC #2320](https://github.com/rust-lang/rfcs/pull/2320) has a more general solution but tackles more complexity. Note that this RFC is a part of #2320 broken out, so we can still implement the rest of it afterwards | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
None |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
The behavior of attaching
#[flame]
to expansion of macros is, as far as I can see, theoretically a breaking change if attaching the attribute has an effect on static or dynamic semantics of the expansion. I'm surprised that this behavior is not opt-in. It seems the proc macro author should request this behavior.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 agree that in theory this is a breaking change. I' would however be surprised if anyone relied on the current behavior, as it doesn't do anything useful. As I commented on #2320, this was the thing I tried because it felt natural and just might have worked – so in effect by adding the attribute, I am opting in to the expansion. What else would a proc macro author expect when adding an attribute that is expanded by a 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 should at least crater run it and the theoretical breakage should be added to the text with a reasoning about why you would be surprised and why it doesn't do anything useful.
It could either expand, as per your RFC, to:
or alternatively:
this might make a world of difference if
#[flame]
does transformations tothis_is_fun
or not.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.
OK, I added some text to this effect. Please be aware that this is a very language-lawyerly perspective (not that there's anything wrong with that) – as a proc macro author I certainly wouldn't write code to add an attribute that does nothing. In fact, I wrote this RFC because this was what I tried to get macros expanded in flamer and I don't want to wait for a more general solution that may take far more effort.
Anyway, a crater run certainly won't hurt.