Rollup merge of #104638 - Nilstrieb:macro-diagnostics, r=compiler-errors
Move macro_rules diagnostics to diagnostics module This will make it easier to add more diagnostics in the future in a centralized place.
This commit is contained in:
commit
9043dfd946
@ -1,7 +1,7 @@
|
|||||||
use crate::base::*;
|
use crate::base::*;
|
||||||
use crate::config::StripUnconfigured;
|
use crate::config::StripUnconfigured;
|
||||||
use crate::hygiene::SyntaxContext;
|
use crate::hygiene::SyntaxContext;
|
||||||
use crate::mbe::macro_rules::annotate_err_with_kind;
|
use crate::mbe::diagnostics::annotate_err_with_kind;
|
||||||
use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
|
use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
|
||||||
use crate::placeholders::{placeholder, PlaceholderExpander};
|
use crate::placeholders::{placeholder, PlaceholderExpander};
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
//! why we call this module `mbe`. For external documentation, prefer the
|
//! why we call this module `mbe`. For external documentation, prefer the
|
||||||
//! official terminology: "declarative macros".
|
//! official terminology: "declarative macros".
|
||||||
|
|
||||||
|
pub(crate) mod diagnostics;
|
||||||
pub(crate) mod macro_check;
|
pub(crate) mod macro_check;
|
||||||
pub(crate) mod macro_parser;
|
pub(crate) mod macro_parser;
|
||||||
pub(crate) mod macro_rules;
|
pub(crate) mod macro_rules;
|
||||||
|
257
compiler/rustc_expand/src/mbe/diagnostics.rs
Normal file
257
compiler/rustc_expand/src/mbe/diagnostics.rs
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::base::{DummyResult, ExtCtxt, MacResult};
|
||||||
|
use crate::expand::{parse_ast_fragment, AstFragmentKind};
|
||||||
|
use crate::mbe::{
|
||||||
|
macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser},
|
||||||
|
macro_rules::{try_match_macro, Tracker},
|
||||||
|
};
|
||||||
|
use rustc_ast::token::{self, Token};
|
||||||
|
use rustc_ast::tokenstream::TokenStream;
|
||||||
|
use rustc_ast_pretty::pprust;
|
||||||
|
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage};
|
||||||
|
use rustc_parse::parser::{Parser, Recovery};
|
||||||
|
use rustc_span::source_map::SourceMap;
|
||||||
|
use rustc_span::symbol::Ident;
|
||||||
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
use super::macro_rules::{parser_from_cx, NoopTracker};
|
||||||
|
|
||||||
|
pub(super) fn failed_to_match_macro<'cx>(
|
||||||
|
cx: &'cx mut ExtCtxt<'_>,
|
||||||
|
sp: Span,
|
||||||
|
def_span: Span,
|
||||||
|
name: Ident,
|
||||||
|
arg: TokenStream,
|
||||||
|
lhses: &[Vec<MatcherLoc>],
|
||||||
|
) -> Box<dyn MacResult + 'cx> {
|
||||||
|
let sess = &cx.sess.parse_sess;
|
||||||
|
|
||||||
|
// An error occurred, try the expansion again, tracking the expansion closely for better diagnostics.
|
||||||
|
let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
|
||||||
|
|
||||||
|
let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);
|
||||||
|
|
||||||
|
if try_success_result.is_ok() {
|
||||||
|
// Nonterminal parser recovery might turn failed matches into successful ones,
|
||||||
|
// but for that it must have emitted an error already
|
||||||
|
tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(result) = tracker.result {
|
||||||
|
// An irrecoverable error occurred and has been emitted.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((token, label, remaining_matcher)) = tracker.best_failure else {
|
||||||
|
return DummyResult::any(sp);
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = token.span.substitute_dummy(sp);
|
||||||
|
|
||||||
|
let mut err = cx.struct_span_err(span, &parse_failure_msg(&token));
|
||||||
|
err.span_label(span, label);
|
||||||
|
if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
|
||||||
|
err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed);
|
||||||
|
let mut tt_parser = TtParser::new(name);
|
||||||
|
|
||||||
|
if let Success(_) =
|
||||||
|
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
|
||||||
|
{
|
||||||
|
if comma_span.is_dummy() {
|
||||||
|
err.note("you might be missing a comma");
|
||||||
|
} else {
|
||||||
|
err.span_suggestion_short(
|
||||||
|
comma_span,
|
||||||
|
"missing comma here",
|
||||||
|
", ",
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err.emit();
|
||||||
|
cx.trace_macros_diag();
|
||||||
|
DummyResult::any(sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The tracker used for the slow error path that collects useful info for diagnostics.
|
||||||
|
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, MatcherLoc)>,
|
||||||
|
root_span: Span,
|
||||||
|
result: Option<Box<dyn MacResult + 'cx>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
match result {
|
||||||
|
Success(_) => {
|
||||||
|
// Nonterminal parser recovery might turn failed matches into successful ones,
|
||||||
|
// but for that it must have emitted an error already
|
||||||
|
self.cx.sess.delay_span_bug(
|
||||||
|
self.root_span,
|
||||||
|
"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,
|
||||||
|
self.remaining_matcher
|
||||||
|
.expect("must have collected matcher already")
|
||||||
|
.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error(err_sp, msg) => {
|
||||||
|
let span = err_sp.substitute_dummy(self.root_span);
|
||||||
|
self.cx.struct_span_err(span, msg).emit();
|
||||||
|
self.result = Some(DummyResult::any(span));
|
||||||
|
}
|
||||||
|
ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description() -> &'static str {
|
||||||
|
"detailed"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recovery() -> Recovery {
|
||||||
|
Recovery::Allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
|
||||||
|
fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
|
||||||
|
Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn emit_frag_parse_err(
|
||||||
|
mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
|
||||||
|
parser: &Parser<'_>,
|
||||||
|
orig_parser: &mut Parser<'_>,
|
||||||
|
site_span: Span,
|
||||||
|
arm_span: Span,
|
||||||
|
kind: AstFragmentKind,
|
||||||
|
) {
|
||||||
|
// FIXME(davidtwco): avoid depending on the error message text
|
||||||
|
if parser.token == token::Eof
|
||||||
|
&& let DiagnosticMessage::Str(message) = &e.message[0].0
|
||||||
|
&& message.ends_with(", found `<eof>`")
|
||||||
|
{
|
||||||
|
let msg = &e.message[0];
|
||||||
|
e.message[0] = (
|
||||||
|
DiagnosticMessage::Str(format!(
|
||||||
|
"macro expansion ends with an incomplete expression: {}",
|
||||||
|
message.replace(", found `<eof>`", ""),
|
||||||
|
)),
|
||||||
|
msg.1,
|
||||||
|
);
|
||||||
|
if !e.span.is_dummy() {
|
||||||
|
// early end of macro arm (#52866)
|
||||||
|
e.replace_span_with(parser.token.span.shrink_to_hi());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.span.is_dummy() {
|
||||||
|
// Get around lack of span in error (#30128)
|
||||||
|
e.replace_span_with(site_span);
|
||||||
|
if !parser.sess.source_map().is_imported(arm_span) {
|
||||||
|
e.span_label(arm_span, "in this macro arm");
|
||||||
|
}
|
||||||
|
} else if parser.sess.source_map().is_imported(parser.token.span) {
|
||||||
|
e.span_label(site_span, "in this macro invocation");
|
||||||
|
}
|
||||||
|
match kind {
|
||||||
|
// Try a statement if an expression is wanted but failed and suggest adding `;` to call.
|
||||||
|
AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
|
||||||
|
Err(err) => err.cancel(),
|
||||||
|
Ok(_) => {
|
||||||
|
e.note(
|
||||||
|
"the macro call doesn't expand to an expression, but it can expand to a statement",
|
||||||
|
);
|
||||||
|
e.span_suggestion_verbose(
|
||||||
|
site_span.shrink_to_hi(),
|
||||||
|
"add `;` to interpret the expansion as a statement",
|
||||||
|
";",
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => annotate_err_with_kind(&mut e, kind, site_span),
|
||||||
|
};
|
||||||
|
e.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) {
|
||||||
|
match kind {
|
||||||
|
AstFragmentKind::Ty => {
|
||||||
|
err.span_label(span, "this macro call doesn't expand to a type");
|
||||||
|
}
|
||||||
|
AstFragmentKind::Pat => {
|
||||||
|
err.span_label(span, "this macro call doesn't expand to a pattern");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subdiagnostic)]
|
||||||
|
enum ExplainDocComment {
|
||||||
|
#[label(expand_explain_doc_comment_inner)]
|
||||||
|
Inner {
|
||||||
|
#[primary_span]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
#[label(expand_explain_doc_comment_outer)]
|
||||||
|
Outer {
|
||||||
|
#[primary_span]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) {
|
||||||
|
if let Ok(src) = sm.span_to_snippet(span) {
|
||||||
|
if src.starts_with("///") || src.starts_with("/**") {
|
||||||
|
err.subdiagnostic(ExplainDocComment::Outer { span });
|
||||||
|
} else if src.starts_with("//!") || src.starts_with("/*!") {
|
||||||
|
err.subdiagnostic(ExplainDocComment::Inner { span });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
|
||||||
|
/// other tokens, this is "unexpected token...".
|
||||||
|
pub(super) fn parse_failure_msg(tok: &Token) -> String {
|
||||||
|
match tok.kind {
|
||||||
|
token::Eof => "unexpected end of macro invocation".to_string(),
|
||||||
|
_ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),),
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ use crate::base::{DummyResult, ExtCtxt, MacResult, TTMacroExpander};
|
|||||||
use crate::base::{SyntaxExtension, SyntaxExtensionKind};
|
use crate::base::{SyntaxExtension, SyntaxExtensionKind};
|
||||||
use crate::expand::{ensure_complete_parse, parse_ast_fragment, AstFragment, AstFragmentKind};
|
use crate::expand::{ensure_complete_parse, parse_ast_fragment, AstFragment, AstFragmentKind};
|
||||||
use crate::mbe;
|
use crate::mbe;
|
||||||
|
use crate::mbe::diagnostics::{annotate_doc_comment, parse_failure_msg};
|
||||||
use crate::mbe::macro_check;
|
use crate::mbe::macro_check;
|
||||||
use crate::mbe::macro_parser::{Error, ErrorReported, Failure, Success, TtParser};
|
use crate::mbe::macro_parser::{Error, ErrorReported, Failure, Success, TtParser};
|
||||||
use crate::mbe::macro_parser::{MatchedSeq, MatchedTokenTree, MatcherLoc};
|
use crate::mbe::macro_parser::{MatchedSeq, MatchedTokenTree, MatcherLoc};
|
||||||
@ -14,9 +15,7 @@ use rustc_ast::{NodeId, DUMMY_NODE_ID};
|
|||||||
use rustc_ast_pretty::pprust;
|
use rustc_ast_pretty::pprust;
|
||||||
use rustc_attr::{self as attr, TransparencyError};
|
use rustc_attr::{self as attr, TransparencyError};
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||||
use rustc_errors::{
|
use rustc_errors::{Applicability, ErrorGuaranteed};
|
||||||
Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed,
|
|
||||||
};
|
|
||||||
use rustc_feature::Features;
|
use rustc_feature::Features;
|
||||||
use rustc_lint_defs::builtin::{
|
use rustc_lint_defs::builtin::{
|
||||||
RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
|
RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
|
||||||
@ -27,7 +26,6 @@ use rustc_session::parse::ParseSess;
|
|||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::hygiene::Transparency;
|
use rustc_span::hygiene::Transparency;
|
||||||
use rustc_span::source_map::SourceMap;
|
|
||||||
use rustc_span::symbol::{kw, sym, Ident, MacroRulesNormalizedIdent};
|
use rustc_span::symbol::{kw, sym, Ident, MacroRulesNormalizedIdent};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
||||||
@ -35,6 +33,7 @@ use std::borrow::Cow;
|
|||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::{mem, slice};
|
use std::{mem, slice};
|
||||||
|
|
||||||
|
use super::diagnostics;
|
||||||
use super::macro_parser::{NamedMatches, NamedParseResult};
|
use super::macro_parser::{NamedMatches, NamedParseResult};
|
||||||
|
|
||||||
pub(crate) struct ParserAnyMacro<'a> {
|
pub(crate) struct ParserAnyMacro<'a> {
|
||||||
@ -51,74 +50,6 @@ pub(crate) struct ParserAnyMacro<'a> {
|
|||||||
is_local: bool,
|
is_local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) {
|
|
||||||
match kind {
|
|
||||||
AstFragmentKind::Ty => {
|
|
||||||
err.span_label(span, "this macro call doesn't expand to a type");
|
|
||||||
}
|
|
||||||
AstFragmentKind::Pat => {
|
|
||||||
err.span_label(span, "this macro call doesn't expand to a pattern");
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_frag_parse_err(
|
|
||||||
mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
|
|
||||||
parser: &Parser<'_>,
|
|
||||||
orig_parser: &mut Parser<'_>,
|
|
||||||
site_span: Span,
|
|
||||||
arm_span: Span,
|
|
||||||
kind: AstFragmentKind,
|
|
||||||
) {
|
|
||||||
// FIXME(davidtwco): avoid depending on the error message text
|
|
||||||
if parser.token == token::Eof
|
|
||||||
&& let DiagnosticMessage::Str(message) = &e.message[0].0
|
|
||||||
&& message.ends_with(", found `<eof>`")
|
|
||||||
{
|
|
||||||
let msg = &e.message[0];
|
|
||||||
e.message[0] = (
|
|
||||||
DiagnosticMessage::Str(format!(
|
|
||||||
"macro expansion ends with an incomplete expression: {}",
|
|
||||||
message.replace(", found `<eof>`", ""),
|
|
||||||
)),
|
|
||||||
msg.1,
|
|
||||||
);
|
|
||||||
if !e.span.is_dummy() {
|
|
||||||
// early end of macro arm (#52866)
|
|
||||||
e.replace_span_with(parser.token.span.shrink_to_hi());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if e.span.is_dummy() {
|
|
||||||
// Get around lack of span in error (#30128)
|
|
||||||
e.replace_span_with(site_span);
|
|
||||||
if !parser.sess.source_map().is_imported(arm_span) {
|
|
||||||
e.span_label(arm_span, "in this macro arm");
|
|
||||||
}
|
|
||||||
} else if parser.sess.source_map().is_imported(parser.token.span) {
|
|
||||||
e.span_label(site_span, "in this macro invocation");
|
|
||||||
}
|
|
||||||
match kind {
|
|
||||||
// Try a statement if an expression is wanted but failed and suggest adding `;` to call.
|
|
||||||
AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
|
|
||||||
Err(err) => err.cancel(),
|
|
||||||
Ok(_) => {
|
|
||||||
e.note(
|
|
||||||
"the macro call doesn't expand to an expression, but it can expand to a statement",
|
|
||||||
);
|
|
||||||
e.span_suggestion_verbose(
|
|
||||||
site_span.shrink_to_hi(),
|
|
||||||
"add `;` to interpret the expansion as a statement",
|
|
||||||
";",
|
|
||||||
Applicability::MaybeIncorrect,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => annotate_err_with_kind(&mut e, kind, site_span),
|
|
||||||
};
|
|
||||||
e.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ParserAnyMacro<'a> {
|
impl<'a> ParserAnyMacro<'a> {
|
||||||
pub(crate) fn make(mut self: Box<ParserAnyMacro<'a>>, kind: AstFragmentKind) -> AstFragment {
|
pub(crate) fn make(mut self: Box<ParserAnyMacro<'a>>, kind: AstFragmentKind) -> AstFragment {
|
||||||
let ParserAnyMacro {
|
let ParserAnyMacro {
|
||||||
@ -134,7 +65,7 @@ impl<'a> ParserAnyMacro<'a> {
|
|||||||
let fragment = match parse_ast_fragment(parser, kind) {
|
let fragment = match parse_ast_fragment(parser, kind) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
emit_frag_parse_err(err, parser, snapshot, site_span, arm_span, kind);
|
diagnostics::emit_frag_parse_err(err, parser, snapshot, site_span, arm_span, kind);
|
||||||
return kind.dummy(site_span);
|
return kind.dummy(site_span);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -224,7 +155,7 @@ pub(super) trait Tracker<'matcher> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
|
/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
|
||||||
struct NoopTracker;
|
pub(super) struct NoopTracker;
|
||||||
|
|
||||||
impl<'matcher> Tracker<'matcher> for NoopTracker {
|
impl<'matcher> Tracker<'matcher> for NoopTracker {
|
||||||
fn before_match_loc(&mut self, _: &TtParser, _: &'matcher MatcherLoc) {}
|
fn before_match_loc(&mut self, _: &TtParser, _: &'matcher MatcherLoc) {}
|
||||||
@ -331,135 +262,10 @@ fn expand_macro<'cx>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An error occurred, try the expansion again, tracking the expansion closely for better diagnostics.
|
diagnostics::failed_to_match_macro(cx, sp, def_span, name, arg, lhses)
|
||||||
let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
|
|
||||||
|
|
||||||
let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);
|
|
||||||
|
|
||||||
if try_success_result.is_ok() {
|
|
||||||
// Nonterminal parser recovery might turn failed matches into successful ones,
|
|
||||||
// but for that it must have emitted an error already
|
|
||||||
tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = tracker.result {
|
pub(super) enum CanRetry {
|
||||||
// An irrecoverable error occurred and has been emitted.
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some((token, label, remaining_matcher)) = tracker.best_failure else {
|
|
||||||
return DummyResult::any(sp);
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = token.span.substitute_dummy(sp);
|
|
||||||
|
|
||||||
let mut err = cx.struct_span_err(span, &parse_failure_msg(&token));
|
|
||||||
err.span_label(span, label);
|
|
||||||
if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
|
|
||||||
err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed);
|
|
||||||
let mut tt_parser = TtParser::new(name);
|
|
||||||
|
|
||||||
if let Success(_) =
|
|
||||||
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
|
|
||||||
{
|
|
||||||
if comma_span.is_dummy() {
|
|
||||||
err.note("you might be missing a comma");
|
|
||||||
} else {
|
|
||||||
err.span_suggestion_short(
|
|
||||||
comma_span,
|
|
||||||
"missing comma here",
|
|
||||||
", ",
|
|
||||||
Applicability::MachineApplicable,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err.emit();
|
|
||||||
cx.trace_macros_diag();
|
|
||||||
DummyResult::any(sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The tracker used for the slow error path that collects useful info for diagnostics.
|
|
||||||
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, MatcherLoc)>,
|
|
||||||
root_span: Span,
|
|
||||||
result: Option<Box<dyn MacResult + 'cx>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
match result {
|
|
||||||
Success(_) => {
|
|
||||||
// Nonterminal parser recovery might turn failed matches into successful ones,
|
|
||||||
// but for that it must have emitted an error already
|
|
||||||
self.cx.sess.delay_span_bug(
|
|
||||||
self.root_span,
|
|
||||||
"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,
|
|
||||||
self.remaining_matcher
|
|
||||||
.expect("must have collected matcher already")
|
|
||||||
.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Error(err_sp, msg) => {
|
|
||||||
let span = err_sp.substitute_dummy(self.root_span);
|
|
||||||
self.cx.struct_span_err(span, msg).emit();
|
|
||||||
self.result = Some(DummyResult::any(span));
|
|
||||||
}
|
|
||||||
ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description() -> &'static str {
|
|
||||||
"detailed"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recovery() -> Recovery {
|
|
||||||
Recovery::Allowed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
|
|
||||||
fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
|
|
||||||
Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CanRetry {
|
|
||||||
Yes,
|
Yes,
|
||||||
/// We are not allowed to retry macro expansion as a fatal error has been emitted already.
|
/// We are not allowed to retry macro expansion as a fatal error has been emitted already.
|
||||||
No(ErrorGuaranteed),
|
No(ErrorGuaranteed),
|
||||||
@ -469,7 +275,7 @@ enum CanRetry {
|
|||||||
/// and nothing if it failed. On failure, it's the callers job to use `track` accordingly to record all errors
|
/// and nothing if it failed. On failure, it's the callers job to use `track` accordingly to record all errors
|
||||||
/// correctly.
|
/// correctly.
|
||||||
#[instrument(level = "debug", skip(sess, arg, lhses, track), fields(tracking = %T::description()))]
|
#[instrument(level = "debug", skip(sess, arg, lhses, track), fields(tracking = %T::description()))]
|
||||||
fn try_match_macro<'matcher, T: Tracker<'matcher>>(
|
pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
|
||||||
sess: &ParseSess,
|
sess: &ParseSess,
|
||||||
name: Ident,
|
name: Ident,
|
||||||
arg: &TokenStream,
|
arg: &TokenStream,
|
||||||
@ -769,30 +575,6 @@ pub fn compile_declarative_macro(
|
|||||||
(mk_syn_ext(expander), rule_spans)
|
(mk_syn_ext(expander), rule_spans)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subdiagnostic)]
|
|
||||||
enum ExplainDocComment {
|
|
||||||
#[label(expand_explain_doc_comment_inner)]
|
|
||||||
Inner {
|
|
||||||
#[primary_span]
|
|
||||||
span: Span,
|
|
||||||
},
|
|
||||||
#[label(expand_explain_doc_comment_outer)]
|
|
||||||
Outer {
|
|
||||||
#[primary_span]
|
|
||||||
span: Span,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) {
|
|
||||||
if let Ok(src) = sm.span_to_snippet(span) {
|
|
||||||
if src.starts_with("///") || src.starts_with("/**") {
|
|
||||||
err.subdiagnostic(ExplainDocComment::Outer { span });
|
|
||||||
} else if src.starts_with("//!") || src.starts_with("/*!") {
|
|
||||||
err.subdiagnostic(ExplainDocComment::Inner { span });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_lhs_nt_follows(sess: &ParseSess, def: &ast::Item, lhs: &mbe::TokenTree) -> bool {
|
fn check_lhs_nt_follows(sess: &ParseSess, def: &ast::Item, lhs: &mbe::TokenTree) -> bool {
|
||||||
// lhs is going to be like TokenTree::Delimited(...), where the
|
// lhs is going to be like TokenTree::Delimited(...), where the
|
||||||
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
|
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
|
||||||
@ -1577,15 +1359,6 @@ fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parser_from_cx(sess: &ParseSess, tts: TokenStream, recovery: Recovery) -> Parser<'_> {
|
pub(super) fn parser_from_cx(sess: &ParseSess, tts: TokenStream, recovery: Recovery) -> Parser<'_> {
|
||||||
Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS).recovery(recovery)
|
Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS).recovery(recovery)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
|
|
||||||
/// other tokens, this is "unexpected token...".
|
|
||||||
fn parse_failure_msg(tok: &Token) -> String {
|
|
||||||
match tok.kind {
|
|
||||||
token::Eof => "unexpected end of macro invocation".to_string(),
|
|
||||||
_ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user