1use std::borrow::Cow;
2
3use rustc_ast::token::{self, Delimiter, Token, TokenKind};
4use rustc_ast::tokenstream::TokenStream;
5use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage};
6use rustc_macros::Subdiagnostic;
7use rustc_parse::parser::{Parser, Recovery, token_descr};
8use rustc_session::parse::ParseSess;
9use rustc_span::source_map::SourceMap;
10use rustc_span::{ErrorGuaranteed, Ident, Span};
11use tracing::debug;
12
13use super::macro_rules::{NoopTracker, parser_from_cx};
14use crate::expand::{AstFragmentKind, parse_ast_fragment};
15use crate::mbe::macro_parser::ParseResult::*;
16use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
17use crate::mbe::macro_rules::{Tracker, try_match_macro};
18
19pub(super) fn failed_to_match_macro(
20 psess: &ParseSess,
21 sp: Span,
22 def_span: Span,
23 name: Ident,
24 arg: TokenStream,
25 lhses: &[Vec<MatcherLoc>],
26) -> (Span, ErrorGuaranteed) {
27 let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
30
31 let try_success_result = try_match_macro(psess, name, &arg, lhses, &mut tracker);
32
33 if try_success_result.is_ok() {
34 assert!(
37 tracker.dcx.has_errors().is_some(),
38 "Macro matching returned a success on the second try"
39 );
40 }
41
42 if let Some(result) = tracker.result {
43 return result;
45 }
46
47 let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
48 else {
49 return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro"));
50 };
51
52 let span = token.span.substitute_dummy(sp);
53
54 let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None));
55 err.span_label(span, label);
56 if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
57 err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro");
58 }
59
60 annotate_doc_comment(&mut err, psess.source_map(), span);
61
62 if let Some(span) = remaining_matcher.span() {
63 err.span_note(span, format!("while trying to match {remaining_matcher}"));
64 } else {
65 err.note(format!("while trying to match {remaining_matcher}"));
66 }
67
68 if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
69 && (matches!(expected_token.kind, TokenKind::Interpolated(_))
70 || matches!(token.kind, TokenKind::Interpolated(_))
71 || matches!(expected_token.kind, TokenKind::OpenDelim(Delimiter::Invisible(_)))
72 || matches!(token.kind, TokenKind::OpenDelim(Delimiter::Invisible(_))))
73 {
74 err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
75 err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
76
77 if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
78 err.help("try using `:tt` instead in the macro definition");
79 }
80 }
81
82 if let Some((arg, comma_span)) = arg.add_comma() {
84 for lhs in lhses {
85 let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
86 let mut tt_parser = TtParser::new(name);
87
88 if let Success(_) =
89 tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
90 {
91 if comma_span.is_dummy() {
92 err.note("you might be missing a comma");
93 } else {
94 err.span_suggestion_short(
95 comma_span,
96 "missing comma here",
97 ", ",
98 Applicability::MachineApplicable,
99 );
100 }
101 }
102 }
103 }
104 let guar = err.emit();
105 (sp, guar)
106}
107
108struct CollectTrackerAndEmitter<'dcx, 'matcher> {
110 dcx: DiagCtxtHandle<'dcx>,
111 remaining_matcher: Option<&'matcher MatcherLoc>,
112 best_failure: Option<BestFailure>,
114 root_span: Span,
115 result: Option<(Span, ErrorGuaranteed)>,
116}
117
118struct BestFailure {
119 token: Token,
120 position_in_tokenstream: u32,
121 msg: &'static str,
122 remaining_matcher: MatcherLoc,
123}
124
125impl BestFailure {
126 fn is_better_position(&self, position: u32) -> bool {
127 position > self.position_in_tokenstream
128 }
129}
130
131impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> {
132 type Failure = (Token, u32, &'static str);
133
134 fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure {
135 (tok, position, msg)
136 }
137
138 fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
139 if self.remaining_matcher.is_none()
140 || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
141 {
142 self.remaining_matcher = Some(matcher);
143 }
144 }
145
146 fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
147 match result {
148 Success(_) => {
149 self.dcx.span_delayed_bug(
152 self.root_span,
153 "should not collect detailed info for successful macro match",
154 );
155 }
156 Failure((token, approx_position, msg)) => {
157 debug!(?token, ?msg, "a new failure of an arm");
158
159 if self
160 .best_failure
161 .as_ref()
162 .is_none_or(|failure| failure.is_better_position(*approx_position))
163 {
164 self.best_failure = Some(BestFailure {
165 token: token.clone(),
166 position_in_tokenstream: *approx_position,
167 msg,
168 remaining_matcher: self
169 .remaining_matcher
170 .expect("must have collected matcher already")
171 .clone(),
172 })
173 }
174 }
175 Error(err_sp, msg) => {
176 let span = err_sp.substitute_dummy(self.root_span);
177 let guar = self.dcx.span_err(span, msg.clone());
178 self.result = Some((span, guar));
179 }
180 ErrorReported(guar) => self.result = Some((self.root_span, *guar)),
181 }
182 }
183
184 fn description() -> &'static str {
185 "detailed"
186 }
187
188 fn recovery() -> Recovery {
189 Recovery::Allowed
190 }
191}
192
193impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> {
194 fn new(dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self {
195 Self { dcx, remaining_matcher: None, best_failure: None, root_span, result: None }
196 }
197}
198
199pub(crate) struct FailureForwarder<'matcher> {
202 expected_token: Option<&'matcher Token>,
203}
204
205impl<'matcher> FailureForwarder<'matcher> {
206 pub(crate) fn new() -> Self {
207 Self { expected_token: None }
208 }
209}
210
211impl<'matcher> Tracker<'matcher> for FailureForwarder<'matcher> {
212 type Failure = (Token, u32, &'static str);
213
214 fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure {
215 (tok, position, msg)
216 }
217
218 fn description() -> &'static str {
219 "failure-forwarder"
220 }
221
222 fn set_expected_token(&mut self, tok: &'matcher Token) {
223 self.expected_token = Some(tok);
224 }
225
226 fn get_expected_token(&self) -> Option<&'matcher Token> {
227 self.expected_token
228 }
229}
230
231pub(super) fn emit_frag_parse_err(
232 mut e: Diag<'_>,
233 parser: &Parser<'_>,
234 orig_parser: &mut Parser<'_>,
235 site_span: Span,
236 arm_span: Span,
237 kind: AstFragmentKind,
238) -> ErrorGuaranteed {
239 if parser.token == token::Eof
241 && let DiagMessage::Str(message) = &e.messages[0].0
242 && message.ends_with(", found `<eof>`")
243 {
244 let msg = &e.messages[0];
245 e.messages[0] = (
246 DiagMessage::from(format!(
247 "macro expansion ends with an incomplete expression: {}",
248 message.replace(", found `<eof>`", ""),
249 )),
250 msg.1,
251 );
252 if !e.span.is_dummy() {
253 e.replace_span_with(parser.token.span.shrink_to_hi(), true);
255 }
256 }
257 if e.span.is_dummy() {
258 e.replace_span_with(site_span, true);
260 if !parser.psess.source_map().is_imported(arm_span) {
261 e.span_label(arm_span, "in this macro arm");
262 }
263 } else if parser.psess.source_map().is_imported(parser.token.span) {
264 e.span_label(site_span, "in this macro invocation");
265 }
266 match kind {
267 AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
269 Err(err) => err.cancel(),
270 Ok(_) => {
271 e.note(
272 "the macro call doesn't expand to an expression, but it can expand to a statement",
273 );
274
275 if parser.token == token::Semi {
276 if let Ok(snippet) = parser.psess.source_map().span_to_snippet(site_span) {
277 e.span_suggestion_verbose(
278 site_span,
279 "surround the macro invocation with `{}` to interpret the expansion as a statement",
280 format!("{{ {snippet}; }}"),
281 Applicability::MaybeIncorrect,
282 );
283 }
284 } else {
285 e.span_suggestion_verbose(
286 site_span.shrink_to_hi(),
287 "add `;` to interpret the expansion as a statement",
288 ";",
289 Applicability::MaybeIncorrect,
290 );
291 }
292 }
293 },
294 _ => annotate_err_with_kind(&mut e, kind, site_span),
295 };
296 e.emit()
297}
298
299pub(crate) fn annotate_err_with_kind(err: &mut Diag<'_>, kind: AstFragmentKind, span: Span) {
300 match kind {
301 AstFragmentKind::Ty => {
302 err.span_label(span, "this macro call doesn't expand to a type");
303 }
304 AstFragmentKind::Pat => {
305 err.span_label(span, "this macro call doesn't expand to a pattern");
306 }
307 _ => {}
308 };
309}
310
311#[derive(Subdiagnostic)]
312enum ExplainDocComment {
313 #[label(expand_explain_doc_comment_inner)]
314 Inner {
315 #[primary_span]
316 span: Span,
317 },
318 #[label(expand_explain_doc_comment_outer)]
319 Outer {
320 #[primary_span]
321 span: Span,
322 },
323}
324
325pub(super) fn annotate_doc_comment(err: &mut Diag<'_>, sm: &SourceMap, span: Span) {
326 if let Ok(src) = sm.span_to_snippet(span) {
327 if src.starts_with("///") || src.starts_with("/**") {
328 err.subdiagnostic(ExplainDocComment::Outer { span });
329 } else if src.starts_with("//!") || src.starts_with("/*!") {
330 err.subdiagnostic(ExplainDocComment::Inner { span });
331 }
332 }
333}
334
335pub(super) fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> {
338 if let Some(expected_token) = expected_token {
339 Cow::from(format!("expected {}, found {}", token_descr(expected_token), token_descr(tok)))
340 } else {
341 match tok.kind {
342 token::Eof => Cow::from("unexpected end of macro invocation"),
343 _ => Cow::from(format!("no rules expected {}", token_descr(tok))),
344 }
345 }
346}