From afcad4302ef508c18a5e41862656218e6028c837 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Thu, 18 Jun 2026 20:00:33 +0200 Subject: [PATCH 01/19] Replace a bool flag with `WhichMatcher` While familiarizing myself with this code, it took me a while to figure out the meaning and need for this boolean. Making it an explicit enum seems to be convention here, and it makes it easy to understand its purpose. `tests/ui/macros` pass. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 18 ++++++--- compiler/rustc_expand/src/mbe/macro_rules.rs | 41 +++++++++++++++++--- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 7abf3ac308805..622e2d1eebe25 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -16,7 +16,7 @@ use crate::expand::{AstFragmentKind, parse_ast_fragment}; use crate::mbe::macro_parser::ParseResult::*; use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser}; use crate::mbe::macro_rules::{ - Tracker, try_match_macro, try_match_macro_attr, try_match_macro_derive, + Tracker, WhichMatcher, try_match_macro, try_match_macro_attr, try_match_macro_derive, }; pub(super) enum FailedMacro<'a> { @@ -155,13 +155,21 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { struct BestFailure { token: Token, - position_in_tokenstream: (bool, u32), + + /// A comparable approximate position. + /// + /// [`MacroRule::Attr`] has two matchers (args and body). Failures can occur across the arms, + /// and we want to prioritize earlier failures over later ones, so we use [`WhichMatcher`]. + /// + /// The second element is the approximate parser position. + position_in_tokenstream: (WhichMatcher, u32), + msg: &'static str, remaining_matcher: MatcherLoc, } impl BestFailure { - fn is_better_position(&self, position: (bool, u32)) -> bool { + fn is_better_position(&self, position: (WhichMatcher, u32)) -> bool { position > self.position_in_tokenstream } } @@ -181,7 +189,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } } - fn after_arm(&mut self, in_body: bool, result: &NamedParseResult) { + fn after_arm(&mut self, which_matcher: WhichMatcher, result: &NamedParseResult) { match result { Success(_) => { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -194,7 +202,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match Failure((token, approx_position, msg)) => { debug!(?token, ?msg, "a new failure of an arm"); - let position_in_tokenstream = (in_body, *approx_position); + let position_in_tokenstream = (which_matcher, *approx_position); if self .best_failure .as_ref() diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 1a1ef0f06963e..c5b92be313cfe 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -162,6 +162,32 @@ pub(crate) enum MacroRule { Derive { body: Vec, body_span: Span, rhs: mbe::TokenTree }, } +/// A selection of a matcher in a [`MacroRule`]. +/// +/// [`MacroRule::Attr`] has two different matchers (args and body). This enum allows distinguishing +/// between them, even when used for other kinds of rules. +/// +/// This type implements [`Ord`]. The arms within a rule come in a fixed order and this type is +/// consistent with that ordering. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum WhichMatcher { + /// The arguments of an attr macro ([`MacroRule::Attr::args`]). + Args, + + /// The body of an attr macro ([`MacroRule::Attr::body`]), **or** the only arm of the rule. + /// + /// This is also used to express the only arm in a [`MacroRule::Func`] or [`MacroRule::Derive`]. + Body, +} + +impl WhichMatcher { + /// The [`WhichMatcher`] for [`MacroRule::Func`]. + pub(crate) const FOR_FUNC: Self = Self::Body; + + /// The [`WhichMatcher`] for [`MacroRule::Derive`]. + pub(crate) const FOR_DERIVE: Self = Self::Body; +} + pub struct MacroRulesMacroExpander { node_id: NodeId, name: Ident, @@ -346,7 +372,12 @@ pub(super) trait Tracker<'matcher> { /// 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, _in_body: bool, _result: &NamedParseResult) {} + fn after_arm( + &mut self, + _which_matcher: WhichMatcher, + _result: &NamedParseResult, + ) { + } /// For tracing. fn description() -> &'static str; @@ -585,7 +616,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); - track.after_arm(true, &result); + track.after_arm(WhichMatcher::FOR_FUNC, &result); match result { Success(named_matches) => { @@ -642,7 +673,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); let result = tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, track); - track.after_arm(false, &result); + track.after_arm(WhichMatcher::Args, &result); let mut named_matches = match result { Success(named_matches) => named_matches, @@ -655,7 +686,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( }; let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); - track.after_arm(true, &result); + track.after_arm(WhichMatcher::Body, &result); match result { Success(body_named_matches) => { @@ -695,7 +726,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); - track.after_arm(true, &result); + track.after_arm(WhichMatcher::FOR_DERIVE, &result); match result { Success(named_matches) => { From 7434aa62e45f02fab2c67f2682b9ad193d6c1c81 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Mon, 29 Jun 2026 09:32:05 +0200 Subject: [PATCH 02/19] Treat duplicate binds as a bug Duplicate meta-variable bindings are already checked for when building a macro definition, in `macro_check.rs`. If duplicates are detected, the macro definition is replaced with a dummy, so duplicate bindings are impossible at macro instantiation time. Instead of treating them like an error, report a `bug!` instead. This simplifies things semantically: now a `NamedParseResult::Error` can only be caused by an ambiguity error. This allows it to be specialized a bit. `tests/ui/macros` pass. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index b8325e3ce7756..b4b4af2d52069 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -71,7 +71,6 @@ //! ``` use std::borrow::Cow; -use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::fmt::Display; use std::rc::Rc; @@ -81,6 +80,7 @@ use rustc_ast::token::{self, DocComment, NonterminalKind, Token, TokenKind}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::ErrorGuaranteed; use rustc_lint_defs::pluralize; +use rustc_middle::span_bug; use rustc_parse::parser::{ParseNtResult, Parser, token_descr}; use rustc_span::{Ident, MacroRulesNormalizedIdent, Span}; @@ -601,7 +601,7 @@ impl TtParser { // Need to take ownership of the matches from within the `Rc`. Rc::make_mut(&mut eof_mp.matches); let matches = Rc::try_unwrap(eof_mp.matches).unwrap().into_iter(); - self.nameize(matcher, matches) + Success(self.nameize(matcher, matches)) } EofMatcherPositions::Multiple => { Error(token.span, "ambiguity: multiple successful parses".to_string()) @@ -745,24 +745,28 @@ impl TtParser { ) } - fn nameize, F>( + fn nameize>( &self, matcher: &[MatcherLoc], mut res: I, - ) -> NamedParseResult { + ) -> NamedMatches { // 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(); for loc in matcher { - if let &MatcherLoc::MetaVarDecl { span, bind, .. } = loc { - match ret_val.entry(MacroRulesNormalizedIdent::new(bind)) { - Vacant(spot) => spot.insert(res.next().unwrap()), - Occupied(..) => { - return Error(span, format!("duplicated bind name: {bind}")); - } - }; + if let &MatcherLoc::MetaVarDecl { span, bind, .. } = loc + && ret_val + .insert(MacroRulesNormalizedIdent::new(bind), res.next().unwrap()) + .is_some() + { + // Duplicate binds are checked for when the macro definition is processed, + // and should have prevented the definition from ever being used. + span_bug!( + span, + "duplicate meta-variable binding went undetected at macro definition" + ) } } - Success(ret_val) + ret_val } } From 12d79df344111617fdf6ca137317ae09dd5f99bc Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Mon, 29 Jun 2026 19:31:47 +0200 Subject: [PATCH 03/19] Replace `EofMatcherPositions` with a regular `SmallVec` `tests/ui/macros` pass. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index b4b4af2d52069..787a94fc2b6e6 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -83,6 +83,7 @@ use rustc_lint_defs::pluralize; use rustc_middle::span_bug; use rustc_parse::parser::{ParseNtResult, Parser, token_descr}; use rustc_span::{Ident, MacroRulesNormalizedIdent, Span}; +use smallvec::SmallVec; use crate::mbe::macro_rules::Tracker; use crate::mbe::{KleeneOp, TokenTree}; @@ -292,12 +293,6 @@ impl MatcherPos { } } -enum EofMatcherPositions { - None, - One(MatcherPos), - Multiple, -} - /// Represents the possible results of an attempted parse. #[derive(Debug)] pub(crate) enum ParseResult { @@ -478,7 +473,7 @@ impl TtParser { ) -> Option> { // Matcher positions that would be valid if the macro invocation was over now. Only // modified if `token == Eof`. - let mut eof_mps = EofMatcherPositions::None; + let mut eof_mps = SmallVec::<[MatcherPos; 1]>::new(); while let Some(mut mp) = self.cur_mps.pop() { let matcher_loc = &matcher[mp.idx]; @@ -582,12 +577,7 @@ impl TtParser { // We are past the matcher's end, and not in a sequence. Try to end things. debug_assert_eq!(mp.idx, matcher.len() - 1); if *token == token::Eof { - eof_mps = match eof_mps { - EofMatcherPositions::None => EofMatcherPositions::One(mp), - EofMatcherPositions::One(_) | EofMatcherPositions::Multiple => { - EofMatcherPositions::Multiple - } - } + eof_mps.push(mp); } } } @@ -596,17 +586,15 @@ impl TtParser { // If we reached the end of input, check that there is EXACTLY ONE possible matcher. // Otherwise, either the parse is ambiguous (which is an error) or there is a syntax error. if *token == token::Eof { - Some(match eof_mps { - EofMatcherPositions::One(mut eof_mp) => { + Some(match *eof_mps { + [_] => { + let mut eof_mp = eof_mps.pop().unwrap(); // Need to take ownership of the matches from within the `Rc`. Rc::make_mut(&mut eof_mp.matches); let matches = Rc::try_unwrap(eof_mp.matches).unwrap().into_iter(); Success(self.nameize(matcher, matches)) } - EofMatcherPositions::Multiple => { - Error(token.span, "ambiguity: multiple successful parses".to_string()) - } - EofMatcherPositions::None => Failure(T::build_failure( + [] => Failure(T::build_failure( Token::new( token::Eof, if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() }, @@ -614,6 +602,7 @@ impl TtParser { approx_position, "missing tokens in macro arguments", )), + _ => Error(token.span, "ambiguity: multiple successful parses".to_string()), }) } else { None From ba2cb22d7b11e4926f8512d2015f9880d05badf8 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:22:45 +0200 Subject: [PATCH 04/19] Call `ambiguity_error()` through `Tracker` --- compiler/rustc_expand/src/mbe/macro_parser.rs | 4 ++-- compiler/rustc_expand/src/mbe/macro_rules.rs | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 787a94fc2b6e6..95081da4d2748 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -696,7 +696,7 @@ impl TtParser { (_, _) => { // Too many possibilities! - return self.ambiguity_error(matcher, parser.token.span); + return track.ambiguity(self, matcher, parser.token.span); } } @@ -704,7 +704,7 @@ impl TtParser { } } - fn ambiguity_error( + pub(super) fn ambiguity_error( &self, matcher: &[MatcherLoc], token_span: rustc_span::Span, diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index c5b92be313cfe..18cc593a3c588 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -379,6 +379,15 @@ pub(super) trait Tracker<'matcher> { ) { } + fn ambiguity( + &mut self, + parser: &TtParser, + matcher: &'matcher [MatcherLoc], + token_span: Span, + ) -> NamedParseResult { + parser.ambiguity_error(matcher, token_span) + } + /// For tracing. fn description() -> &'static str; From 4287b166ab80d6ce62392b40f28a94faf681c549 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:27:36 +0200 Subject: [PATCH 05/19] Stop computing ambiguity error messages in `NoopTracker` --- compiler/rustc_expand/src/mbe/diagnostics.rs | 9 +++++++++ compiler/rustc_expand/src/mbe/macro_rules.rs | 13 ++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 622e2d1eebe25..ccd35d55aff48 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -228,6 +228,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } } + fn ambiguity( + &mut self, + parser: &TtParser, + matcher: &'matcher [MatcherLoc], + token_span: Span, + ) -> NamedParseResult { + parser.ambiguity_error(matcher, token_span) + } + fn description() -> &'static str { "detailed" } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 18cc593a3c588..449e9e9a58885 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -384,9 +384,7 @@ pub(super) trait Tracker<'matcher> { parser: &TtParser, matcher: &'matcher [MatcherLoc], token_span: Span, - ) -> NamedParseResult { - parser.ambiguity_error(matcher, token_span) - } + ) -> NamedParseResult; /// For tracing. fn description() -> &'static str; @@ -405,6 +403,15 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { fn build_failure(_tok: Token, _position: u32, _msg: &'static str) -> Self::Failure {} + fn ambiguity( + &mut self, + _parser: &TtParser, + _matcher: &'matcher [MatcherLoc], + token_span: Span, + ) -> NamedParseResult { + Error(token_span, "ignored".into()) + } + fn description() -> &'static str { "none" } From a9e16c7d432e534c47715081062bd9a2bbf97abb Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:41:21 +0200 Subject: [PATCH 06/19] Introduce `bb_locs` and `next_locs` intermediates `bb_mps` and `next_mps` have type `Vec`, and `MatcherPos`, which cannot be named in `Tracker` because `MatcherPos` is private. `bb_locs` and `next_locs` have type `impl Iterator`, which *can* be named in `Tracker`. This commit introduces these variables so they can become parameters in the next commit. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 95081da4d2748..8c9bd8e0cd457 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -709,10 +709,12 @@ impl TtParser { matcher: &[MatcherLoc], token_span: rustc_span::Span, ) -> NamedParseResult { - let nts = self - .bb_mps - .iter() - .map(|mp| match &matcher[mp.idx] { + let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); + let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); + + let nts = bb_locs + .into_iter() + .map(|loc| match loc { MatcherLoc::MetaVarDecl { bind, kind, .. } => { format!("{kind} ('{bind}')") } @@ -726,7 +728,7 @@ impl TtParser { format!( "local ambiguity when calling macro `{}`: multiple parsing options: {}", self.macro_name, - match self.next_mps.len() { + match next_locs.into_iter().count() { 0 => format!("built-in NTs {nts}."), n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), } From 1a822957e702454122452440e6d91b33ec542645 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:43:47 +0200 Subject: [PATCH 07/19] Expand `TtParser` argument to `Tracker::ambiguity()` This removes the `&self` argument to `TtParser::ambiguity_error()`, so it stops relying on module-private access. This makes it possible to inline into `diagnostics.rs` in the next commit. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 7 ++++--- compiler/rustc_expand/src/mbe/macro_parser.rs | 16 ++++++++-------- compiler/rustc_expand/src/mbe/macro_rules.rs | 10 ++++++---- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index ccd35d55aff48..b4ea6b01bdc97 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -230,11 +230,12 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match fn ambiguity( &mut self, - parser: &TtParser, - matcher: &'matcher [MatcherLoc], + macro_name: Ident, token_span: Span, + bb_locs: impl IntoIterator, + next_locs: impl IntoIterator, ) -> NamedParseResult { - parser.ambiguity_error(matcher, token_span) + TtParser::ambiguity_error(macro_name, token_span, bb_locs, next_locs) } fn description() -> &'static str { diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 8c9bd8e0cd457..75dfce922a987 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -696,7 +696,9 @@ impl TtParser { (_, _) => { // Too many possibilities! - return track.ambiguity(self, matcher, parser.token.span); + let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); + let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); + return track.ambiguity(self.macro_name, parser.token.span, bb_locs, next_locs); } } @@ -704,14 +706,12 @@ impl TtParser { } } - pub(super) fn ambiguity_error( - &self, - matcher: &[MatcherLoc], + pub(super) fn ambiguity_error<'matcher, F>( + macro_name: Ident, token_span: rustc_span::Span, + bb_locs: impl IntoIterator, + next_locs: impl IntoIterator, ) -> NamedParseResult { - let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); - let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); - let nts = bb_locs .into_iter() .map(|loc| match loc { @@ -727,7 +727,7 @@ impl TtParser { token_span, format!( "local ambiguity when calling macro `{}`: multiple parsing options: {}", - self.macro_name, + macro_name, match next_locs.into_iter().count() { 0 => format!("built-in NTs {nts}."), n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 449e9e9a58885..a93e6c4d7068a 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -381,9 +381,10 @@ pub(super) trait Tracker<'matcher> { fn ambiguity( &mut self, - parser: &TtParser, - matcher: &'matcher [MatcherLoc], + macro_name: Ident, token_span: Span, + bb_locs: impl IntoIterator, + next_locs: impl IntoIterator, ) -> NamedParseResult; /// For tracing. @@ -405,9 +406,10 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { fn ambiguity( &mut self, - _parser: &TtParser, - _matcher: &'matcher [MatcherLoc], + _macro_name: Ident, token_span: Span, + _bb_locs: impl IntoIterator, + _next_locs: impl IntoIterator, ) -> NamedParseResult { Error(token_span, "ignored".into()) } From e5ce736300f82161d49fcf167fab21bd7c087b7f Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:46:25 +0200 Subject: [PATCH 08/19] Inline `TtParser::ambiguity_error()` --- compiler/rustc_expand/src/mbe/diagnostics.rs | 25 +++++++++++++-- compiler/rustc_expand/src/mbe/macro_parser.rs | 31 ------------------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index b4ea6b01bdc97..5f788ae36cc84 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream::TokenStream; -use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage}; +use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage, pluralize}; use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; @@ -235,7 +235,28 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match bb_locs: impl IntoIterator, next_locs: impl IntoIterator, ) -> NamedParseResult { - TtParser::ambiguity_error(macro_name, token_span, bb_locs, next_locs) + let nts = bb_locs + .into_iter() + .map(|loc| match loc { + MatcherLoc::MetaVarDecl { bind, kind, .. } => { + format!("{kind} ('{bind}')") + } + _ => unreachable!(), + }) + .collect::>() + .join(" or "); + + Error( + token_span, + format!( + "local ambiguity when calling macro `{}`: multiple parsing options: {}", + macro_name, + match next_locs.into_iter().count() { + 0 => format!("built-in NTs {nts}."), + n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), + } + ), + ) } fn description() -> &'static str { diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 75dfce922a987..b4acbe8faa884 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -79,7 +79,6 @@ pub(crate) use ParseResult::*; use rustc_ast::token::{self, DocComment, NonterminalKind, Token, TokenKind}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::ErrorGuaranteed; -use rustc_lint_defs::pluralize; use rustc_middle::span_bug; use rustc_parse::parser::{ParseNtResult, Parser, token_descr}; use rustc_span::{Ident, MacroRulesNormalizedIdent, Span}; @@ -706,36 +705,6 @@ impl TtParser { } } - pub(super) fn ambiguity_error<'matcher, F>( - macro_name: Ident, - token_span: rustc_span::Span, - bb_locs: impl IntoIterator, - next_locs: impl IntoIterator, - ) -> NamedParseResult { - let nts = bb_locs - .into_iter() - .map(|loc| match loc { - MatcherLoc::MetaVarDecl { bind, kind, .. } => { - format!("{kind} ('{bind}')") - } - _ => unreachable!(), - }) - .collect::>() - .join(" or "); - - Error( - token_span, - format!( - "local ambiguity when calling macro `{}`: multiple parsing options: {}", - macro_name, - match next_locs.into_iter().count() { - 0 => format!("built-in NTs {nts}."), - n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), - } - ), - ) - } - fn nameize>( &self, matcher: &[MatcherLoc], From 0b183c335e4d60573d4e86da8bdcaeccdaafc605 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:50:24 +0200 Subject: [PATCH 09/19] Move `macro_name` to `CollectTrackerAndEmitter` `TtParser::macro_name` is only used for passing to `CollectTrackerAndEmitter` for `Tracker::ambiguity()`; this commit moves the field directly into `CollectTrackerAndEmitter`. In theory, this means `TtParser` could be shared across different macro invocations (although storing it an appropriate place is hard). --- compiler/rustc_expand/src/mbe/diagnostics.rs | 19 +++++++++++++------ compiler/rustc_expand/src/mbe/macro_parser.rs | 7 ++----- compiler/rustc_expand/src/mbe/macro_rules.rs | 8 +++----- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 5f788ae36cc84..0da6249ce3b2a 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -44,7 +44,7 @@ pub(super) fn failed_to_match_macro( // An error occurred, try the expansion again, tracking the expansion closely for better // diagnostics. - let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); + let mut tracker = CollectTrackerAndEmitter::new(name, psess.dcx(), sp); let try_success_result = match args { FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker), @@ -121,7 +121,7 @@ pub(super) fn failed_to_match_macro( for rule in rules { let MacroRule::Func { lhs, .. } = rule else { continue }; let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed); - let mut tt_parser = TtParser::new(name); + let mut tt_parser = TtParser::new(); if let Success(_) = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker) @@ -145,6 +145,7 @@ pub(super) fn failed_to_match_macro( /// The tracker used for the slow error path that collects useful info for diagnostics. struct CollectTrackerAndEmitter<'dcx, 'matcher> { + macro_name: Ident, dcx: DiagCtxtHandle<'dcx>, remaining_matcher: Option<&'matcher MatcherLoc>, /// Which arm's failure should we report? (the one furthest along) @@ -230,7 +231,6 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match fn ambiguity( &mut self, - macro_name: Ident, token_span: Span, bb_locs: impl IntoIterator, next_locs: impl IntoIterator, @@ -250,7 +250,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match token_span, format!( "local ambiguity when calling macro `{}`: multiple parsing options: {}", - macro_name, + self.macro_name, match next_locs.into_iter().count() { 0 => format!("built-in NTs {nts}."), n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), @@ -269,8 +269,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> { - fn new(dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self { - Self { dcx, remaining_matcher: None, best_failure: None, root_span, result: None } + fn new(macro_name: Ident, dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self { + Self { + macro_name, + dcx, + remaining_matcher: None, + best_failure: None, + root_span, + result: None, + } } } diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index b4acbe8faa884..f0309af14bd2a 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -423,8 +423,6 @@ fn token_name_eq(t1: &Token, t2: &Token) -> bool { // Note: the vectors could be created and dropped within `parse_tt`, but to avoid excess // allocations we have a single vector for each kind that is cleared and reused repeatedly. pub(crate) struct TtParser { - macro_name: Ident, - /// The set of current mps to be processed. This should be empty by the end of a successful /// execution of `parse_tt_inner`. cur_mps: Vec, @@ -442,9 +440,8 @@ pub(crate) struct TtParser { } impl TtParser { - pub(super) fn new(macro_name: Ident) -> TtParser { + pub(super) fn new() -> TtParser { TtParser { - macro_name, cur_mps: vec![], next_mps: vec![], bb_mps: vec![], @@ -697,7 +694,7 @@ impl TtParser { // Too many possibilities! let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); - return track.ambiguity(self.macro_name, parser.token.span, bb_locs, next_locs); + return track.ambiguity(parser.token.span, bb_locs, next_locs); } } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index a93e6c4d7068a..e1f6a6e104aa2 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -381,7 +381,6 @@ pub(super) trait Tracker<'matcher> { fn ambiguity( &mut self, - macro_name: Ident, token_span: Span, bb_locs: impl IntoIterator, next_locs: impl IntoIterator, @@ -406,7 +405,6 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { fn ambiguity( &mut self, - _macro_name: Ident, token_span: Span, _bb_locs: impl IntoIterator, _next_locs: impl IntoIterator, @@ -621,7 +619,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // this situation.) let parser = parser_from_cx(psess, arg.clone(), T::recovery()); // Try each arm's matchers. - let mut tt_parser = TtParser::new(name); + let mut tt_parser = TtParser::new(); for (i, rule) in rules.iter().enumerate() { let MacroRule::Func { lhs, .. } = rule else { continue }; let _tracing_span = trace_span!("Matching arm", %i); @@ -684,7 +682,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( // This uses the same strategy as `try_match_macro` let args_parser = parser_from_cx(psess, attr_args.clone(), T::recovery()); let body_parser = parser_from_cx(psess, attr_body.clone(), T::recovery()); - let mut tt_parser = TtParser::new(name); + let mut tt_parser = TtParser::new(); for (i, rule) in rules.iter().enumerate() { let MacroRule::Attr { args, body, .. } = rule else { continue }; @@ -737,7 +735,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { // This uses the same strategy as `try_match_macro` let body_parser = parser_from_cx(psess, body.clone(), T::recovery()); - let mut tt_parser = TtParser::new(name); + let mut tt_parser = TtParser::new(); for (i, rule) in rules.iter().enumerate() { let MacroRule::Derive { body, .. } = rule else { continue }; From 01d10f7ba18216222d722f51b2c19cf3f5b8cfc5 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:57:55 +0200 Subject: [PATCH 10/19] Out-line `TtParser::ambiguity_error()` --- compiler/rustc_expand/src/mbe/macro_parser.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index f0309af14bd2a..253eea7eecda2 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -692,9 +692,7 @@ impl TtParser { (_, _) => { // Too many possibilities! - let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); - let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); - return track.ambiguity(parser.token.span, bb_locs, next_locs); + return self.ambiguity_error(parser, matcher, track); } } @@ -702,6 +700,17 @@ impl TtParser { } } + fn ambiguity_error<'matcher, T: Tracker<'matcher>>( + &self, + parser: &Parser<'_>, + matcher: &'matcher [MatcherLoc], + track: &mut T, + ) -> NamedParseResult { + let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); + let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); + return track.ambiguity(parser.token.span, bb_locs, next_locs); + } + fn nameize>( &self, matcher: &[MatcherLoc], From e54925ea5ba48b9bac717d12f6f27c66cf643d31 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 09:02:21 +0200 Subject: [PATCH 11/19] Pass `&Parser<'_>` to `Tracker::ambiguity()` We primarily need `parser.token` here, but more data can't hurt. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 4 ++-- compiler/rustc_expand/src/mbe/macro_parser.rs | 2 +- compiler/rustc_expand/src/mbe/macro_rules.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 0da6249ce3b2a..293cb91420f20 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -231,7 +231,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match fn ambiguity( &mut self, - token_span: Span, + parser: &Parser<'_>, bb_locs: impl IntoIterator, next_locs: impl IntoIterator, ) -> NamedParseResult { @@ -247,7 +247,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match .join(" or "); Error( - token_span, + parser.token.span, format!( "local ambiguity when calling macro `{}`: multiple parsing options: {}", self.macro_name, diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 253eea7eecda2..f2c22ce165435 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -708,7 +708,7 @@ impl TtParser { ) -> NamedParseResult { let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); - return track.ambiguity(parser.token.span, bb_locs, next_locs); + return track.ambiguity(parser, bb_locs, next_locs); } fn nameize>( diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index e1f6a6e104aa2..571849c9c7786 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -381,7 +381,7 @@ pub(super) trait Tracker<'matcher> { fn ambiguity( &mut self, - token_span: Span, + parser: &Parser<'_>, bb_locs: impl IntoIterator, next_locs: impl IntoIterator, ) -> NamedParseResult; @@ -405,11 +405,11 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { fn ambiguity( &mut self, - token_span: Span, + parser: &Parser<'_>, _bb_locs: impl IntoIterator, _next_locs: impl IntoIterator, ) -> NamedParseResult { - Error(token_span, "ignored".into()) + Error(parser.token.span, "ignored".into()) } fn description() -> &'static str { From 33af508a16cc3e844e63e08079d31bab9d01011e Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 09:02:21 +0200 Subject: [PATCH 12/19] Pass `&Parser<'_>` to `TtParser::parse_tt_inner()` `&Parser<'_>` will be needed because `parse_tt_inner()` will begin calling `ambiguity_error()` soon. This parameter replaces two previous parameters, one of which was only needed in a failure case. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index f2c22ce165435..5b22784a55b3e 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -462,13 +462,13 @@ impl TtParser { /// track of through the mps generated. fn parse_tt_inner<'matcher, T: Tracker<'matcher>>( &mut self, + parser: &Parser<'_>, matcher: &'matcher [MatcherLoc], - token: &Token, - approx_position: u32, track: &mut T, ) -> Option> { // Matcher positions that would be valid if the macro invocation was over now. Only // modified if `token == Eof`. + let token = &parser.token; let mut eof_mps = SmallVec::<[MatcherPos; 1]>::new(); while let Some(mut mp) = self.cur_mps.pop() { @@ -595,7 +595,7 @@ impl TtParser { token::Eof, if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() }, ), - approx_position, + parser.approx_token_stream_pos(), "missing tokens in macro arguments", )), _ => Error(token.span, "ambiguity: multiple successful parses".to_string()), @@ -626,12 +626,7 @@ impl TtParser { // Process `cur_mps` until either we have finished the input or we need to get some // parsing from the black-box parser done. - let res = self.parse_tt_inner( - matcher, - &parser.token, - parser.approx_token_stream_pos(), - track, - ); + let res = self.parse_tt_inner(parser, matcher, track); if let Some(res) = res { return res; From 939350bd7c1e4f175a5e4e20953e66a6dba2b79e Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 09:08:34 +0200 Subject: [PATCH 13/19] Move "multiple successful parses" error to `ambiguity()` This is also a kind of ambiguity error, so it makes sense to hanlde it with `Tracker::ambiguity()` too. After this commit, there is only one place where `ParseResult::Error` is constructed; this is important for the next commit. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 4 ++++ compiler/rustc_expand/src/mbe/macro_parser.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 293cb91420f20..a633ab05dfee1 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -235,6 +235,10 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match bb_locs: impl IntoIterator, next_locs: impl IntoIterator, ) -> NamedParseResult { + if parser.token == token::Eof { + return Error(parser.token.span, "ambiguity: multiple successful parses".to_string()); + } + let nts = bb_locs .into_iter() .map(|loc| match loc { diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 5b22784a55b3e..0b8245e8ac0dd 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -598,7 +598,7 @@ impl TtParser { parser.approx_token_stream_pos(), "missing tokens in macro arguments", )), - _ => Error(token.span, "ambiguity: multiple successful parses".to_string()), + _ => self.ambiguity_error(parser, matcher, track), }) } else { None From a309ab3ceff13f3e29a90788dd6473ee7da23d16 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 09:19:59 +0200 Subject: [PATCH 14/19] Rename `Error` to `Ambiguity` It's only used for ambiguity errors now. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 9 ++++++--- compiler/rustc_expand/src/mbe/macro_parser.rs | 4 ++-- compiler/rustc_expand/src/mbe/macro_rules.rs | 12 ++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index a633ab05dfee1..d7f9d8cfc0b21 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -220,7 +220,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match }) } } - Error(err_sp, msg) => { + Ambiguity(err_sp, msg) => { let span = err_sp.substitute_dummy(self.root_span); let guar = self.dcx.span_err(span, msg.clone()); self.result = Some((span, guar)); @@ -236,7 +236,10 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match next_locs: impl IntoIterator, ) -> NamedParseResult { if parser.token == token::Eof { - return Error(parser.token.span, "ambiguity: multiple successful parses".to_string()); + return Ambiguity( + parser.token.span, + "ambiguity: multiple successful parses".to_string(), + ); } let nts = bb_locs @@ -250,7 +253,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match .collect::>() .join(" or "); - Error( + Ambiguity( parser.token.span, format!( "local ambiguity when calling macro `{}`: multiple parsing options: {}", diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 0b8245e8ac0dd..23e40204d490a 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -301,8 +301,8 @@ pub(crate) enum ParseResult { /// 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(F), - /// Fatal error (malformed macro?). Abort compilation. - Error(rustc_span::Span, String), + /// The input could be parsed in multiple distinct ways. + Ambiguity(rustc_span::Span, String), ErrorReported(ErrorGuaranteed), } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 571849c9c7786..e8b7e2e8c62fc 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -40,7 +40,7 @@ use crate::base::{ use crate::diagnostics; use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment}; use crate::mbe::macro_check::check_meta_variables; -use crate::mbe::macro_parser::{Error, ErrorReported, Failure, MatcherLoc, Success, TtParser}; +use crate::mbe::macro_parser::{Ambiguity, ErrorReported, Failure, MatcherLoc, Success, TtParser}; use crate::mbe::quoted::{RulePart, parse_one_tt}; use crate::mbe::transcribe::transcribe; use crate::mbe::{self, KleeneOp}; @@ -409,7 +409,7 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { _bb_locs: impl IntoIterator, _next_locs: impl IntoIterator, ) -> NamedParseResult { - Error(parser.token.span, "ignored".into()) + Ambiguity(parser.token.span, "ignored".into()) } fn description() -> &'static str { @@ -647,7 +647,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( trace!("Failed to match arm, trying the next one"); // Try the next arm. } - Error(_, _) => { + Ambiguity(_, _) => { debug!("Fatal error occurred during matching"); // We haven't emitted an error yet, so we can retry. return Err(CanRetry::Yes); @@ -697,7 +697,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()); continue; } - Error(_, _) => return Err(CanRetry::Yes), + Ambiguity(_, _) => return Err(CanRetry::Yes), ErrorReported(guar) => return Err(CanRetry::No(guar)), }; @@ -714,7 +714,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( Failure(_) => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) } - Error(_, _) => return Err(CanRetry::Yes), + Ambiguity(_, _) => return Err(CanRetry::Yes), ErrorReported(guar) => return Err(CanRetry::No(guar)), } } @@ -752,7 +752,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( Failure(_) => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) } - Error(_, _) => return Err(CanRetry::Yes), + Ambiguity(_, _) => return Err(CanRetry::Yes), ErrorReported(guar) => return Err(CanRetry::No(guar)), } } From 814e57fd2ec2562512cbc4340abd6195127dc97f Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 09:21:10 +0200 Subject: [PATCH 15/19] Set `self.result` directly from `ambiguity()` Instead of building an `Ambiguity` error in `ambiguity()` which is processed in `after_arm()`, perform the relevant processing (setting `self.result`) directly in `ambiguity()`. This makes the fields of `Ambiguity` unused, so they are removed. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 42 ++++++++++--------- compiler/rustc_expand/src/mbe/macro_parser.rs | 2 +- compiler/rustc_expand/src/mbe/macro_rules.rs | 12 +++--- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index d7f9d8cfc0b21..ee14399bb5b15 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -5,6 +5,7 @@ use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage, pluralize}; use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_macros::Subdiagnostic; +use rustc_middle::bug; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; use rustc_span::source_map::SourceMap; @@ -220,10 +221,10 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match }) } } - Ambiguity(err_sp, msg) => { - let span = err_sp.substitute_dummy(self.root_span); - let guar = self.dcx.span_err(span, msg.clone()); - self.result = Some((span, guar)); + Ambiguity => { + if self.result.is_none() { + bug!("`Error(..)` is only constructed through `Self::ambiguity()`"); + } } ErrorReported(guar) => self.result = Some((self.root_span, *guar)), } @@ -235,11 +236,13 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match bb_locs: impl IntoIterator, next_locs: impl IntoIterator, ) -> NamedParseResult { + let span = parser.token.span.substitute_dummy(self.root_span); + if parser.token == token::Eof { - return Ambiguity( - parser.token.span, - "ambiguity: multiple successful parses".to_string(), - ); + let msg = "ambiguity: multiple successful parses".to_string(); + let guar = self.dcx.span_err(span, msg); + self.result = Some((span, guar)); + return Ambiguity; } let nts = bb_locs @@ -253,17 +256,18 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match .collect::>() .join(" or "); - Ambiguity( - parser.token.span, - format!( - "local ambiguity when calling macro `{}`: multiple parsing options: {}", - self.macro_name, - match next_locs.into_iter().count() { - 0 => format!("built-in NTs {nts}."), - n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), - } - ), - ) + let msg = format!( + "local ambiguity when calling macro `{}`: multiple parsing options: {}", + self.macro_name, + match next_locs.into_iter().count() { + 0 => format!("built-in NTs {nts}."), + n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), + } + ); + + let guar = self.dcx.span_err(span, msg); + self.result = Some((span, guar)); + Ambiguity } fn description() -> &'static str { diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 23e40204d490a..3dcc33ee9c3e2 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -302,7 +302,7 @@ pub(crate) enum ParseResult { /// The usize is the approximate position of the token in the input token stream. Failure(F), /// The input could be parsed in multiple distinct ways. - Ambiguity(rustc_span::Span, String), + Ambiguity, ErrorReported(ErrorGuaranteed), } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index e8b7e2e8c62fc..f38f8504ff980 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -405,11 +405,11 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { fn ambiguity( &mut self, - parser: &Parser<'_>, + _parser: &Parser<'_>, _bb_locs: impl IntoIterator, _next_locs: impl IntoIterator, ) -> NamedParseResult { - Ambiguity(parser.token.span, "ignored".into()) + Ambiguity } fn description() -> &'static str { @@ -647,7 +647,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( trace!("Failed to match arm, trying the next one"); // Try the next arm. } - Ambiguity(_, _) => { + Ambiguity => { debug!("Fatal error occurred during matching"); // We haven't emitted an error yet, so we can retry. return Err(CanRetry::Yes); @@ -697,7 +697,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()); continue; } - Ambiguity(_, _) => return Err(CanRetry::Yes), + Ambiguity => return Err(CanRetry::Yes), ErrorReported(guar) => return Err(CanRetry::No(guar)), }; @@ -714,7 +714,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( Failure(_) => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) } - Ambiguity(_, _) => return Err(CanRetry::Yes), + Ambiguity => return Err(CanRetry::Yes), ErrorReported(guar) => return Err(CanRetry::No(guar)), } } @@ -752,7 +752,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( Failure(_) => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) } - Ambiguity(_, _) => return Err(CanRetry::Yes), + Ambiguity => return Err(CanRetry::Yes), ErrorReported(guar) => return Err(CanRetry::No(guar)), } } From fc82796b0a93d6a98993fda400d553e032858936 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 09:26:39 +0200 Subject: [PATCH 16/19] Return `()` from `Track::ambiguity()` The returned value is always `Ambiguity` so it can be handled by the (singular) call site. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 5 ++--- compiler/rustc_expand/src/mbe/macro_parser.rs | 3 ++- compiler/rustc_expand/src/mbe/macro_rules.rs | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index ee14399bb5b15..2afcf133d8f4e 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -235,14 +235,14 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match parser: &Parser<'_>, bb_locs: impl IntoIterator, next_locs: impl IntoIterator, - ) -> NamedParseResult { + ) { let span = parser.token.span.substitute_dummy(self.root_span); if parser.token == token::Eof { let msg = "ambiguity: multiple successful parses".to_string(); let guar = self.dcx.span_err(span, msg); self.result = Some((span, guar)); - return Ambiguity; + return; } let nts = bb_locs @@ -267,7 +267,6 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match let guar = self.dcx.span_err(span, msg); self.result = Some((span, guar)); - Ambiguity } fn description() -> &'static str { diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 3dcc33ee9c3e2..885a369831dce 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -703,7 +703,8 @@ impl TtParser { ) -> NamedParseResult { let bb_locs = self.bb_mps.iter().map(|mp| &matcher[mp.idx]); let next_locs = self.next_mps.iter().map(|mp| &matcher[mp.idx]); - return track.ambiguity(parser, bb_locs, next_locs); + track.ambiguity(parser, bb_locs, next_locs); + Ambiguity } fn nameize>( diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index f38f8504ff980..944602e84a94c 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -384,7 +384,7 @@ pub(super) trait Tracker<'matcher> { parser: &Parser<'_>, bb_locs: impl IntoIterator, next_locs: impl IntoIterator, - ) -> NamedParseResult; + ); /// For tracing. fn description() -> &'static str; @@ -408,8 +408,7 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { _parser: &Parser<'_>, _bb_locs: impl IntoIterator, _next_locs: impl IntoIterator, - ) -> NamedParseResult { - Ambiguity + ) { } fn description() -> &'static str { From 2d63f860bc2205d6adbfea99cfcc920a91170fe3 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Mon, 29 Jun 2026 19:44:29 +0200 Subject: [PATCH 17/19] Out-line non-terminal parsing error code - Also simplify control flow at the call site. `tests/ui/macros` pass. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 885a369831dce..8bded7662f423 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -78,7 +78,7 @@ pub(crate) use NamedMatch::*; pub(crate) use ParseResult::*; use rustc_ast::token::{self, DocComment, NonterminalKind, Token, TokenKind}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::ErrorGuaranteed; +use rustc_errors::{Diag, ErrorGuaranteed}; use rustc_middle::span_bug; use rustc_parse::parser::{ParseNtResult, Parser, token_descr}; use rustc_span::{Ident, MacroRulesNormalizedIdent, Span}; @@ -658,30 +658,19 @@ impl TtParser { // We need to call the black-box parser to get some nonterminal. let mut mp = self.bb_mps.pop().unwrap(); let loc = &matcher[mp.idx]; - if let &MatcherLoc::MetaVarDecl { - span, kind, next_metavar, seq_depth, .. - } = loc - { - // We use the span of the metavariable declaration to determine any - // edition-specific matching behavior for non-terminals. - let nt = match parser.to_mut().parse_nonterminal(kind) { - Err(err) => { - let guarantee = err.with_span_label( - span, - format!( - "while parsing argument for this `{kind}` macro fragment" - ), - ) - .emit(); - return ErrorReported(guarantee); - } - Ok(nt) => nt, - }; - mp.push_match(next_metavar, seq_depth, MatchedSingle(nt)); - mp.idx += 1; - } else { + let MatcherLoc::MetaVarDecl { kind, next_metavar, seq_depth, .. } = *loc else { unreachable!() - } + }; + + // We use the span of the metavariable declaration to determine any + // edition-specific matching behavior for non-terminals. + let nt = match parser.to_mut().parse_nonterminal(kind) { + Err(err) => return self.nt_parsing_error(loc, err), + Ok(nt) => nt, + }; + mp.push_match(next_metavar, seq_depth, MatchedSingle(nt)); + + mp.idx += 1; self.cur_mps.push(mp); } @@ -695,6 +684,17 @@ impl TtParser { } } + fn nt_parsing_error(&self, loc: &MatcherLoc, err: Diag<'_>) -> NamedParseResult { + let &MatcherLoc::MetaVarDecl { span, kind, .. } = loc else { unreachable!() }; + let guarantee = err + .with_span_label( + span, + format!("while parsing argument for this `{kind}` macro fragment"), + ) + .emit(); + ErrorReported(guarantee) + } + fn ambiguity_error<'matcher, T: Tracker<'matcher>>( &self, parser: &Parser<'_>, From 57a15bfe349b0a97fe0007ce82e8277c4b326a93 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Mon, 29 Jun 2026 20:01:13 +0200 Subject: [PATCH 18/19] Remove method defaults from `Tracker` Defaults make it easy to forget to implement something for `CollectTrackerAndEmitter`. --- compiler/rustc_expand/src/mbe/macro_rules.rs | 26 ++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 944602e84a94c..70b16324086f5 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -368,16 +368,11 @@ pub(super) trait Tracker<'matcher> { fn build_failure(tok: Token, position: u32, 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, - _which_matcher: WhichMatcher, - _result: &NamedParseResult, - ) { - } + fn after_arm(&mut self, which_matcher: WhichMatcher, result: &NamedParseResult); fn ambiguity( &mut self, @@ -389,9 +384,7 @@ pub(super) trait Tracker<'matcher> { /// For tracing. fn description() -> &'static str; - fn recovery() -> Recovery { - Recovery::Forbidden - } + fn recovery() -> Recovery; } /// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to @@ -403,6 +396,8 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { fn build_failure(_tok: Token, _position: u32, _msg: &'static str) -> Self::Failure {} + fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {} + fn ambiguity( &mut self, _parser: &Parser<'_>, @@ -411,9 +406,20 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { ) { } + fn after_arm( + &mut self, + _which_matcher: WhichMatcher, + _result: &NamedParseResult, + ) { + } + fn description() -> &'static str { "none" } + + fn recovery() -> Recovery { + Recovery::Forbidden + } } /// Expands the rules based macro defined by `rules` for a given input `arg`. From c1aa8d71a6d35cfa53e80b7a0e76c0fb601ae25a Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Fri, 3 Jul 2026 08:13:22 +0200 Subject: [PATCH 19/19] Split up `BestFailure::position_in_tokenstream` Suggested by @nnethercote. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 34 +++++++++----------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 2afcf133d8f4e..4aafd5e1785c5 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -158,21 +158,21 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { struct BestFailure { token: Token, - /// A comparable approximate position. - /// - /// [`MacroRule::Attr`] has two matchers (args and body). Failures can occur across the arms, - /// and we want to prioritize earlier failures over later ones, so we use [`WhichMatcher`]. + /// The matcher in which the failure occurred. + matcher: WhichMatcher, + + /// The approximate (parser) position of the failure. /// - /// The second element is the approximate parser position. - position_in_tokenstream: (WhichMatcher, u32), + /// This is relative to [`Self::matcher`]. + position: u32, msg: &'static str, remaining_matcher: MatcherLoc, } impl BestFailure { - fn is_better_position(&self, position: (WhichMatcher, u32)) -> bool { - position > self.position_in_tokenstream + fn is_better_position(&self, matcher: WhichMatcher, position: u32) -> bool { + (matcher, position) > (self.matcher, self.position) } } @@ -192,7 +192,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } fn after_arm(&mut self, which_matcher: WhichMatcher, result: &NamedParseResult) { - match result { + match *result { Success(_) => { // Nonterminal parser recovery might turn failed matches into successful ones, // but for that it must have emitted an error already @@ -204,15 +204,13 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match Failure((token, approx_position, msg)) => { debug!(?token, ?msg, "a new failure of an arm"); - let position_in_tokenstream = (which_matcher, *approx_position); - if self - .best_failure - .as_ref() - .is_none_or(|failure| failure.is_better_position(position_in_tokenstream)) - { + if self.best_failure.as_ref().is_none_or(|failure| { + failure.is_better_position(which_matcher, approx_position) + }) { self.best_failure = Some(BestFailure { - token: *token, - position_in_tokenstream, + token, + matcher: which_matcher, + position: approx_position, msg, remaining_matcher: self .remaining_matcher @@ -226,7 +224,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match bug!("`Error(..)` is only constructed through `Self::ambiguity()`"); } } - ErrorReported(guar) => self.result = Some((self.root_span, *guar)), + ErrorReported(guar) => self.result = Some((self.root_span, guar)), } }