]> git.lizzy.rs Git - rust.git/blobdiff - compiler/rustc_expand/src/mbe/macro_rules.rs
Rollup merge of #104394 - oli-obk:suggest_method_call, r=lcnr
[rust.git] / compiler / rustc_expand / src / mbe / macro_rules.rs
index 99af91072882efc99011b95871c34c32e80a6be7..c02680b77fb152e5cd6bc9f2c5d15be21e9ada66 100644 (file)
@@ -22,7 +22,7 @@
     RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
 };
 use rustc_lint_defs::BuiltinLintDiagnostics;
-use rustc_parse::parser::Parser;
+use rustc_parse::parser::{Parser, Recovery};
 use rustc_session::parse::ParseSess;
 use rustc_session::Session;
 use rustc_span::edition::Edition;
@@ -219,6 +219,8 @@ pub(super) trait Tracker<'matcher> {
 
     /// For tracing.
     fn description() -> &'static str;
+
+    fn recovery() -> Recovery;
 }
 
 /// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
@@ -230,6 +232,9 @@ fn after_arm(&mut self, _: &NamedParseResult) {}
     fn description() -> &'static str {
         "none"
     }
+    fn recovery() -> Recovery {
+        Recovery::Forbidden
+    }
 }
 
 /// Expands the rules based macro defined by `lhses` and `rhses` for a given
@@ -330,15 +335,20 @@ fn expand_macro<'cx>(
     let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
 
     let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);
-    assert!(try_success_result.is_err(), "Macro matching returned a success on the second try");
+
+    if try_success_result.is_ok() {
+        // Nonterminal parser recovery might turn failed matches into successful ones,
+        // but for that it must have emitted an error already
+        tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try");
+    }
 
     if let Some(result) = tracker.result {
-        // An irrecoverable error occured and has been emitted.
+        // An irrecoverable error occurred and has been emitted.
         return result;
     }
 
-    let Some((token, label)) = tracker.best_failure else {
-        return tracker.result.expect("must have encountered Error or ErrorReported");
+    let Some((token, label, remaining_matcher)) = tracker.best_failure else {
+        return DummyResult::any(sp);
     };
 
     let span = token.span.substitute_dummy(sp);
@@ -351,10 +361,16 @@ fn expand_macro<'cx>(
 
     annotate_doc_comment(&mut err, sess.source_map(), span);
 
+    if let Some(span) = remaining_matcher.span() {
+        err.span_note(span, format!("while trying to match {remaining_matcher}"));
+    } else {
+        err.note(format!("while trying to match {remaining_matcher}"));
+    }
+
     // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
     if let Some((arg, comma_span)) = arg.add_comma() {
         for lhs in lhses {
-            let parser = parser_from_cx(sess, arg.clone());
+            let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed);
             let mut tt_parser = TtParser::new(name);
 
             if let Success(_) =
@@ -379,27 +395,45 @@ fn expand_macro<'cx>(
 }
 
 /// The tracker used for the slow error path that collects useful info for diagnostics.
-struct CollectTrackerAndEmitter<'a, 'cx> {
+struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
     cx: &'a mut ExtCtxt<'cx>,
+    remaining_matcher: Option<&'matcher MatcherLoc>,
     /// Which arm's failure should we report? (the one furthest along)
-    best_failure: Option<(Token, &'static str)>,
+    best_failure: Option<(Token, &'static str, MatcherLoc)>,
     root_span: Span,
     result: Option<Box<dyn MacResult + 'cx>>,
 }
 
-impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx> {
-    fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {
-        // Empty for now.
+impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
+    fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
+        if self.remaining_matcher.is_none()
+            || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
+        {
+            self.remaining_matcher = Some(matcher);
+        }
     }
 
     fn after_arm(&mut self, result: &NamedParseResult) {
         match result {
             Success(_) => {
-                unreachable!("should not collect detailed info for successful macro match");
+                // Nonterminal parser recovery might turn failed matches into successful ones,
+                // but for that it must have emitted an error already
+                self.cx.sess.delay_span_bug(
+                    self.root_span,
+                    "should not collect detailed info for successful macro match",
+                );
             }
             Failure(token, msg) => match self.best_failure {
-                Some((ref best_token, _)) if best_token.span.lo() >= token.span.lo() => {}
-                _ => self.best_failure = Some((token.clone(), msg)),
+                Some((ref best_token, _, _)) if best_token.span.lo() >= token.span.lo() => {}
+                _ => {
+                    self.best_failure = Some((
+                        token.clone(),
+                        msg,
+                        self.remaining_matcher
+                            .expect("must have collected matcher already")
+                            .clone(),
+                    ))
+                }
             },
             Error(err_sp, msg) => {
                 let span = err_sp.substitute_dummy(self.root_span);
@@ -413,11 +447,15 @@ fn after_arm(&mut self, result: &NamedParseResult) {
     fn description() -> &'static str {
         "detailed"
     }
+
+    fn recovery() -> Recovery {
+        Recovery::Allowed
+    }
 }
 
-impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx> {
+impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
     fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
-        Self { cx, best_failure: None, root_span, result: None }
+        Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }
     }
 }
 
@@ -458,7 +496,7 @@ fn try_match_macro<'matcher, T: Tracker<'matcher>>(
     // 68836 suggests a more comprehensive but more complex change to deal with
     // this situation.)
     // FIXME(Nilstrieb): Stop recovery from happening on this parser and retry later with recovery if the macro failed to match.
-    let parser = parser_from_cx(sess, arg.clone());
+    let parser = parser_from_cx(sess, arg.clone(), T::recovery());
     // Try each arm's matchers.
     let mut tt_parser = TtParser::new(name);
     for (i, lhs) in lhses.iter().enumerate() {
@@ -1540,8 +1578,8 @@ fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String {
     }
 }
 
-fn parser_from_cx(sess: &ParseSess, tts: TokenStream) -> Parser<'_> {
-    Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS)
+fn parser_from_cx(sess: &ParseSess, tts: TokenStream, recovery: Recovery) -> Parser<'_> {
+    Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS).recovery(recovery)
 }
 
 /// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For