2022-11-20 13:06:44 +01:00
|
|
|
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},
|
|
|
|
};
|
2023-03-03 20:37:51 +00:00
|
|
|
use rustc_ast::token::{self, Token, TokenKind};
|
2022-11-20 13:06:44 +01:00
|
|
|
use rustc_ast::tokenstream::TokenStream;
|
|
|
|
use rustc_ast_pretty::pprust;
|
2024-02-14 14:17:27 +00:00
|
|
|
use rustc_errors::{Applicability, DiagCtxt, Diagnostic, DiagnosticBuilder, DiagnosticMessage};
|
2022-11-20 13:06:44 +01:00
|
|
|
use rustc_parse::parser::{Parser, Recovery};
|
|
|
|
use rustc_span::source_map::SourceMap;
|
|
|
|
use rustc_span::symbol::Ident;
|
|
|
|
use rustc_span::Span;
|
2023-03-03 20:37:51 +00:00
|
|
|
use std::borrow::Cow;
|
2022-11-20 13:06:44 +01:00
|
|
|
|
|
|
|
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
|
2023-12-18 22:21:37 +11:00
|
|
|
tracker
|
|
|
|
.cx
|
|
|
|
.dcx()
|
|
|
|
.span_delayed_bug(sp, "Macro matching returned a success on the second try");
|
2022-11-20 13:06:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(result) = tracker.result {
|
|
|
|
// An irrecoverable error occurred and has been emitted.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-07-12 21:49:27 -04:00
|
|
|
let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
|
|
|
|
else {
|
2022-11-20 13:06:44 +01:00
|
|
|
return DummyResult::any(sp);
|
|
|
|
};
|
|
|
|
|
|
|
|
let span = token.span.substitute_dummy(sp);
|
|
|
|
|
2023-12-18 20:54:03 +11:00
|
|
|
let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token));
|
2022-11-20 13:06:44 +01:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2024-02-14 14:17:27 +00:00
|
|
|
annotate_doc_comment(cx.sess.dcx(), &mut err, sess.source_map(), span);
|
2022-11-20 13:06:44 +01:00
|
|
|
|
|
|
|
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}"));
|
|
|
|
}
|
|
|
|
|
2023-03-03 20:37:51 +00:00
|
|
|
if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
|
|
|
|
&& (matches!(expected_token.kind, TokenKind::Interpolated(_))
|
|
|
|
|| matches!(token.kind, TokenKind::Interpolated(_)))
|
|
|
|
{
|
2023-07-31 14:55:47 +00:00
|
|
|
if let TokenKind::Interpolated(node) = &expected_token.kind {
|
|
|
|
err.span_label(node.1, "");
|
|
|
|
}
|
|
|
|
if let TokenKind::Interpolated(node) = &token.kind {
|
|
|
|
err.span_label(node.1, "");
|
|
|
|
}
|
2023-04-12 11:18:50 +02:00
|
|
|
err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
|
2023-04-12 15:43:50 +02:00
|
|
|
err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
|
|
|
|
|
|
|
|
if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
|
|
|
|
err.help("try using `:tt` instead in the macro definition");
|
|
|
|
}
|
2023-03-03 20:37:51 +00:00
|
|
|
}
|
|
|
|
|
2022-11-20 13:06:44 +01:00
|
|
|
// 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)
|
2022-12-11 21:46:30 +01:00
|
|
|
best_failure: Option<BestFailure>,
|
2022-11-20 13:06:44 +01:00
|
|
|
root_span: Span,
|
|
|
|
result: Option<Box<dyn MacResult + 'cx>>,
|
|
|
|
}
|
|
|
|
|
2022-12-11 21:46:30 +01:00
|
|
|
struct BestFailure {
|
|
|
|
token: Token,
|
|
|
|
position_in_tokenstream: usize,
|
|
|
|
msg: &'static str,
|
|
|
|
remaining_matcher: MatcherLoc,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BestFailure {
|
|
|
|
fn is_better_position(&self, position: usize) -> bool {
|
|
|
|
position > self.position_in_tokenstream
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-20 13:06:44 +01:00
|
|
|
impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
|
2023-01-03 19:28:28 +01:00
|
|
|
type Failure = (Token, usize, &'static str);
|
|
|
|
|
|
|
|
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
|
|
|
|
(tok, position, msg)
|
|
|
|
}
|
|
|
|
|
2022-11-20 13:06:44 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 19:28:28 +01:00
|
|
|
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
|
2022-11-20 13:06:44 +01:00
|
|
|
match result {
|
|
|
|
Success(_) => {
|
|
|
|
// Nonterminal parser recovery might turn failed matches into successful ones,
|
|
|
|
// but for that it must have emitted an error already
|
2023-12-18 22:21:37 +11:00
|
|
|
self.cx.dcx().span_delayed_bug(
|
2022-11-20 13:06:44 +01:00
|
|
|
self.root_span,
|
|
|
|
"should not collect detailed info for successful macro match",
|
|
|
|
);
|
|
|
|
}
|
2023-01-03 19:28:28 +01:00
|
|
|
Failure((token, approx_position, msg)) => {
|
2022-12-11 21:46:30 +01:00
|
|
|
debug!(?token, ?msg, "a new failure of an arm");
|
|
|
|
|
|
|
|
if self
|
|
|
|
.best_failure
|
|
|
|
.as_ref()
|
|
|
|
.map_or(true, |failure| failure.is_better_position(*approx_position))
|
|
|
|
{
|
|
|
|
self.best_failure = Some(BestFailure {
|
|
|
|
token: token.clone(),
|
|
|
|
position_in_tokenstream: *approx_position,
|
2022-11-20 13:06:44 +01:00
|
|
|
msg,
|
2022-12-11 21:46:30 +01:00
|
|
|
remaining_matcher: self
|
|
|
|
.remaining_matcher
|
2022-11-20 13:06:44 +01:00
|
|
|
.expect("must have collected matcher already")
|
|
|
|
.clone(),
|
2022-12-11 21:46:30 +01:00
|
|
|
})
|
2022-11-20 13:06:44 +01:00
|
|
|
}
|
2022-12-11 21:46:30 +01:00
|
|
|
}
|
2022-11-20 13:06:44 +01:00
|
|
|
Error(err_sp, msg) => {
|
|
|
|
let span = err_sp.substitute_dummy(self.root_span);
|
2024-01-04 10:38:10 +11:00
|
|
|
self.cx.dcx().span_err(span, msg.clone());
|
2022-11-20 13:06:44 +01:00
|
|
|
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 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 19:28:28 +01:00
|
|
|
/// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
|
|
|
|
pub struct FailureForwarder;
|
|
|
|
|
|
|
|
impl<'matcher> Tracker<'matcher> for FailureForwarder {
|
|
|
|
type Failure = (Token, usize, &'static str);
|
|
|
|
|
|
|
|
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
|
|
|
|
(tok, position, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn description() -> &'static str {
|
|
|
|
"failure-forwarder"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-20 13:06:44 +01:00
|
|
|
pub(super) fn emit_frag_parse_err(
|
2023-12-19 15:26:24 +11:00
|
|
|
mut e: DiagnosticBuilder<'_>,
|
2022-11-20 13:06:44 +01:00
|
|
|
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
|
2023-12-20 17:12:17 +11:00
|
|
|
&& let DiagnosticMessage::Str(message) = &e.messages[0].0
|
2022-11-20 13:06:44 +01:00
|
|
|
&& message.ends_with(", found `<eof>`")
|
|
|
|
{
|
2023-12-20 17:12:17 +11:00
|
|
|
let msg = &e.messages[0];
|
|
|
|
e.messages[0] = (
|
Use `Cow` in `{D,Subd}iagnosticMessage`.
Each of `{D,Subd}iagnosticMessage::{Str,Eager}` has a comment:
```
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
```
This commit answers that question in the affirmative. It's not the most
compelling change ever, but it might be worth merging.
This requires changing the `impl<'a> From<&'a str>` impls to `impl
From<&'static str>`, which involves a bunch of knock-on changes that
require/result in call sites being a little more precise about exactly
what kind of string they use to create errors, and not just `&str`. This
will result in fewer unnecessary allocations, though this will not have
any notable perf effects given that these are error paths.
Note that I was lazy within Clippy, using `to_string` in a few places to
preserve the existing string imprecision. I could have used `impl
Into<{D,Subd}iagnosticMessage>` in various places as is done in the
compiler, but that would have required changes to *many* call sites
(mostly changing `&format("...")` to `format!("...")`) which didn't seem
worthwhile.
2023-05-04 10:55:21 +10:00
|
|
|
DiagnosticMessage::from(format!(
|
2022-11-20 13:06:44 +01:00
|
|
|
"macro expansion ends with an incomplete expression: {}",
|
|
|
|
message.replace(", found `<eof>`", ""),
|
|
|
|
)),
|
|
|
|
msg.1,
|
|
|
|
);
|
|
|
|
if !e.span.is_dummy() {
|
|
|
|
// early end of macro arm (#52866)
|
2022-12-29 22:46:17 -08:00
|
|
|
e.replace_span_with(parser.token.span.shrink_to_hi(), true);
|
2022-11-20 13:06:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if e.span.is_dummy() {
|
|
|
|
// Get around lack of span in error (#30128)
|
2022-12-29 22:46:17 -08:00
|
|
|
e.replace_span_with(site_span, true);
|
2022-11-20 13:06:44 +01:00
|
|
|
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",
|
|
|
|
);
|
2023-03-17 14:36:22 +08:00
|
|
|
|
|
|
|
if parser.token == token::Semi {
|
|
|
|
if let Ok(snippet) = parser.sess.source_map().span_to_snippet(site_span) {
|
|
|
|
e.span_suggestion_verbose(
|
|
|
|
site_span,
|
|
|
|
"surround the macro invocation with `{}` to interpret the expansion as a statement",
|
2023-07-25 22:00:13 +02:00
|
|
|
format!("{{ {snippet}; }}"),
|
2023-03-17 14:36:22 +08:00
|
|
|
Applicability::MaybeIncorrect,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
e.span_suggestion_verbose(
|
|
|
|
site_span.shrink_to_hi(),
|
|
|
|
"add `;` to interpret the expansion as a statement",
|
|
|
|
";",
|
|
|
|
Applicability::MaybeIncorrect,
|
|
|
|
);
|
|
|
|
}
|
2022-11-20 13:06:44 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => 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,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-02-14 14:17:27 +00:00
|
|
|
pub(super) fn annotate_doc_comment(
|
|
|
|
dcx: &DiagCtxt,
|
|
|
|
err: &mut Diagnostic,
|
|
|
|
sm: &SourceMap,
|
|
|
|
span: Span,
|
|
|
|
) {
|
2022-11-20 13:06:44 +01:00
|
|
|
if let Ok(src) = sm.span_to_snippet(span) {
|
|
|
|
if src.starts_with("///") || src.starts_with("/**") {
|
2024-02-14 14:17:27 +00:00
|
|
|
err.subdiagnostic(dcx, ExplainDocComment::Outer { span });
|
2022-11-20 13:06:44 +01:00
|
|
|
} else if src.starts_with("//!") || src.starts_with("/*!") {
|
2024-02-14 14:17:27 +00:00
|
|
|
err.subdiagnostic(dcx, ExplainDocComment::Inner { span });
|
2022-11-20 13:06:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
|
|
|
|
/// other tokens, this is "unexpected token...".
|
Use `Cow` in `{D,Subd}iagnosticMessage`.
Each of `{D,Subd}iagnosticMessage::{Str,Eager}` has a comment:
```
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
```
This commit answers that question in the affirmative. It's not the most
compelling change ever, but it might be worth merging.
This requires changing the `impl<'a> From<&'a str>` impls to `impl
From<&'static str>`, which involves a bunch of knock-on changes that
require/result in call sites being a little more precise about exactly
what kind of string they use to create errors, and not just `&str`. This
will result in fewer unnecessary allocations, though this will not have
any notable perf effects given that these are error paths.
Note that I was lazy within Clippy, using `to_string` in a few places to
preserve the existing string imprecision. I could have used `impl
Into<{D,Subd}iagnosticMessage>` in various places as is done in the
compiler, but that would have required changes to *many* call sites
(mostly changing `&format("...")` to `format!("...")`) which didn't seem
worthwhile.
2023-05-04 10:55:21 +10:00
|
|
|
pub(super) fn parse_failure_msg(tok: &Token) -> Cow<'static, str> {
|
2022-11-20 13:06:44 +01:00
|
|
|
match tok.kind {
|
Use `Cow` in `{D,Subd}iagnosticMessage`.
Each of `{D,Subd}iagnosticMessage::{Str,Eager}` has a comment:
```
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
```
This commit answers that question in the affirmative. It's not the most
compelling change ever, but it might be worth merging.
This requires changing the `impl<'a> From<&'a str>` impls to `impl
From<&'static str>`, which involves a bunch of knock-on changes that
require/result in call sites being a little more precise about exactly
what kind of string they use to create errors, and not just `&str`. This
will result in fewer unnecessary allocations, though this will not have
any notable perf effects given that these are error paths.
Note that I was lazy within Clippy, using `to_string` in a few places to
preserve the existing string imprecision. I could have used `impl
Into<{D,Subd}iagnosticMessage>` in various places as is done in the
compiler, but that would have required changes to *many* call sites
(mostly changing `&format("...")` to `format!("...")`) which didn't seem
worthwhile.
2023-05-04 10:55:21 +10:00
|
|
|
token::Eof => Cow::from("unexpected end of macro invocation"),
|
|
|
|
_ => Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))),
|
2022-11-20 13:06:44 +01:00
|
|
|
}
|
|
|
|
}
|