1use std::borrow::Cow;
2
3use rustc_ast::token::{self, Token};
4use rustc_ast::tokenstream::TokenStream;
5use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage};
6use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs};
7use rustc_macros::Subdiagnostic;
8use rustc_parse::parser::{Parser, Recovery, token_descr};
9use rustc_session::parse::ParseSess;
10use rustc_span::source_map::SourceMap;
11use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span};
12use tracing::debug;
13
14use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
15use crate::expand::{AstFragmentKind, parse_ast_fragment};
16use crate::mbe::macro_parser::ParseResult::*;
17use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
18use crate::mbe::macro_rules::{
19 Tracker, try_match_macro, try_match_macro_attr, try_match_macro_derive,
20};
21
22pub(super) enum FailedMacro<'a> {
23 Func,
24 Attr(&'a TokenStream),
25 Derive,
26}
27
28pub(super) fn failed_to_match_macro(
29 psess: &ParseSess,
30 sp: Span,
31 def_span: Span,
32 name: Ident,
33 args: FailedMacro<'_>,
34 body: &TokenStream,
35 rules: &[MacroRule],
36 on_unmatch_args: Option<&Directive>,
37) -> (Span, ErrorGuaranteed) {
38 {
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:38",
"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(38u32),
::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");
39 let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
40 psess.source_map().guess_head_span(def_span)
41 } else {
42 DUMMY_SP
43 };
44
45 let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
48
49 let try_success_result = match args {
50 FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker),
51 FailedMacro::Attr(attr_args) => {
52 try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
53 }
54 FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker),
55 };
56
57 if try_success_result.is_ok() {
58 if !tracker.dcx.has_errors().is_some() {
{
::core::panicking::panic_fmt(format_args!("Macro matching returned a success on the second try"));
}
};assert!(
61 tracker.dcx.has_errors().is_some(),
62 "Macro matching returned a success on the second try"
63 );
64 }
65
66 if let Some(result) = tracker.result {
67 return result;
69 }
70
71 let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
72 else {
73 return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro"));
74 };
75
76 let span = token.span.substitute_dummy(sp);
77 let CustomDiagnostic {
78 message: custom_message, label: custom_label, notes: custom_notes, ..
79 } = {
80 on_unmatch_args
81 .map(|directive| directive.eval(None, &FormatArgs { this: name.to_string(), .. }))
82 .unwrap_or_default()
83 };
84
85 let mut err = match custom_message {
86 Some(message) => psess.dcx().struct_span_err(span, message),
87 None => psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)),
88 };
89 err.span_label(span, custom_label.unwrap_or_else(|| label.to_string()));
90 if !def_head_span.is_dummy() {
91 err.span_label(def_head_span, "when calling this macro");
92 }
93
94 annotate_doc_comment(&mut err, psess.source_map(), span);
95
96 if let Some(span) = remaining_matcher.span() {
97 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}"));
98 } else {
99 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}"));
100 }
101 for note in custom_notes {
102 err.note(note);
103 }
104
105 if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
106 && (#[allow(non_exhaustive_omitted_patterns)] match expected_token.kind {
token::OpenInvisible(_) => true,
_ => false,
}matches!(expected_token.kind, token::OpenInvisible(_))
107 || #[allow(non_exhaustive_omitted_patterns)] match token.kind {
token::OpenInvisible(_) => true,
_ => false,
}matches!(token.kind, token::OpenInvisible(_)))
108 {
109 err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
110 err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
111
112 if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
113 err.help("try using `:tt` instead in the macro definition");
114 }
115 }
116
117 if let FailedMacro::Func = args
119 && let Some((body, comma_span)) = body.add_comma()
120 {
121 for rule in rules {
122 let MacroRule::Func { lhs, .. } = rule else { continue };
123 let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed);
124 let mut tt_parser = TtParser::new(name);
125
126 if let Success(_) =
127 tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
128 {
129 if comma_span.is_dummy() {
130 err.note("you might be missing a comma");
131 } else {
132 err.span_suggestion_short(
133 comma_span,
134 "missing comma here",
135 ", ",
136 Applicability::MachineApplicable,
137 );
138 }
139 }
140 }
141 }
142 let guar = err.emit();
143 (sp, guar)
144}
145
146struct CollectTrackerAndEmitter<'dcx, 'matcher> {
148 dcx: DiagCtxtHandle<'dcx>,
149 remaining_matcher: Option<&'matcher MatcherLoc>,
150 best_failure: Option<BestFailure>,
152 root_span: Span,
153 result: Option<(Span, ErrorGuaranteed)>,
154}
155
156struct BestFailure {
157 token: Token,
158 position_in_tokenstream: (bool, u32),
159 msg: &'static str,
160 remaining_matcher: MatcherLoc,
161}
162
163impl BestFailure {
164 fn is_better_position(&self, position: (bool, u32)) -> bool {
165 position > self.position_in_tokenstream
166 }
167}
168
169impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> {
170 type Failure = (Token, u32, &'static str);
171
172 fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure {
173 (tok, position, msg)
174 }
175
176 fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
177 if self.remaining_matcher.is_none()
178 || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
179 {
180 self.remaining_matcher = Some(matcher);
181 }
182 }
183
184 fn after_arm(&mut self, in_body: bool, result: &NamedParseResult<Self::Failure>) {
185 match result {
186 Success(_) => {
187 self.dcx.span_delayed_bug(
190 self.root_span,
191 "should not collect detailed info for successful macro match",
192 );
193 }
194 Failure((token, approx_position, msg)) => {
195 {
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:195",
"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(195u32),
::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");
196
197 let position_in_tokenstream = (in_body, *approx_position);
198 if self
199 .best_failure
200 .as_ref()
201 .is_none_or(|failure| failure.is_better_position(position_in_tokenstream))
202 {
203 self.best_failure = Some(BestFailure {
204 token: *token,
205 position_in_tokenstream,
206 msg,
207 remaining_matcher: self
208 .remaining_matcher
209 .expect("must have collected matcher already")
210 .clone(),
211 })
212 }
213 }
214 Error(err_sp, msg) => {
215 let span = err_sp.substitute_dummy(self.root_span);
216 let guar = self.dcx.span_err(span, msg.clone());
217 self.result = Some((span, guar));
218 }
219 ErrorReported(guar) => self.result = Some((self.root_span, *guar)),
220 }
221 }
222
223 fn description() -> &'static str {
224 "detailed"
225 }
226
227 fn recovery() -> Recovery {
228 Recovery::Allowed
229 }
230}
231
232impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> {
233 fn new(dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self {
234 Self { dcx, remaining_matcher: None, best_failure: None, root_span, result: None }
235 }
236}
237
238pub(super) fn emit_frag_parse_err(
239 mut e: Diag<'_>,
240 parser: &mut Parser<'_>,
241 orig_parser: &mut Parser<'_>,
242 site_span: Span,
243 arm_span: Span,
244 kind: AstFragmentKind,
245 bindings: &[MacroRule],
246 matched_rule_bindings: &[MatcherLoc],
247) -> ErrorGuaranteed {
248 if parser.token == token::Eof
250 && let DiagMessage::Str(message) = &e.messages[0].0
251 && message.ends_with(", found `<eof>`")
252 {
253 let msg = &e.messages[0];
254 e.messages[0] = (
255 DiagMessage::from(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("macro expansion ends with an incomplete expression: {0}",
message.replace(", found `<eof>`", "")))
})format!(
256 "macro expansion ends with an incomplete expression: {}",
257 message.replace(", found `<eof>`", ""),
258 )),
259 msg.1,
260 );
261 if !e.span.is_dummy() {
262 e.replace_span_with(parser.token.span.shrink_to_hi(), true);
264 }
265 }
266 if e.span.is_dummy() {
267 e.replace_span_with(site_span, true);
269 if !parser.psess.source_map().is_imported(arm_span) {
270 e.span_label(arm_span, "in this macro arm");
271 }
272 } else if parser.psess.source_map().is_imported(parser.token.span) {
273 e.span_label(site_span, "in this macro invocation");
274 }
275 match kind {
276 AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
278 Err(err) => err.cancel(),
279 Ok(_) => {
280 e.note(
281 "the macro call doesn't expand to an expression, but it can expand to a statement",
282 );
283
284 if parser.token == token::Semi {
285 if let Ok(snippet) = parser.psess.source_map().span_to_snippet(site_span) {
286 e.span_suggestion_verbose(
287 site_span,
288 "surround the macro invocation with `{}` to interpret the expansion as a statement",
289 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{{ {0}; }}", snippet))
})format!("{{ {snippet}; }}"),
290 Applicability::MaybeIncorrect,
291 );
292 }
293 } else {
294 e.span_suggestion_verbose(
295 site_span.shrink_to_hi(),
296 "add `;` to interpret the expansion as a statement",
297 ";",
298 Applicability::MaybeIncorrect,
299 );
300 }
301 }
302 },
303 _ => annotate_err_with_kind(&mut e, kind, site_span),
304 };
305
306 if parser.token.kind == token::Dollar {
307 parser.bump();
308 if let token::Ident(name, _) = parser.token.kind {
309 let mut bindings_names = ::alloc::vec::Vec::new()vec![];
310 for rule in bindings {
311 let MacroRule::Func { lhs, .. } = rule else { continue };
312 for param in lhs {
313 let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
314 bindings_names.push(bind.name);
315 }
316 }
317
318 let mut matched_rule_bindings_names = ::alloc::vec::Vec::new()vec![];
319 for param in matched_rule_bindings {
320 let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
321 matched_rule_bindings_names.push(bind.name);
322 }
323
324 if let Some(matched_name) = rustc_span::edit_distance::find_best_match_for_name(
325 &matched_rule_bindings_names[..],
326 name,
327 None,
328 ) {
329 e.span_suggestion_verbose(
330 parser.token.span,
331 "there is a macro metavariable with a similar name",
332 matched_name,
333 Applicability::MaybeIncorrect,
334 );
335 } else if bindings_names.contains(&name) {
336 e.span_label(
337 parser.token.span,
338 "there is an macro metavariable with this name in another macro matcher",
339 );
340 } else if let Some(matched_name) =
341 rustc_span::edit_distance::find_best_match_for_name(&bindings_names[..], name, None)
342 {
343 e.span_suggestion_verbose(
344 parser.token.span,
345 "there is a macro metavariable with a similar name in another macro matcher",
346 matched_name,
347 Applicability::MaybeIncorrect,
348 );
349 } else {
350 let msg = matched_rule_bindings_names
351 .iter()
352 .map(|sym| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("${0}", sym))
})format!("${}", sym))
353 .collect::<Vec<_>>()
354 .join(", ");
355
356 e.span_label(parser.token.span, "macro metavariable not found");
357 if !matched_rule_bindings_names.is_empty() {
358 e.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("available metavariable names are: {0}",
msg))
})format!("available metavariable names are: {msg}"));
359 }
360 }
361 }
362 }
363 e.emit()
364}
365
366pub(crate) fn annotate_err_with_kind(err: &mut Diag<'_>, kind: AstFragmentKind, span: Span) {
367 match kind {
368 AstFragmentKind::Ty => {
369 err.span_label(span, "this macro call doesn't expand to a type");
370 }
371 AstFragmentKind::Pat => {
372 err.span_label(span, "this macro call doesn't expand to a pattern");
373 }
374 _ => {}
375 };
376}
377
378#[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)]
379enum ExplainDocComment {
380 #[label(
381 "inner doc comments expand to `#![doc = \"...\"]`, which is what this macro attempted to match"
382 )]
383 Inner {
384 #[primary_span]
385 span: Span,
386 },
387 #[label(
388 "outer doc comments expand to `#[doc = \"...\"]`, which is what this macro attempted to match"
389 )]
390 Outer {
391 #[primary_span]
392 span: Span,
393 },
394}
395
396fn annotate_doc_comment(err: &mut Diag<'_>, sm: &SourceMap, span: Span) {
397 if let Ok(src) = sm.span_to_snippet(span) {
398 if src.starts_with("///") || src.starts_with("/**") {
399 err.subdiagnostic(ExplainDocComment::Outer { span });
400 } else if src.starts_with("//!") || src.starts_with("/*!") {
401 err.subdiagnostic(ExplainDocComment::Inner { span });
402 }
403 }
404}
405
406fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> {
409 if let Some(expected_token) = expected_token {
410 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)))
411 } else {
412 match tok.kind {
413 token::Eof => Cow::from("unexpected end of macro invocation"),
414 _ => 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))),
415 }
416 }
417}