Skip to content

Commit 4b8cbce

Browse files
committed
Introduce '$self' metavar for hygienic macro items
1 parent 68e17ac commit 4b8cbce

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed

text/0000-self-macro-metavar.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
- Feature Name: `self_macro_metavar`
2+
- Start Date: 2020-08-03
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Introduce the `$self` macro metavariable, a companion to `$crate`, that allows
10+
macros hygienic access to items.
11+
12+
# Motivation
13+
[motivation]: #motivation
14+
15+
It is presently impossible to define macros with identifiers that resolve at the
16+
macro's definition site upon expansion. This shortcoming is well-acknowledged
17+
and well-known, and, while [declarative macros 2.0] aimed to resolve this issue,
18+
its implementation and subsequent stabilization sit in limbo.
19+
20+
As an example of a macro that's presently impossible to write, consider the
21+
following, where `PRIVATE` is expected to resolve to `submod::PRIVATE`
22+
regardless of where `m` is expanded:
23+
24+
```rust
25+
mod submod {
26+
static PRIVATE: &'static str = "PRIVATE_SUBMOD";
27+
28+
#[macro_export]
29+
macro_rules! m {
30+
() => (println!("{}", PRIVATE))
31+
}
32+
33+
pub use m;
34+
}
35+
36+
pub fn main() {
37+
submod::m!(); // error[E0425]: cannot find value `PRIVATE` in this scope
38+
}
39+
```
40+
41+
As illustrated, the call to the `m!()` errors as "`PRIVATE` is not in-scope".
42+
Specifically, the call to `m!()` expands to `println!("{}, PRIVATE);`, where
43+
`PRIVATE` resolves as if it were an `item` identifier. This implies that the
44+
following _does_ compile, printing `Hi!` when run, perhaps unexpectedly:
45+
46+
```rust
47+
fn main() {
48+
submod::m!();
49+
static PRIVATE: &'static str = "Hi!";
50+
}
51+
```
52+
53+
Today, no combination of `macro_rules!()` or `proc_macro` invocations embedded
54+
within allows for declaring an `m` that expands such that `PRIVATE` in the
55+
expansion resolves to `submod::PRIVATE`. Even the following example, which
56+
mimics what is possible with identifiers today, fails:
57+
58+
```rust
59+
mod submod {
60+
static PRIVATE: &'static str = "PRIVATE_SUBMOD";
61+
62+
macro_rules! make_local {
63+
($local:expr) => (
64+
#[macro_export]
65+
macro_rules! m {
66+
() => (println!("{}", $local))
67+
}
68+
69+
pub use m;
70+
)
71+
}
72+
73+
make_local!(PRIVATE);
74+
}
75+
76+
pub fn main() {
77+
submod::m!(); // error[E0425]: cannot find value `PRIVATE` in this scope
78+
}
79+
```
80+
81+
`$self` resolves this deficiency. With `$self`, `m` could be declared as:
82+
83+
```rust
84+
mod submod {
85+
static PRIVATE: &'static str = "PRIVATE_SUBMOD";
86+
87+
#[macro_export]
88+
macro_rules! m {
89+
() => (println!("{}", $self::PRIVATE))
90+
}
91+
92+
pub use m;
93+
}
94+
95+
pub fn main() {
96+
submod::m!(); // `PRIVATE` unconditionally resolves to `submod::PRIVATE`
97+
}
98+
```
99+
100+
On expansion of `m`, `PRIVATE` unambiguously and unconditionally resolves as if
101+
it were at the definition site, that is, to `submod::PRIVATE`.
102+
103+
[declarative macros 2.0]: https://github.com/rust-lang/rust/issues/39412
104+
105+
# Guide-level explanation
106+
[guide-level-explanation]: #guide-level-explanation
107+
108+
The `$self` macro metavariable, like the `$crate` metavariable, can be used to
109+
modify the hygeine of identifiers in a macro. `$self` works a lot like the
110+
`self` in module paths: when used at the start of a path in a macro, the
111+
succeeding path will be resolved as if it were in the module where the macro is
112+
defined, regardless of where the macro is expanded. Different from `self` in
113+
module paths, however, `$self` _also_ captures the visibility of the module path
114+
at the definition site: the succeeding path will be visible in the expansion if
115+
it is visible at the macro's definition site.
116+
117+
Said differently, `$self` _captures_ the module scope at the macro definition
118+
site and applies it to the succeeding path upon expansion. As an example,
119+
consider the definition of the macro `submod::m!`:
120+
121+
```rust
122+
mod submod {
123+
static PRIVATE: &'static str = "PRIVATE_SUBMOD";
124+
125+
#[macro_export]
126+
macro_rules! m {
127+
() => (println!("{}", $self::PRIVATE))
128+
}
129+
}
130+
131+
pub fn main() {
132+
submod::m!(); // `PRIVATE` unconditionally resolves to `submod::PRIVATE`
133+
}
134+
```
135+
136+
Without `$self`, it would not be possible to reference `submod::PRIVATE` outside
137+
of `submod`. Observe, too, that unlike `$crate`, `$self` _does_ have an effect
138+
on visibility: while `submod::PRIVATE` in `main` would _not_ resolve, the
139+
expansion including `$self::PRIVATE` does!
140+
141+
# Reference-level explanation
142+
[reference-level-explanation]: #reference-level-explanation
143+
144+
At its core, `$self` is `$crate` at the module-level as opposed to the crate
145+
level. Macro metavariable naming collisions are handled in the same way as with
146+
`$crate`. In particular, a declaration of `$self` in a macro shadows the `$self`
147+
described here. The following works as expected, and importantly, as it does
148+
today:
149+
150+
```rust
151+
macro_rules! m {
152+
($self:ident) => (println!("{}", $self))
153+
}
154+
```
155+
156+
Additionally, like `$crate`, a non-user-declared `$self` _must_ be followed by
157+
`::`.
158+
159+
Notably different is that while `$crate` can be implemented as a purely
160+
syntactic transformation, substituting `$crate` for the name of the crate in
161+
which the macro is defined, `$self` must apply the full resolution context of
162+
the macro's definition site to the succeeding path. When calling a macro using
163+
`$self` cross-crate, this requires cross-crate hygiene. Thankfully, this was
164+
recently added to the compiler in https://github.com/rust-lang/rust/pull/72121.
165+
166+
Thus, `$self` can be simply and without further caveats by specified as: for
167+
every path in the expansion that begins with `$self`, the resolution context of
168+
the path is set to resolution context of the `Span::source()` of `$self`.
169+
170+
In addition to the examples in the introductory text, consider the following:
171+
172+
```rust
173+
mod a {
174+
static PRIVATE: &'static str = "B";
175+
176+
#[macro_export]
177+
macro_rules! m1 {
178+
($($var:tt)*) => (println!("{}, {}", $self::PRIVATE, $($var)*))
179+
}
180+
}
181+
182+
mod b {
183+
static PRIVATE: &'static str = "A";
184+
185+
#[macro_export]
186+
macro_rules! m2 {
187+
() => (m1!($self::PRIVATE))
188+
}
189+
}
190+
191+
pub fn main() {
192+
m2!();
193+
}
194+
```
195+
196+
The resulting program prints `B, A`.
197+
198+
# Drawbacks
199+
[drawbacks]: #drawbacks
200+
201+
As always, introducing new language-level features can add the cognitive
202+
overhead. However, `$self`'s similarity to `$crate` means that it doesn't
203+
introduce an entirely new concept. What's more, it is orthogonal to all existing
204+
language features, which means users find one solution to the problem it
205+
resolves.
206+
207+
`$self` as described here is backwards-compatible: there are no compatibility
208+
hazards.
209+
210+
# Rationale and alternatives
211+
[rationale-and-alternatives]: #rationale-and-alternatives
212+
213+
1. Wait for Macros 2.0
214+
215+
Self-explanatory. Unfortunately, the implementation and stabilization of
216+
macros 2.0 is in limbo.
217+
218+
2. Propagate Resolution Context for Items, Too
219+
220+
The second `submod` example in the introductory text could be made to work.
221+
Unfortunately, this has the major drawback that it breaks existing code. That
222+
is, it is not backwards-compatible. Furthermore, it requires two expansions
223+
to achieve the same net-effect that this proposal allows in one.
224+
225+
3. Use some other syntax, like `#PRIVATE`, to capture hygiene
226+
227+
Instead of `$self::PRIVATE`, `#PRIVATE` could yield the same effect. This
228+
introduces brand new syntax with no existing analogy, however, and so would
229+
be harder to teach.
230+
231+
# Prior art
232+
[prior-art]: #prior-art
233+
234+
I am not aware of an existing `$self`-like mechanism in other languages. Rust's
235+
own `$crate` is the inspiration for this feature. Other issues, notably going
236+
back to https://github.com/rust-lang/rust/issues/22462, have also considered the
237+
deficiency resolved by this proposal.
238+
239+
# Unresolved questions
240+
[unresolved-questions]: #unresolved-questions
241+
242+
None.
243+
244+
# Future possibilities
245+
[future-possibilities]: #future-possibilities
246+
247+
None. Macros 2.0 continues to be the eventual goal.

0 commit comments

Comments
 (0)