Rollup merge of #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 https://github.com/rust-lang/rust/pull/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 (https://github.com/rust-lang/rust/issues/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
This commit is contained in:
commit
2a9bd75994
10 changed files with 179 additions and 35 deletions
|
@ -148,6 +148,7 @@ macro_rules! with_api {
|
|||
fn debug($self: $S::Span) -> String;
|
||||
fn def_site() -> $S::Span;
|
||||
fn call_site() -> $S::Span;
|
||||
fn mixed_site() -> $S::Span;
|
||||
fn source_file($self: $S::Span) -> $S::SourceFile;
|
||||
fn parent($self: $S::Span) -> Option<$S::Span>;
|
||||
fn source($self: $S::Span) -> $S::Span;
|
||||
|
|
|
@ -271,6 +271,15 @@ impl Span {
|
|||
Span(bridge::client::Span::call_site())
|
||||
}
|
||||
|
||||
/// A span that represents `macro_rules` hygiene, and sometimes resolves at the macro
|
||||
/// definition site (local variables, labels, `$crate`) and sometimes at the macro
|
||||
/// call site (everything else).
|
||||
/// The span location is taken from the call-site.
|
||||
#[unstable(feature = "proc_macro_mixed_site", issue = "65049")]
|
||||
pub fn mixed_site() -> Span {
|
||||
Span(bridge::client::Span::mixed_site())
|
||||
}
|
||||
|
||||
/// The original source file into which this span points.
|
||||
#[unstable(feature = "proc_macro_span", issue = "54725")]
|
||||
pub fn source_file(&self) -> SourceFile {
|
||||
|
|
|
@ -953,6 +953,12 @@ impl<'a> ExtCtxt<'a> {
|
|||
span.with_call_site_ctxt(self.current_expansion.id)
|
||||
}
|
||||
|
||||
/// Equivalent of `Span::mixed_site` from the proc macro API,
|
||||
/// except that the location is taken from the span passed as an argument.
|
||||
pub fn with_mixed_site_ctxt(&self, span: Span) -> Span {
|
||||
span.with_mixed_site_ctxt(self.current_expansion.id)
|
||||
}
|
||||
|
||||
/// Returns span for the macro which originally caused the current expansion to happen.
|
||||
///
|
||||
/// Stops backtracing at include! boundary.
|
||||
|
|
|
@ -355,6 +355,7 @@ pub(crate) struct Rustc<'a> {
|
|||
sess: &'a ParseSess,
|
||||
def_site: Span,
|
||||
call_site: Span,
|
||||
mixed_site: Span,
|
||||
}
|
||||
|
||||
impl<'a> Rustc<'a> {
|
||||
|
@ -364,6 +365,7 @@ impl<'a> Rustc<'a> {
|
|||
sess: cx.parse_sess,
|
||||
def_site: cx.with_def_site_ctxt(expn_data.def_site),
|
||||
call_site: cx.with_call_site_ctxt(expn_data.call_site),
|
||||
mixed_site: cx.with_mixed_site_ctxt(expn_data.call_site),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -664,6 +666,9 @@ impl server::Span for Rustc<'_> {
|
|||
fn call_site(&mut self) -> Self::Span {
|
||||
self.call_site
|
||||
}
|
||||
fn mixed_site(&mut self) -> Self::Span {
|
||||
self.mixed_site
|
||||
}
|
||||
fn source_file(&mut self, span: Self::Span) -> Self::SourceFile {
|
||||
self.sess.source_map().lookup_char_pos(span.lo()).file
|
||||
}
|
||||
|
|
|
@ -526,6 +526,12 @@ impl Span {
|
|||
self.with_ctxt_from_mark(expn_id, Transparency::Transparent)
|
||||
}
|
||||
|
||||
/// Equivalent of `Span::mixed_site` from the proc macro API,
|
||||
/// except that the location is taken from the `self` span.
|
||||
pub fn with_mixed_site_ctxt(&self, expn_id: ExpnId) -> Span {
|
||||
self.with_ctxt_from_mark(expn_id, Transparency::SemiTransparent)
|
||||
}
|
||||
|
||||
/// Produces a span with the same location as `self` and context produced by a macro with the
|
||||
/// given ID and transparency, assuming that macro was defined directly and not produced by
|
||||
/// some other macro (which is the case for built-in and procedural macros).
|
||||
|
|
42
src/test/ui/proc-macro/auxiliary/mixed-site-span.rs
Normal file
42
src/test/ui/proc-macro/auxiliary/mixed-site-span.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
// force-host
|
||||
// no-prefer-dynamic
|
||||
|
||||
#![feature(proc_macro_hygiene)]
|
||||
#![feature(proc_macro_mixed_site)]
|
||||
#![feature(proc_macro_quote)]
|
||||
|
||||
#![crate_type = "proc-macro"]
|
||||
|
||||
extern crate proc_macro;
|
||||
use proc_macro::*;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn proc_macro_rules(input: TokenStream) -> TokenStream {
|
||||
if input.is_empty() {
|
||||
let id = |s| TokenTree::from(Ident::new(s, Span::mixed_site()));
|
||||
let item_def = id("ItemDef");
|
||||
let local_def = id("local_def");
|
||||
let item_use = id("ItemUse");
|
||||
let local_use = id("local_use");
|
||||
let mut single_quote = Punct::new('\'', Spacing::Joint);
|
||||
single_quote.set_span(Span::mixed_site());
|
||||
let label_use: TokenStream = [
|
||||
TokenTree::from(single_quote),
|
||||
id("label_use"),
|
||||
].iter().cloned().collect();
|
||||
quote!(
|
||||
struct $item_def;
|
||||
let $local_def = 0;
|
||||
|
||||
$item_use; // OK
|
||||
$local_use; // ERROR
|
||||
break $label_use; // ERROR
|
||||
)
|
||||
} else {
|
||||
let mut dollar_crate = input.into_iter().next().unwrap();
|
||||
dollar_crate.set_span(Span::mixed_site());
|
||||
quote!(
|
||||
type A = $dollar_crate::ItemUse;
|
||||
)
|
||||
}
|
||||
}
|
|
@ -59,54 +59,54 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct B (identity ! ($crate :: S)) ;
|
|||
PRINT-ATTR INPUT (DEBUG): TokenStream [
|
||||
Ident {
|
||||
ident: "struct",
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "B",
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
ident: "identity",
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: '!',
|
||||
spacing: Alone,
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
ident: "$crate",
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Joint,
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Alone,
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "S",
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
],
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
],
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ';',
|
||||
spacing: Alone,
|
||||
span: #8 bytes(LO..HI),
|
||||
span: #10 bytes(LO..HI),
|
||||
},
|
||||
]
|
||||
|
|
|
@ -124,40 +124,40 @@ PRINT-BANG INPUT (DISPLAY): struct M ($crate :: S) ;
|
|||
PRINT-BANG INPUT (DEBUG): TokenStream [
|
||||
Ident {
|
||||
ident: "struct",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "M",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
ident: "$crate",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Joint,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Alone,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "S",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
],
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ';',
|
||||
spacing: Alone,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
]
|
||||
PRINT-ATTR INPUT (DISPLAY): struct A(::dollar_crate_external::S);
|
||||
|
@ -165,40 +165,40 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ($crate :: S) ;
|
|||
PRINT-ATTR INPUT (DEBUG): TokenStream [
|
||||
Ident {
|
||||
ident: "struct",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "A",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
ident: "$crate",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Joint,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Alone,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "S",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
],
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ';',
|
||||
spacing: Alone,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
]
|
||||
PRINT-DERIVE INPUT (DISPLAY): struct D(::dollar_crate_external::S);
|
||||
|
@ -206,39 +206,39 @@ PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ($crate :: S) ;
|
|||
PRINT-DERIVE INPUT (DEBUG): TokenStream [
|
||||
Ident {
|
||||
ident: "struct",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "D",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
ident: "$crate",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Joint,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ':',
|
||||
spacing: Alone,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Ident {
|
||||
ident: "S",
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
],
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
Punct {
|
||||
ch: ';',
|
||||
spacing: Alone,
|
||||
span: #10 bytes(LO..HI),
|
||||
span: #13 bytes(LO..HI),
|
||||
},
|
||||
]
|
||||
|
|
26
src/test/ui/proc-macro/mixed-site-span.rs
Normal file
26
src/test/ui/proc-macro/mixed-site-span.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Proc macros using `mixed_site` spans exhibit usual properties of `macro_rules` hygiene.
|
||||
|
||||
// aux-build:mixed-site-span.rs
|
||||
|
||||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate mixed_site_span;
|
||||
|
||||
struct ItemUse;
|
||||
|
||||
fn main() {
|
||||
'label_use: loop {
|
||||
let local_use = 1;
|
||||
proc_macro_rules!();
|
||||
//~^ ERROR use of undeclared label `'label_use`
|
||||
//~| ERROR cannot find value `local_use` in this scope
|
||||
ItemDef; // OK
|
||||
local_def; //~ ERROR cannot find value `local_def` in this scope
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! pass_dollar_crate {
|
||||
() => (proc_macro_rules!($crate);) //~ ERROR cannot find type `ItemUse` in crate `$crate`
|
||||
}
|
||||
pass_dollar_crate!();
|
49
src/test/ui/proc-macro/mixed-site-span.stderr
Normal file
49
src/test/ui/proc-macro/mixed-site-span.stderr
Normal file
|
@ -0,0 +1,49 @@
|
|||
error[E0426]: use of undeclared label `'label_use`
|
||||
--> $DIR/mixed-site-span.rs:15:9
|
||||
|
|
||||
LL | proc_macro_rules!();
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| undeclared label `'label_use`
|
||||
| in this macro invocation
|
||||
|
||||
error[E0425]: cannot find value `local_use` in this scope
|
||||
--> $DIR/mixed-site-span.rs:15:9
|
||||
|
|
||||
LL | proc_macro_rules!();
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| not found in this scope
|
||||
| in this macro invocation
|
||||
|
||||
error[E0425]: cannot find value `local_def` in this scope
|
||||
--> $DIR/mixed-site-span.rs:19:9
|
||||
|
|
||||
LL | local_def;
|
||||
| ^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0412]: cannot find type `ItemUse` in crate `$crate`
|
||||
--> $DIR/auxiliary/mixed-site-span.rs:14:1
|
||||
|
|
||||
LL | / pub fn proc_macro_rules(input: TokenStream) -> TokenStream {
|
||||
LL | | if input.is_empty() {
|
||||
LL | | let id = |s| TokenTree::from(Ident::new(s, Span::mixed_site()));
|
||||
LL | | let item_def = id("ItemDef");
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_^ not found in `$crate`
|
||||
|
|
||||
::: $DIR/mixed-site-span.rs:26:1
|
||||
|
|
||||
LL | pass_dollar_crate!();
|
||||
| --------------------- in this macro invocation
|
||||
help: possible candidate is found in another module, you can import it into scope
|
||||
|
|
||||
LL | use ItemUse;
|
||||
|
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
Some errors have detailed explanations: E0412, E0425, E0426.
|
||||
For more information about an error, try `rustc --explain E0412`.
|
Loading…
Add table
Reference in a new issue