diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index cd0c54f293e44..dba3f2b2da85d 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_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage, pluralize}; use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_macros::Subdiagnostic; @@ -155,13 +155,18 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { // FIXME: Factor out a per-arm `Tracker` so that the `Option` is unnecessary. current: Option<(WhichMatcher, &'matcher [MatcherLoc])>, - /// Matches of [`MatcherLoc`]s that successfully consumed input from the parser. + /// Matches of [`MatcherLoc`]s. /// - /// This accumulates all calls to [`Tracker::matched_one()`]. It is used to identify all - /// competing matches for ambiguity errors. - matches: FxHashSet, + /// This accumulates all calls to [`Tracker::trying_match()`] and [`Tracker::matched_one()`]. It + /// is used to identify all competing matches for ambiguity errors, and relevant match attempts + /// for failures. + matches: FxHashMap, + + /// Tokens seen during parsing. + tokens: FxHashMap, + + input_pos_offset: u32, - remaining_matcher: Option<&'matcher MatcherLoc>, /// Which arm's failure should we report? (the one furthest along) best_failure: Option, root_span: Span, @@ -169,7 +174,7 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct SuccessfulMatch { +struct Match { /// The position in the parser. /// /// As per [`Parser::approx_token_stream_pos()`]. @@ -179,6 +184,12 @@ struct SuccessfulMatch { loc_index: u32, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum MatchResult { + Success, + Failure, +} + struct BestFailure { token: Token, @@ -209,19 +220,27 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match self.current = Some((which_matcher, 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 trying_match(&mut self, input_pos: u32, token: &Token, loc_index: u32) { + let input_pos = self.input_pos_offset + input_pos; + let old_token = self.tokens.insert(input_pos, *token); + debug_assert!(old_token.is_none_or(|t| t == *token)); - fn matched_one(&mut self, parser: &Parser<'_>, loc_index: usize) { - let input_pos = parser.approx_token_stream_pos(); + // Insert failure for now, will be updated in `matched_one()`. let loc_index: u32 = loc_index.try_into().unwrap(); - let m = SuccessfulMatch { input_pos, loc_index }; - self.matches.insert(m); + let m = Match { input_pos, loc_index }; + self.matches.entry(m).or_insert(MatchResult::Failure); + } + + fn matched_one(&mut self, input_pos: u32, loc_index: u32) { + let input_pos = self.input_pos_offset + input_pos; + let m = Match { input_pos, loc_index }; + let match_result = + self.matches.get_mut(&m).unwrap_or_else(|| bug!("no corresponding `trying_match()`")); + *match_result = MatchResult::Success; + } + + fn reset_input_pos(&mut self, parser: &Parser<'_>) { + self.input_pos_offset = parser.approx_token_stream_pos(); } fn after_arm(&mut self, result: &NamedParseResult) { @@ -249,10 +268,12 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match self.current = None; self.matches.clear(); + self.tokens.clear(); + self.input_pos_offset = 0; } fn failure(&mut self, parser: &Parser<'_>) { - let Some((which_matcher, _)) = self.current else { + let Some((which_matcher, matcher)) = self.current else { bug!("`Self::prepare()` was not called to initialize context"); }; @@ -275,20 +296,37 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match .as_ref() .is_none_or(|failure| failure.is_better_position(which_matcher, approx_position)) { + // Use the furthest-along non-EOF matcher. If none exists, use an EOF matcher. + #[expect(rustc::potential_query_instability, reason = "finding a unique maximum value")] + let (&Match { input_pos, loc_index }, &result) = self + .matches + .iter() + .max_by_key(|&(m, _res)| { + // sort EOFs before others to deprioritize them + let eof = match matcher[m.loc_index as usize] { + MatcherLoc::Eof => 0, + _ => 1, + }; + (eof, m) + }) + .unwrap_or_else(|| bug!("failure without a `trying_match()` call")); + // NOTE: `input_pos` might differ from `approx_position` (it might be a little older, + // if there were no non-EOF candidates at the right position), and it might have been + // a successful match for the same reason. + let _ = (input_pos, result); + let matcher = matcher[loc_index as usize].clone(); + self.best_failure = Some(BestFailure { token, matcher: which_matcher, position: approx_position, msg, - remaining_matcher: self - .remaining_matcher - .expect("must have collected matcher already") - .clone(), + remaining_matcher: matcher, }) } } - fn ambiguity(&mut self, parser: &Parser<'_>) { + fn ambiguity(&mut self) { let Some((_, matcher)) = self.current else { bug!("`Self::prepare()` was not called to initialize context"); }; @@ -297,22 +335,40 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match rustc::potential_query_instability, reason = "sorting the results deterministically afterwards" )] - let (mut bb_locs, mut next_locs) = self + let mut matches = self .matches .iter() - .filter(|m| m.input_pos == parser.approx_token_stream_pos()) - .partition::, _>(|m| { + .filter_map(|(m, result)| matches!(result, MatchResult::Success).then_some(m)) + .collect::>(); + // Sort by input position, then `MatcherLoc` index. + matches.sort_unstable(); + + // Identify the earliest position where ambiguity occurred. + let input_pos = matches + .array_windows::<2>() + .find(|ms @ [a, b]| { + let mut locs = ms.iter().map(|x| &matcher[x.loc_index as usize]); + a.input_pos == b.input_pos + && locs + .any(|loc| matches!(loc, MatcherLoc::MetaVarDecl { .. } | MatcherLoc::Eof)) + }) + .map(|[a, _]| a.input_pos) + .unwrap_or_else(|| bug!("no ambiguity detected")); + + let (bb_locs, next_locs) = + matches.iter().filter(|m| m.input_pos == input_pos).partition::, _>(|m| { let loc = &matcher[m.loc_index as usize]; matches!(loc, MatcherLoc::MetaVarDecl { .. }) }); - // Use a reasonable and deterministic ordering for data in the error message. - bb_locs.sort_unstable_by_key(|m| m.loc_index); - next_locs.sort_unstable_by_key(|m| m.loc_index); + debug_assert!(bb_locs.iter().is_sorted()); + debug_assert!(next_locs.iter().is_sorted()); + + let token = *self.tokens.get(&input_pos).unwrap(); - let span = parser.token.span.substitute_dummy(self.root_span); + let span = token.span.substitute_dummy(self.root_span); - if parser.token == token::Eof { + if token == token::Eof { let msg = "ambiguity: multiple successful parses".to_string(); let guar = self.dcx.span_err(span, msg); self.result = Some((span, guar)); @@ -357,8 +413,9 @@ impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> { macro_name, dcx, current: None, - matches: FxHashSet::default(), - remaining_matcher: None, + matches: FxHashMap::default(), + tokens: FxHashMap::default(), + input_pos_offset: 0, 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 95a4ebc63d38b..a8d9e02ca4723 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -1,74 +1,64 @@ -//! This is an NFA-based parser, which calls out to the main Rust parser for named non-terminals -//! (which it commits to fully when it hits one in a grammar). There's a set of current NFA threads -//! and a set of next ones. Instead of NTs, we have a special case for Kleene star. The big-O, in -//! pathological cases, is worse than traditional use of NFA or Earley parsing, but it's an easier -//! fit for Macro-by-Example-style rules. +//! Parsing macros-by-example invocations. //! -//! (In order to prevent the pathological case, we'd need to lazily construct the resulting -//! `NamedMatch`es at the very end. It'd be a pain, and require more memory to keep around old -//! matcher positions, but it would also save overhead) +//! The MBE macro matcher language allows for some limited ambiguity: //! -//! We don't say this parser uses the Earley algorithm, because it's unnecessarily inaccurate. -//! The macro parser restricts itself to the features of finite state automata. Earley parsers -//! can be described as an extension of NFAs with completion rules, prediction rules, and recursion. -//! -//! Quick intro to how the parser works: -//! -//! A "matcher position" (a.k.a. "position" or "mp") is a dot in the middle of a matcher, usually -//! written as a `·`. For example `· a $( a )* a b` is one, as is `a $( · a )* a b`. -//! -//! The parser walks through the input a token at a time, maintaining a list -//! of threads consistent with the current position in the input string: `cur_mps`. -//! -//! As it processes them, it fills up `eof_mps` with threads that would be valid if -//! the macro invocation is now over, `bb_mps` with threads that are waiting on -//! a Rust non-terminal like `$e:expr`, and `next_mps` with threads that are waiting -//! on a particular token. Most of the logic concerns moving the · through the -//! repetitions indicated by Kleene stars. The rules for moving the · without -//! consuming any input are called epsilon transitions. It only advances or calls -//! out to the real Rust parser when no `cur_mps` threads remain. -//! -//! Example: -//! -//! ```text, ignore -//! Start parsing a a a a b against [· a $( a )* a b]. +//! ``` +//! macro_rules! foo { +//! ($(,)? , $(,)?) => {}; +//! } //! -//! Remaining input: a a a a b -//! next: [· a $( a )* a b] +//! foo!(,); // can be parsed unambiguously +//! //foo!(,,); // fails to compile due to ambiguity +//! ``` //! -//! - - - Advance over an a. - - - +//! When a repetition or optional matcher is encountered, the macro parser will not prioritize one +//! possibility over another (as occurs with e.g. PEG); it will explore all possibilities. If there +//! are multiple ways to parse the macro invocation, an ambiguity error is raised. //! -//! Remaining input: a a a b -//! cur: [a · $( a )* a b] -//! Descend/Skip (first position). -//! next: [a $( · a )* a b] [a $( a )* · a b]. +//! The possible ways to parse an input can be visualized as a tree, where the root represents the +//! start of parsing, and the children of each node are the parsing steps that follow from it. For +//! the above macro, that would look like: //! -//! - - - Advance over an a. - - - +//! ```text +//! start - token ',' - token ',' - token ',' - eof +//! | \\ +//! | skip ------ eof +//! \\ +//! skip ------ token ',' - token ',' - eof +//! \\ +//! skip ------ eof +//! ``` //! -//! Remaining input: a a b -//! cur: [a $( a · )* a b] [a $( a )* a · b] -//! Follow epsilon transition: Finish/Repeat (first position) -//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b] +//! This module implements a depth-first, backtracking traversal of that tree. It maintains a +//! stack of `MatcherPos`-es (i.e. mps), which represent paths taken through the tree. It will +//! continuously expand the latest `MatcherPos`, backtracking if the mp has no more children to +//! explore. //! -//! - - - Advance over an a. - - - (this looks exactly like the last step) +//! An important caveat is that meta-variables, e.g. `$e:expr`, require unambiguity to be parsed. No +//! other `MatcherPos`-es are allowed to match the same tokens as those consumed by a meta-variable; +//! doing so raises an ambiguity error. //! -//! Remaining input: a b -//! cur: [a $( a · )* a b] [a $( a )* a · b] -//! Follow epsilon transition: Finish/Repeat (first position) -//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b] +//! ``` +//! macro_rules! foo { +//! ($(a)? $x:ident b) => {}; +//! } //! -//! - - - Advance over an a. - - - (this looks exactly like the last step) +//! foo!(b b); // can be parsed unambiguously +//! //foo!(a b); // fails to compile due to ambiguity +//! ``` //! -//! Remaining input: b -//! cur: [a $( a · )* a b] [a $( a )* a · b] -//! Follow epsilon transition: Finish/Repeat (first position) -//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b] +//! In theory, the latter invocation could be parsed unambiguously. But, at the first input position +//! where a meta-variable needs to be matched (matching `$x` against `a`), another path through the +//! parse tree is valid (matching `a` in `$(a)?` against `a`), and this is not allowed. //! -//! - - - Advance over a b. - - - +//! # Pathological Behavior //! -//! Remaining input: '' -//! eof: [a $( a )* a b ·] -//! ``` +//! It is possible to construct macros which require an exponential runtime to parse. This is +//! because we don't deduplicate equivalent mps, or cache parsing results. Pathological macros are +//! very rare in the real world. While they could be handled in linear time like everything else, +//! doing so would add unnecessary overhead. We could retain the existing parsing algorithm and +//! switch to a guaranteed-linear-time alternative for a particular macro invocation if it takes +//! more than N parsing steps. use std::borrow::Cow; use std::fmt::Display; @@ -155,7 +145,9 @@ impl Display for MatcherLoc { } MatcherLoc::Eof => f.write_str("end of macro"), - // These are not printed in the diagnostic + // FIXME: A prior comment noted that the following variants should not be printed in + // diagnostics. "while trying to match sequence end" appears in several stderrs in the + // ui tests. Other variants might be reachable too. MatcherLoc::Delimited => f.write_str("delimiter"), MatcherLoc::Sequence { .. } => f.write_str("sequence start"), MatcherLoc::SequenceKleeneOpNoSep { .. } => f.write_str("sequence end"), @@ -244,7 +236,13 @@ pub(super) fn compute_locs(matcher: &[TokenTree]) -> Vec { #[derive(Debug)] struct MatcherPos { /// The index into `TtParser::locs`, which represents the "dot". - idx: usize, + idx: u32, + + /// The input position being targeted. + /// + /// This is an index into or to the end of `seen_tokens`; in the latter case, it then refers + /// to the latest token from `parser`. + input_pos: u32, /// The matches made against metavar decls so far. On a successful match, this vector ends up /// with one element per metavar decl in the matcher. Each element records token trees matched @@ -425,18 +423,30 @@ 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 { - /// 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, + /// mps at older input positions that are yet to be explored. + /// + /// Invariant: `backtrack.iter().is_sorted_by_key(|mp| mp.input_pos)`. + backtrack: Vec, - /// The set of newly generated mps. These are used to replenish `cur_mps` in the function - /// `parse_tt`. - next_mps: Vec, + /// Previously seen tokens from the parser. + /// + /// Tokens after the latest meta-variable that have been matched against a fixed token and + /// [`Parser::bump()`]-ed past are stored here. This list is cleared every time a meta-variable + /// is parsed. + seen_tokens: Vec, /// Pre-allocate an empty match array, so it can be cloned cheaply for macros with many rules /// that have no metavars. empty_matches: Rc>, + /// A potentially-ambiguous mp waiting to be parsed. + /// + /// This is an mp that has been successfully matched, and that is unambiguous iff no other mps + /// match at the same input position. It is stored here until all other mps have been exhausted. + /// If another mp conflicts with this, this is left untouched and [`Self::found_ambiguity`] is + /// set. + maybe_ambig_mp: Option, + /// Whether an ambiguity error has occurred. found_ambiguity: bool, } @@ -444,68 +454,82 @@ pub(crate) struct TtParser { impl TtParser { pub(super) fn new() -> TtParser { TtParser { - cur_mps: vec![], - next_mps: vec![], + backtrack: vec![], + seen_tokens: vec![], empty_matches: Rc::new(vec![]), + maybe_ambig_mp: None, found_ambiguity: false, } } - pub(super) fn has_no_remaining_items_for_step(&self) -> bool { - self.cur_mps.is_empty() - } - - /// Process the matcher positions of `cur_mps` until it is empty. In the process, this will - /// produce more mps in `next_mps` and `bb_mps`. - /// - /// # Returns - /// - /// `Some(result)` if everything is finished, `None` otherwise. Note that matches are kept - /// track of through the mps generated. - fn parse_tt_inner<'matcher, T: Tracker<'matcher>>( + /// Match the token stream from `parser` against `matcher`. + pub(super) fn parse_tt<'matcher, T: Tracker<'matcher>>( &mut self, parser: &mut Cow<'_, Parser<'_>>, matcher: &'matcher [MatcherLoc], track: &mut T, - ) -> Option { - while let Some(mp) = self.cur_mps.pop() { - if let Some(result) = self.match_one(parser, matcher, mp, track, false) { - return Some(result); + ) -> NamedParseResult { + self.backtrack.clear(); + self.seen_tokens.clear(); + let mut mp = MatcherPos { idx: 0, input_pos: 0, matches: Rc::clone(&self.empty_matches) }; + + loop { + match self.match_one(parser, matcher, mp, track) { + ControlFlow::Continue(Some(next_mp)) => { + mp = next_mp; + continue; + } + ControlFlow::Continue(None) => {} + ControlFlow::Break(result) => { + std::hint::cold_path(); + return result; + } } - } - // FIXME: Error messages here could be improved with links to original rules. + // Check for a matched meta-variable or EOF. + let Some(mamp) = self.maybe_ambig_mp.take() else { + // There was no valid way to parse the input. + std::hint::cold_path(); + track.failure(parser); + return Failure; + }; + + if self.found_ambiguity || self.seen_tokens.len() > mamp.input_pos as usize { + // Either: + // - A second maybe-ambig mp was found, setting `found_ambiguity` + // - Something else was parsed successfully, advancing `parser` past `mp` + // - `mp` was matched while backtracking + std::hint::cold_path(); + track.ambiguity(); + return Ambiguity; + } - if self.next_mps.is_empty() { - // There are no possible next positions: syntax error. - track.failure(parser); - return Some(Failure); + match self.process_special(parser, matcher, mamp, track) { + ControlFlow::Break(result) => { + std::hint::cold_path(); + return result; + } + ControlFlow::Continue(next_mp) => { + mp = next_mp; + continue; + } + } } - - // Dump all possible `next_mps` into `cur_mps` for the next iteration. Then - // process the next token. - self.cur_mps.append(&mut self.next_mps); - parser.to_mut().bump(); - - None } /// Match a single [`MatcherPos`]. - /// - /// If a meta-variable is encountered and `checking_for_ambiguity` is `false`, `cur_mps` will be - /// drained to eagerly check for ambiguity, and `parser` will be modified. - #[inline(always)] // must be inlined in `parse_tt_inner()` + #[inline(always)] // must be inlined in `parse_tt()` fn match_one<'matcher, T: Tracker<'matcher>>( &mut self, parser: &mut Cow<'_, Parser<'_>>, matcher: &'matcher [MatcherLoc], mut mp: MatcherPos, track: &mut T, - checking_for_ambiguity: bool, - ) -> Option { - let matcher_loc = &matcher[mp.idx]; - track.before_match_loc(self, matcher_loc); - let token = &parser.token; + ) -> ControlFlow> { + let matcher_loc = &matcher[mp.idx as usize]; + let input_pos = mp.input_pos as usize; + let token = self.seen_tokens.get(input_pos).unwrap_or(&parser.token); + track.trying_match(mp.input_pos, token, mp.idx); match matcher_loc { MatcherLoc::Token { token: t } => { @@ -518,18 +542,25 @@ impl TtParser { // Otherwise, this match has failed, there is nothing to do, and hopefully another // mp in `cur_mps` will match. if matches!(t, Token { kind: DocComment(..), .. }) { - mp.idx += 1; - self.cur_mps.push(mp); + std::hint::cold_path(); + // skip } else if token_name_eq(t, token) { - track.matched_one(parser, mp.idx); - mp.idx += 1; - self.next_mps.push(mp); + track.matched_one(mp.input_pos, mp.idx); + mp.input_pos += 1; + if mp.input_pos as usize > self.seen_tokens.len() { + self.seen_tokens.push(parser.token); + parser.to_mut().bump(); + } + } else { + return ControlFlow::Continue(self.backtrack.pop()); } + mp.idx += 1; + ControlFlow::Continue(Some(mp)) } MatcherLoc::Delimited => { // Entering the delimiter is trivial. mp.idx += 1; - self.cur_mps.push(mp); + ControlFlow::Continue(Some(mp)) } &MatcherLoc::Sequence { op, @@ -545,170 +576,164 @@ impl TtParser { if matches!(op, KleeneOp::ZeroOrMore | KleeneOp::ZeroOrOne) { // Try zero matches of this sequence, by skipping over it. - self.cur_mps - .push(MatcherPos { idx: idx_first_after, matches: Rc::clone(&mp.matches) }); + let idx = idx_first_after.try_into().unwrap(); + self.backtrack.push(MatcherPos { + idx, + input_pos: mp.input_pos, + matches: Rc::clone(&mp.matches), + }); } // Try one or more matches of this sequence, by entering it. mp.idx += 1; - self.cur_mps.push(mp); + ControlFlow::Continue(Some(mp)) } &MatcherLoc::SequenceKleeneOpNoSep { op, idx_first } => { - // We are past the end of a sequence with no separator. Try ending the sequence. If - // that's not possible, `ending_mp` will fail quietly when it is processed next time - // around the loop. - let ending_mp = MatcherPos { - idx: mp.idx + 1, // +1 skips the Kleene op - matches: Rc::clone(&mp.matches), - }; - self.cur_mps.push(ending_mp); - if op != KleeneOp::ZeroOrOne { // Try another repetition. - mp.idx = idx_first; - self.cur_mps.push(mp); + let repeating_mp = MatcherPos { + idx: idx_first.try_into().unwrap(), + input_pos: mp.input_pos, + matches: Rc::clone(&mp.matches), + }; + self.backtrack.push(repeating_mp); } + + // Try ending the sequence. + mp.idx += 1; + ControlFlow::Continue(Some(mp)) } MatcherLoc::SequenceSep { separator } => { // We are past the end of a sequence with a separator but we haven't seen the - // separator yet. Try ending the sequence. If that's not possible, `ending_mp` will - // fail quietly when it is processed next time around the loop. + // separator yet. Try ending the sequence. let ending_mp = MatcherPos { idx: mp.idx + 2, // +2 skips the separator and the Kleene op + input_pos: mp.input_pos, matches: Rc::clone(&mp.matches), }; - self.cur_mps.push(ending_mp); if token_name_eq(token, separator) { // The separator matches the current token. Advance past it. - track.matched_one(parser, mp.idx); + track.matched_one(mp.input_pos, mp.idx); mp.idx += 1; - self.next_mps.push(mp); + mp.input_pos += 1; + if mp.input_pos as usize > self.seen_tokens.len() { + self.seen_tokens.push(parser.token); + parser.to_mut().bump(); + } + self.backtrack.push(ending_mp); + ControlFlow::Continue(Some(mp)) + } else { + ControlFlow::Continue(Some(ending_mp)) } } &MatcherLoc::SequenceKleeneOpAfterSep { idx_first } => { // We are past the sequence separator. This can't be a `?` Kleene op, because they // don't permit separators. Try another repetition. - mp.idx = idx_first; - self.cur_mps.push(mp); + mp.idx = idx_first.try_into().unwrap(); + ControlFlow::Continue(Some(mp)) } &MatcherLoc::MetaVarDecl { kind, next_metavar, seq_depth, .. } => { // Built-in nonterminals never start with these tokens, so we can eliminate them // from consideration. We use the span of the metavariable declaration to determine // any edition-specific matching behavior for non-terminals. if !Parser::nonterminal_may_begin_with(kind, token) { - return None; + return ControlFlow::Continue(self.backtrack.pop()); } // EOF tokens would cause unexpected processing in `match_one()`. debug_assert!(parser.token != token::Eof, "{kind:?} should not accept EOF tokens"); - track.matched_one(parser, mp.idx); + track.matched_one(mp.input_pos, mp.idx); - if let ControlFlow::Break(result) = - self.check_for_ambiguity(parser, matcher, track, checking_for_ambiguity) - { - return result; + if self.maybe_ambig_mp.is_some() || input_pos < self.seen_tokens.len() { + std::hint::cold_path(); + self.found_ambiguity = true; + return ControlFlow::Continue(self.backtrack.pop()); + } else if let Some(next_mp) = self.backtrack.pop() { + std::hint::cold_path(); + self.maybe_ambig_mp = Some(mp); + return ControlFlow::Continue(Some(next_mp)); } // 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 Some(self.nt_parsing_error(matcher_loc, err)), + Err(err) => { + std::hint::cold_path(); + return ControlFlow::Break(self.nt_parsing_error(matcher_loc, err)); + } Ok(nt) => nt, }; mp.push_match(next_metavar, seq_depth, MatchedSingle(nt)); mp.idx += 1; - self.cur_mps.push(mp); + mp.input_pos = 0; + self.seen_tokens.clear(); + track.reset_input_pos(parser); + ControlFlow::Continue(Some(mp)) } MatcherLoc::Eof => { // We are past the matcher's end, and not in a sequence. Try to end things. - debug_assert_eq!(mp.idx, matcher.len() - 1); + debug_assert_eq!(mp.idx as usize, matcher.len() - 1); if *token != token::Eof { - return None; + return ControlFlow::Continue(self.backtrack.pop()); } - track.matched_one(parser, mp.idx); + track.matched_one(mp.input_pos, mp.idx); - if let ControlFlow::Break(result) = - self.check_for_ambiguity(parser, matcher, track, checking_for_ambiguity) - { - return result; + if self.maybe_ambig_mp.is_some() || input_pos < self.seen_tokens.len() { + std::hint::cold_path(); + self.found_ambiguity = true; + return ControlFlow::Continue(self.backtrack.pop()); + } else if let Some(next_mp) = self.backtrack.pop() { + std::hint::cold_path(); + self.maybe_ambig_mp = Some(mp); + return ControlFlow::Continue(Some(next_mp)); } + self.seen_tokens.clear(); let matches = Rc::unwrap_or_clone(mp.matches).into_iter(); - return Some(Success(self.nameize(matcher, matches))); + ControlFlow::Break(Success(self.nameize(matcher, matches))) } } - - None - } - - /// Look for ambiguity before parsing a non-terminal. - /// - /// - When `checking_for_ambiguity`: immediately an ambiguity error. - /// - Otherwise: eagerly consumes [`Self::cur_mps`] to check for ambiguity. - /// - /// If [`ControlFlow::Continue`] is returned, ambiguity has not been detected. - fn check_for_ambiguity<'matcher, R, T: Tracker<'matcher>>( - &mut self, - parser: &mut Cow<'_, Parser<'_>>, - matcher: &'matcher [MatcherLoc], - track: &mut T, - checking_for_ambiguity: bool, - ) -> ControlFlow>> { - if checking_for_ambiguity { - // This was called in the context of a _different_ `MatcherLoc` that was about to be - // matched. Prevent the caller from doing more work, but don't prepare the actual error - // yet; let the outer `check_for_ambiguity()` do that. - self.found_ambiguity = true; - return ControlFlow::Break(None); - } - - assert!(!self.found_ambiguity); - - // Consume all pending mps at the current input position. - while let Some(mp) = self.cur_mps.pop() { - let result = self.match_one(parser, matcher, mp, track, true); - // A result cannot be returned when `check_for_ambiguity` is `true`. - assert!(result.is_none()); - } - - if std::mem::take(&mut self.found_ambiguity) || !self.next_mps.is_empty() { - track.ambiguity(parser); - ControlFlow::Break(Some(Ambiguity)) - } else { - ControlFlow::Continue(()) - } } - /// Match the token stream from `parser` against `matcher`. - pub(super) fn parse_tt<'matcher, T: Tracker<'matcher>>( + /// Finish processing a matched special [`MatcherPos`]. + #[cold] + fn process_special<'matcher, T: Tracker<'matcher>>( &mut self, parser: &mut Cow<'_, Parser<'_>>, matcher: &'matcher [MatcherLoc], + mut mp: MatcherPos, track: &mut T, - ) -> NamedParseResult { - // 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 - // possible next positions into `next_mps`. After some post-processing, the contents of - // `next_mps` replenish `cur_mps` and we start over again. - self.cur_mps.clear(); - self.cur_mps.push(MatcherPos { idx: 0, matches: Rc::clone(&self.empty_matches) }); - - loop { - assert!(!self.cur_mps.is_empty()); - self.next_mps.clear(); + ) -> ControlFlow { + let matcher_loc = &matcher[mp.idx as usize]; + match matcher_loc { + &MatcherLoc::MetaVarDecl { kind, next_metavar, seq_depth, .. } => { + // 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 ControlFlow::Break(self.nt_parsing_error(matcher_loc, err)), + Ok(nt) => nt, + }; + mp.push_match(next_metavar, seq_depth, MatchedSingle(nt)); - // Parse all mps at the current input position, then progress the parser. - let res = self.parse_tt_inner(parser, matcher, track); + mp.idx += 1; + mp.input_pos = 0; + self.seen_tokens.clear(); + track.reset_input_pos(parser); + ControlFlow::Continue(mp) + } - if let Some(res) = res { - return res; + MatcherLoc::Eof => { + self.seen_tokens.clear(); + let matches = Rc::unwrap_or_clone(mp.matches).into_iter(); + ControlFlow::Break(Success(self.nameize(matcher, matches))) } + + _ => unreachable!(), } } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index cc4e08e2877b6..f2c35949a7d7f 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -362,16 +362,18 @@ pub(super) trait Tracker<'matcher> { /// Provide context on the arm that's about to be matched. fn prepare(&mut self, which_matcher: WhichMatcher, matcher: &'matcher [MatcherLoc]); - /// This is called before trying to match next MatcherLoc on the current token. - fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc); + /// A [`MatcherLoc`] is about to be matched against `token` at `input_pos`. + fn trying_match(&mut self, input_pos: u32, token: &Token, loc_index: u32); /// A [`MatcherLoc`] successfully consumed input from the parser. /// /// This is called for [`MatcherLoc::Token`] and [`MatcherLoc::SequenceSep`], which consume - /// single tokens, when they successfully match [`Parser::token`]. It is also called for + /// single tokens, when they successfully match the token at `input_pos`. It is also called for /// [`MatcherLoc::MetaVarDecl`] when non-terminal parsing is guaranteed to occur (i.e. after /// [`Parser::nonterminal_may_begin_with()`] returns `true`). - fn matched_one(&mut self, parser: &Parser<'_>, loc_index: usize); + fn matched_one(&mut self, input_pos: u32, loc_index: u32); + + fn reset_input_pos(&mut self, parser: &Parser<'_>); /// 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`). @@ -389,7 +391,7 @@ pub(super) trait Tracker<'matcher> { /// An ambiguity error occurred. /// /// The parser will return [`NamedParseResult::Ambiguity`] after calling this. - fn ambiguity(&mut self, parser: &Parser<'_>); + fn ambiguity(&mut self); /// For tracing. fn description() -> &'static str; @@ -402,16 +404,25 @@ pub(super) trait Tracker<'matcher> { pub(super) struct NoopTracker; impl<'matcher> Tracker<'matcher> for NoopTracker { + #[inline] fn prepare(&mut self, _which_matcher: WhichMatcher, _matcher: &'matcher [MatcherLoc]) {} - fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {} + #[inline] + fn trying_match(&mut self, _input_pos: u32, _token: &Token, _loc_index: u32) {} + + #[inline] + fn matched_one(&mut self, _input_pos: u32, _loc_index: u32) {} - fn matched_one(&mut self, _parser: &Parser<'_>, _loc_index: usize) {} + #[inline] + fn ambiguity(&mut self) {} - fn ambiguity(&mut self, _parser: &Parser<'_>) {} + #[inline] + fn reset_input_pos(&mut self, _parser: &Parser<'_>) {} + #[inline] fn after_arm(&mut self, _result: &NamedParseResult) {} + #[inline] fn failure(&mut self, _parser: &Parser<'_>) {} fn description() -> &'static str { diff --git a/tests/ui/macros/assert-trailing-junk.with-generic-asset.stderr b/tests/ui/macros/assert-trailing-junk.with-generic-asset.stderr index 2af779a467a3f..6ff0e76afb2a5 100644 --- a/tests/ui/macros/assert-trailing-junk.with-generic-asset.stderr +++ b/tests/ui/macros/assert-trailing-junk.with-generic-asset.stderr @@ -18,7 +18,8 @@ LL | assert!(true, "whatever" blah); | | | help: missing comma here | - = note: while trying to match sequence start +note: while trying to match `,` + --> $SRC_DIR/std/src/panic.rs:LL:COL error: unexpected string literal --> $DIR/assert-trailing-junk.rs:19:18 @@ -36,7 +37,8 @@ LL | assert!(true "whatever" blah); | | | help: missing comma here | - = note: while trying to match sequence start +note: while trying to match `,` + --> $SRC_DIR/std/src/panic.rs:LL:COL error: macro requires an expression as an argument --> $DIR/assert-trailing-junk.rs:23:5 diff --git a/tests/ui/macros/assert-trailing-junk.without-generic-asset.stderr b/tests/ui/macros/assert-trailing-junk.without-generic-asset.stderr index 2af779a467a3f..6ff0e76afb2a5 100644 --- a/tests/ui/macros/assert-trailing-junk.without-generic-asset.stderr +++ b/tests/ui/macros/assert-trailing-junk.without-generic-asset.stderr @@ -18,7 +18,8 @@ LL | assert!(true, "whatever" blah); | | | help: missing comma here | - = note: while trying to match sequence start +note: while trying to match `,` + --> $SRC_DIR/std/src/panic.rs:LL:COL error: unexpected string literal --> $DIR/assert-trailing-junk.rs:19:18 @@ -36,7 +37,8 @@ LL | assert!(true "whatever" blah); | | | help: missing comma here | - = note: while trying to match sequence start +note: while trying to match `,` + --> $SRC_DIR/std/src/panic.rs:LL:COL error: macro requires an expression as an argument --> $DIR/assert-trailing-junk.rs:23:5