Skip to content

Commit eb364e9

Browse files
committed
Make sure that macros that didn't pass LHS checking are not expanded.
This avoids duplicate errors for things like invalid fragment specifiers, or parsing errors for ambiguous macros. Fixes #29231.
1 parent 310d899 commit eb364e9

File tree

5 files changed

+93
-42
lines changed

5 files changed

+93
-42
lines changed

src/libsyntax/ext/tt/macro_parser.rs

+3-8
Original file line numberDiff line numberDiff line change
@@ -549,13 +549,8 @@ pub fn parse_nt<'a>(p: &mut Parser<'a>, sp: Span, name: &str) -> Nonterminal {
549549
token::NtPath(Box::new(panictry!(p.parse_path(PathStyle::Type))))
550550
},
551551
"meta" => token::NtMeta(panictry!(p.parse_meta_item())),
552-
_ => {
553-
p.span_fatal_help(sp,
554-
&format!("invalid fragment specifier `{}`", name),
555-
"valid fragment specifiers are `ident`, `block`, \
556-
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
557-
and `item`").emit();
558-
panic!(FatalError);
559-
}
552+
// this is not supposed to happen, since it has been checked
553+
// when compiling the macro.
554+
_ => p.span_bug(sp, "invalid fragment specifier")
560555
}
561556
}

src/libsyntax/ext/tt/macro_rules.rs

+53-33
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
299299
};
300300

301301
for lhs in &lhses {
302-
check_lhs_nt_follows(cx, lhs, def.span);
302+
valid &= check_lhs_nt_follows(cx, lhs);
303303
}
304304

305305
let rhses = match **argument_map.get(&rhs_nm.name).unwrap() {
@@ -330,19 +330,19 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
330330
// why is this here? because of https://github.com/rust-lang/rust/issues/27774
331331
fn ref_slice<A>(s: &A) -> &[A] { use std::slice::from_raw_parts; unsafe { from_raw_parts(s, 1) } }
332332

333-
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree, sp: Span) {
333+
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree) -> bool {
334334
// lhs is going to be like TokenTree::Delimited(...), where the
335335
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
336336
match lhs {
337-
&TokenTree::Delimited(_, ref tts) => {
338-
check_matcher(cx, &tts.tts);
339-
},
340-
tt @ &TokenTree::Sequence(..) => {
341-
check_matcher(cx, ref_slice(tt));
342-
},
343-
_ => cx.span_err(sp, "invalid macro matcher; matchers must be contained \
344-
in balanced delimiters or a repetition indicator")
345-
};
337+
&TokenTree::Delimited(_, ref tts) => check_matcher(cx, &tts.tts),
338+
tt @ &TokenTree::Sequence(..) => check_matcher(cx, ref_slice(tt)),
339+
_ => {
340+
cx.span_err(lhs.get_span(),
341+
"invalid macro matcher; matchers must be contained \
342+
in balanced delimiters or a repetition indicator");
343+
false
344+
}
345+
}
346346
// we don't abort on errors on rejection, the driver will do that for us
347347
// after parsing/expansion. we can report every error in every macro this way.
348348
}
@@ -364,28 +364,33 @@ struct OnFail {
364364
action: OnFailAction,
365365
}
366366

367-
#[derive(Copy, Clone, Debug)]
367+
#[derive(Copy, Clone, Debug, PartialEq)]
368368
enum OnFailAction { Warn, Error, DoNothing }
369369

370370
impl OnFail {
371371
fn warn() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Warn } }
372372
fn error() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Error } }
373373
fn do_nothing() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::DoNothing } }
374-
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str) {
374+
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str, help: Option<&str>) {
375375
match self.action {
376376
OnFailAction::DoNothing => {}
377-
OnFailAction::Error => cx.span_err(sp, msg),
377+
OnFailAction::Error => {
378+
let mut err = cx.struct_span_err(sp, msg);
379+
if let Some(msg) = help { err.span_help(sp, msg); }
380+
err.emit();
381+
}
378382
OnFailAction::Warn => {
379-
cx.struct_span_warn(sp, msg)
380-
.span_note(sp, "The above warning will be a hard error in the next release.")
383+
let mut warn = cx.struct_span_warn(sp, msg);
384+
if let Some(msg) = help { warn.span_help(sp, msg); }
385+
warn.span_note(sp, "The above warning will be a hard error in the next release.")
381386
.emit();
382387
}
383388
};
384389
self.saw_failure = true;
385390
}
386391
}
387392

388-
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) {
393+
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) -> bool {
389394
// Issue 30450: when we are through a warning cycle, we can just
390395
// error on all failure conditions (and remove check_matcher_old).
391396

@@ -400,6 +405,9 @@ fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) {
400405
OnFail::warn()
401406
};
402407
check_matcher_new(cx, matcher, &mut on_fail);
408+
// matcher is valid if the new pass didn't see any error,
409+
// or if errors were considered warnings
410+
on_fail.action != OnFailAction::Error || !on_fail.saw_failure
403411
}
404412

