Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always capture tokens for macro_rules! arguments #73293

Merged
merged 1 commit into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/librustc_expand/mbe/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,18 +866,39 @@ fn parse_nt(p: &mut Parser<'_>, sp: Span, name: Symbol) -> Result<Nonterminal, (
}

fn parse_nt_inner<'a>(p: &mut Parser<'a>, sp: Span, name: Symbol) -> PResult<'a, Nonterminal> {
// Any `Nonterminal` which stores its tokens (currently `NtItem` and `NtExpr`)
// needs to have them force-captured here.
// A `macro_rules!` invocation may pass a captured item/expr to a proc-macro,
// which requires having captured tokens available. Since we cannot determine
// in advance whether or not a proc-macro will be (transitively) invoked,
// we always capture tokens for any `Nonterminal` which needs them.
Ok(match name {
sym::item => match p.parse_item()? {
Some(i) => token::NtItem(i),
None => return Err(p.struct_span_err(p.token.span, "expected an item keyword")),
sym::item => match p.collect_tokens(|this| this.parse_item())? {
(Some(mut item), tokens) => {
// If we captured tokens during parsing (due to outer attributes),
// use those.
if item.tokens.is_none() {
item.tokens = Some(tokens);
}
token::NtItem(item)
}
(None, _) => return Err(p.struct_span_err(p.token.span, "expected an item keyword")),
},
sym::block => token::NtBlock(p.parse_block()?),
sym::stmt => match p.parse_stmt()? {
Some(s) => token::NtStmt(s),
None => return Err(p.struct_span_err(p.token.span, "expected a statement")),
},
sym::pat => token::NtPat(p.parse_pat(None)?),
sym::expr => token::NtExpr(p.parse_expr()?),
sym::expr => {
let (mut expr, tokens) = p.collect_tokens(|this| this.parse_expr())?;
// If we captured tokens during parsing (due to outer attributes),
// use those.
if expr.tokens.is_none() {
expr.tokens = Some(tokens);
}
token::NtExpr(expr)
}
sym::literal => token::NtLiteral(p.parse_literal_maybe_minus()?),
sym::ty => token::NtTy(p.parse_ty()?),
// this could be handled like a token, since it is one
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_parse/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ impl<'a> Parser<'a> {
/// This restriction shouldn't be an issue in practice,
/// since this function is used to record the tokens for
/// a parsed AST item, which always has matching delimiters.
fn collect_tokens<R>(
pub fn collect_tokens<R>(
&mut self,
f: impl FnOnce(&mut Self) -> PResult<'a, R>,
) -> PResult<'a, (R, TokenStream)> {
Expand Down
20 changes: 20 additions & 0 deletions src/test/ui/proc-macro/auxiliary/first-second.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// force-host
// no-prefer-dynamic

#![crate_type = "proc-macro"]

extern crate proc_macro;

use proc_macro::{TokenStream, TokenTree, Group, Delimiter};

#[proc_macro_attribute]
pub fn first(_attr: TokenStream, item: TokenStream) -> TokenStream {
let tokens: TokenStream = "#[derive(Second)]".parse().unwrap();
let wrapped = TokenTree::Group(Group::new(Delimiter::None, item.into_iter().collect()));
tokens.into_iter().chain(std::iter::once(wrapped)).collect()
}

#[proc_macro_derive(Second)]
pub fn second(item: TokenStream) -> TokenStream {
TokenStream::new()
}
12 changes: 12 additions & 0 deletions src/test/ui/proc-macro/auxiliary/recollect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// force-host
// no-prefer-dynamic

#![crate_type = "proc-macro"]

extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro]
pub fn recollect(tokens: TokenStream) -> TokenStream {
tokens.into_iter().collect()
}
48 changes: 48 additions & 0 deletions src/test/ui/proc-macro/auxiliary/weird-hygiene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// force-host
// no-prefer-dynamic

#![crate_type = "proc-macro"]

extern crate proc_macro;

use proc_macro::{TokenStream, TokenTree, Group};

fn find_my_ident(tokens: TokenStream) -> Option<TokenStream> {
for token in tokens {
if let TokenTree::Ident(ident) = &token {
if ident.to_string() == "hidden_ident" {
return Some(vec![token].into_iter().collect())
}
} else if let TokenTree::Group(g) = token {
if let Some(stream) = find_my_ident(g.stream()) {
return Some(stream)
}
}
}
return None;
}


#[proc_macro_derive(WeirdDerive)]
pub fn weird_derive(item: TokenStream) -> TokenStream {
let my_ident = find_my_ident(item).expect("Missing 'my_ident'!");
let tokens: TokenStream = "call_it!();".parse().unwrap();
let final_call = tokens.into_iter().map(|tree| {
if let TokenTree::Group(g) = tree {
return Group::new(g.delimiter(), my_ident.clone()).into()
} else {
return tree
}
}).collect();
final_call
}

#[proc_macro]
pub fn recollect(item: TokenStream) -> TokenStream {
item.into_iter().collect()
}

#[proc_macro_attribute]
pub fn recollect_attr(_attr: TokenStream, mut item: TokenStream) -> TokenStream {
item.into_iter().collect()
}
22 changes: 22 additions & 0 deletions src/test/ui/proc-macro/capture-macro-rules-invoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// aux-build:test-macros.rs
// check-pass

extern crate test_macros;
use test_macros::recollect;

macro_rules! use_expr {
($expr:expr) => {
recollect!($expr)
}
}

#[allow(dead_code)]
struct Foo;
impl Foo {
#[allow(dead_code)]
fn use_self(self) {
drop(use_expr!(self));
}
}

fn main() {}
20 changes: 20 additions & 0 deletions src/test/ui/proc-macro/macro-rules-derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// aux-build:first-second.rs
// FIXME: The spans here are bad, see PR #73084

extern crate first_second;
use first_second::*;

macro_rules! produce_it {
($name:ident) => {
#[first] //~ ERROR cannot find type
struct $name {
field: MissingType
}
}
}

produce_it!(MyName);

fn main() {
println!("Hello, world!");
}
9 changes: 9 additions & 0 deletions src/test/ui/proc-macro/macro-rules-derive.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0412]: cannot find type `MissingType` in this scope
--> $DIR/macro-rules-derive.rs:9:9
|
LL | #[first]
| ^^^^^^^^ not found in this scope

error: aborting due to previous error

For more information about this error, try `rustc --explain E0412`.
48 changes: 48 additions & 0 deletions src/test/ui/proc-macro/weird-hygiene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// aux-build:weird-hygiene.rs
// check-pass
// FIXME: This should actually error, see PR #73084

#![feature(stmt_expr_attributes)]
#![feature(proc_macro_hygiene)]

extern crate weird_hygiene;
use weird_hygiene::*;

macro_rules! other {
($tokens:expr) => {
macro_rules! call_it {
($outer_ident:ident) => {
macro_rules! inner {
() => {
$outer_ident;
}
}
}
}

#[derive(WeirdDerive)]
enum MyEnum {
Value = (stringify!($tokens + hidden_ident), 1).1
}

inner!();
}
}

macro_rules! invoke_it {
($token:expr) => {
#[recollect_attr] {
$token;
hidden_ident
}
}
}

fn main() {
// `other` and `invoke_it` are both macro_rules! macros,
// so it should be impossible for them to ever see `hidden_ident`,
// even if they invoke a proc macro.
let hidden_ident = "Hello1";
other!(50);
invoke_it!(25);
}