Skip to content

Commit ac8e105

Browse files
committed
Stricter rules surrounding adjacent nonterminals and sequences
1 parent e0b4287 commit ac8e105

File tree

3 files changed

+82
-30
lines changed

3 files changed

+82
-30
lines changed

src/libcore/macros.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,11 @@ macro_rules! write {
186186
#[macro_export]
187187
#[stable]
188188
macro_rules! writeln {
189-
($dst:expr, $fmt:expr $($arg:tt)*) => (
189+
($dst:expr, $fmt:expr, $($arg:tt)*) => (
190190
write!($dst, concat!($fmt, "\n") $($arg)*)
191+
);
192+
($dst:expr, $fmt:expr) => (
193+
write!($dst, concat!($fmt, "\n"))
191194
)
192195
}
193196

src/libsyntax/ext/tt/macro_rules.rs

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -292,58 +292,102 @@ fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &NamedMatch, sp: Span) {
292292
_ => cx.span_bug(sp, "wrong-structured lhs for follow check")
293293
};
294294

295-
check_matcher(cx, matcher, &Eof);
295+
check_matcher(cx, matcher.iter(), &Eof);
296296
// we don't abort on errors on rejection, the driver will do that for us
297297
// after parsing/expansion. we can report every error in every macro this way.
298298
}
299299

300-
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree], follow: &Token) {
300+
// returns the last token that was checked, for TtSequence. this gets used later on.
301+
fn check_matcher<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token)
302+
-> Option<(Span, Token)> where I: Iterator<Item=&'a TokenTree> {
301303
use print::pprust::token_to_string;
302304

303-
// 1. If there are no tokens in M, accept
304-
if matcher.is_empty() {
305-
return;
306-
}
305+
let mut last = None;
307306

