Skip to content

Commit 6680c9c

Browse files
committed
syntax: implement 'macro input future proofing'
See RFC 550 (rust-lang/rfcs#550) for the motivation and details. If this breaks your code, add one of the listed tokens after the relevant non-terminal in your matcher. [breaking-change]
1 parent 8efd990 commit 6680c9c

File tree

3 files changed

+167
-8
lines changed

3 files changed

+167
-8
lines changed

src/libsyntax/ext/tt/macro_rules.rs

Lines changed: 140 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
22
// file at the top-level directory of this distribution and at
33
// http://rust-lang.org/COPYRIGHT.
44
//
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use ast::{Ident, TtDelimited, TtSequence, TtToken};
11+
use ast::{TokenTree, TtDelimited, TtSequence, TtToken};
1212
use ast;
1313
use codemap::{Span, DUMMY_SP};
1414
use ext::base::{ExtCtxt, MacResult, SyntaxExtension};
@@ -19,8 +19,8 @@ use ext::tt::macro_parser::{parse, parse_or_else};
1919
use parse::lexer::new_tt_reader;
2020
use parse::parser::Parser;
2121
use parse::attr::ParserAttr;
22-
use parse::token::{special_idents, gensym_ident};
23-
use parse::token::{MatchNt, NtTT};
22+
use parse::token::{special_idents, gensym_ident, NtTT, Token};
23+
use parse::token::Token::*;
2424
use parse::token;
2525
use print;
2626
use ptr::P;
@@ -109,8 +109,8 @@ impl<'a> MacResult for ParserAnyMacro<'a> {
109109
}
110110

111111
struct MacroRulesMacroExpander {
112-
name: Ident,
113-
imported_from: Option<Ident>,
112+
name: ast::Ident,
113+
imported_from: Option<ast::Ident>,
114114
lhses: Vec<Rc<NamedMatch>>,
115115
rhses: Vec<Rc<NamedMatch>>,
116116
}
@@ -134,8 +134,8 @@ impl TTMacroExpander for MacroRulesMacroExpander {
134134
/// Given `lhses` and `rhses`, this is the new macro we create
135135
fn generic_extension<'cx>(cx: &'cx ExtCtxt,
136136
sp: Span,
137-
name: Ident,
138-
imported_from: Option<Ident>,
137+
name: ast::Ident,
138+
imported_from: Option<ast::Ident>,
139139
arg: &[ast::TokenTree],
140140
lhses: &[Rc<NamedMatch>],
141141
rhses: &[Rc<NamedMatch>])
@@ -260,6 +260,10 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
260260
_ => cx.span_bug(def.span, "wrong-structured lhs")
261261
};
262262

