diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index e1746039963f4..f603e8604e646 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -699,7 +699,7 @@ impl Token { /// Returns `true` if the token can appear at the start of a pattern. /// - /// Shamelessly borrowed from `can_begin_expr`, only used for diagnostics right now. + /// Shamelessly borrowed from `can_begin_expr`. pub fn can_begin_pattern(&self, pat_kind: NtPatKind) -> bool { match &self.uninterpolate().kind { // box, ref, mut, and other identifiers (can stricten) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 5a88d57917640..e4ed5b9806da7 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -148,6 +148,12 @@ pub(super) fn failed_to_match_macro( struct CollectTrackerAndEmitter<'dcx, 'matcher> { macro_name: Ident, dcx: DiagCtxtHandle<'dcx>, + + /// The matcher currently being parsed. + // + // FIXME: Factor out a per-arm `Tracker` so that the `Option` is unnecessary. + current: Option, + remaining_matcher: Option<&'matcher MatcherLoc>, /// Which arm's failure should we report? (the one furthest along) best_failure: Option, @@ -177,10 +183,12 @@ impl BestFailure { } impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> { - type Failure = (Token, u32, &'static str); + fn prepare(&mut self, which_matcher: WhichMatcher) { + if self.current.is_some() { + bug!("`Self::after_arm()` was not called to clean up context"); + } - fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure { - (tok, position, msg) + self.current = Some(which_matcher); } fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) { @@ -191,7 +199,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } } - fn after_arm(&mut self, which_matcher: WhichMatcher, result: &NamedParseResult) { + fn after_arm(&mut self, result: &NamedParseResult) { match *result { Success(_) => { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -201,31 +209,57 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match "should not collect detailed info for successful macro match", ); } - Failure((token, approx_position, msg)) => { - debug!(?token, ?msg, "a new failure of an arm"); - - if self.best_failure.as_ref().is_none_or(|failure| { - failure.is_better_position(which_matcher, approx_position) - }) { - 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(), - }) + Failure => { + if self.best_failure.is_none() { + bug!("A matching failure occurred but `Self::failure()` was not called"); } } Ambiguity => { if self.result.is_none() { - bug!("`Error(..)` is only constructed through `Self::ambiguity()`"); + bug!("An ambiguity error occurred but `Self::ambiguity()` was not called"); } } ErrorReported(guar) => self.result = Some((self.root_span, guar)), } + + self.current = None; + } + + fn failure(&mut self, parser: &Parser<'_>) { + let Some(which_matcher) = self.current else { + bug!("`Self::prepare()` was not called to initialize context"); + }; + + let mut token = parser.token; + let approx_position = parser.approx_token_stream_pos(); + let msg = if token.kind == token::Eof { + // FIXME: Can this be factored out of the EOF case? + if !token.span.is_dummy() { + token.span = token.span.shrink_to_hi(); + } + "missing tokens in macro arguments" + } else { + "no rules expected this token in macro call" + }; + + debug!(?token, ?msg, "a new failure of an arm"); + + if self + .best_failure + .as_ref() + .is_none_or(|failure| failure.is_better_position(which_matcher, approx_position)) + { + 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(), + }) + } } fn ambiguity( @@ -281,6 +315,7 @@ impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> { Self { macro_name, dcx, + current: None, remaining_matcher: None, best_failure: None, root_span, diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 641c50b620ff5..1ff5cd6a61787 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -294,14 +294,16 @@ impl MatcherPos { /// Represents the possible results of an attempted parse. #[derive(Debug)] -pub(crate) enum ParseResult { +pub(crate) enum ParseResult { /// Parsed successfully. Success(T), - /// Arm failed to match. If the second parameter is `token::Eof`, it indicates an unexpected - /// end of macro invocation. Otherwise, it indicates that no rules expected the given token. - /// The usize is the approximate position of the token in the input token stream. - Failure(F), + /// Arm failed to match. + /// + /// [`Tracker::failure()`] will be called beforehand. + Failure, /// The input could be parsed in multiple distinct ways. + /// + /// [`Tracker::ambiguity()`] will be called beforehand. Ambiguity, ErrorReported(ErrorGuaranteed), } @@ -309,7 +311,7 @@ pub(crate) enum ParseResult { /// A `ParseResult` where the `Success` variant contains a mapping of /// `MacroRulesNormalizedIdent`s to `NamedMatch`es. This represents the mapping /// of metavars to the token trees they bind to. -pub(crate) type NamedParseResult = ParseResult; +pub(crate) type NamedParseResult = ParseResult; /// Contains a mapping of `MacroRulesNormalizedIdent`s to `NamedMatch`es. /// This represents the mapping of metavars to the token trees they bind to. @@ -465,143 +467,151 @@ impl TtParser { parser: &Parser<'_>, matcher: &'matcher [MatcherLoc], track: &mut T, - ) -> Option> { + ) -> 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() { - let matcher_loc = &matcher[mp.idx]; - track.before_match_loc(self, matcher_loc); - - match matcher_loc { - MatcherLoc::Token { token: t } => { - // If it's a doc comment, we just ignore it and move on to the next tt in the - // matcher. This is a bug, but #95267 showed that existing programs rely on - // this behaviour, and changing it would require some care and a transition - // period. - // - // If the token matches, we can just advance the parser. - // - // 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); - } else if token_name_eq(t, token) { - mp.idx += 1; - self.next_mps.push(mp); - } + while let Some(mp) = self.cur_mps.pop() { + self.match_one(parser, matcher, mp, track, &mut eof_mps); + } + + // 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. + let token = &parser.token; + if *token == token::Eof { + assert!(self.next_mps.is_empty()); + assert!(self.bb_mps.is_empty()); + + Some(match *eof_mps { + [_] => { + let eof_mp = eof_mps.pop().unwrap(); + let matches = Rc::unwrap_or_clone(eof_mp.matches).into_iter(); + Success(self.nameize(matcher, matches)) } - MatcherLoc::Delimited => { - // Entering the delimiter is trivial. - mp.idx += 1; - self.cur_mps.push(mp); + [] => { + track.failure(parser); + Failure } - &MatcherLoc::Sequence { - op, - num_metavar_decls, - idx_first_after, - next_metavar, - seq_depth, - } => { - // Install an empty vec for each metavar within the sequence. - for metavar_idx in next_metavar..next_metavar + num_metavar_decls { - mp.push_match(metavar_idx, seq_depth, MatchedSeq(vec![])); - } + _ => self.ambiguity_error(parser, matcher, track), + }) + } else { + None + } + } - 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), - }); - } + /// Match a single [`MatcherPos`]. + #[inline(always)] // must be inlined in `parse_tt_inner()` + fn match_one<'matcher, T: Tracker<'matcher>>( + &mut self, + parser: &Parser<'_>, + matcher: &'matcher [MatcherLoc], + mut mp: MatcherPos, + track: &mut T, + eof_mps: &mut SmallVec<[MatcherPos; 1]>, + ) { + let matcher_loc = &matcher[mp.idx]; + track.before_match_loc(self, matcher_loc); + let token = &parser.token; - // Try one or more matches of this sequence, by entering it. + match matcher_loc { + MatcherLoc::Token { token: t } => { + // If it's a doc comment, we just ignore it and move on to the next tt in the + // matcher. This is a bug, but #95267 showed that existing programs rely on this + // behaviour, and changing it would require some care and a transition period. + // + // If the token matches, we can just advance the parser. + // + // 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); + } else if token_name_eq(t, token) { + mp.idx += 1; + self.next_mps.push(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); - } + } + MatcherLoc::Delimited => { + // Entering the delimiter is trivial. + mp.idx += 1; + self.cur_mps.push(mp); + } + &MatcherLoc::Sequence { + op, + num_metavar_decls, + idx_first_after, + next_metavar, + seq_depth, + } => { + // Install an empty vec for each metavar within the sequence. + for metavar_idx in next_metavar..next_metavar + num_metavar_decls { + mp.push_match(metavar_idx, seq_depth, MatchedSeq(vec![])); } - 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. - let ending_mp = MatcherPos { - idx: mp.idx + 2, // +2 skips the separator and the Kleene op - 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. - mp.idx += 1; - self.next_mps.push(mp); - } + 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) }); } - &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. + + // Try one or more matches of this sequence, by entering it. + mp.idx += 1; + self.cur_mps.push(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); } - &MatcherLoc::MetaVarDecl { kind, .. } => { - // 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) { - self.bb_mps.push(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. + let ending_mp = MatcherPos { + idx: mp.idx + 2, // +2 skips the separator and the Kleene op + 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. + mp.idx += 1; + self.next_mps.push(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); - if *token == token::Eof { - eof_mps.push(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); + } + &MatcherLoc::MetaVarDecl { kind, .. } => { + // 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) { + self.bb_mps.push(mp); } } - } - - // 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 { - [_] => { - 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)) + 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); + if *token == token::Eof { + eof_mps.push(mp); } - [] => Failure(T::build_failure( - Token::new( - token::Eof, - if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() }, - ), - parser.approx_token_stream_pos(), - "missing tokens in macro arguments", - )), - _ => self.ambiguity_error(parser, matcher, track), - }) - } else { - None + } } } @@ -611,7 +621,7 @@ impl TtParser { parser: &mut Cow<'_, Parser<'_>>, matcher: &'matcher [MatcherLoc], track: &mut T, - ) -> NamedParseResult { + ) -> 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 @@ -640,11 +650,8 @@ impl TtParser { (0, 0) => { // There are no possible next positions AND we aren't waiting for the black-box // parser: syntax error. - return Failure(T::build_failure( - parser.token, - parser.approx_token_stream_pos(), - "no rules expected this token in macro call", - )); + track.failure(parser); + return Failure; } (_, 0) => { @@ -684,7 +691,7 @@ impl TtParser { } } - fn nt_parsing_error(&self, loc: &MatcherLoc, err: Diag<'_>) -> NamedParseResult { + fn nt_parsing_error(&self, loc: &MatcherLoc, err: Diag<'_>) -> NamedParseResult { let &MatcherLoc::MetaVarDecl { span, kind, .. } = loc else { unreachable!() }; let guarantee = err .with_span_label( @@ -700,7 +707,7 @@ impl TtParser { parser: &Parser<'_>, matcher: &'matcher [MatcherLoc], track: &mut T, - ) -> NamedParseResult { + ) -> NamedParseResult { // Use a reasonable and deterministic ordering for data in the error message. self.bb_mps.sort_unstable_by_key(|mp| mp.idx); self.next_mps.sort_unstable_by_key(|mp| mp.idx); diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index a6acccd38ab1b..4a0cacc977529 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -359,21 +359,28 @@ fn trace_macros_note(cx_expansions: &mut FxIndexMap>, sp: Span } pub(super) trait Tracker<'matcher> { - /// The contents of `ParseResult::Failure`. - type Failure; - - /// Arm failed to match. If the token is `token::Eof`, it indicates an unexpected - /// end of macro invocation. Otherwise, it indicates that no rules expected the given token. - /// The usize is the approximate position of the token in the input token stream. - fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure; + /// Provide context on the arm that's about to be matched. + fn prepare(&mut self, which_matcher: WhichMatcher); /// This is called before trying to match next MatcherLoc on the current token. 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, result: &NamedParseResult); + + /// The arm could not be matched successfully. + /// + /// If the parser is located at [`token::Eof`], it indicates an unexpected end of macro + /// invocation. Otherwise, the parser is located at a token in the middle of the input, and it + /// indicates that no rules in the arm expected the given token. + /// + /// The parser will return [`NamedParseResult::Failure`] after calling this. + fn failure(&mut self, parser: &Parser<'_>); + /// An ambiguity error occurred. + /// + /// The parser will return [`NamedParseResult::Ambiguity`] after calling this. fn ambiguity( &mut self, parser: &Parser<'_>, @@ -392,9 +399,7 @@ pub(super) trait Tracker<'matcher> { pub(super) struct NoopTracker; impl<'matcher> Tracker<'matcher> for NoopTracker { - type Failure = (); - - fn build_failure(_tok: Token, _position: u32, _msg: &'static str) -> Self::Failure {} + fn prepare(&mut self, _which_matcher: WhichMatcher) {} fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {} @@ -406,12 +411,9 @@ impl<'matcher> Tracker<'matcher> for NoopTracker { ) { } - fn after_arm( - &mut self, - _which_matcher: WhichMatcher, - _result: &NamedParseResult, - ) { - } + fn after_arm(&mut self, _result: &NamedParseResult) {} + + fn failure(&mut self, _parser: &Parser<'_>) {} fn description() -> &'static str { "none" @@ -635,9 +637,9 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // are not recorded. On the first `Success(..)`ful matcher, the spans are merged. let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); + track.prepare(WhichMatcher::FOR_FUNC); let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); - - track.after_arm(WhichMatcher::FOR_FUNC, &result); + track.after_arm(&result); match result { Success(named_matches) => { @@ -648,7 +650,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( return Ok((i, rule, named_matches)); } - Failure(_) => { + Failure => { trace!("Failed to match arm, trying the next one"); // Try the next arm. } @@ -693,12 +695,13 @@ 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()); + track.prepare(WhichMatcher::Args); let result = tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, track); - track.after_arm(WhichMatcher::Args, &result); + track.after_arm(&result); let mut named_matches = match result { Success(named_matches) => named_matches, - Failure(_) => { + Failure => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()); continue; } @@ -706,8 +709,9 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( ErrorReported(guar) => return Err(CanRetry::No(guar)), }; + track.prepare(WhichMatcher::Body); let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); - track.after_arm(WhichMatcher::Body, &result); + track.after_arm(&result); match result { Success(body_named_matches) => { @@ -716,7 +720,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( named_matches.extend(body_named_matches); return Ok((i, rule, named_matches)); } - Failure(_) => { + Failure => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) } Ambiguity => return Err(CanRetry::Yes), @@ -746,15 +750,16 @@ 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()); + track.prepare(WhichMatcher::FOR_DERIVE); let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); - track.after_arm(WhichMatcher::FOR_DERIVE, &result); + track.after_arm(&result); match result { Success(named_matches) => { psess.gated_spans.merge(gated_spans_snapshot); return Ok((i, rule, named_matches)); } - Failure(_) => { + Failure => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) } Ambiguity => return Err(CanRetry::Yes), diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 1b1d7ddb24b1b..744ae3f7f711e 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -109,7 +109,7 @@ impl<'a> Parser<'a> { _ => token.is_keyword(kw::If), }, NonterminalKind::TT | NonterminalKind::Item | NonterminalKind::Stmt => { - token.kind.close_delim().is_none() + token.kind != token::Eof && token.kind.close_delim().is_none() } } } diff --git a/tests/ui/macros/metavar-at-eof.rs b/tests/ui/macros/metavar-at-eof.rs new file mode 100644 index 0000000000000..04bed9eb010e4 --- /dev/null +++ b/tests/ui/macros/metavar-at-eof.rs @@ -0,0 +1,40 @@ +// Test the errors resulting from meta-variables hitting EOF. + +#![crate_type = "lib"] +#![feature(macro_guard_matcher)] + +macro_rules! unambig { + (item $x:item ) => {}; + (block $x:block ) => {}; + (stmt $x:stmt ) => {}; + (pat $x:pat ) => {}; + (pat_param $x:pat_param ) => {}; + (expr $x:expr ) => {}; + (expr_2021 $x:expr_2021 ) => {}; + (ty $x:ty ) => {}; + (ident $x:ident ) => {}; + (lifetime $x:lifetime ) => {}; + (literal $x:literal ) => {}; + (meta $x:meta ) => {}; + (path $x:path ) => {}; + (vis $x:vis ) => {}; + (guard $x:guard ) => {}; + (tt $x:tt ) => {}; +} + +unambig!(item /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(block /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(stmt /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(pat /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(pat_param /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(expr /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(expr_2021 /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(ty /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(ident /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(lifetime /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(literal /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(meta /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(path /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(vis /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(guard /* eof */); //~ ERROR unexpected end of macro invocation +unambig!(tt /* eof */); //~ ERROR unexpected end of macro invocation diff --git a/tests/ui/macros/metavar-at-eof.stderr b/tests/ui/macros/metavar-at-eof.stderr new file mode 100644 index 0000000000000..50eaa97454848 --- /dev/null +++ b/tests/ui/macros/metavar-at-eof.stderr @@ -0,0 +1,242 @@ +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:25:14 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(item /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:item` + --> $DIR/metavar-at-eof.rs:7:16 + | +LL | (item $x:item ) => {}; + | ^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:26:15 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(block /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:block` + --> $DIR/metavar-at-eof.rs:8:16 + | +LL | (block $x:block ) => {}; + | ^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:27:14 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(stmt /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:stmt` + --> $DIR/metavar-at-eof.rs:9:16 + | +LL | (stmt $x:stmt ) => {}; + | ^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:28:13 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(pat /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:pat` + --> $DIR/metavar-at-eof.rs:10:16 + | +LL | (pat $x:pat ) => {}; + | ^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:29:19 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(pat_param /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:pat_param` + --> $DIR/metavar-at-eof.rs:11:16 + | +LL | (pat_param $x:pat_param ) => {}; + | ^^^^^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:30:14 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(expr /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:expr` + --> $DIR/metavar-at-eof.rs:12:16 + | +LL | (expr $x:expr ) => {}; + | ^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:31:19 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(expr_2021 /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:expr_2021` + --> $DIR/metavar-at-eof.rs:13:16 + | +LL | (expr_2021 $x:expr_2021 ) => {}; + | ^^^^^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:32:12 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(ty /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:ty` + --> $DIR/metavar-at-eof.rs:14:16 + | +LL | (ty $x:ty ) => {}; + | ^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:33:15 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(ident /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:ident` + --> $DIR/metavar-at-eof.rs:15:16 + | +LL | (ident $x:ident ) => {}; + | ^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:34:18 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(lifetime /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:lifetime` + --> $DIR/metavar-at-eof.rs:16:16 + | +LL | (lifetime $x:lifetime ) => {}; + | ^^^^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:35:17 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(literal /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:literal` + --> $DIR/metavar-at-eof.rs:17:16 + | +LL | (literal $x:literal ) => {}; + | ^^^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:36:14 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(meta /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:meta` + --> $DIR/metavar-at-eof.rs:18:16 + | +LL | (meta $x:meta ) => {}; + | ^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:37:14 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(path /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:path` + --> $DIR/metavar-at-eof.rs:19:16 + | +LL | (path $x:path ) => {}; + | ^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:38:13 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(vis /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:vis` + --> $DIR/metavar-at-eof.rs:20:16 + | +LL | (vis $x:vis ) => {}; + | ^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:39:15 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(guard /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:guard` + --> $DIR/metavar-at-eof.rs:21:16 + | +LL | (guard $x:guard ) => {}; + | ^^^^^^^^ + +error: unexpected end of macro invocation + --> $DIR/metavar-at-eof.rs:40:12 + | +LL | macro_rules! unambig { + | -------------------- when calling this macro +... +LL | unambig!(tt /* eof */); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$x:tt` + --> $DIR/metavar-at-eof.rs:22:16 + | +LL | (tt $x:tt ) => {}; + | ^^^^^ + +error: aborting due to 16 previous errors +