Skip to content

Commit da66f2f

Browse files
committed
Auto merge of rust-lang#33713 - LeoTestard:macro-rules-invalid-lhs, r=pnkfelix
Make sure that macros that didn't pass LHS checking are not expanded. This avoid duplicate errors for things like invalid fragment specifiers, or parsing errors for ambiguous macros.
2 parents 5229e0e + 7d52144 commit da66f2f

File tree

5 files changed

+96
-46
lines changed

5 files changed

+96
-46
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

+56-37
Original file line numberDiff line numberDiff line change
@@ -291,17 +291,16 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
291291
let lhses = match **argument_map.get(&lhs_nm.name).unwrap() {
292292
MatchedSeq(ref s, _) => {
293293
s.iter().map(|m| match **m {
294-
MatchedNonterminal(NtTT(ref tt)) => (**tt).clone(),
294+
MatchedNonterminal(NtTT(ref tt)) => {
295+
valid &= check_lhs_nt_follows(cx, tt);
296+
(**tt).clone()
297+
}
295298
_ => cx.span_bug(def.span, "wrong-structured lhs")
296299
}).collect()
297300
}
298301
_ => cx.span_bug(def.span, "wrong-structured lhs")
299302
};
300303

301-
for lhs in &lhses {
302-
check_lhs_nt_follows(cx, lhs, def.span);
303-
}
304-
305304
let rhses = match **argument_map.get(&rhs_nm.name).unwrap() {
306305
MatchedSeq(ref s, _) => {
307306
s.iter().map(|m| match **m {
@@ -330,19 +329,19 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
330329
// why is this here? because of https://github.com/rust-lang/rust/issues/27774
331330
fn ref_slice<A>(s: &A) -> &[A] { use std::slice::from_raw_parts; unsafe { from_raw_parts(s, 1) } }
332331

333-
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree, sp: Span) {
332+
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree) -> bool {
334333
// lhs is going to be like TokenTree::Delimited(...), where the
335334
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
336335
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-
};
336+
&TokenTree::Delimited(_, ref tts) => check_matcher(cx, &tts.tts),
337+
tt @ &TokenTree::Sequence(..) => check_matcher(cx, ref_slice(tt)),
338+
_ => {
339+
cx.span_err(lhs.get_span(),
340+
"invalid macro matcher; matchers must be contained \
341+
in balanced delimiters or a repetition indicator");
342+
false
343+
}
344+
}
346345
// we don't abort on errors on rejection, the driver will do that for us
347346
// after parsing/expansion. we can report every error in every macro this way.
348347
}
@@ -364,28 +363,33 @@ struct OnFail {
364363
action: OnFailAction,
365364
}
366365

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

370369
impl OnFail {
371370
fn warn() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Warn } }
372371
fn error() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Error } }
373372
fn do_nothing() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::DoNothing } }
374-
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str) {
373+
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str, help: Option<&str>) {
375374
match self.action {
376375
OnFailAction::DoNothing => {}
377-
OnFailAction::Error => cx.span_err(sp, msg),
376+
OnFailAction::Error => {
377+
let mut err = cx.struct_span_err(sp, msg);
378+
if let Some(msg) = help { err.span_help(sp, msg); }
379+
err.emit();
380+
}
378381
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.")
382+
let mut warn = cx.struct_span_warn(sp, msg);
383+
if let Some(msg) = help { warn.span_help(sp, msg); }
384+
warn.span_note(sp, "The above warning will be a hard error in the next release.")
381385
.emit();
382386
}
383387
};
384388
self.saw_failure = true;
385389
}
386390
}
387391

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

@@ -400,6 +404,9 @@ fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) {
400404
OnFail::warn()
401405
};
402406
check_matcher_new(cx, matcher, &mut on_fail);
407+
// matcher is valid if the new pass didn't see any error,
408+
// or if errors were considered warnings
409+
on_fail.action != OnFailAction::Error || !on_fail.saw_failure
403410
}
404411

405412
// returns the last token that was checked, for TokenTree::Sequence.
@@ -435,11 +442,11 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
435442
// sequence, which may itself be a sequence,
436443
// and so on).
437444
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-
);
445+
&format!("`${0}:{1}` is followed by a \
446+
sequence repetition, which is not \
447+
allowed for `{1}` fragments",
448+
name, frag_spec),
449+
None);
443450
Eof
444451
},
445452
// die next iteration
@@ -456,8 +463,10 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
456463

457464
// If T' is in the set FOLLOW(NT), continue. Else, reject.
458465
match (&next_token, is_in_follow(cx, &next_token, &frag_spec.name.as_str())) {
459-
(_, Err(msg)) => {
460-
on_fail.react(cx, sp, &msg);
466+
(_, Err((msg, _))) => {
467+
// no need for help message, those messages
468+
// are never emitted anyway...
469+
on_fail.react(cx, sp, &msg, None);
461470
continue
462471
}
463472
(&Eof, _) => return Some((sp, tok.clone())),
@@ -466,7 +475,7 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
466475
on_fail.react(cx, sp, &format!("`${0}:{1}` is followed by `{2}`, which \
467476
is not allowed for `{1}` fragments",
468477
name, frag_spec,
469-
token_to_string(next)));
478+
token_to_string(next)), None);
470479
continue
471480
},
472481
}
@@ -494,7 +503,8 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
494503
delim.close_token(),
495504
Some(_) => {
496505
on_fail.react(cx, sp, "sequence repetition followed by \
497-
another sequence repetition, which is not allowed");
506+
another sequence repetition, which is not allowed",
507+
None);
498508
Eof
499509
},
500510
None => Eof
@@ -514,7 +524,7 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
514524
Some(&&TokenTree::Delimited(_, ref delim)) => delim.close_token(),
515525
Some(_) => {
516526
on_fail.react(cx, sp, "sequence repetition followed by another \
517-
sequence repetition, which is not allowed");
527+
sequence repetition, which is not allowed", None);
518528
Eof
519529
},
520530
None => Eof
@@ -810,7 +820,11 @@ fn check_matcher_core(cx: &mut ExtCtxt,
810820
TokenTree::Token(sp, ref tok) => {
811821
let can_be_followed_by_any;
812822
if let Err(bad_frag) = has_legal_fragment_specifier(tok) {
813-
on_fail.react(cx, sp, &format!("invalid fragment specifier `{}`", bad_frag));
823+
on_fail.react(cx, sp,
824+
&format!("invalid fragment specifier `{}`", bad_frag),
825+
Some("valid fragment specifiers are `ident`, `block`, \
826+
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
827+
and `item`"));
814828
// (This eliminates false positives and duplicates
815829
// from error messages.)
816830
can_be_followed_by_any = true;
@@ -884,8 +898,8 @@ fn check_matcher_core(cx: &mut ExtCtxt,
884898
if let MatchNt(ref name, ref frag_spec) = *t {
885899
for &(sp, ref next_token) in &suffix_first.tokens {
886900
match is_in_follow(cx, next_token, &frag_spec.name.as_str()) {
887-
Err(msg) => {
888-
on_fail.react(cx, sp, &msg);
901+
Err((msg, help)) => {
902+
on_fail.react(cx, sp, &msg, Some(help));
889903
// don't bother reporting every source of
890904
// conflict for a particular element of `last`.
891905
continue 'each_last;
@@ -907,7 +921,9 @@ fn check_matcher_core(cx: &mut ExtCtxt,
907921
name=name,
908922
frag=frag_spec,
909923
next=token_to_string(next_token),
910-
may_be=may_be));
924+
may_be=may_be),
925+
None
926+
);
911927
}
912928
}
913929
}
@@ -978,7 +994,7 @@ fn can_be_followed_by_any(frag: &str) -> bool {
978994
/// break macros that were relying on that binary operator as a
979995
/// separator.
980996
// 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> {
997+
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, (String, &'static str)> {
982998
if let &CloseDelim(_) = tok {
983999
// closing a token tree can never be matched by any fragment;
9841000
// iow, we always require that `(` and `)` match, etc.
@@ -1027,7 +1043,10 @@ fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
10271043
// harmless
10281044
Ok(true)
10291045
},
1030-
_ => Err(format!("invalid fragment specifier `{}`", frag))
1046+
_ => Err((format!("invalid fragment specifier `{}`", frag),
1047+
"valid fragment specifiers are `ident`, `block`, \
1048+
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
1049+
and `item`"))
10311050
}
10321051
}
10331052
}

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)