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.
This commit is contained in:
parent
310d8996f4
commit
eb364e9c29
@ -549,13 +549,8 @@ pub fn parse_nt<'a>(p: &mut Parser<'a>, sp: Span, name: &str) -> Nonterminal {
|
|||||||
token::NtPath(Box::new(panictry!(p.parse_path(PathStyle::Type))))
|
token::NtPath(Box::new(panictry!(p.parse_path(PathStyle::Type))))
|
||||||
},
|
},
|
||||||
"meta" => token::NtMeta(panictry!(p.parse_meta_item())),
|
"meta" => token::NtMeta(panictry!(p.parse_meta_item())),
|
||||||
_ => {
|
// this is not supposed to happen, since it has been checked
|
||||||
p.span_fatal_help(sp,
|
// when compiling the macro.
|
||||||
&format!("invalid fragment specifier `{}`", name),
|
_ => p.span_bug(sp, "invalid fragment specifier")
|
||||||
"valid fragment specifiers are `ident`, `block`, \
|
|
||||||
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
|
|
||||||
and `item`").emit();
|
|
||||||
panic!(FatalError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
|
|||||||
};
|
};
|
||||||
|
|
||||||
for lhs in &lhses {
|
for lhs in &lhses {
|
||||||
check_lhs_nt_follows(cx, lhs, def.span);
|
valid &= check_lhs_nt_follows(cx, lhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rhses = match **argument_map.get(&rhs_nm.name).unwrap() {
|
let rhses = match **argument_map.get(&rhs_nm.name).unwrap() {
|
||||||
@ -330,19 +330,19 @@ pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
|
|||||||
// why is this here? because of https://github.com/rust-lang/rust/issues/27774
|
// why is this here? because of https://github.com/rust-lang/rust/issues/27774
|
||||||
fn ref_slice<A>(s: &A) -> &[A] { use std::slice::from_raw_parts; unsafe { from_raw_parts(s, 1) } }
|
fn ref_slice<A>(s: &A) -> &[A] { use std::slice::from_raw_parts; unsafe { from_raw_parts(s, 1) } }
|
||||||
|
|
||||||
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree, sp: Span) {
|
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree) -> bool {
|
||||||
// lhs is going to be like TokenTree::Delimited(...), where the
|
// lhs is going to be like TokenTree::Delimited(...), where the
|
||||||
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
|
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
|
||||||
match lhs {
|
match lhs {
|
||||||
&TokenTree::Delimited(_, ref tts) => {
|
&TokenTree::Delimited(_, ref tts) => check_matcher(cx, &tts.tts),
|
||||||
check_matcher(cx, &tts.tts);
|
tt @ &TokenTree::Sequence(..) => check_matcher(cx, ref_slice(tt)),
|
||||||
},
|
_ => {
|
||||||
tt @ &TokenTree::Sequence(..) => {
|
cx.span_err(lhs.get_span(),
|
||||||
check_matcher(cx, ref_slice(tt));
|
"invalid macro matcher; matchers must be contained \
|
||||||
},
|
in balanced delimiters or a repetition indicator");
|
||||||
_ => cx.span_err(sp, "invalid macro matcher; matchers must be contained \
|
false
|
||||||
in balanced delimiters or a repetition indicator")
|
}
|
||||||
};
|
}
|
||||||
// we don't abort on errors on rejection, the driver will do that for us
|
// we don't abort on errors on rejection, the driver will do that for us
|
||||||
// after parsing/expansion. we can report every error in every macro this way.
|
// after parsing/expansion. we can report every error in every macro this way.
|
||||||
}
|
}
|
||||||
@ -364,20 +364,25 @@ struct OnFail {
|
|||||||
action: OnFailAction,
|
action: OnFailAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
enum OnFailAction { Warn, Error, DoNothing }
|
enum OnFailAction { Warn, Error, DoNothing }
|
||||||
|
|
||||||
impl OnFail {
|
impl OnFail {
|
||||||
fn warn() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Warn } }
|
fn warn() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Warn } }
|
||||||
fn error() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Error } }
|
fn error() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Error } }
|
||||||
fn do_nothing() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::DoNothing } }
|
fn do_nothing() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::DoNothing } }
|
||||||
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str) {
|
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str, help: Option<&str>) {
|
||||||
match self.action {
|
match self.action {
|
||||||
OnFailAction::DoNothing => {}
|
OnFailAction::DoNothing => {}
|
||||||
OnFailAction::Error => cx.span_err(sp, msg),
|
OnFailAction::Error => {
|
||||||
|
let mut err = cx.struct_span_err(sp, msg);
|
||||||
|
if let Some(msg) = help { err.span_help(sp, msg); }
|
||||||
|
err.emit();
|
||||||
|
}
|
||||||
OnFailAction::Warn => {
|
OnFailAction::Warn => {
|
||||||
cx.struct_span_warn(sp, msg)
|
let mut warn = cx.struct_span_warn(sp, msg);
|
||||||
.span_note(sp, "The above warning will be a hard error in the next release.")
|
if let Some(msg) = help { warn.span_help(sp, msg); }
|
||||||
|
warn.span_note(sp, "The above warning will be a hard error in the next release.")
|
||||||
.emit();
|
.emit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -385,7 +390,7 @@ impl OnFail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) {
|
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) -> bool {
|
||||||
// Issue 30450: when we are through a warning cycle, we can just
|
// Issue 30450: when we are through a warning cycle, we can just
|
||||||
// error on all failure conditions (and remove check_matcher_old).
|
// error on all failure conditions (and remove check_matcher_old).
|
||||||
|
|
||||||
@ -400,6 +405,9 @@ fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) {
|
|||||||
OnFail::warn()
|
OnFail::warn()
|
||||||
};
|
};
|
||||||
check_matcher_new(cx, matcher, &mut on_fail);
|
check_matcher_new(cx, matcher, &mut on_fail);
|
||||||
|
// matcher is valid if the new pass didn't see any error,
|
||||||
|
// or if errors were considered warnings
|
||||||
|
on_fail.action != OnFailAction::Error || !on_fail.saw_failure
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the last token that was checked, for TokenTree::Sequence.
|
// 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
|
|||||||
// sequence, which may itself be a sequence,
|
// sequence, which may itself be a sequence,
|
||||||
// and so on).
|
// and so on).
|
||||||
on_fail.react(cx, sp,
|
on_fail.react(cx, sp,
|
||||||
&format!("`${0}:{1}` is followed by a \
|
&format!("`${0}:{1}` is followed by a \
|
||||||
sequence repetition, which is not \
|
sequence repetition, which is not \
|
||||||
allowed for `{1}` fragments",
|
allowed for `{1}` fragments",
|
||||||
name, frag_spec)
|
name, frag_spec),
|
||||||
);
|
None);
|
||||||
Eof
|
Eof
|
||||||
},
|
},
|
||||||
// die next iteration
|
// die next iteration
|
||||||
@ -456,8 +464,10 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
|
|||||||
|
|
||||||
// If T' is in the set FOLLOW(NT), continue. Else, reject.
|
// If T' is in the set FOLLOW(NT), continue. Else, reject.
|
||||||
match (&next_token, is_in_follow(cx, &next_token, &frag_spec.name.as_str())) {
|
match (&next_token, is_in_follow(cx, &next_token, &frag_spec.name.as_str())) {
|
||||||
(_, Err(msg)) => {
|
(_, Err((msg, _))) => {
|
||||||
on_fail.react(cx, sp, &msg);
|
// no need for help message, those messages
|
||||||
|
// are never emitted anyway...
|
||||||
|
on_fail.react(cx, sp, &msg, None);
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
(&Eof, _) => return Some((sp, tok.clone())),
|
(&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
|
|||||||
on_fail.react(cx, sp, &format!("`${0}:{1}` is followed by `{2}`, which \
|
on_fail.react(cx, sp, &format!("`${0}:{1}` is followed by `{2}`, which \
|
||||||
is not allowed for `{1}` fragments",
|
is not allowed for `{1}` fragments",
|
||||||
name, frag_spec,
|
name, frag_spec,
|
||||||
token_to_string(next)));
|
token_to_string(next)), None);
|
||||||
continue
|
continue
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -494,7 +504,8 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
|
|||||||
delim.close_token(),
|
delim.close_token(),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
on_fail.react(cx, sp, "sequence repetition followed by \
|
on_fail.react(cx, sp, "sequence repetition followed by \
|
||||||
another sequence repetition, which is not allowed");
|
another sequence repetition, which is not allowed",
|
||||||
|
None);
|
||||||
Eof
|
Eof
|
||||||
},
|
},
|
||||||
None => Eof
|
None => Eof
|
||||||
@ -514,7 +525,7 @@ fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fai
|
|||||||
Some(&&TokenTree::Delimited(_, ref delim)) => delim.close_token(),
|
Some(&&TokenTree::Delimited(_, ref delim)) => delim.close_token(),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
on_fail.react(cx, sp, "sequence repetition followed by another \
|
on_fail.react(cx, sp, "sequence repetition followed by another \
|
||||||
sequence repetition, which is not allowed");
|
sequence repetition, which is not allowed", None);
|
||||||
Eof
|
Eof
|
||||||
},
|
},
|
||||||
None => Eof
|
None => Eof
|
||||||
@ -810,7 +821,11 @@ fn check_matcher_core(cx: &mut ExtCtxt,
|
|||||||
TokenTree::Token(sp, ref tok) => {
|
TokenTree::Token(sp, ref tok) => {
|
||||||
let can_be_followed_by_any;
|
let can_be_followed_by_any;
|
||||||
if let Err(bad_frag) = has_legal_fragment_specifier(tok) {
|
if let Err(bad_frag) = has_legal_fragment_specifier(tok) {
|
||||||
on_fail.react(cx, sp, &format!("invalid fragment specifier `{}`", bad_frag));
|
on_fail.react(cx, sp,
|
||||||
|
&format!("invalid fragment specifier `{}`", bad_frag),
|
||||||
|
Some("valid fragment specifiers are `ident`, `block`, \
|
||||||
|
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
|
||||||
|
and `item`"));
|
||||||
// (This eliminates false positives and duplicates
|
// (This eliminates false positives and duplicates
|
||||||
// from error messages.)
|
// from error messages.)
|
||||||
can_be_followed_by_any = true;
|
can_be_followed_by_any = true;
|
||||||
@ -884,8 +899,8 @@ fn check_matcher_core(cx: &mut ExtCtxt,
|
|||||||
if let MatchNt(ref name, ref frag_spec) = *t {
|
if let MatchNt(ref name, ref frag_spec) = *t {
|
||||||
for &(sp, ref next_token) in &suffix_first.tokens {
|
for &(sp, ref next_token) in &suffix_first.tokens {
|
||||||
match is_in_follow(cx, next_token, &frag_spec.name.as_str()) {
|
match is_in_follow(cx, next_token, &frag_spec.name.as_str()) {
|
||||||
Err(msg) => {
|
Err((msg, help)) => {
|
||||||
on_fail.react(cx, sp, &msg);
|
on_fail.react(cx, sp, &msg, Some(help));
|
||||||
// don't bother reporting every source of
|
// don't bother reporting every source of
|
||||||
// conflict for a particular element of `last`.
|
// conflict for a particular element of `last`.
|
||||||
continue 'each_last;
|
continue 'each_last;
|
||||||
@ -907,7 +922,9 @@ fn check_matcher_core(cx: &mut ExtCtxt,
|
|||||||
name=name,
|
name=name,
|
||||||
frag=frag_spec,
|
frag=frag_spec,
|
||||||
next=token_to_string(next_token),
|
next=token_to_string(next_token),
|
||||||
may_be=may_be));
|
may_be=may_be),
|
||||||
|
None
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -978,7 +995,7 @@ fn can_be_followed_by_any(frag: &str) -> bool {
|
|||||||
/// break macros that were relying on that binary operator as a
|
/// break macros that were relying on that binary operator as a
|
||||||
/// separator.
|
/// separator.
|
||||||
// when changing this do not forget to update doc/book/macros.md!
|
// when changing this do not forget to update doc/book/macros.md!
|
||||||
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
|
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, (String, &'static str)> {
|
||||||
if let &CloseDelim(_) = tok {
|
if let &CloseDelim(_) = tok {
|
||||||
// closing a token tree can never be matched by any fragment;
|
// closing a token tree can never be matched by any fragment;
|
||||||
// iow, we always require that `(` and `)` match, etc.
|
// iow, we always require that `(` and `)` match, etc.
|
||||||
@ -1027,7 +1044,10 @@ fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
|
|||||||
// harmless
|
// harmless
|
||||||
Ok(true)
|
Ok(true)
|
||||||
},
|
},
|
||||||
_ => Err(format!("invalid fragment specifier `{}`", frag))
|
_ => Err((format!("invalid fragment specifier `{}`", frag),
|
||||||
|
"valid fragment specifiers are `ident`, `block`, \
|
||||||
|
`stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \
|
||||||
|
and `item`"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
macro_rules! invalid {
|
macro_rules! invalid {
|
||||||
_ => (); //~^ ERROR invalid macro matcher
|
_ => (); //~ ERROR invalid macro matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
19
src/test/compile-fail/macro-invalid-fragment-spec.rs
Normal file
19
src/test/compile-fail/macro-invalid-fragment-spec.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
macro_rules! foo(
|
||||||
|
($x:foo) => ()
|
||||||
|
//~^ ERROR invalid fragment specifier
|
||||||
|
//~| HELP valid fragment specifiers are
|
||||||
|
);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
foo!(foo);
|
||||||
|
}
|
17
src/test/compile-fail/macro-missing-delimiters.rs
Normal file
17
src/test/compile-fail/macro-missing-delimiters.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
macro_rules! baz(
|
||||||
|
baz => () //~ ERROR invalid macro matcher;
|
||||||
|
);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
baz!(baz);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user