-
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
Introduce '$self' macro metavar for hygienic macro items #2968
Open
SergioBenitez
wants to merge
1
commit into
rust-lang:master
Choose a base branch
from
SergioBenitez:self-metavar
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,247 @@ | ||
- Feature Name: `self_macro_metavar` | ||
- Start Date: 2020-08-03 | ||
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Introduce the `$self` macro metavariable, a companion to `$crate`, that allows | ||
macros hygienic access to items. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
It is presently impossible to define macros with identifiers that resolve at the | ||
macro's definition site upon expansion. This shortcoming is well-acknowledged | ||
and well-known, and, while [declarative macros 2.0] aimed to resolve this issue, | ||
its implementation and subsequent stabilization sit in limbo. | ||
|
||
As an example of a macro that's presently impossible to write, consider the | ||
following, where `PRIVATE` is expected to resolve to `submod::PRIVATE` | ||
regardless of where `m` is expanded: | ||
|
||
```rust | ||
mod submod { | ||
static PRIVATE: &'static str = "PRIVATE_SUBMOD"; | ||
|
||
#[macro_export] | ||
macro_rules! m { | ||
() => (println!("{}", PRIVATE)) | ||
} | ||
|
||
pub use m; | ||
} | ||
|
||
pub fn main() { | ||
submod::m!(); // error[E0425]: cannot find value `PRIVATE` in this scope | ||
} | ||
``` | ||
|
||
As illustrated, the call to the `m!()` errors as "`PRIVATE` is not in-scope". | ||
Specifically, the call to `m!()` expands to `println!("{}, PRIVATE);`, where | ||
`PRIVATE` resolves as if it were an `item` identifier. This implies that the | ||
following _does_ compile, printing `Hi!` when run, perhaps unexpectedly: | ||
|
||
```rust | ||
fn main() { | ||
submod::m!(); | ||
static PRIVATE: &'static str = "Hi!"; | ||
} | ||
``` | ||
|
||
Today, no combination of `macro_rules!()` or `proc_macro` invocations embedded | ||
within allows for declaring an `m` that expands such that `PRIVATE` in the | ||
expansion resolves to `submod::PRIVATE`. Even the following example, which | ||
mimics what is possible with identifiers today, fails: | ||
|
||
```rust | ||
mod submod { | ||
static PRIVATE: &'static str = "PRIVATE_SUBMOD"; | ||
|
||
macro_rules! make_local { | ||
($local:expr) => ( | ||
#[macro_export] | ||
macro_rules! m { | ||
() => (println!("{}", $local)) | ||
} | ||
|
||
pub use m; | ||
) | ||
} | ||
|
||
make_local!(PRIVATE); | ||
} | ||
|
||
pub fn main() { | ||
submod::m!(); // error[E0425]: cannot find value `PRIVATE` in this scope | ||
} | ||
``` | ||
|
||
`$self` resolves this deficiency. With `$self`, `m` could be declared as: | ||
|
||
```rust | ||
mod submod { | ||
static PRIVATE: &'static str = "PRIVATE_SUBMOD"; | ||
|
||
#[macro_export] | ||
macro_rules! m { | ||
() => (println!("{}", $self::PRIVATE)) | ||
} | ||
|
||
pub use m; | ||
} | ||
|
||
pub fn main() { | ||
submod::m!(); // `PRIVATE` unconditionally resolves to `submod::PRIVATE` | ||
} | ||
``` | ||
|
||
On expansion of `m`, `PRIVATE` unambiguously and unconditionally resolves as if | ||
it were at the definition site, that is, to `submod::PRIVATE`. | ||
|
||
[declarative macros 2.0]: https://github.com/rust-lang/rust/issues/39412 | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
The `$self` macro metavariable, like the `$crate` metavariable, can be used to | ||
modify the hygeine of identifiers in a macro. `$self` works a lot like the | ||
`self` in module paths: when used at the start of a path in a macro, the | ||
succeeding path will be resolved as if it were in the module where the macro is | ||
defined, regardless of where the macro is expanded. Different from `self` in | ||
module paths, however, `$self` _also_ captures the visibility of the module path | ||
at the definition site: the succeeding path will be visible in the expansion if | ||
it is visible at the macro's definition site. | ||
|
||
Said differently, `$self` _captures_ the module scope at the macro definition | ||
site and applies it to the succeeding path upon expansion. As an example, | ||
consider the definition of the macro `submod::m!`: | ||
|
||
```rust | ||
mod submod { | ||
static PRIVATE: &'static str = "PRIVATE_SUBMOD"; | ||
|
||
#[macro_export] | ||
macro_rules! m { | ||
() => (println!("{}", $self::PRIVATE)) | ||
} | ||
} | ||
|
||
pub fn main() { | ||
submod::m!(); // `PRIVATE` unconditionally resolves to `submod::PRIVATE` | ||
} | ||
``` | ||
|
||
Without `$self`, it would not be possible to reference `submod::PRIVATE` outside | ||
of `submod`. Observe, too, that unlike `$crate`, `$self` _does_ have an effect | ||
on visibility: while `submod::PRIVATE` in `main` would _not_ resolve, the | ||
expansion including `$self::PRIVATE` does! | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
At its core, `$self` is `$crate` at the module-level as opposed to the crate | ||
level. Macro metavariable naming collisions are handled in the same way as with | ||
`$crate`. In particular, a declaration of `$self` in a macro shadows the `$self` | ||
described here. The following works as expected, and importantly, as it does | ||
today: | ||
|
||
```rust | ||
macro_rules! m { | ||
($self:ident) => (println!("{}", $self)) | ||
} | ||
``` | ||
|
||
Additionally, like `$crate`, a non-user-declared `$self` _must_ be followed by | ||
`::`. | ||
|
||
Notably different is that while `$crate` can be implemented as a purely | ||
syntactic transformation, substituting `$crate` for the name of the crate in | ||
which the macro is defined, `$self` must apply the full resolution context of | ||
the macro's definition site to the succeeding path. When calling a macro using | ||
`$self` cross-crate, this requires cross-crate hygiene. Thankfully, this was | ||
recently added to the compiler in https://github.com/rust-lang/rust/pull/72121. | ||
|
||
Thus, `$self` can be simply and without further caveats by specified as: for | ||
every path in the expansion that begins with `$self`, the resolution context of | ||
the path is set to resolution context of the `Span::source()` of `$self`. | ||
|
||
In addition to the examples in the introductory text, consider the following: | ||
|
||
```rust | ||
mod a { | ||
static PRIVATE: &'static str = "B"; | ||
|
||
#[macro_export] | ||
macro_rules! m1 { | ||
($($var:tt)*) => (println!("{}, {}", $self::PRIVATE, $($var)*)) | ||
} | ||
} | ||
|
||
mod b { | ||
static PRIVATE: &'static str = "A"; | ||
|
||
#[macro_export] | ||
macro_rules! m2 { | ||
() => (m1!($self::PRIVATE)) | ||
} | ||
} | ||
|
||
pub fn main() { | ||
m2!(); | ||
} | ||
``` | ||
|
||
The resulting program prints `B, A`. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
As always, introducing new language-level features can add the cognitive | ||
overhead. However, `$self`'s similarity to `$crate` means that it doesn't | ||
introduce an entirely new concept. What's more, it is orthogonal to all existing | ||
language features, which means users find one solution to the problem it | ||
resolves. | ||
|
||
`$self` as described here is backwards-compatible: there are no compatibility | ||
hazards. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
1. Wait for Macros 2.0 | ||
|
||
Self-explanatory. Unfortunately, the implementation and stabilization of | ||
macros 2.0 is in limbo. | ||
|
||
2. Propagate Resolution Context for Items, Too | ||
|
||
The second `submod` example in the introductory text could be made to work. | ||
Unfortunately, this has the major drawback that it breaks existing code. That | ||
is, it is not backwards-compatible. Furthermore, it requires two expansions | ||
to achieve the same net-effect that this proposal allows in one. | ||
|
||
3. Use some other syntax, like `#PRIVATE`, to capture hygiene | ||
|
||
Instead of `$self::PRIVATE`, `#PRIVATE` could yield the same effect. This | ||
introduces brand new syntax with no existing analogy, however, and so would | ||
be harder to teach. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
I am not aware of an existing `$self`-like mechanism in other languages. Rust's | ||
own `$crate` is the inspiration for this feature. Other issues, notably going | ||
back to https://github.com/rust-lang/rust/issues/22462, have also considered the | ||
deficiency resolved by this proposal. | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
None. | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
None. Macros 2.0 continues to be the eventual goal. |
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.
Instead of
Span::source()
, I think it would make more sense to talk aboutSpan::def_site()
. That is, using a$self
path (conceptually) first substitutes in the path to the macro's parent module, and then behaves as if a proc-macro had produced the tokens withSpan::resolved_at(Span::def_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.
Unfortunately, I don't think
Span::def_site()
is quite right either. For example, in the expansion chain#[attr] -> macro_rules foo!() { $self }
,$self
should resolve wherever#[attr]
was called (or equivalently, where the tokensmacro_rules foo!() { $self }
expanded to), not where#[attr]
was defined.I believe the precise way to phrase this is:
Then proceed to show, as examples, 1) nested
macro_rules!
, where the outer scope is used, and 2) aproc-macro
that expands to amacro_rules!
, where the call-site of theproc-macro
is used.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.
Sorry for being unclear - I meant that the
$self
path should be resolved as if it was produced by a proc-macro withSpan::def_site()
, instead of amacro_rules!
macro (after the parent module path has been substituted).