Skip to content

Commit

Permalink
Rollup merge of #104638 - Nilstrieb:macro-diagnostics, r=compiler-errors
Browse files Browse the repository at this point in the history
Move macro_rules diagnostics to diagnostics module

This will make it easier to add more diagnostics in the future in a centralized place.
  • Loading branch information
Manishearth authored Nov 22, 2022
2 parents 952d385 + a1e5fea commit 9043dfd
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 237 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_expand/src/expand.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::base::*;
use crate::config::StripUnconfigured;
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::placeholders::{placeholder, PlaceholderExpander};

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/mbe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! why we call this module `mbe`. For external documentation, prefer the
//! official terminology: "declarative macros".
pub(crate) mod diagnostics;
pub(crate) mod macro_check;
pub(crate) mod macro_parser;
pub(crate) mod macro_rules;
Expand Down
257 changes: 257 additions & 0 deletions compiler/rustc_expand/src/mbe/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -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),),
}
}
Loading

0 comments on commit 9043dfd

Please sign in to comment.