308307
// 2. For each token T in M:
309-
let mut tokens = matcher.iter().peekable();
308+
let mut tokens = matcher.peekable();
310309
while let Some(token) = tokens.next() {
311-
match *token {
310+
last = match *token {
312311
TtToken(sp, MatchNt(ref name, ref frag_spec, _, _)) => {
313312
// ii. If T is a simple NT, look ahead to the next token T' in
314313
// M.
315314
let next_token = match tokens.peek() {
316315
// If T' closes a complex NT, replace T' with F
317-
Some(&&TtToken(_, CloseDelim(_))) => follow,
318-
Some(&&TtToken(_, ref tok)) => tok,
319-
// T' is any NT (this catches complex NTs, the next
320-
// iteration will die if it's a TtDelimited).
321-
Some(_) => continue,
316+
Some(&&TtToken(_, CloseDelim(_))) => follow.clone(),
317+
Some(&&TtToken(_, ref tok)) => tok.clone(),
318+
Some(&&TtSequence(sp, _)) => {
319+
cx.span_err(sp, format!("`${0}:{1}` is followed by a sequence \
320+
repetition, which is not allowed for `{1}` \
321+
fragments", name.as_str(), frag_spec.as_str())[]);
322+
Eof
323+
},
324+
// die next iteration
325+
Some(&&TtDelimited(_, ref delim)) => delim.close_token(),
322326
// else, we're at the end of the macro or sequence
323-
None => follow
327+
None => follow.clone()
324328
};
325329

330+
let tok = if let TtToken(_, ref tok) = *token { tok } else { unreachable!() };
326331
// If T' is in the set FOLLOW(NT), continue. Else, reject.
327-
match *next_token {
328-
Eof | MatchNt(..) => continue,
329-
_ if is_in_follow(cx, next_token, frag_spec.as_str()) => continue,
330-
ref tok => cx.span_err(sp, format!("`${0}:{1}` is followed by `{2}`, which \
331-
is not allowed for `{1}` fragments",
332-
name.as_str(), frag_spec.as_str(),
333-
token_to_string(tok))[])
332+
match &next_token {
333+
&Eof => return Some((sp, tok.clone())),
334+
_ if is_in_follow(cx, &next_token, frag_spec.as_str()) => continue,
335+
next => {
336+
cx.span_err(sp, format!("`${0}:{1}` is followed by `{2}`, which \
337+
is not allowed for `{1}` fragments",
338+
name.as_str(), frag_spec.as_str(),
339+
token_to_string(next))[]);
340+
continue
341+
},
334342
}
335343
},
336-
TtSequence(_, ref seq) => {
344+
TtSequence(sp, ref seq) => {
337345
// iii. Else, T is a complex NT.
338346
match seq.separator {
339347
// If T has the form $(...)U+ or $(...)U* for some token U,
340348
// run the algorithm on the contents with F set to U. If it
341349
// accepts, continue, else, reject.
342-
Some(ref u) => check_matcher(cx, seq.tts[], u),
343-
// If T has the form $(...)+ or $(...)*, run the algorithm
344-
// on the contents with F set to EOF. If it accepts,
345-
// continue, else, reject.
346-
None => check_matcher(cx, seq.tts[], &Eof)
350+
Some(ref u) => {
351+
let last = check_matcher(cx, seq.tts.iter(), u);
352+
match last {
353+
// Since the delimiter isn't required after the last repetition, make
354+
// sure that the *next* token is sane. This doesn't actually compute
355+
// the FIRST of the rest of the matcher yet, it only considers single
356+
// tokens and simple NTs. This is imprecise, but conservatively
357+
// correct.
358+
Some((span, tok)) => {
359+
let fol = match tokens.peek() {
360+
Some(&&TtToken(_, ref tok)) => tok.clone(),
361+
Some(&&TtDelimited(_, ref delim)) => delim.close_token(),
362+
Some(_) => {
363+
cx.span_err(sp, "sequence repetition followed by \
364+
another sequence repetition, which is not allowed");
365+
Eof
366+
},
367+
None => Eof
368+
};
369+
check_matcher(cx, Some(&TtToken(span, tok.clone())).into_iter(),
370+
&fol)
371+
},
372+
None => last,
373+
}
374+
},
375+
// If T has the form $(...)+ or $(...)*, run the algorithm on the contents with
376+
// F set to the token following the sequence. If it accepts, continue, else,
377+
// reject.
378+
None => {
379+
let fol = match tokens.peek() {
380+
Some(&&TtToken(_, ref tok)) => tok.clone(),
381+
Some(&&TtDelimited(_, ref delim)) => delim.close_token(),
382+
Some(_) => {
383+
cx.span_err(sp, "sequence repetition followed by another \
384+
sequence repetition, which is not allowed");
385+
Eof
386+
},
387+
None => Eof
388+
};
389+
check_matcher(cx, seq.tts.iter(), &fol)
390+
}
347391
}
348392
},
349393
TtToken(..) => {
@@ -352,11 +396,12 @@ fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree], follow: &Token) {
352396
},
353397
TtDelimited(_, ref tts) => {
354398
// if we don't pass in that close delimiter, we'll incorrectly consider the matcher
355-
// `{ $foo:ty }` as having a follow that isn't `}`
356-
check_matcher(cx, tts.tts[], &tts.close_token())
399+
// `{ $foo:ty }` as having a follow that isn't `RBrace`
400+
check_matcher(cx, tts.tts.iter(), &tts.close_token())
357401
}
358402
}
359403
}
404+
last
360405
}
361406

362407
fn is_in_follow(cx: &ExtCtxt, tok: &Token, frag: &str) -> bool {

src/test/compile-fail/macro-input-future-proofing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ macro_rules! errors_everywhere {
2020
($pa:pat , ) => ();
2121
($pa:pat | ) => (); //~ ERROR `$pa:pat` is followed by `|`
2222
($pa:pat $pb:pat $ty:ty ,) => ();
23+
//~^ ERROR `$pa:pat` is followed by `$pb:pat`, which is not allowed
24+
//~^^ ERROR `$pb:pat` is followed by `$ty:ty`, which is not allowed
25+
($($ty:ty)* -) => (); //~ ERROR `$ty:ty` is followed by `-`
26+
($($a:ty, $b:ty)* -) => (); //~ ERROR `$b:ty` is followed by `-`
2327
($($ty:ty)-+) => (); //~ ERROR `$ty:ty` is followed by `-`, which is not allowed for `ty`
2428
}
2529

0 commit comments

Comments
 (0)