Rollup merge of #103439 - Nilstrieb:help-me-with-my-macro, r=estebank
Show note where the macro failed to match When feeding the wrong tokens, it used to fail with a very generic error that wasn't very helpful. This change tries to help by noting where specifically the matching went wrong. ```rust macro_rules! uwu { (a a a b) => {}; } uwu! { a a a c } ``` ```diff error: no rules expected the token `c` --> macros.rs:5:14 | 1 | macro_rules! uwu { | ---------------- when calling this macro ... 4 | uwu! { a a a c } | ^ no rules expected this token in macro call | +note: while trying to match `b` + --> macros.rs:2:12 + | +2 | (a a a b) => {}; + | ^ ```
This commit is contained in:
commit
1a6ed3050f
25 changed files with 355 additions and 11 deletions
|
@ -76,6 +76,7 @@ pub(crate) use ParseResult::*;
|
|||
use crate::mbe::{macro_rules::Tracker, KleeneOp, TokenTree};
|
||||
|
||||
use rustc_ast::token::{self, DocComment, Nonterminal, NonterminalKind, Token};
|
||||
use rustc_ast_pretty::pprust;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
|
@ -86,6 +87,7 @@ use rustc_span::symbol::MacroRulesNormalizedIdent;
|
|||
use rustc_span::Span;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::fmt::Display;
|
||||
|
||||
/// A unit within a matcher that a `MatcherPos` can refer to. Similar to (and derived from)
|
||||
/// `mbe::TokenTree`, but designed specifically for fast and easy traversal during matching.
|
||||
|
@ -96,7 +98,7 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
|
|||
///
|
||||
/// This means a matcher can be represented by `&[MatcherLoc]`, and traversal mostly involves
|
||||
/// simply incrementing the current matcher position index by one.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) enum MatcherLoc {
|
||||
Token {
|
||||
token: Token,
|
||||
|
@ -129,6 +131,46 @@ pub(crate) enum MatcherLoc {
|
|||
Eof,
|
||||
}
|
||||
|
||||
impl MatcherLoc {
|
||||
pub(super) fn span(&self) -> Option<Span> {
|
||||
match self {
|
||||
MatcherLoc::Token { token } => Some(token.span),
|
||||
MatcherLoc::Delimited => None,
|
||||
MatcherLoc::Sequence { .. } => None,
|
||||
MatcherLoc::SequenceKleeneOpNoSep { .. } => None,
|
||||
MatcherLoc::SequenceSep { .. } => None,
|
||||
MatcherLoc::SequenceKleeneOpAfterSep { .. } => None,
|
||||
MatcherLoc::MetaVarDecl { span, .. } => Some(*span),
|
||||
MatcherLoc::Eof => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MatcherLoc {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MatcherLoc::Token { token } | MatcherLoc::SequenceSep { separator: token } => {
|
||||
write!(f, "`{}`", pprust::token_to_string(token))
|
||||
}
|
||||
MatcherLoc::MetaVarDecl { bind, kind, .. } => {
|
||||
write!(f, "meta-variable `${bind}")?;
|
||||
if let Some(kind) = kind {
|
||||
write!(f, ":{}", kind)?;
|
||||
}
|
||||
write!(f, "`")?;
|
||||
Ok(())
|
||||
}
|
||||
MatcherLoc::Eof => f.write_str("end of macro"),
|
||||
|
||||
// These are not printed in the diagnostic
|
||||
MatcherLoc::Delimited => f.write_str("delimiter"),
|
||||
MatcherLoc::Sequence { .. } => f.write_str("sequence start"),
|
||||
MatcherLoc::SequenceKleeneOpNoSep { .. } => f.write_str("sequence end"),
|
||||
MatcherLoc::SequenceKleeneOpAfterSep { .. } => f.write_str("sequence end"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compute_locs(matcher: &[TokenTree]) -> Vec<MatcherLoc> {
|
||||
fn inner(
|
||||
tts: &[TokenTree],
|
||||
|
@ -398,6 +440,10 @@ impl TtParser {
|
|||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
///
|
||||
|
|
|
@ -337,7 +337,7 @@ fn expand_macro<'cx>(
|
|||
return result;
|
||||
}
|
||||
|
||||
let Some((token, label)) = tracker.best_failure else {
|
||||
let Some((token, label, remaining_matcher)) = tracker.best_failure else {
|
||||
return tracker.result.expect("must have encountered Error or ErrorReported");
|
||||
};
|
||||
|
||||
|
@ -351,6 +351,12 @@ fn expand_macro<'cx>(
|
|||
|
||||
annotate_doc_comment(&mut err, sess.source_map(), span);
|
||||
|
||||
if let Some(span) = remaining_matcher.span() {
|
||||
err.span_note(span, format!("while trying to match {remaining_matcher}"));
|
||||
} else {
|
||||
err.note(format!("while trying to match {remaining_matcher}"));
|
||||
}
|
||||
|
||||
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
|
||||
if let Some((arg, comma_span)) = arg.add_comma() {
|
||||
for lhs in lhses {
|
||||
|
@ -379,17 +385,22 @@ fn expand_macro<'cx>(
|
|||
}
|
||||
|
||||
/// The tracker used for the slow error path that collects useful info for diagnostics.
|
||||
struct CollectTrackerAndEmitter<'a, 'cx> {
|
||||
struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
|
||||
cx: &'a mut ExtCtxt<'cx>,
|
||||
remaining_matcher: Option<&'matcher MatcherLoc>,
|
||||
/// Which arm's failure should we report? (the one furthest along)
|
||||
best_failure: Option<(Token, &'static str)>,
|
||||
best_failure: Option<(Token, &'static str, MatcherLoc)>,
|
||||
root_span: Span,
|
||||
result: Option<Box<dyn MacResult + 'cx>>,
|
||||
}
|
||||
|
||||
impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx> {
|
||||
fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {
|
||||
// Empty for now.
|
||||
impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
|
||||
fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
|
||||
if self.remaining_matcher.is_none()
|
||||
|| (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
|
||||
{
|
||||
self.remaining_matcher = Some(matcher);
|
||||
}
|
||||
}
|
||||
|
||||
fn after_arm(&mut self, result: &NamedParseResult) {
|
||||
|
@ -398,8 +409,16 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx>
|
|||
unreachable!("should not collect detailed info for successful macro match");
|
||||
}
|
||||
Failure(token, msg) => match self.best_failure {
|
||||
Some((ref best_token, _)) if best_token.span.lo() >= token.span.lo() => {}
|
||||
_ => self.best_failure = Some((token.clone(), msg)),
|
||||
Some((ref best_token, _, _)) if best_token.span.lo() >= token.span.lo() => {}
|
||||
_ => {
|
||||
self.best_failure = Some((
|
||||
token.clone(),
|
||||
msg,
|
||||
self.remaining_matcher
|
||||
.expect("must have collected matcher already")
|
||||
.clone(),
|
||||
))
|
||||
}
|
||||
},
|
||||
Error(err_sp, msg) => {
|
||||
let span = err_sp.substitute_dummy(self.root_span);
|
||||
|
@ -415,9 +434,9 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx>
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx> {
|
||||
impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
|
||||
fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
|
||||
Self { cx, best_failure: None, root_span, result: None }
|
||||
Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue