Skip to content

Commit 4a1b698

Browse files
authored
Rollup merge of rust-lang#64690 - petrochenkov:mixed, r=dtolnay
proc_macro API: Expose `macro_rules` hygiene Proc macros do not have direct access to our oldest and most stable hygiene kind - `macro_rules` hygiene. To emulate it macro authors have to go through two steps - first generate a temporary `macro_rules` item (using a derive, at least until rust-lang#64035 is merged), then generate a macro call to that item. Popular crates like [proc_macro_hack](https://crates.io/crates/proc-macro-hack) use this trick to generate hygienic identifiers from proc macros. I'd say that these workarounds with nested macro definitions have more chances to hit some corner cases in our hygiene system, in which we don't have full confidence. So, let's provide a direct access to `macro_rules` hygiene instead. This PR does that by adding a new method `Span::mixed_site` (bikeshedding is welcome) in addition to existing `Span::call_site` (stable) and `Span::def_site` (unstable). Identifiers with this span resolve at def-site in for local variables, labels and `$crate`, and resolve at call-site for everything else, i.e. exactly like identifiers produced by `macro_rules`. This API addition opens the way to stabilizing proc macros in expression positions (rust-lang#54727), for which use of call-site hygiene or workarounds with temporary items would be quite unfortunate. (`macro_rules` expanded in expression position, on the other hand, are stable since 1.0 and widely used.) r? @dtolnay @alexcrichton
2 parents 0f4661c + d1310dc commit 4a1b698

File tree

10 files changed

+179
-35
lines changed

10 files changed

+179
-35
lines changed

src/libproc_macro/bridge/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ macro_rules! with_api {
148148
fn debug($self: $S::Span) -> String;
149149
fn def_site() -> $S::Span;
150150
fn call_site() -> $S::Span;
151+
fn mixed_site() -> $S::Span;
151152
fn source_file($self: $S::Span) -> $S::SourceFile;
152153
fn parent($self: $S::Span) -> Option<$S::Span>;
153154
fn source($self: $S::Span) -> $S::Span;

src/libproc_macro/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,15 @@ impl Span {
271271
Span(bridge::client::Span::call_site())
272272
}
273273

274+
/// A span that represents `macro_rules` hygiene, and sometimes resolves at the macro
275+
/// definition site (local variables, labels, `$crate`) and sometimes at the macro
276+
/// call site (everything else).
277+
/// The span location is taken from the call-site.
278+
#[unstable(feature = "proc_macro_mixed_site", issue = "65049")]
279+
pub fn mixed_site() -> Span {
280+
Span(bridge::client::Span::mixed_site())
281+
}
282+
274283
/// The original source file into which this span points.
275284
#[unstable(feature = "proc_macro_span", issue = "54725")]
276285
pub fn source_file(&self) -> SourceFile {

src/libsyntax/ext/base.rs

+6
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,12 @@ impl<'a> ExtCtxt<'a> {
953953
span.with_call_site_ctxt(self.current_expansion.id)
954954
}
955955

956+
/// Equivalent of `Span::mixed_site` from the proc macro API,
957+
/// except that the location is taken from the span passed as an argument.
958+
pub fn with_mixed_site_ctxt(&self, span: Span) -> Span {
959+
span.with_mixed_site_ctxt(self.current_expansion.id)
960+
}
961+
956962
/// Returns span for the macro which originally caused the current expansion to happen.
957963
///
958964
/// Stops backtracing at include! boundary.

src/libsyntax/ext/proc_macro_server.rs

+5
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ pub(crate) struct Rustc<'a> {
355355
sess: &'a ParseSess,
356356
def_site: Span,
357357
call_site: Span,
358+
mixed_site: Span,
358359
}
359360

360361
impl<'a> Rustc<'a> {
@@ -364,6 +365,7 @@ impl<'a> Rustc<'a> {
364365
sess: cx.parse_sess,
365366
def_site: cx.with_def_site_ctxt(expn_data.def_site),
366367
call_site: cx.with_call_site_ctxt(expn_data.call_site),
368+
mixed_site: cx.with_mixed_site_ctxt(expn_data.call_site),
367369
}
368370
}
369371

@@ -664,6 +666,9 @@ impl server::Span for Rustc<'_> {
664666
fn call_site(&mut self) -> Self::Span {
665667
self.call_site
666668
}
669+
fn mixed_site(&mut self) -> Self::Span {
670+
self.mixed_site
671+
}
667672
fn source_file(&mut self, span: Self::Span) -> Self::SourceFile {
668673
self.sess.source_map().lookup_char_pos(span.lo()).file
669674
}

src/libsyntax_pos/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,12 @@ impl Span {
526526
self.with_ctxt_from_mark(expn_id, Transparency::Transparent)
527527
}
528528

529+
/// Equivalent of `Span::mixed_site` from the proc macro API,
530+
/// except that the location is taken from the `self` span.
531+
pub fn with_mixed_site_ctxt(&self, expn_id: ExpnId) -> Span {
532+
self.with_ctxt_from_mark(expn_id, Transparency::SemiTransparent)
533+
}
534+
529535
/// Produces a span with the same location as `self` and context produced by a macro with the
530536
/// given ID and transparency, assuming that macro was defined directly and not produced by
531537
/// some other macro (which is the case for built-in and procedural macros).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// force-host
2+
// no-prefer-dynamic
3+
4+
#![feature(proc_macro_hygiene)]
5+
#![feature(proc_macro_mixed_site)]
6+
#![feature(proc_macro_quote)]
7+
8+
#![crate_type = "proc-macro"]
9+
10+
extern crate proc_macro;
11+
use proc_macro::*;
12+
13+
#[proc_macro]
14+
pub fn proc_macro_rules(input: TokenStream) -> TokenStream {
15+
if input.is_empty() {
16+
let id = |s| TokenTree::from(Ident::new(s, Span::mixed_site()));
17+
let item_def = id("ItemDef");
18+
let local_def = id("local_def");
19+
let item_use = id("ItemUse");
20+
let local_use = id("local_use");
21+
let mut single_quote = Punct::new('\'', Spacing::Joint);
22+
single_quote.set_span(Span::mixed_site());
23+
let label_use: TokenStream = [
24+
TokenTree::from(single_quote),
25+
id("label_use"),
26+
].iter().cloned().collect();
27+
quote!(
28+
struct $item_def;
29+
let $local_def = 0;
30+
31+
$item_use; // OK
32+
$local_use; // ERROR
33+
break $label_use; // ERROR
34+
)
35+
} else {
36+
let mut dollar_crate = input.into_iter().next().unwrap();
37+
dollar_crate.set_span(Span::mixed_site());
38+
quote!(
39+
type A = $dollar_crate::ItemUse;
40+
)
41+
}
42+
}

src/test/ui/proc-macro/dollar-crate-issue-62325.stdout

+11-11
Original file line numberDiff line numberDiff line change
@@ -59,54 +59,54 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct B (identity ! ($crate :: S)) ;
5959
PRINT-ATTR INPUT (DEBUG): TokenStream [
6060
Ident {
6161
ident: "struct",
62-
span: #8 bytes(LO..HI),
62+
span: #10 bytes(LO..HI),
6363
},
6464
Ident {
6565
ident: "B",
66-
span: #8 bytes(LO..HI),
66+
span: #10 bytes(LO..HI),
6767
},
6868
Group {
6969
delimiter: Parenthesis,
7070
stream: TokenStream [
7171
Ident {
7272
ident: "identity",
73-
span: #8 bytes(LO..HI),
73+
span: #10 bytes(LO..HI),
7474
},
7575
Punct {
7676
ch: '!',
7777
spacing: Alone,
78-
span: #8 bytes(LO..HI),
78+
span: #10 bytes(LO..HI),
7979
},
8080
Group {
8181
delimiter: Parenthesis,
8282
stream: TokenStream [
8383
Ident {
8484
ident: "$crate",
85-
span: #8 bytes(LO..HI),
85+
span: #10 bytes(LO..HI),
8686
},
8787
Punct {
8888
ch: ':',
8989
spacing: Joint,
90-
span: #8 bytes(LO..HI),
90+
span: #10 bytes(LO..HI),
9191
},
9292
Punct {
9393
ch: ':',
9494
spacing: Alone,
95-
span: #8 bytes(LO..HI),
95+
span: #10 bytes(LO..HI),
9696
},
9797
Ident {
9898
ident: "S",
99-
span: #8 bytes(LO..HI),
99+
span: #10 bytes(LO..HI),
100100
},
101101
],
102-
span: #8 bytes(LO..HI),
102+
span: #10 bytes(LO..HI),
103103
},
104104
],
105-
span: #8 bytes(LO..HI),
105+
span: #10 bytes(LO..HI),
106106
},
107107
Punct {
108108
ch: ';',
109109
spacing: Alone,
110-
span: #8 bytes(LO..HI),
110+
span: #10 bytes(LO..HI),
111111
},
112112
]

src/test/ui/proc-macro/dollar-crate.stdout

+24-24
Original file line numberDiff line numberDiff line change
@@ -124,121 +124,121 @@ PRINT-BANG INPUT (DISPLAY): struct M ($crate :: S) ;
124124
PRINT-BANG INPUT (DEBUG): TokenStream [
125125
Ident {
126126
ident: "struct",
127-
span: #10 bytes(LO..HI),
127+
span: #13 bytes(LO..HI),
128128
},
129129
Ident {
130130
ident: "M",
131-
span: #10 bytes(LO..HI),
131+
span: #13 bytes(LO..HI),
132132
},
133133
Group {
134134
delimiter: Parenthesis,
135135
stream: TokenStream [
136136
Ident {
137137
ident: "$crate",
138-
span: #10 bytes(LO..HI),
138+
span: #13 bytes(LO..HI),
139139
},
140140
Punct {
141141
ch: ':',
142142
spacing: Joint,
143-
span: #10 bytes(LO..HI),
143+
span: #13 bytes(LO..HI),
144144
},
145145
Punct {
146146
ch: ':',
147147
spacing: Alone,
148-
span: #10 bytes(LO..HI),
148+
span: #13 bytes(LO..HI),
149149
},
150150
Ident {
151151
ident: "S",
152-
span: #10 bytes(LO..HI),
152+
span: #13 bytes(LO..HI),
153153
},
154154
],
155-
span: #10 bytes(LO..HI),
155+
span: #13 bytes(LO..HI),
156156
},
157157
Punct {
158158
ch: ';',
159159
spacing: Alone,
160-
span: #10 bytes(LO..HI),
160+
span: #13 bytes(LO..HI),
161161
},
162162
]
163163
PRINT-ATTR INPUT (DISPLAY): struct A(::dollar_crate_external::S);
164164
PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ($crate :: S) ;
165165
PRINT-ATTR INPUT (DEBUG): TokenStream [
166166
Ident {
167167
ident: "struct",
168-
span: #10 bytes(LO..HI),
168+
span: #13 bytes(LO..HI),
169169
},
170170
Ident {
171171
ident: "A",
172-
span: #10 bytes(LO..HI),
172+
span: #13 bytes(LO..HI),
173173
},
174174
Group {
175175
delimiter: Parenthesis,
176176
stream: TokenStream [
177177
Ident {
178178
ident: "$crate",
179-
span: #10 bytes(LO..HI),
179+
span: #13 bytes(LO..HI),
180180
},
181181
Punct {
182182
ch: ':',
183183
spacing: Joint,
184-
span: #10 bytes(LO..HI),
184+
span: #13 bytes(LO..HI),
185185
},
186186
Punct {
187187
ch: ':',
188188
spacing: Alone,
189-
span: #10 bytes(LO..HI),
189+
span: #13 bytes(LO..HI),
190190
},
191191
Ident {
192192
ident: "S",
193-
span: #10 bytes(LO..HI),
193+
span: #13 bytes(LO..HI),
194194
},
195195
],
196-
span: #10 bytes(LO..HI),
196+
span: #13 bytes(LO..HI),
197197
},
198198
Punct {
199199
ch: ';',
200200
spacing: Alone,
201-
span: #10 bytes(LO..HI),
201+
span: #13 bytes(LO..HI),
202202
},
203203
]
204204
PRINT-DERIVE INPUT (DISPLAY): struct D(::dollar_crate_external::S);
205205
PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ($crate :: S) ;
206206
PRINT-DERIVE INPUT (DEBUG): TokenStream [
207207
Ident {
208208
ident: "struct",
209-
span: #10 bytes(LO..HI),
209+
span: #13 bytes(LO..HI),
210210
},
211211
Ident {
212212
ident: "D",
213-
span: #10 bytes(LO..HI),
213+
span: #13 bytes(LO..HI),
214214
},
215215
Group {
216216
delimiter: Parenthesis,
217217
stream: TokenStream [
218218
Ident {
219219
ident: "$crate",
220-
span: #10 bytes(LO..HI),
220+
span: #13 bytes(LO..HI),
221221
},
222222
Punct {
223223
ch: ':',
224224
spacing: Joint,
225-
span: #10 bytes(LO..HI),
225+
span: #13 bytes(LO..HI),
226226
},
227227
Punct {
228228
ch: ':',
229229
spacing: Alone,
230-
span: #10 bytes(LO..HI),
230+
span: #13 bytes(LO..HI),
231231
},
232232
Ident {
233233
ident: "S",
234-
span: #10 bytes(LO..HI),
234+
span: #13 bytes(LO..HI),
235235
},
236236
],
237-
span: #10 bytes(LO..HI),
237+
span: #13 bytes(LO..HI),
238238
},
239239
Punct {
240240
ch: ';',
241241
spacing: Alone,
242-
span: #10 bytes(LO..HI),
242+
span: #13 bytes(LO..HI),
243243
},
244244
]
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Proc macros using `mixed_site` spans exhibit usual properties of `macro_rules` hygiene.
2+
3+
// aux-build:mixed-site-span.rs
4+
5+
#![feature(proc_macro_hygiene)]
6+
7+
#[macro_use]
8+
extern crate mixed_site_span;
9+
10+
struct ItemUse;
11+
12+
fn main() {
13+
'label_use: loop {
14+
let local_use = 1;
15+
proc_macro_rules!();
16+
//~^ ERROR use of undeclared label `'label_use`
17+
//~| ERROR cannot find value `local_use` in this scope
18+
ItemDef; // OK
19+
local_def; //~ ERROR cannot find value `local_def` in this scope
20+
}
21+
}
22+
23+
macro_rules! pass_dollar_crate {
24+
() => (proc_macro_rules!($crate);) //~ ERROR cannot find type `ItemUse` in crate `$crate`
25+
}
26+
pass_dollar_crate!();

0 commit comments

Comments
 (0)