Skip to content

Commit

Permalink
Auto merge of #42913 - kennytm:fix-40569-ident-without-backtrack, r=j…
Browse files Browse the repository at this point in the history
…seyfried

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

When trying to match a fragment specifier, we first predict whether the current token can be matched at all. If it cannot be matched, don't bother to push the Earley item to `bb_eis`. This can fix a lot of issues which otherwise requires full backtracking (#42838).

In this PR the prediction treatment is not done for `:item`, `:stmt` and `:tt`, but it could be expanded in the future.

Fixes #24189.
Fixes #26444.
Fixes #27832.
Fixes #34030.
Fixes #35650.
Fixes #39964.
Fixes the 4th comment in #40569.
Fixes the issue blocking #40984.
  • Loading branch information
bors committed Jul 11, 2017
2 parents 9228d23 + 6008004 commit 1999bfa
Show file tree
Hide file tree
Showing 4 changed files with 317 additions and 6 deletions.
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();
}

0 comments on commit 1999bfa

Please sign in to comment.