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

Only match a fragment specifier if it starts with certain tokens. #42913

Merged
merged 1 commit into from
Jul 11, 2017
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
74 changes: 70 additions & 4 deletions src/libsyntax/ext/tt/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,12 +386,11 @@ fn inner_parse_loop(sess: &ParseSess,
return Error(span, "missing fragment specifier".to_string());
}
}
TokenTree::MetaVarDecl(..) => {
TokenTree::MetaVarDecl(_, _, id) => {
// Built-in nonterminals never start with these tokens,
// so we can eliminate them from consideration.
match *token {
token::CloseDelim(_) => {},
_ => bb_eis.push(ei),
if may_begin_with(&*id.name.as_str(), token) {
bb_eis.push(ei);
}
}
seq @ TokenTree::Delimited(..) | seq @ TokenTree::Token(_, DocComment(..)) => {
Expand Down Expand Up @@ -493,6 +492,73 @@ pub fn parse(sess: &ParseSess,
}
}

/// Checks whether a non-terminal may begin with a particular token.
///
/// Returning `false` is a *stability guarantee* that such a matcher will *never* begin with that
/// token. Be conservative (return true) if not sure.
fn may_begin_with(name: &str, token: &Token) -> bool {
/// Checks whether the non-terminal may contain a single (non-keyword) identifier.
fn may_be_ident(nt: &token::Nonterminal) -> bool {
match *nt {
token::NtItem(_) | token::NtBlock(_) | token::NtVis(_) => false,
_ => true,
}
}

match name {
"expr" => token.can_begin_expr(),
"ty" => token.can_begin_type(),
"ident" => token.is_ident(),
"vis" => match *token { // The follow-set of :vis + "priv" keyword + interpolated
Token::Comma | Token::Ident(_) | Token::Interpolated(_) => true,
_ => token.can_begin_type(),
},
"block" => match *token {
Token::OpenDelim(token::Brace) => true,
Token::Interpolated(ref nt) => match nt.0 {
token::NtItem(_) |
token::NtPat(_) |
token::NtTy(_) |
token::NtIdent(_) |
token::NtMeta(_) |
token::NtPath(_) |
token::NtVis(_) => false, // none of these may start with '{'.
_ => true,
},
_ => false,
},
"path" | "meta" => match *token {
Token::ModSep | Token::Ident(_) => true,
Token::Interpolated(ref nt) => match nt.0 {
token::NtPath(_) | token::NtMeta(_) => true,
_ => may_be_ident(&nt.0),
},
_ => false,
},
"pat" => match *token {
Token::Ident(_) | // box, ref, mut, and other identifiers (can stricten)
Token::OpenDelim(token::Paren) | // tuple pattern
Token::OpenDelim(token::Bracket) | // slice pattern
Token::BinOp(token::And) | // reference
Token::BinOp(token::Minus) | // negative literal
Token::AndAnd | // double reference
Token::Literal(..) | // literal
Token::DotDot | // range pattern (future compat)
Token::DotDotDot | // range pattern (future compat)
Token::ModSep | // path
Token::Lt | // path (UFCS constant)
Token::BinOp(token::Shl) | // path (double UFCS)
Token::Underscore => true, // placeholder
Token::Interpolated(ref nt) => may_be_ident(&nt.0),
_ => false,
},
_ => match *token {
token::CloseDelim(_) => false,
_ => true,
},
}
}

fn parse_nt<'a>(p: &mut Parser<'a>, sp: Span, name: &str) -> Nonterminal {
if name == "tt" {
return token::NtTT(p.parse_token_tree());
Expand Down
2 changes: 1 addition & 1 deletion src/test/compile-fail/fail-simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
// except according to those terms.

fn main() {
panic!(@); //~ ERROR expected expression, found `@`
panic!(@); //~ ERROR no rules expected the token `@`
}
2 changes: 1 addition & 1 deletion src/test/compile-fail/vec-macro-with-comma-only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
// except according to those terms.

pub fn main() {
vec![,]; //~ ERROR expected expression, found `,`
vec![,]; //~ ERROR no rules expected the token `,`
}
245 changes: 245 additions & 0 deletions src/test/run-pass/macro-first-set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(macro_vis_matcher)]

//{{{ issue 40569 ==============================================================

macro_rules! my_struct {
($(#[$meta:meta])* $ident:ident) => {
$(#[$meta])* struct $ident;
}
}

my_struct!(#[derive(Debug, PartialEq)] Foo40569);

fn test_40569() {
assert_eq!(Foo40569, Foo40569);
}

//}}}

//{{{ issue 26444 ==============================================================

macro_rules! foo_26444 {
($($beginning:ident),*; $middle:ident; $($end:ident),*) => {
stringify!($($beginning,)* $middle $(,$end)*)
}
}

fn test_26444() {
assert_eq!("a , b , c , d , e", foo_26444!(a, b; c; d, e));
assert_eq!("f", foo_26444!(; f ;));
}

macro_rules! pat_26444 {
($fname:ident $($arg:pat)* =) => {}
}

pat_26444!(foo 1 2 5...7 =);
pat_26444!(bar Some(ref x) Ok(ref mut y) &(w, z) =);

//}}}

//{{{ issue 40984 ==============================================================

macro_rules! thread_local_40984 {
() => {};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => {
thread_local_40984!($($rest)*);
};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => {};
}

thread_local_40984! {
// no docs
#[allow(unused)]
static FOO: i32 = 42;
/// docs
pub static BAR: String = String::from("bar");

// look at these restrictions!!
pub(crate) static BAZ: usize = 0;
pub(in foo) static QUUX: usize = 0;
}

//}}}

//{{{ issue 35650 ==============================================================

macro_rules! size {
($ty:ty) => {
std::mem::size_of::<$ty>()
};
($size:tt) => {
$size
};
}

fn test_35650() {
assert_eq!(size!(u64), 8);
assert_eq!(size!(5), 5);
}

//}}}

//{{{ issue 27832 ==============================================================

macro_rules! m {
( $i:ident ) => ();
( $t:tt $j:tt ) => ();
}

m!(c);
m!(t 9);
m!(0 9);
m!(struct);
m!(struct Foo);

macro_rules! m2 {
( $b:expr ) => ();
( $t:tt $u:tt ) => ();
}

m2!(3);
m2!(1 2);
m2!(_ 1);
m2!(enum Foo);

//}}}

//{{{ issue 39964 ==============================================================

macro_rules! foo_39964 {
($a:ident) => {};
(_) => {};
}

foo_39964!(_);

//}}}

//{{{ issue 34030 ==============================================================

macro_rules! foo_34030 {
($($t:ident),* /) => {};
}

foo_34030!(a, b/);
foo_34030!(a/);
foo_34030!(/);

//}}}

//{{{ issue 24189 ==============================================================

macro_rules! foo_24189 {
(
pub enum $name:ident {
$( #[$attr:meta] )* $var:ident
}
) => {
pub enum $name {
$( #[$attr] )* $var
}
};
}

foo_24189! {
pub enum Foo24189 {
#[doc = "Bar"] Baz
}
}

macro_rules! serializable {
(
$(#[$struct_meta:meta])*
pub struct $name:ident {
$(
$(#[$field_meta:meta])*
$field:ident: $type_:ty
),* ,
}
) => {
$(#[$struct_meta])*
pub struct $name {
$(
$(#[$field_meta])*
$field: $type_
),* ,
}
}
}

serializable! {
#[allow(dead_code)]
/// This is a test
pub struct Tester {
#[allow(dead_code)]
name: String,
}
}

macro_rules! foo_24189_c {
( $( > )* $x:ident ) => { };
}
foo_24189_c!( > a );

fn test_24189() {
let _ = Foo24189::Baz;
let _ = Tester { name: "".to_owned() };
}

//}}}

//{{{ some more tests ==========================================================

macro_rules! test_block {
(< $($b:block)* >) => {}
}

test_block!(<>);
test_block!(<{}>);
test_block!(<{1}{2}>);

macro_rules! test_ty {
($($t:ty),* $(,)*) => {}
}

test_ty!();
test_ty!(,);
test_ty!(u8);
test_ty!(u8,);

macro_rules! test_path {
($($t:path),* $(,)*) => {}
}

test_path!();
test_path!(,);
test_path!(::std);
test_path!(std::u8,);
test_path!(any, super, super::super::self::path, X<Y>::Z<'a, T=U>);

macro_rules! test_meta_block {
($($m:meta)* $b:block) => {};
}

test_meta_block!(windows {});

//}}}

fn main() {
test_26444();
test_40569();
test_35650();
test_24189();
}