-
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
Hygiene opt-out (escaping) for declarative macros 2.0 #2498
Conversation
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.
Nicely written :)
text/0000-macro-hygiene-optout.md
Outdated
# Summary | ||
[summary]: #summary | ||
|
||
This feature introduces the ability to "opt-out" of the usual macro hygiene rules within definitions of [declarative macros][decl-macro], for designated identifiers or occurrences of identifiers. In other words, the feature will enable one to annotate occurrences of identifiers with macro call-site hygiene rather than the default definition-site hygiene. |
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.
Could be good to mention here that "declarative macros" does not refer to macro_rules!
(it is apparent if you click the link, but in the interest of not having to do so...)
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.
Fair point. I originally had this, but somehow removed it.
text/0000-macro-hygiene-optout.md
Outdated
# Motivation | ||
[motivation]: #motivation | ||
|
||
The use of [hygienic macros] in Rust is justified by much prior research and experience, and solves several common issues that programmers would otherwise encounter with macros due to the nature of syntactical substitution. The principal deficit of this approach is that it requires that names/identifiers of any items generated by a macro be *explicitly passed to* the macro as arguments. This both requires the logic for name selection to remain entirely external to the macro, and even if that is not a problem, the passing of all identifiers-to-export into a macro can quickly become unwieldy for macros that generate many identifiers. |
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.
justified by much prior research and experience
A link would be good for curious readers :)
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 think the "hygienic macros" links offers good justification, no?
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.
Truthfully I expected more papers and citations given "much prior research", but I suppose it's enough :)
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.
Hah, okay, I'll add one or two!
text/0000-macro-hygiene-optout.md
Outdated
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Escaping of hygiene for identifiers within macros allows one to define identifiers with syntax contexts (**hygiene**) corresponding to the place the macro is invoked (the **call-site**) rather than the place it is defined (**definition-site**). It also enables one to use/reference existing identifiers from the call-site from within macro definitions, though this is not the true aim of the feature, but rather a side-effect, and will be discussed 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.
Could be more clear: "Place" => "location in the source code"
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.
Fair point.
text/0000-macro-hygiene-optout.md
Outdated
|
||
Escaping of hygiene for identifiers within macros allows one to define identifiers with syntax contexts (**hygiene**) corresponding to the place the macro is invoked (the **call-site**) rather than the place it is defined (**definition-site**). It also enables one to use/reference existing identifiers from the call-site from within macro definitions, though this is not the true aim of the feature, but rather a side-effect, and will be discussed later. | ||
|
||
Note that for the purposes of this RFC, an **identifier** can roughly be considered to be an textual name (e.g. `foo_bar`) of any sort (for a variable, function, trait, etc.) or a lifetime (e.g. `'a`). |
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.
So what is the relation of this RFC to #2151?
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.
None. I might add a sentence to make that clear.
text/0000-macro-hygiene-optout.md
Outdated
|
||
Note that for the purposes of this RFC, an **identifier** can roughly be considered to be an textual name (e.g. `foo_bar`) of any sort (for a variable, function, trait, etc.) or a lifetime (e.g. `'a`). | ||
|
||
To escape an identifier in code, one simply prefixes an identifier with the [sigil] `#`. This changes the syntax context (hygiene) of the identifier from the usual definition-site to the call-site. |
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 quote!
macro uses #
... Have you considered conflicts if and when quote
is redefined as a 2.0 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 wonder if backslash ("escaping") can be valid
pub mod \foo {
const \BAR: u32 = 123;
}
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.
@Centril No, I'm not sure. I wonder why it doesn't use $
? Grr. Maybe someone can clarify for me whether it would conflict.
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.
Added an "unresolved question" about this, incidentally.
text/0000-macro-hygiene-optout.md
Outdated
## Reference: Example D | ||
[reference-example-d]: #reference-example-d | ||
|
||
In [example B][guide-example-b], the situation is almost identical to [example C][reference-example-c], except that the name of the module is defined within the macro as `foo`, and hygiene-escaped, so that it has the call-site syntax context. |
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.
Typo here? Should say "In [example D][guide-example-d]"?
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.
yep!
cc @dtolnay on possible conflicts, due to using |
I think the RFC doesn't specify the semantics of nested macro calls:
Given: x!();
macro x() {
y!();
}
macro y() {
struct #Foo;
} In the invocation of |
text/0000-macro-hygiene-optout.md
Outdated
When the macro is invoked (expanded), each token tree is transcribed according to the following rules, depending on its hygiene tag. | ||
|
||
- *definition-site*: a normal mark is applied for the current expansion | ||
- *call-site*: a transparent mark is applied for the current expansion and the syntax context for every identifier in the token tree is changed to the syntax context of the call site. |
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.
and the syntax context for every identifier in the token tree is changed to the syntax context of the call site
What is this part about?
When a macro is expanded, an identifier gets an opaque mark added by default (Span::def_site()
in proc macro API) or transparent mark if opt-out is in place (Span::call_site()
in proc macro API), that's all what happens.
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.
Ah, I was slightly confused about how your transparent mark worked. I'll clarify that.
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.
Let me know if it's better now.
@tikue I agree that this presents a problem if you have several layers of macro helpers, this problem needs some other solution in addition to call-site hygiene. |
The definition site of |
Possibly a proc macro that can change the syntax context to that of a given identifier? Speaking of this, this whole feature could be implemented as a proc macro with eager expansion, couldn't it? |
When the macro is invoked (expanded), each token tree is transcribed according to the following rules, depending on its hygiene tag. | ||
|
||
- *definition-site*: a normal mark is applied for the current expansion, which leaves the syntax context alone | ||
- *call-site*: a transparent mark is applied for the current expansion, which changes the syntax context for every identifier in the token tree to that of the call site. |
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.
Syntax context of an identifier is a sequence of marks RootMark -> Mark1 -> Mark2
.
Both "def-site" and "call-site" variants change it, the former to RootMark -> Mark1 -> Mark2 -> OpaqueMark
, the latter to RootMark -> Mark1 -> Mark2 -> TransparentMark
.
(All this is an implementation detail anyway.)
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.
What exactly are Marks? What does the sequence of marks in this example mean?
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.
What exactly are Marks?
Right now a mark is a combination of expansion ID and transparency :)
What does the sequence of marks in this example mean?
A syntactic context fully identifying what macros produced an identifier (or other token).
I'll write some docs after doing a number of refactorings in the compiler.
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.
Yeah. And an expansion ID is a particular expansion (instance of an expansion) of a macro, as I understand. Furthermore, I believe a RootMark
is constructed from a span or set of spans, though I'm not 100% clear on this. Perhaps @petrochenkov can clarify.
Right, but I think I explain it correctly now at least. :-)
… On 14 Jul 2018, at 18:26, Vadim Petrochenkov ***@***.***> wrote:
@petrochenkov commented on this pull request.
In text/0000-macro-hygiene-optout.md <#2498 (comment)>:
> +While the motivation of this feature stems from defining or "exporting" new identifiers from macros to their call-site, where it is appropriate for the macro itself to choose/compute the name, it is clear from the above semantics that this feature allows for other potential uses cases. Most notably, one can use or "import" an identifier from their call-site. This, however, is *not* recommended, since this purpose is already fulfilled well by macro parameters. On the other hand, it is not explicitly disallowed, for two reasons:
+
+- Defining an identifier with call-site hygiene within that macro and then using it is a perfectly reasonable scenario.
+- Macro expansion is performed at the syntactical (token stream) level, before parsing, so definitions and uses cannot be easily distinguished.
+
+# Reference-level explanation
+[reference-level-explanation]: #reference-level-explanation
+
+The macro parser routine first parses the macro definition into a token stream (as before), but now also tags tokens and meta-variables with an enum value representing the kind of hygiene (definition-site or call-site). This is only enabled for new-style `macro!` macros (i.e. *decl_macro* or macros 2.0); for `macro_rules!` macros, the call-site sigil `#` is not handled specially, and gives rise to an error. The sigil is always treated as a separate token outside of macros, on the LHS of macro rules, and when not followed by an identifier on the RHS.
+
+It should be noted that the sigil `#` has nothing to do with the syntax for [raw identifiers][rfc-raw-identifiers], and can be disambiguated without problems. In fact, they can be used together without any issues, e.g. `#r#foo`.
+
+When the macro is invoked (expanded), each token tree is transcribed according to the following rules, depending on its hygiene tag.
+
+- *definition-site*: a normal mark is applied for the current expansion, which leaves the syntax context alone
+- *call-site*: a transparent mark is applied for the current expansion, which changes the syntax context for every identifier in the token tree to that of the call site.
Syntax context of an identifier is a sequence of marks RootMark -> Mark1 -> Mark2.
Both "def-site" and "call-site" variants change it, the former to RootMark -> Mark1 -> Mark2 -> OpaqueMark, the latter to RootMark -> Mark1 -> Mark2 -> TransparentMark.
(All this is an implementation detail anyway.)
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <#2498 (review)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AAEF3P0rcDMWTfIOtHt9OfBxv7Q-AVZDks5uGinfgaJpZM4VPRYG>.
|
@alexreg Thanks for the RFC! I'm glad to see macros 2.0 starting to get RFC-ified :) I had a couple of questions:
|
I think this could work as an eRFC or RFC. I think we know what we want, and the feature is very well-motivated, but I'm starting to think this feature may be best implemented as a proc macro now. (And can thus be extensible to the case I mentioned above.)
Yep, I think so. Let's wait until it's merged and I have a bit more experience with things, but I'd be glad to do that. |
@petrochenkov Can this feature be implemented using a proc macro yet? I presume not. What more would it require though? |
@alexreg |
I think it's worth considering a) whether we should implement this RFC as a proc macro, b) the alternative of |
|
||
Escaping of hygiene for identifiers within macros allows one to define identifiers with syntax contexts (**hygiene**) corresponding to the location in the source code from which the macro is invoked (the **call-site**) rather than the location it is defined (**definition-site**). It also enables one to use/reference existing identifiers from the call-site from within macro definitions, though this is not the true aim of the feature, but rather a side-effect, and will be discussed later. | ||
|
||
Note that for the purposes of this RFC, an **identifier** can roughly be considered to be an textual name (e.g. `foo_bar`) of any sort (for a variable, function, trait, etc.) or a lifetime (e.g. `'a`). |
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.
Currently all lifetime parameters are unhygienic, not sure if we will fix that for macros 2.0 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.
Yeah. Hopefully we will!
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.
Lifetimes are already hygienic in macro
macros and with Span::def_site()
in proc macros.
## Meta-variables | ||
[meta-variables]: #meta-variables | ||
|
||
Hygiene escaping of meta-variables (i.e. `#$foo` and `$#foo`) does not have immediately obvious semantics or usefulness, so is explicitly disallowed for the present, and yields error messages. |
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 obvious semantics to me is that the resulting identifier takes the name from the metavariable and the hygiene context from the call site.
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.
Yes, I really meant the former in't obviously useful, why the latter isn't obviously useful either nor does it have obvious semantics.
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
Extended discussion on this subject was carried out in a [pull request][pr-47992] for this feature, which was closed due to the decision that an RFC such as this one be accepted first. [Alternatives][pr-47992-alternatives] were originally evaluated there, with discussion initiated by @jseyfried, and [continued][pr-47992-alternatives-eval] by @petrochenkov. |
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'd expect some discussion of how this works in other languages here. In particular, Scheme has a rich system for doing this sort of thing.
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.
Hmm. I'd like to avoid learning Scheme properly for this... maybe I can dig up a decent explanation somewhere?
I had always imagined this feature being implemented as a proc macro, rather than having dedicated syntax. I imagined that we would provide some functions to proc macros for doing things like apply the hygiene context from I think there is a problem with this approach which is what exactly is the use site? If you have nested macros, do you mean the root of the expansion or one level of expansion? If you pass a macro to a macro before expanding it, which use site do you use. What about interactions with eager expansion? One solution is to always take the hygiene context from an identifier, so the user has to pass some identifier in. This can be done in conjunction with a 'concat idents' kind of macro too. This does make cases like your first example a bit weird. The alternative is that you pick a default and offer ways to access the other variations as needed. So, the solution I would propose is that you change the RFC to the functions other support necessary to support ergonomic hygiene manipulation in proc macros, then create some library macros which use these to provide hygiene manipulation to decl macros and RFC any language changes required to make that work. I think this approach will lead to a more flexible and orthogonal system. |
Yes, that could work well. Having written this, I immediately started thinking an expanded proc macros system was the better way to go... I was just encouraged down this path initially. Do you think my Also, maybe you could clarify if eager expansion is actually needed for this purpose? I was convinced it was needed for If you're up for a chat on Discourse about what extensions we need to make to the proc macro system, that might be nice. |
I think there should be some mention of computed unhygienic symbols (for example concatenating an input parameter with a constant string to create an output symbol) either as a possible extension or in alternatives. |
@tmccombs I see that as somewhat orthogonal to the concern of this RFC, although hygiene control/opt-out can certainly help with using it. |
@alexreg I don't think it is completely orthogonol. If there was a mechanism to generate computed unhygienic symbols, that mechanism could probably be used to achieve the same goals as this RFC. |
@tmccombs There's no such thing as "unhygienic" symbols in the parse tree. The way we've discussed implementing the |
Do you have any pointers to info about the current support for hygiene retrieval and manipulation in proc macros? I can then rework this RFC accordingly. |
What's the status of this? |
@mark-i-m The plan is to take some of the macros @nrc described in his blog post a while ago and integrate these into I'd also be curious to get @Centril's view on this, as the language design guru. ;-) |
Thanks for asking, but I'm not really knowledgeable about hygiene at all. I was mainly curious what the progress was on macros 2.0. While I would like to see it continue, it seems from the Rust2019 posts that there are other things that are priorities ATM (e.g. GATs, specialization). Macros in the ident position does seem like an excellent feature for exploring how Rust will proceed in this area, though. Is there more documentation that can be found about these issues somewhere? |
Not a lot. There was an attempt at an RFC (a while back, by @Manishearth if I remember), and some discussion on a GitHub issue, plus a series of posts by @nrc (including that one), but nothing nothing more formal. The nice thing about enhancements to the macro system is that they can largely proceed independently of the type system or trait solving, since there aren't too many interactions. The main thing macros needs to be concerned with are other syntactical developments, like what WG-Grammar are doing, perhaps. |
(yeah, I posted an RFC , but this was back when RFCs were much smaller and also I was pretty new to it, so it's not a substantial RFC and it's not too relevant now) |
If you decide to go the "special proc macro" route you'll definitely need eager expansion. One consistent way to add it to the syntax is $let $myident = lift!(hello);
let $myident =5; IIRC there have been other proposals |
@Manishearth Yeah, no worries, I realised it doesn't have the usual level of detail of recent RFCs, but worth referencing in any case. I've talked about this before with a few people, and I don't think there's any inherent reason we need eager expansion for this... unless I misremember something. Is there? |
The reason is that supporting macros directly in ident positions is a bit of an annoying syntax minefield.
There are probably other solutions. |
@Manishearth Yeah, this is what I meant by "inherent"... the syntax gets ugly, and intrudes on normal Rust code more than usual, but there's no technical obstacle, as far as I'm aware. :-) I recall discussing macro bindings as well, but I think we discounted this on the basis that syntactical substitutions don't really work like bindings, and to even create a coherent system, it would require a significant expansion in complexity. I believe one argument was to allow macros in ident-position (normal, lazy expansion), but only allow it within other macros. This is nice in a way, but I think the main argument against is was consistency (if ident-position is allowed inside macros, why not every position? -- though I forget what others are currently disallowed). This is where eager expansion (most recent attempt here) comes back again, and starts to look like the best solution, if you combine it with allowing invocations in every position/context. |
Oh neat, identifier-position macros might be a use-case for the same 'eager expansion' macro idea from #2320 (I'm working on cleaning that up after discussions with @alexreg). Assuming you've got your fancy hygiene-scope-adjusting, identifier-token-producing macro eager! {
x = mk_ident!();
let #x = whatever;
} Here, the line |
Yes, exactly like that. :-) (There will also be other token-producing macros, probably along the lines of https://www.ncameron.org/blog/untitledconcat_idents-and-macros-in-ident-position/, but I need to write up a short RFC for that.) I just realised there are some ambiguities in the above syntax for eager! {
$x = mk_ident!(),
let #x = whatever;
} Or, if these forms of macros are still supported in 2018 (I forget): eager!(x = mk_ident!(), ...) {
let #x = whatever;
} |
@rfcbot fcp postpone Hello everyone; we discussed this RFC in our backlog bonanza. The consensus was that we that we should postpone it, as we don't think we have the bandwidth to see to it right now. We do think that macros need some more work, though, and that this RFC in particular is looking at real problems (even if we're not sure whether it's the right solution or not). We would like to encourage folks to discuss "macros 2.0" when the time comes for us to discuss our upcoming roadmap (one of the procedural changes we have in mind is to make it clearer when we'd be open to bigger proposals). |
Team member @nikomatsakis has proposed to postpone this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to postpone, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC is now postponed. |
This feature introduces the ability to "opt-out" of the usual macro hygiene rules within definitions of declarative macros (macros 2.0), for designated occurrences of identifiers. In other words, the feature will enable one to annotate occurrences of identifiers with macro call-site hygiene rather than the default definition-site hygiene.
Rendered
CC @jseyfried @petrochenkov @nrc