405413
// returns the last token that was checked, for TokenTree::Sequence.
@@ -435,11 +443,11 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
435443
// sequence, which may itself be a sequence,
436444
// and so on).
437445
on_fail.react(cx, sp,
438-
&format!("`${0}:{1}` is followed by a \
439-
sequence repetition, which is not \
440-
allowed for `{1}` fragments",
441-
name, frag_spec)
442-
);
446+
&format!("`${0}:{1}` is followed by a \
447+
sequence repetition, which is not \
448+
allowed for `{1}` fragments",
449+
name, frag_spec),
450+
None);
443451
Eof
444452
},
445453
// die next iteration
@@ -456,8 +464,10 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
456464

457465
// If T' is in the set FOLLOW(NT), continue. Else, reject.
458466
match (&next_token, is_in_follow(cx, &next_token, &frag_spec.name.as_str())) {
459-
(_, Err(msg)) => {
460-
on_fail.react(cx, sp, &msg);
467+
(_, Err((msg, _))) => {
468+
// no need for help message, those messages
469+
// are never emitted anyway...
470+
on_fail.react(cx, sp, &msg, None);
461471
continue
462472
}
463473
(&Eof, _) => return Some((sp, tok.clone())),
@@ -466,7 +476,7 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
466476
on_fail.react(cx, sp, &format!("`${0}:{1}` is followed by `{2}`, which \
467477
is not allowed for `{1}` fragments",
468478
name, frag_spec,
469-
token_to_string(next)));
479+
token_to_string(next)), None);
470480
continue
471481
},
472482
}
@@ -494,7 +504,8 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
494504
delim.close_token(),
495505
Some(_) => {
496506
on_fail.react(cx, sp, "sequence repetition followed by \
497-
another sequence repetition, which is not allowed");
507+
another sequence repetition, which is not allowed",
508+
None);
498509
Eof
499510
},
500511
None => Eof
@@ -514,7 +525,7 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
514525
Some(&&TokenTree::Delimited(_, ref delim)) => delim.close_token(),
515526
Some(_) => {
516527
on_fail.react(cx, sp, "sequence repetition followed by another \
517-
sequence repetition, which is not allowed");
528+
sequence repetition, which is not allowed", None);
518529
Eof
519530
},
520531
None => Eof
@@ -810,7 +821,11 @@ fn check_matcher_core(cx: &mut ExtCtxt,
810821
TokenTree::Token(sp, ref tok) => {
811822
let can_be_followed_by_any;
812823
if let Err(bad_frag) = has_legal_fragment_specifier(tok) {
813-
on_fail.react(cx, sp, &format!("invalid fragment specifier `{}`", bad_frag));
824+
on_fail.react(cx, sp,
825+
&format!("invalid fragment specifier `{}`", bad_frag),
826+
Some("valid fragment specifiers are `ident`, `block`, \
827+
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
828+
and `item`"));
814829
// (This eliminates false positives and duplicates
815830
// from error messages.)
816831
can_be_followed_by_any = true;
@@ -884,8 +899,8 @@ fn check_matcher_core(cx: &mut ExtCtxt,
884899
if let MatchNt(ref name, ref frag_spec) = *t {
885900
for &(sp, ref next_token) in &suffix_first.tokens {
886901
match is_in_follow(cx, next_token, &frag_spec.name.as_str()) {
887-
Err(msg) => {
888-
on_fail.react(cx, sp, &msg);
902+
Err((msg, help)) => {
903+
on_fail.react(cx, sp, &msg, Some(help));
889904
// don't bother reporting every source of
890905
// conflict for a particular element of `last`.
891906
continue 'each_last;
@@ -907,7 +922,9 @@ fn check_matcher_core(cx: &mut ExtCtxt,
907922
name=name,
908923
frag=frag_spec,
909924
next=token_to_string(next_token),
910-
may_be=may_be));
925+
may_be=may_be),
926+
None
927+
);
911928
}
912929
}
913930
}
@@ -978,7 +995,7 @@ fn can_be_followed_by_any(frag: &str) -> bool {
978995
/// break macros that were relying on that binary operator as a
979996
/// separator.
980997
// when changing this do not forget to update doc/book/macros.md!
981-
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
998+
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, (String, &'static str)> {
982999
if let &CloseDelim(_) = tok {
9831000
// closing a token tree can never be matched by any fragment;
9841001
// iow, we always require that `(` and `)` match, etc.
@@ -1027,7 +1044,10 @@ fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
10271044
// harmless
10281045
Ok(true)
10291046
},
1030-
_ => Err(format!("invalid fragment specifier `{}`", frag))
1047+
_ => Err((format!("invalid fragment specifier `{}`", frag),
1048+
"valid fragment specifiers are `ident`, `block`, \
1049+
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
1050+
and `item`"))
10311051
}
10321052
}
10331053
}

src/test/compile-fail/invalid-macro-matcher.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// except according to those terms.
1010

1111
macro_rules! invalid {
12-
_ => (); //~^ ERROR invalid macro matcher
12+
_ => (); //~ ERROR invalid macro matcher
1313
}
1414

1515
fn main() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2016 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! foo(
12+
($x:foo) => ()
13+
//~^ ERROR invalid fragment specifier
14+
//~| HELP valid fragment specifiers are
15+
);
16+
17+
fn main() {
18+
foo!(foo);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2016 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! baz(
12+
baz => () //~ ERROR invalid macro matcher;
13+
);
14+
15+
fn main() {
16+
baz!(baz);
17+
}

0 commit comments

Comments
 (0)