Skip to content

Commit

Permalink
Rollup merge of rust-lang#64690 - petrochenkov:mixed, r=dtolnay
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tmandry authored Oct 3, 2019
2 parents ae41bcd + d1310dc commit a647b74
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/libproc_macro/bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/libproc_macro/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions src/libsyntax/ext/proc_macro_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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),
}
}

Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions src/libsyntax_pos/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
42 changes: 42 additions & 0 deletions src/test/ui/proc-macro/auxiliary/mixed-site-span.rs
Original file line number Diff line number Diff line change
@@ -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;
)
}
}
22 changes: 11 additions & 11 deletions src/test/ui/proc-macro/dollar-crate-issue-62325.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
]
48 changes: 24 additions & 24 deletions src/test/ui/proc-macro/dollar-crate.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -124,121 +124,121 @@ 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);
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);
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 changes: 26 additions & 0 deletions src/test/ui/proc-macro/mixed-site-span.rs
Original file line number Diff line number Diff line change
@@ -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!();
Loading

0 comments on commit a647b74

Please sign in to comment.