-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New RFC: proc-macro-attribute-recursion #2628
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
base: master
Are you sure you want to change the base?
Conversation
8b017e6
to
91ac6d3
Compare
ae7e822
to
665feb4
Compare
So.... what currently happens if a |
It just gets ignored. |
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
The expander is extended to search the expansion 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. |
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 whole section (and the RFC in general) seems rather underspecified; I'd like to see examples of proc_macro
added to the previous section at least. This also doesn't seem like a full description of behavior since #[flame]
gets applied to macro expansions. This also doesn't say what happens if you write #[flame(alpha, beta)]
. This should be specified including with examples.
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 didn't detail what #[flame(alpha, beta)]
does (currently it should give you an error), because this RFC does not change this part of functionality. As for bang-macros, it doesn't matter if they are defined via macro
, macro_rules!
or as a proc macro. Again the functionality of expansion is unchanged.
The only two things this RFC defines: That the output of proc_macro_attribute
s get expanded and that the order of expansion follows the order of appearance (so that things from the original code get expanded before things added by the expansion).
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.
Well, from what I can tell, the reference says nothing about how #[flame]
gets attached to this_is_fun
.
That the output of proc_macro_attributes get expanded and that the order of expansion follows the order of appearance (so that things from the original code get expanded before things added by the expansion).
That's not what the text says; it says "The expander is extended to search the expansion of proc_macro
and proc_macro_attributes
for other macro invocations." -- you've added examples for the latter but not the former.
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 themselves. This can easily be done by examining the `Span`s of the expansion and ordering them by `SyntaxContext`. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks |
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.
I' would however be surprised if anyone relied on the current behavior, as it doesn't do anything useful.
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.
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?
It could either expand, as per your RFC, to:
mod fun {
#[flame]
fn this_is_fun(x: u64) -> u64 { x + 1 }
}
or alternatively:
mod fun {
fn this_is_fun(x: u64) -> u64 { x + 1 } // This is what I'd expect.
}
this might make a world of difference if #[flame]
does transformations to this_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.
5555c94
to
a08b6cd
Compare
For things to work as described three somewhat independent changes are needed:
TLDR: This is probably not a small and simple change, and it may have implications of the "Small and simple change" would be to provide |
Good point; while this RFC is conceptually simple, the implementation has some subtle details.
Also your proposal is incomplete – we'd need multiple functions, for macros in item, expr, pat and ty positions (and I'm not sure if I forgot one). Given this, it is far from simple, too. While I agree that it would indeed be somewhat usable, I'd rather not create such a strong dependency on resolve from expansion. Better to keep the API boundary small. |
@llogiq
, is (The proper solution should probably be a position agnostic |
True. Fair enough, if I get to pull this trick, you may too. 😄 I still maintain that this would create an API burden that we will never want to stabilize, whereas my proposal, even if more complex, would be completely forwards-compatible. Apart from that, I outlined how, given a support library, this can be quite usable from |
cc @nrc |
cc @matklad and @jonathandturner who might be reimplementing macro expansion for their respective IDE support projects. What do you think? Would you prefer a magic function that lets proc macros call back into resolve+macro expansion or this recursive method? |
Heh, as an IDE writer, I would prefer neither of those options :-) The "magic function" approach frightens me, because macro expansion is no longer a pure This suggestion seems more amendable to caching, probably, but it seems to change how expansion works pretty significantly. The "copy-paste attributes on macros onto expansions" and "the order of expansion of Could we instead tag macro definitions with |
@matklad so this tag would also apply to proc_macro_attributes? That would make my suggestion from above feasible: we could use a proc_macro_attribute to have 'inner' macro invocations expanded (or perhaps we can do this in general, but there may be some problems with expansion order). Unless there is some snag I'm overlooking, I'm totally for it! |
@matklad thinking a bit more about it, wouldn't that be functionally equivalent to my proposal? What would happen if I create a proc_macro that returns a macro by example invocation to be eagerly expanded that will force-expand the innards and call-back a proc-macro? |
I have a fuzzy understanding of proc macros, so I don't really know what exactly either proposal means, but I think "eager expand all inputs" is different on the implementation side in that it
From the macro author POV, I think this is less flexible: you can't selectively process some macro invocations as token trees and others as expanded code, it's all or nothing. |
True, that is a difference. However, for my use cases your suggestion works, too. @petrochenkov what do you think? Should we close this RFC and setup another? @matklad do you have time to write up a new RFC or should I do it? |
I don't have neither time, nor the required knowledge here :-) |
@petrochenkov you know more about the current macro expansion implementation – how complex would you rate @matklad 's suggestion? |
We talked about this in today's @rust-lang/lang meeting. We're generally in favor of the idea, if it turns out to be reasonably feasible and performant to implement in the compiler; that's where our primary concerns are with this, because that expertise isn't widely available. For that reason, we'd be willing to approve a project group for this, if that project group were willing to help see this through to completion. |
So, I actually meant to suggest this here, but I accidentally commented on the original RFC instead... Here's my idea:
IMO, this is simpler to understand, and the recursion part does not require any special-casing - it's simply an expected by-product of having two categories of macros. Rustdoc could even automatically document whether a particular macro is pre-expanded or not. |
Reading through the long comment chain here it's hard for me to tell -- does requiring the input to be expanded first mean that any macro invocations that could not be expanded in the input because a definition was not available cause an error? The idea being that if it weren't an error, the macro taking the expanded-as-far-as-possible input could then come up with its own substitutions for the remaining invocations. This would make it possible to write macros that expand differently depending on the context in which they are invoked. For example, I have been fiddling around with a port of the C++ catch unit test library to rust. In the library there is a macro called |
What do you mean by input? Variables? Code that is constant during compile time?
Bikeshed: Same reasoning. Input not specified.
What type of additional work? Macro number computations ( |
Macros are a compile-time construct that take a TokenStream as input and produce a TokenStream as output.
I'm not sure what you mean by this. I'm proposing a simple opt-in to pre-expand macros in the input TokenStream. For example, let's say we have a procedural macro foo!(unexpanded!()) Today, |
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.
(Comment to get rid or the pending review request, GitHub doesn't support rejecting review requests in any other way.)
This breaks out a small part of #2320 that is simple to implement and reason about and will give us a simple workable solution to macro expansion in proc macros while we wait for the more complete solution to emerge.
Rendered