1use std::borrow::Cow;
2
3use rustc_ast::token::{self, Token};
4use rustc_ast::tokenstream::TokenStream;
5use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage, pluralize};
6use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs};
7use rustc_macros::Subdiagnostic;
8use rustc_middle::bug;
9use rustc_parse::parser::{Parser, Recovery, token_descr};
10use rustc_session::parse::ParseSess;
11use rustc_span::source_map::SourceMap;
12use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span};
13use tracing::debug;
14
15use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
16use crate::expand::{AstFragmentKind, parse_ast_fragment};
17use crate::mbe::macro_parser::ParseResult::*;
18use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
19use crate::mbe::macro_rules::{
20 Tracker, WhichMatcher, try_match_macro, try_match_macro_attr, try_match_macro_derive,
21};
22
23pub(super) enum FailedMacro<'a> {
24 Func,
25 Attr(&'a TokenStream),
26 Derive,
27}
28
29pub(super) fn failed_to_match_macro(
30 psess: &ParseSess,
31 sp: Span,
32 def_span: Span,
33 name: Ident,
34 args: FailedMacro<'_>,
35 body: &TokenStream,
36 rules: &[MacroRule],
37 on_unmatched_args: Option<&Directive>,
38) -> (Span, ErrorGuaranteed) {
39 {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_expand/src/mbe/diagnostics.rs:39",
"rustc_expand::mbe::diagnostics", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_expand/src/mbe/diagnostics.rs"),
::tracing_core::__macro_support::Option::Some(39u32),
::tracing_core::__macro_support::Option::Some("rustc_expand::mbe::diagnostics"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("failed to match macro")
as &dyn Value))])
});
} else { ; }
};debug!("failed to match macro");
40 let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
41 psess.source_map().guess_head_span(def_span)
42 } else {
43 DUMMY_SP
44 };
45
46 let mut tracker = CollectTrackerAndEmitter::new(name, psess.dcx(), sp);
49
50 let try_success_result = match args {
51 FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker),
52 FailedMacro::Attr(attr_args) => {
53 try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
54 }
55 FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker),
56 };
57
58 if try_success_result.is_ok() {
59 if !tracker.dcx.has_errors().is_some() {
{
::core::panicking::panic_fmt(format_args!("Macro matching returned a success on the second try"));
}
};assert!(
62 tracker.dcx.has_errors().is_some(),
63 "Macro matching returned a success on the second try"
64 );
65 }
66
67 if let Some(result) = tracker.result {
68 return result;
70 }
71
72 let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
73 else {
74 return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro"));
75 };
76
77 let span = token.span.substitute_dummy(sp);
78 let CustomDiagnostic {
79 message: custom_message, label: custom_label, notes: custom_notes, ..
80 } = {
81 on_unmatched_args
82 .map(|directive| directive.eval(None, &FormatArgs { this: name.to_string(), .. }))
83 .unwrap_or_default()
84 };
85
86 let mut err = match custom_message {
87 Some(message) => psess.dcx().struct_span_err(span, message),
88 None => psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)),
89 };
90 err.span_label(span, custom_label.unwrap_or_else(|| label.to_string()));
91 if !def_head_span.is_dummy() {
92 err.span_label(def_head_span, "when calling this macro");
93 }
94
95 annotate_doc_comment(&mut err, psess.source_map(), span);
96
97 if let Some(span) = remaining_matcher.span() {
98 err.span_note(span, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("while trying to match {0}",
remaining_matcher))
})format!("while trying to match {remaining_matcher}"));
99 } else {
100 err.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("while trying to match {0}",
remaining_matcher))
})format!("while trying to match {remaining_matcher}"));
101 }
102 for note in custom_notes {
103 err.note(note);
104 }
105
106 if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
107 && (#[allow(non_exhaustive_omitted_patterns)] match expected_token.kind {
token::OpenInvisible(_) => true,
_ => false,
}matches!(expected_token.kind, token::OpenInvisible(_))
108 || #[allow(non_exhaustive_omitted_patterns)] match token.kind {
token::OpenInvisible(_) => true,
_ => false,
}matches!(token.kind, token::OpenInvisible(_)))
109 {
110 err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
111 err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
112
113 if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
114 err.help("try using `:tt` instead in the macro definition");
115 }
116 }
117
118 if let FailedMacro::Func = args
120 && let Some((body, comma_span)) = body.add_comma()
121 {
122 for rule in rules {
123 let MacroRule::Func { lhs, .. } = rule else { continue };
124 let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed);
125 let mut tt_parser = TtParser::new();
126
127 if let Success(_) =
128 tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
129 {
130 if comma_span.is_dummy() {
131 err.note("you might be missing a comma");
132 } else {
133 err.span_suggestion_short(
134 comma_span,
135 "missing comma here",
136 ", ",
137 Applicability::MachineApplicable,
138 );
139 }
140 }
141 }
142 }
143 let guar = err.emit();
144 (sp, guar)
145}
146
147struct CollectTrackerAndEmitter<'dcx, 'matcher> {
149 macro_name: Ident,
150 dcx: DiagCtxtHandle<'dcx>,
151
152 current: Option<WhichMatcher>,
156
157 remaining_matcher: Option<&'matcher MatcherLoc>,
158 best_failure: Option<BestFailure>,
160 root_span: Span,
161 result: Option<(Span, ErrorGuaranteed)>,
162}
163
164struct BestFailure {
165 token: Token,
166
167 matcher: WhichMatcher,
169
170 position: u32,
174
175 msg: &'static str,
176 remaining_matcher: MatcherLoc,
177}
178
179impl BestFailure {
180 fn is_better_position(&self, matcher: WhichMatcher, position: u32) -> bool {
181 (matcher, position) > (self.matcher, self.position)
182 }
183}
184
185impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> {
186 fn prepare(&mut self, which_matcher: WhichMatcher) {
187 if self.current.is_some() {
188 ::rustc_middle::util::bug::bug_fmt(format_args!("`Self::after_arm()` was not called to clean up context"));bug!("`Self::after_arm()` was not called to clean up context");
189 }
190
191 self.current = Some(which_matcher);
192 }
193
194 fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
195 if self.remaining_matcher.is_none()
196 || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
197 {
198 self.remaining_matcher = Some(matcher);
199 }
200 }
201
202 fn after_arm(&mut self, result: &NamedParseResult) {
203 match *result {
204 Success(_) => {
205 self.dcx.span_delayed_bug(
208 self.root_span,
209 "should not collect detailed info for successful macro match",
210 );
211 }
212 Failure => {
213 if self.best_failure.is_none() {
214 ::rustc_middle::util::bug::bug_fmt(format_args!("A matching failure occurred but `Self::failure()` was not called"));bug!("A matching failure occurred but `Self::failure()` was not called");
215 }
216 }
217 Ambiguity => {
218 if self.result.is_none() {
219 ::rustc_middle::util::bug::bug_fmt(format_args!("An ambiguity error occurred but `Self::ambiguity()` was not called"));bug!("An ambiguity error occurred but `Self::ambiguity()` was not called");
220 }
221 }
222 ErrorReported(guar) => self.result = Some((self.root_span, guar)),
223 }
224
225 self.current = None;
226 }
227
228 fn failure(&mut self, parser: &Parser<'_>) {
229 let Some(which_matcher) = self.current else {
230 ::rustc_middle::util::bug::bug_fmt(format_args!("`Self::prepare()` was not called to initialize context"));bug!("`Self::prepare()` was not called to initialize context");
231 };
232
233 let mut token = parser.token;
234 let approx_position = parser.approx_token_stream_pos();
235 let msg = if token.kind == token::Eof {
236 if !token.span.is_dummy() {
238 token.span = token.span.shrink_to_hi();
239 }
240 "missing tokens in macro arguments"
241 } else {
242 "no rules expected this token in macro call"
243 };
244
245 {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_expand/src/mbe/diagnostics.rs:245",
"rustc_expand::mbe::diagnostics", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_expand/src/mbe/diagnostics.rs"),
::tracing_core::__macro_support::Option::Some(245u32),
::tracing_core::__macro_support::Option::Some("rustc_expand::mbe::diagnostics"),
::tracing_core::field::FieldSet::new(&["message", "token",
"msg"], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("a new failure of an arm")
as &dyn Value)),
(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&debug(&token) as
&dyn Value)),
(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&debug(&msg) as
&dyn Value))])
});
} else { ; }
};debug!(?token, ?msg, "a new failure of an arm");
246
247 if self
248 .best_failure
249 .as_ref()
250 .is_none_or(|failure| failure.is_better_position(which_matcher, approx_position))
251 {
252 self.best_failure = Some(BestFailure {
253 token,
254 matcher: which_matcher,
255 position: approx_position,
256 msg,
257 remaining_matcher: self
258 .remaining_matcher
259 .expect("must have collected matcher already")
260 .clone(),
261 })
262 }
263 }
264
265 fn ambiguity(
266 &mut self,
267 parser: &Parser<'_>,
268 bb_locs: impl IntoIterator<Item = &'matcher MatcherLoc>,
269 next_locs: impl IntoIterator<Item = &'matcher MatcherLoc>,
270 ) {
271 let span = parser.token.span.substitute_dummy(self.root_span);
272
273 if parser.token == token::Eof {
274 let msg = "ambiguity: multiple successful parses".to_string();
275 let guar = self.dcx.span_err(span, msg);
276 self.result = Some((span, guar));
277 return;
278 }
279
280 let nts = bb_locs
281 .into_iter()
282 .map(|loc| match loc {
283 MatcherLoc::MetaVarDecl { bind, kind, .. } => {
284 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} (\'{1}\')", kind, bind))
})format!("{kind} ('{bind}')")
285 }
286 _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
287 })
288 .collect::<Vec<String>>()
289 .join(" or ");
290
291 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("local ambiguity when calling macro `{0}`: multiple parsing options: {1}",
self.macro_name,
match next_locs.into_iter().count() {
0 =>
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("built-in NTs {0}.", nts))
}),
n =>
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("built-in NTs {1} or {2} other option{0}.",
if n == 1 { "" } else { "s" }, nts, n))
}),
}))
})format!(
292 "local ambiguity when calling macro `{}`: multiple parsing options: {}",
293 self.macro_name,
294 match next_locs.into_iter().count() {
295 0 => format!("built-in NTs {nts}."),
296 n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)),
297 }
298 );
299
300 let guar = self.dcx.span_err(span, msg);
301 self.result = Some((span, guar));
302 }
303
304 fn description() -> &'static str {
305 "detailed"
306 }
307
308 fn recovery() -> Recovery {
309 Recovery::Allowed
310 }
311}
312
313impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> {
314 fn new(macro_name: Ident, dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self {
315 Self {
316 macro_name,
317 dcx,
318 current: None,
319 remaining_matcher: None,
320 best_failure: None,
321 root_span,
322 result: None,
323 }
324 }
325}
326
327pub(super) fn emit_frag_parse_err(
328 mut e: Diag<'_>,
329 parser: &mut Parser<'_>,
330 orig_parser: &mut Parser<'_>,
331 site_span: Span,
332 arm_span: Span,
333 kind: AstFragmentKind,
334 bindings: &[MacroRule],
335 matched_rule_bindings: &[MatcherLoc],
336) -> ErrorGuaranteed {
337 if parser.token == token::Eof
339 && let DiagMessage::Str(message) = &e.messages[0].0
340 && message.ends_with(", found `<eof>`")
341 {
342 let msg = &e.messages[0];
343 e.messages[0] = (
344 DiagMessage::from(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("macro expansion ends with an incomplete expression: {0}",
message.replace(", found `<eof>`", "")))
})format!(
345 "macro expansion ends with an incomplete expression: {}",
346 message.replace(", found `<eof>`", ""),
347 )),
348 msg.1,
349 );
350 if !e.span.is_dummy() {
351 e.replace_span_with(parser.token.span.shrink_to_hi(), true);
353 }
354 }
355 if e.span.is_dummy() {
356 e.replace_span_with(site_span, true);
358 if !parser.psess.source_map().is_imported(arm_span) {
359 e.span_label(arm_span, "in this macro arm");
360 }
361 } else if parser.psess.source_map().is_imported(parser.token.span) {
362 e.span_label(site_span, "in this macro invocation");
363 }
364 match kind {
365 AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
367 Err(err) => err.cancel(),
368 Ok(_) => {
369 e.note(
370 "the macro call doesn't expand to an expression, but it can expand to a statement",
371 );
372
373 if parser.token == token::Semi {
374 if let Ok(snippet) = parser.psess.source_map().span_to_snippet(site_span) {
375 e.span_suggestion_verbose(
376 site_span,
377 "surround the macro invocation with `{}` to interpret the expansion as a statement",
378 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{{ {0}; }}", snippet))
})format!("{{ {snippet}; }}"),
379 Applicability::MaybeIncorrect,
380 );
381 }
382 } else {
383 e.span_suggestion_verbose(
384 site_span.shrink_to_hi(),
385 "add `;` to interpret the expansion as a statement",
386 ";",
387 Applicability::MaybeIncorrect,
388 );
389 }
390 }
391 },
392 _ => annotate_err_with_kind(&mut e, kind, site_span),
393 };
394
395 if parser.token.kind == token::Dollar {
396 let dollar_span = parser.token.span;
397 parser.bump();
398 if let token::Ident(name, _) = parser.token.kind {
399 let metavar_span = dollar_span.to(parser.token.span);
400 let mut bindings_names = ::alloc::vec::Vec::new()vec![];
401 for rule in bindings {
402 let MacroRule::Func { lhs, .. } = rule else { continue };
403 for param in lhs {
404 let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
405 bindings_names.push(bind.name);
406 }
407 }
408
409 let mut matched_rule_bindings_names = ::alloc::vec::Vec::new()vec![];
410 for param in matched_rule_bindings {
411 let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
412 matched_rule_bindings_names.push(bind.name);
413 }
414
415 e.primary_message(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("cannot find macro parameter `${0}` in this scope",
name))
})format!("cannot find macro parameter `${name}` in this scope"));
418 e.span(metavar_span);
419 e.span_label(metavar_span, "not found in this scope");
420 if parser.psess.source_map().is_imported(metavar_span) {
421 e.span_label(site_span, "in this macro invocation");
422 }
423
424 if let Some(matched_name) = rustc_span::edit_distance::find_best_match_for_name(
425 &matched_rule_bindings_names[..],
426 name,
427 None,
428 ) {
429 e.span_suggestion_verbose(
430 parser.token.span,
431 "there is a macro metavariable with a similar name",
432 matched_name,
433 Applicability::MaybeIncorrect,
434 );
435 } else if bindings_names.contains(&name) {
436 e.span_label(
437 parser.token.span,
438 "there is an macro metavariable with this name in another macro matcher",
439 );
440 } else if let Some(matched_name) =
441 rustc_span::edit_distance::find_best_match_for_name(&bindings_names[..], name, None)
442 {
443 e.span_suggestion_verbose(
444 parser.token.span,
445 "there is a macro metavariable with a similar name in another macro matcher",
446 matched_name,
447 Applicability::MaybeIncorrect,
448 );
449 } else if !matched_rule_bindings_names.is_empty() {
450 let msg = matched_rule_bindings_names
451 .iter()
452 .map(|sym| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("${0}", sym))
})format!("${}", sym))
453 .collect::<Vec<_>>()
454 .join(", ");
455 e.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("available metavariable names are: {0}",
msg))
})format!("available metavariable names are: {msg}"));
456 }
457 }
458 }
459 e.emit()
460}
461
462pub(crate) fn annotate_err_with_kind(err: &mut Diag<'_>, kind: AstFragmentKind, span: Span) {
463 match kind {
464 AstFragmentKind::Ty => {
465 err.span_label(span, "this macro call doesn't expand to a type");
466 }
467 AstFragmentKind::Pat => {
468 err.span_label(span, "this macro call doesn't expand to a pattern");
469 }
470 _ => {}
471 };
472}
473
474#[derive(const _: () =
{
impl rustc_errors::Subdiagnostic for ExplainDocComment {
fn add_to_diag<__G>(self, diag: &mut rustc_errors::Diag<'_, __G>)
where __G: rustc_errors::EmissionGuarantee {
match self {
ExplainDocComment::Inner { span: __binding_0 } => {
let mut sub_args = rustc_errors::DiagArgMap::default();
let __message =
rustc_errors::format_diag_message(&rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("inner doc comments expand to `#![doc = \"...\"]`, which is what this macro attempted to match")),
&sub_args);
diag.span_label(__binding_0, __message);
}
ExplainDocComment::Outer { span: __binding_0 } => {
let mut sub_args = rustc_errors::DiagArgMap::default();
let __message =
rustc_errors::format_diag_message(&rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("outer doc comments expand to `#[doc = \"...\"]`, which is what this macro attempted to match")),
&sub_args);
diag.span_label(__binding_0, __message);
}
}
}
}
};Subdiagnostic)]
475enum ExplainDocComment {
476 #[label(
477 "inner doc comments expand to `#![doc = \"...\"]`, which is what this macro attempted to match"
478 )]
479 Inner {
480 #[primary_span]
481 span: Span,
482 },
483 #[label(
484 "outer doc comments expand to `#[doc = \"...\"]`, which is what this macro attempted to match"
485 )]
486 Outer {
487 #[primary_span]
488 span: Span,
489 },
490}
491
492fn annotate_doc_comment(err: &mut Diag<'_>, sm: &SourceMap, span: Span) {
493 if let Ok(src) = sm.span_to_snippet(span) {
494 if src.starts_with("///") || src.starts_with("/**") {
495 err.subdiagnostic(ExplainDocComment::Outer { span });
496 } else if src.starts_with("//!") || src.starts_with("/*!") {
497 err.subdiagnostic(ExplainDocComment::Inner { span });
498 }
499 }
500}
501
502fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> {
505 if let Some(expected_token) = expected_token {
506 Cow::from(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected {0}, found {1}",
token_descr(expected_token), token_descr(tok)))
})format!("expected {}, found {}", token_descr(expected_token), token_descr(tok)))
507 } else {
508 match tok.kind {
509 token::Eof => Cow::from("unexpected end of macro invocation"),
510 _ => Cow::from(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("no rules expected {0}",
token_descr(tok)))
})format!("no rules expected {}", token_descr(tok))),
511 }
512 }
513}