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:
Tyler Mandry 2019-10-03 16:25:39 -07:00 committed by GitHub
commit 2a9bd75994
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 35 deletions

View file

@ -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;

View file

@ -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 {

View file

@ -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.

View file

@ -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
}

View 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).

View 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;
)
}
}

View file

@ -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),
},
]

View file

@ -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),
},
]

View 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!();

View 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`.