diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 3fee39dd085..442fd654b6a 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -55,7 +55,7 @@ pub(super) fn failed_to_match_macro<'cx>( let span = token.span.substitute_dummy(sp); - let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token)); + let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token, None)); err.span_label(span, label); if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) { err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro"); @@ -200,9 +200,17 @@ fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self { } /// Currently used by macro_rules! compilation to extract a little information from the `Failure` case. -pub struct FailureForwarder; +pub struct FailureForwarder<'matcher> { + expected_token: Option<&'matcher Token>, +} -impl<'matcher> Tracker<'matcher> for FailureForwarder { +impl<'matcher> FailureForwarder<'matcher> { + pub fn new() -> Self { + Self { expected_token: None } + } +} + +impl<'matcher> Tracker<'matcher> for FailureForwarder<'matcher> { type Failure = (Token, usize, &'static str); fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure { @@ -212,6 +220,14 @@ fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failur fn description() -> &'static str { "failure-forwarder" } + + fn set_expected_token(&mut self, tok: &'matcher Token) { + self.expected_token = Some(tok); + } + + fn get_expected_token(&self) -> Option<&'matcher Token> { + self.expected_token + } } pub(super) fn emit_frag_parse_err( @@ -320,9 +336,19 @@ pub(super) fn annotate_doc_comment(dcx: &DiagCtxt, err: &mut Diag<'_>, sm: &Sour /// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For /// other tokens, this is "unexpected token...". -pub(super) fn parse_failure_msg(tok: &Token) -> Cow<'static, str> { - match tok.kind { - token::Eof => Cow::from("unexpected end of macro invocation"), - _ => Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))), +pub(super) fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> { + if let Some(expected_token) = expected_token { + Cow::from(format!( + "expected `{}`, found `{}`", + pprust::token_to_string(expected_token), + pprust::token_to_string(tok), + )) + } else { + match tok.kind { + token::Eof => Cow::from("unexpected end of macro invocation"), + _ => { + Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))) + } + } } } diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 27cf6fee702..2fbd09fd9ae 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -541,6 +541,8 @@ fn parse_tt_inner<'matcher, T: Tracker<'matcher>>( // The separator matches the current token. Advance past it. mp.idx += 1; self.next_mps.push(mp); + } else { + track.set_expected_token(separator); } } &MatcherLoc::SequenceKleeneOpAfterSep { idx_first } => { @@ -632,6 +634,7 @@ pub(super) fn parse_tt<'matcher, T: Tracker<'matcher>>( parser.approx_token_stream_pos(), track, ); + if let Some(res) = res { return res; } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 71daaacd056..8f18055f838 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -167,6 +167,11 @@ fn after_arm(&mut self, _result: &NamedParseResult) {} fn recovery() -> Recovery { Recovery::Forbidden } + + fn set_expected_token(&mut self, _tok: &'matcher Token) {} + fn get_expected_token(&self) -> Option<&'matcher Token> { + None + } } /// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to @@ -447,16 +452,14 @@ pub fn compile_declarative_macro( // For this we need to reclone the macro body as the previous parser consumed it. let retry_parser = create_parser(); - let parse_result = tt_parser.parse_tt( - &mut Cow::Owned(retry_parser), - &argument_gram, - &mut diagnostics::FailureForwarder, - ); + let mut track = diagnostics::FailureForwarder::new(); + let parse_result = + tt_parser.parse_tt(&mut Cow::Owned(retry_parser), &argument_gram, &mut track); let Failure((token, _, msg)) = parse_result else { unreachable!("matcher returned something other than Failure after retry"); }; - let s = parse_failure_msg(&token); + let s = parse_failure_msg(&token, track.get_expected_token()); let sp = token.span.substitute_dummy(def.span); let mut err = sess.dcx().struct_span_err(sp, s); err.span_label(sp, msg); diff --git a/tests/ui/macros/missing-semi.rs b/tests/ui/macros/missing-semi.rs new file mode 100644 index 00000000000..b7e90e9e442 --- /dev/null +++ b/tests/ui/macros/missing-semi.rs @@ -0,0 +1,11 @@ +#[allow(unused_macros)] +macro_rules! foo { + () => { + + } + () => { + //~^ ERROR expected `;`, found `(` + } +} + +fn main() {} diff --git a/tests/ui/macros/missing-semi.stderr b/tests/ui/macros/missing-semi.stderr new file mode 100644 index 00000000000..0a7afe50059 --- /dev/null +++ b/tests/ui/macros/missing-semi.stderr @@ -0,0 +1,8 @@ +error: expected `;`, found `(` + --> $DIR/missing-semi.rs:6:5 + | +LL | () => { + | ^ no rules expected this token in macro call + +error: aborting due to 1 previous error +