about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_expand/src/mbe/diagnostics.rs25
-rw-r--r--compiler/rustc_expand/src/mbe/macro_parser.rs26
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs60
3 files changed, 81 insertions, 30 deletions
diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs
index 3a38d7a9669..f469b2daef5 100644
--- a/compiler/rustc_expand/src/mbe/diagnostics.rs
+++ b/compiler/rustc_expand/src/mbe/diagnostics.rs
@@ -114,6 +114,12 @@ impl BestFailure {
 }
 
 impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
+    type Failure = (Token, usize, &'static str);
+
+    fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
+        (tok, position, msg)
+    }
+
     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)
@@ -122,7 +128,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
         }
     }
 
-    fn after_arm(&mut self, result: &NamedParseResult) {
+    fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
         match result {
             Success(_) => {
                 // Nonterminal parser recovery might turn failed matches into successful ones,
@@ -132,7 +138,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
                     "should not collect detailed info for successful macro match",
                 );
             }
-            Failure(token, approx_position, msg) => {
+            Failure((token, approx_position, msg)) => {
                 debug!(?token, ?msg, "a new failure of an arm");
 
                 if self
@@ -175,6 +181,21 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
     }
 }
 
+/// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
+pub struct FailureForwarder;
+
+impl<'matcher> Tracker<'matcher> for FailureForwarder {
+    type Failure = (Token, usize, &'static str);
+
+    fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
+        (tok, position, msg)
+    }
+
+    fn description() -> &'static str {
+        "failure-forwarder"
+    }
+}
+
 pub(super) fn emit_frag_parse_err(
     mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
     parser: &Parser<'_>,
diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs
index df1c1834c1d..2e199541b92 100644
--- a/compiler/rustc_expand/src/mbe/macro_parser.rs
+++ b/compiler/rustc_expand/src/mbe/macro_parser.rs
@@ -305,13 +305,13 @@ enum EofMatcherPositions {
 }
 
 /// Represents the possible results of an attempted parse.
-pub(crate) enum ParseResult<T> {
+pub(crate) enum ParseResult<T, F> {
     /// Parsed successfully.
     Success(T),
     /// Arm failed to match. If the second parameter is `token::Eof`, it indicates an unexpected
     /// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
     /// The usize is the approximate position of the token in the input token stream.
-    Failure(Token, usize, &'static str),
+    Failure(F),
     /// Fatal error (malformed macro?). Abort compilation.
     Error(rustc_span::Span, String),
     ErrorReported(ErrorGuaranteed),
@@ -320,7 +320,7 @@ pub(crate) enum ParseResult<T> {
 /// A `ParseResult` where the `Success` variant contains a mapping of
 /// `MacroRulesNormalizedIdent`s to `NamedMatch`es. This represents the mapping
 /// of metavars to the token trees they bind to.
-pub(crate) type NamedParseResult = ParseResult<NamedMatches>;
+pub(crate) type NamedParseResult<F> = ParseResult<NamedMatches, F>;
 
 /// Contains a mapping of `MacroRulesNormalizedIdent`s to `NamedMatch`es.
 /// This represents the mapping of metavars to the token trees they bind to.
@@ -458,7 +458,7 @@ impl TtParser {
         token: &Token,
         approx_position: usize,
         track: &mut T,
-    ) -> Option<NamedParseResult> {
+    ) -> Option<NamedParseResult<T::Failure>> {
         // Matcher positions that would be valid if the macro invocation was over now. Only
         // modified if `token == Eof`.
         let mut eof_mps = EofMatcherPositions::None;
@@ -595,14 +595,14 @@ impl TtParser {
                 EofMatcherPositions::Multiple => {
                     Error(token.span, "ambiguity: multiple successful parses".to_string())
                 }
-                EofMatcherPositions::None => Failure(
+                EofMatcherPositions::None => Failure(T::build_failure(
                     Token::new(
                         token::Eof,
                         if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() },
                     ),
                     approx_position,
                     "missing tokens in macro arguments",
-                ),
+                )),
             })
         } else {
             None
@@ -615,7 +615,7 @@ impl TtParser {
         parser: &mut Cow<'_, Parser<'_>>,
         matcher: &'matcher [MatcherLoc],
         track: &mut T,
-    ) -> NamedParseResult {
+    ) -> NamedParseResult<T::Failure> {
         // A queue of possible matcher positions. We initialize it with the matcher position in
         // which the "dot" is before the first token of the first token tree in `matcher`.
         // `parse_tt_inner` then processes all of these possible matcher positions and produces
@@ -648,11 +648,11 @@ impl TtParser {
                 (0, 0) => {
                     // There are no possible next positions AND we aren't waiting for the black-box
                     // parser: syntax error.
-                    return Failure(
+                    return Failure(T::build_failure(
                         parser.token.clone(),
                         parser.approx_token_stream_pos(),
                         "no rules expected this token in macro call",
-                    );
+                    ));
                 }
 
                 (_, 0) => {
@@ -711,11 +711,11 @@ impl TtParser {
         }
     }
 
-    fn ambiguity_error(
+    fn ambiguity_error<F>(
         &self,
         matcher: &[MatcherLoc],
         token_span: rustc_span::Span,
-    ) -> NamedParseResult {
+    ) -> NamedParseResult<F> {
         let nts = self
             .bb_mps
             .iter()
@@ -741,11 +741,11 @@ impl TtParser {
         )
     }
 
-    fn nameize<I: Iterator<Item = NamedMatch>>(
+    fn nameize<I: Iterator<Item = NamedMatch>, F>(
         &self,
         matcher: &[MatcherLoc],
         mut res: I,
-    ) -> NamedParseResult {
+    ) -> NamedParseResult<F> {
         // Make that each metavar has _exactly one_ binding. If so, insert the binding into the
         // `NamedParseResult`. Otherwise, it's an error.
         let mut ret_val = FxHashMap::default();
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index fbb806fe81b..c0489f68633 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -141,31 +141,40 @@ fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span
 }
 
 pub(super) trait Tracker<'matcher> {
+    /// The contents of `ParseResult::Failure`.
+    type Failure;
+
+    /// Arm failed to match. If the token is `token::Eof`, it indicates an unexpected
+    /// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
+    /// The usize is the approximate position of the token in the input token stream.
+    fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure;
+
     /// This is called before trying to match next MatcherLoc on the current token.
-    fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc);
+    fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {}
 
     /// This is called after an arm has been parsed, either successfully or unsuccessfully. When this is called,
     /// `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
-    fn after_arm(&mut self, result: &NamedParseResult);
+    fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {}
 
     /// For tracing.
     fn description() -> &'static str;
 
-    fn recovery() -> Recovery;
+    fn recovery() -> Recovery {
+        Recovery::Forbidden
+    }
 }
 
 /// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
 pub(super) struct NoopTracker;
 
 impl<'matcher> Tracker<'matcher> for NoopTracker {
-    fn before_match_loc(&mut self, _: &TtParser, _: &'matcher MatcherLoc) {}
-    fn after_arm(&mut self, _: &NamedParseResult) {}
+    type Failure = ();
+
+    fn build_failure(_tok: Token, _position: usize, _msg: &'static str) -> Self::Failure {}
+
     fn description() -> &'static str {
         "none"
     }
-    fn recovery() -> Recovery {
-        Recovery::Forbidden
-    }
 }
 
 /// Expands the rules based macro defined by `lhses` and `rhses` for a given
@@ -326,8 +335,8 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
 
                 return Ok((i, named_matches));
             }
-            Failure(_, reached_position, _) => {
-                trace!(%reached_position, "Failed to match arm, trying the next one");
+            Failure(_) => {
+                trace!("Failed to match arm, trying the next one");
                 // Try the next arm.
             }
             Error(_, _) => {
@@ -381,11 +390,13 @@ pub fn compile_declarative_macro(
     let rhs_nm = Ident::new(sym::rhs, def.span);
     let tt_spec = Some(NonterminalKind::TT);
 
-    // Parse the macro_rules! invocation
-    let (macro_rules, body) = match &def.kind {
-        ast::ItemKind::MacroDef(def) => (def.macro_rules, def.body.tokens.clone()),
+    let macro_def = match &def.kind {
+        ast::ItemKind::MacroDef(def) => def,
         _ => unreachable!(),
     };
+    let macro_rules = macro_def.macro_rules;
+
+    // Parse the macro_rules! invocation
 
     // The pattern that macro_rules matches.
     // The grammar for macro_rules! is:
@@ -426,13 +437,32 @@ pub fn compile_declarative_macro(
     // Convert it into `MatcherLoc` form.
     let argument_gram = mbe::macro_parser::compute_locs(&argument_gram);
 
-    let parser = Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS);
+    let create_parser = || {
+        let body = macro_def.body.tokens.clone();
+        Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS)
+    };
+
+    let parser = create_parser();
     let mut tt_parser =
         TtParser::new(Ident::with_dummy_span(if macro_rules { kw::MacroRules } else { kw::Macro }));
     let argument_map =
         match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) {
             Success(m) => m,
-            Failure(token, _, msg) => {
+            Failure(()) => {
+                // The fast `NoopTracker` doesn't have any info on failure, so we need to retry it with another one
+                // that gives us the information we need.
+                // 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 Failure((token, _, msg)) = parse_result else {
+                    unreachable!("matcher returned something other than Failure after retry");
+                };
+
                 let s = parse_failure_msg(&token);
                 let sp = token.span.substitute_dummy(def.span);
                 let mut err = sess.parse_sess.span_diagnostic.struct_span_err(sp, &s);