about summary refs log tree commit diff
diff options
context:
space:
mode:
authorr0cky <mu001999@outlook.com>2024-05-18 18:56:12 +0800
committerr0cky <mu001999@outlook.com>2024-05-18 18:56:12 +0800
commitc2be1342b7f9c2c32df01426f066c3a00a39e398 (patch)
tree203780cecf710efc9d941adc5b856b4dee766c5b
parent9e7aff794539aa040362f4424eb29207449ffce0 (diff)
downloadrust-c2be1342b7f9c2c32df01426f066c3a00a39e398.tar.gz
rust-c2be1342b7f9c2c32df01426f066c3a00a39e398.zip
Improve error message: missing `;` in macro_rules
-rw-r--r--compiler/rustc_expand/src/mbe/diagnostics.rs40
-rw-r--r--compiler/rustc_expand/src/mbe/macro_parser.rs3
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs15
-rw-r--r--tests/ui/macros/missing-semi.rs11
-rw-r--r--tests/ui/macros/missing-semi.stderr8
5 files changed, 64 insertions, 13 deletions
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 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
 }
 
 /// 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> FailureForwarder<'matcher> {
+    pub fn new() -> Self {
+        Self { expected_token: None }
+    }
+}
 
-impl<'matcher> Tracker<'matcher> for FailureForwarder {
+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 @@ impl<'matcher> Tracker<'matcher> for FailureForwarder {
     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 @@ impl TtParser {
                         // 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 @@ impl TtParser {
                 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 470bde232d7..6ce1de5ad39 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -167,6 +167,11 @@ pub(super) trait Tracker<'matcher> {
     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
+