Skip to content

Commit 63b000b

Browse files
committed
Auto merge of #25444 - nikomatsakis:macro-tt-fix, r=pnkfelix
Permit token trees, identifiers, and blocks to be following by sequences. Fixes #25436. r? @pnkfelix
2 parents 8fdb3a4 + 7a5d748 commit 63b000b

File tree

5 files changed

+173
-34
lines changed

5 files changed

+173
-34
lines changed

src/libsyntax/ext/tt/macro_rules.rs

+78-34
Original file line numberDiff line numberDiff line change
@@ -325,42 +325,55 @@ fn check_matcher<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token)
325325
last = match *token {
326326
TtToken(sp, MatchNt(ref name, ref frag_spec, _, _)) => {
327327
// ii. If T is a simple NT, look ahead to the next token T' in
328-
// M.
329-
let next_token = match tokens.peek() {
330-
// If T' closes a complex NT, replace T' with F
331-
Some(&&TtToken(_, CloseDelim(_))) => follow.clone(),
332-
Some(&&TtToken(_, ref tok)) => tok.clone(),
333-
Some(&&TtSequence(sp, _)) => {
334-
cx.span_err(sp,
335-
&format!("`${0}:{1}` is followed by a \
336-
sequence repetition, which is not \
337-
allowed for `{1}` fragments",
338-
name.as_str(), frag_spec.as_str())
328+
// M. If T' is in the set FOLLOW(NT), continue. Else; reject.
329+
if can_be_followed_by_any(frag_spec.as_str()) {
330+
continue
331+
} else {
332+
let next_token = match tokens.peek() {
333+
// If T' closes a complex NT, replace T' with F
334+
Some(&&TtToken(_, CloseDelim(_))) => follow.clone(),
335+
Some(&&TtToken(_, ref tok)) => tok.clone(),
336+
Some(&&TtSequence(sp, _)) => {
337+
// Be conservative around sequences: to be
338+
// more specific, we would need to
339+
// consider FIRST sets, but also the
340+
// possibility that the sequence occurred
341+
// zero times (in which case we need to
342+
// look at the token that follows the
343+
// sequence, which may itself a sequence,
344+
// and so on).
345+
cx.span_err(sp,
346+
&format!("`${0}:{1}` is followed by a \
347+
sequence repetition, which is not \
348+
allowed for `{1}` fragments",
349+
name.as_str(), frag_spec.as_str())
339350
);
340-
Eof
341-
},
342-
// die next iteration
343-
Some(&&TtDelimited(_, ref delim)) => delim.close_token(),
344-
// else, we're at the end of the macro or sequence
345-
None => follow.clone()
346-
};
347-
348-
let tok = if let TtToken(_, ref tok) = *token { tok } else { unreachable!() };
349-
// If T' is in the set FOLLOW(NT), continue. Else, reject.
350-
match (&next_token, is_in_follow(cx, &next_token, frag_spec.as_str())) {
351-
(_, Err(msg)) => {
352-
cx.span_err(sp, &msg);
353-
continue
351+
Eof
352+
},
353+
// die next iteration
354+
Some(&&TtDelimited(_, ref delim)) => delim.close_token(),
355+
// else, we're at the end of the macro or sequence
356+
None => follow.clone()
357+
};
358+
359+
let tok = if let TtToken(_, ref tok) = *token { tok } else { unreachable!() };
360+
361+
// If T' is in the set FOLLOW(NT), continue. Else, reject.
362+
match (&next_token, is_in_follow(cx, &next_token, frag_spec.as_str())) {
363+
(_, Err(msg)) => {
364+
cx.span_err(sp, &msg);
365+
continue
366+
}
367+
(&Eof, _) => return Some((sp, tok.clone())),
368+
(_, Ok(true)) => continue,
369+
(next, Ok(false)) => {
370+
cx.span_err(sp, &format!("`${0}:{1}` is followed by `{2}`, which \
371+
is not allowed for `{1}` fragments",
372+
name.as_str(), frag_spec.as_str(),
373+
token_to_string(next)));
374+
continue
375+
},
354376
}
355-
(&Eof, _) => return Some((sp, tok.clone())),
356-
(_, Ok(true)) => continue,
357-
(next, Ok(false)) => {
358-
cx.span_err(sp, &format!("`${0}:{1}` is followed by `{2}`, which \
359-
is not allowed for `{1}` fragments",
360-
name.as_str(), frag_spec.as_str(),
361-
token_to_string(next)));
362-
continue
363-
},
364377
}
365378
},
366379
TtSequence(sp, ref seq) => {
@@ -427,8 +440,39 @@ fn check_matcher<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token)
427440
last
428441
}
429442

443+
/// True if a fragment of type `frag` can be followed by any sort of
444+
/// token. We use this (among other things) as a useful approximation
445+
/// for when `frag` can be followed by a repetition like `$(...)*` or
446+
/// `$(...)+`. In general, these can be a bit tricky to reason about,
447+
/// so we adopt a conservative position that says that any fragment
448+
/// specifier which consumes at most one token tree can be followed by
449+
/// a fragment specifier (indeed, these fragments can be followed by
450+
/// ANYTHING without fear of future compatibility hazards).
451+
fn can_be_followed_by_any(frag: &str) -> bool {
452+
match frag {
453+
"item" | // always terminated by `}` or `;`
454+
"block" | // exactly one token tree
455+
"ident" | // exactly one token tree
456+
"meta" | // exactly one token tree
457+
"tt" => // exactly one token tree
458+
true,
459+
460+
_ =>
461+
false,
462+
}
463+
}
464+
465+
/// True if `frag` can legally be followed by the token `tok`. For
466+
/// fragments that can consume an unbounded numbe of tokens, `tok`
467+
/// must be within a well-defined follow set. This is intended to
468+
/// guarantee future compatibility: for example, without this rule, if
469+
/// we expanded `expr` to include a new binary operator, we might
470+
/// break macros that were relying on that binary operator as a
471+
/// separator.
430472
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
431473
if let &CloseDelim(_) = tok {
474+
// closing a token tree can never be matched by any fragment;
475+
// iow, we always require that `(` and `)` match, etc.
432476
Ok(true)
433477
} else {
434478
match frag {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Regression test for issue #25436: check that things which can be
12+
// followed by any token also permit X* to come afterwards.
13+
14+
macro_rules! foo {
15+
( $a:expr $($b:tt)* ) => { }; //~ ERROR not allowed for `expr` fragments
16+
( $a:ty $($b:tt)* ) => { }; //~ ERROR not allowed for `ty` fragments
17+
}
18+
19+
fn main() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Check that we cannot have two sequence repetitions in a row.
12+
13+
macro_rules! foo {
14+
( $($a:expr)* $($b:tt)* ) => { }; //~ ERROR sequence repetition followed by another sequence
15+
( $($a:tt)* $($b:tt)* ) => { }; //~ ERROR sequence repetition followed by another sequence
16+
}
17+
18+
fn main() { }
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Regression test for issue #25436: check that things which can be
12+
// followed by any token also permit X* to come afterwards.
13+
14+
macro_rules! foo {
15+
( $a:tt $($b:tt)* ) => { };
16+
( $a:ident $($b:tt)* ) => { };
17+
( $a:item $($b:tt)* ) => { };
18+
( $a:block $($b:tt)* ) => { };
19+
( $a:meta $($b:tt)* ) => { }
20+
}
21+
22+
fn main() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Regression test for issue #25436: permit token-trees to be followed
12+
// by sequences, enabling more general parsing.
13+
14+
use self::Join::*;
15+
16+
#[derive(Debug)]
17+
enum Join<A,B> {
18+
Keep(A,B),
19+
Skip(A,B),
20+
}
21+
22+
macro_rules! parse_list {
23+
( < $a:expr; > $($b:tt)* ) => { Keep(parse_item!($a),parse_list!($($b)*)) };
24+
( $a:tt $($b:tt)* ) => { Skip(parse_item!($a), parse_list!($($b)*)) };
25+
( ) => { () };
26+
}
27+
28+
macro_rules! parse_item {
29+
( $x:expr ) => { $x }
30+
}
31+
32+
fn main() {
33+
let list = parse_list!(<1;> 2 <3;> 4);
34+
assert_eq!("Keep(1, Skip(2, Keep(3, Skip(4, ()))))",
35+
format!("{:?}", list));
36+
}

0 commit comments

Comments
 (0)