263+
for lhs in lhses.iter() {
264+
check_lhs_nt_follows(cx, &**lhs, def.span);
265+
}
266+
263267
let rhses = match *argument_map[rhs_nm] {
264268
MatchedSeq(ref s, _) => /* FIXME (#2543) */ (*s).clone(),
265269
_ => cx.span_bug(def.span, "wrong-structured rhs")
@@ -274,3 +278,131 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
274278

275279
NormalTT(exp, Some(def.span))
276280
}
281+
282+
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &NamedMatch, sp: Span) {
283+
// lhs is going to be like MatchedNonterminal(NtTT(TtDelimited(...))), where
284+
// the entire lhs is those tts.
285+
// if ever we get box/deref patterns, this could turn into an `if let
286+
// &MatchedNonterminal(NtTT(box TtDelimited(...))) = lhs`
287+
let matcher = match lhs {
288+
&MatchedNonterminal(NtTT(ref inner)) => match &**inner {
289+
&TtDelimited(_, ref tts) => tts.tts[],
290+
_ => cx.span_bug(sp, "wrong-structured lhs for follow check")
291+
},
292+
_ => cx.span_bug(sp, "wrong-structured lhs for follow check")
293+
};
294+
295+
check_matcher(cx, matcher, &Eof);
296+
// we don't abort on errors on rejection, the driver will do that for us
297+
// after parsing/expansion. we can report every error in every macro this way.
298+
}
299+
300+
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree], follow: &Token) {
301+
use print::pprust::token_to_string;
302+
303+
// 1. If there are no tokens in M, accept
304+
if matcher.is_empty() {
305+
return;
306+
}
307+
308+
// 2. For each token T in M:
309+
let mut tokens = matcher.iter().peekable();
310+
while let Some(token) = tokens.next() {
311+
match *token {
312+
TtToken(sp, MatchNt(ref name, ref frag_spec, _, _)) => {
313+
// ii. If T is a simple NT, look ahead to the next token T' in
314+
// M.
315+
let next_token = match tokens.peek() {
316+
// 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,
322+
// else, we're at the end of the macro or sequence
323+
None => follow
324+
};
325+
326+
// 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))[])
334+
}
335+
},
336+
TtSequence(_, ref seq) => {
337+
// iii. Else, T is a complex NT.
338+
match seq.separator {
339+
// If T has the form $(...)U+ or $(...)U* for some token U,
340+
// run the algorithm on the contents with F set to U. If it
341+
// 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)
347+
}
348+
},
349+
TtToken(..) => {
350+
// i. If T is not an NT, continue.
351+
continue
352+
},
353+
TtDelimited(_, ref tts) => {
354+
// 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())
357+
}
358+
}
359+
}
360+
}
361+
362+
fn is_in_follow(cx: &ExtCtxt, tok: &Token, frag: &str) -> bool {
363+
if let &CloseDelim(_) = tok {
364+
return true;
365+
}
366+
367+
match frag {
368+
"item" => {
369+
// since items *must* be followed by either a `;` or a `}`, we can
370+
// accept anything after them
371+
true
372+
},
373+
"block" => {
374+
// anything can follow block, the braces provide a easy boundary to
375+
// maintain
376+
true
377+
},
378+
"stmt" | "expr" => {
379+
match *tok {
380+
Comma | Semi => true,
381+
_ => false
382+
}
383+
},
384+
"pat" => {
385+
match *tok {
386+
FatArrow | Comma | Eq => true,
387+
_ => false
388+
}
389+
},
390+
"path" | "ty" => {
391+
match *tok {
392+
Comma | RArrow | Colon | Eq | Gt => true,
393+
Ident(i, _) if i.as_str() == "as" => true,
394+
_ => false
395+
}
396+
},
397+
"ident" => {
398+
// being a single token, idents are harmless
399+
true
400+
},
401+
"meta" | "tt" => {
402+
// being either a single token or a delimited sequence, tt is
403+
// harmless
404+
true
405+
},
406+
_ => cx.bug(format!("unrecognized builtin nonterminal {}", frag)[]),
407+
}
408+
}

src/libsyntax/parse/token.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ impl fmt::Show for Nonterminal {
392392
}
393393
}
394394

395+
395396
// Get the first "argument"
396397
macro_rules! first {
397398
( $first:expr, $( $remainder:expr, )* ) => ( $first )
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
macro_rules! errors_everywhere {
12+
($ty:ty <) => () //~ ERROR `$ty:ty` is followed by `<`, which is not allowed for `ty` fragments
13+
($ty:ty < foo ,) => () //~ ERROR `$ty:ty` is followed by `<`, which is not allowed for `ty`
14+
($ty:ty , ) => ()
15+
( ( $ty:ty ) ) => ()
16+
( { $ty:ty } ) => ()
17+
( [ $ty:ty ] ) => ()
18+
($bl:block < ) => ()
19+
($pa:pat >) => () //~ ERROR `$pa:pat` is followed by `>` which is not allowed for `pat`
20+
($pa:pat , ) => ()
21+
($pa:pat | ) => ()
22+
($pa:pat $pb:pat $ty:ty ,) => ()
23+
($($ty:ty)-+) => () //~ ERROR `$ty:ty` is followed by `-` which is not allowed for `ty`
24+
}
25+
26+
fn main() { }

0 commit comments

Comments
 (0)