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_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, token::OpenInvisible(_))
70 || matches!(token.kind, token::OpenInvisible(_)))
71 {
72 err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
73 err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
74
75 if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
76 err.help("try using `:tt` instead in the macro definition");
77 }
78 }
79
80 if let Some((arg, comma_span)) = arg.add_comma() {
82 for lhs in lhses {
83 let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
84 let mut tt_parser = TtParser::new(name);
85
86 if let Success(_) =
87 tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
88 {
89 if comma_span.is_dummy() {
90 err.note("you might be missing a comma");
91 } else {
92 err.span_suggestion_short(
93 comma_span,
94 "missing comma here",
95 ", ",
96 Applicability::MachineApplicable,
97 );
98 }
99 }
100 }
101 }
102 let guar = err.emit();
103 (sp, guar)
104}
105
106struct CollectTrackerAndEmitter<'dcx, 'matcher> {
108 dcx: DiagCtxtHandle<'dcx>,
109 remaining_matcher: Option<&'matcher MatcherLoc>,
110 best_failure: Option<BestFailure>,
112 root_span: Span,
113 result: Option<(Span, ErrorGuaranteed)>,
114}
115
116struct BestFailure {
117 token: Token,
118 position_in_tokenstream: u32,
119 msg: &'static str,
120 remaining_matcher: MatcherLoc,
121}
122
123impl BestFailure {
124 fn is_better_position(&self, position: u32) -> bool {
125 position > self.position_in_tokenstream
126 }
127}
128
129impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> {
130 type Failure = (Token, u32, &'static str);
131
132 fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure {
133 (tok, position, msg)
134 }
135
136 fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
137 if self.remaining_matcher.is_none()
138 || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
139 {
140 self.remaining_matcher = Some(matcher);
141 }
142 }
143
144 fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
145 match result {
146 Success(_) => {
147 self.dcx.span_delayed_bug(
150 self.root_span,
151 "should not collect detailed info for successful macro match",
152 );
153 }
154 Failure((token, approx_position, msg)) => {
155 debug!(?token, ?msg, "a new failure of an arm");
156
157 if self
158 .best_failure
159 .as_ref()
160 .is_none_or(|failure| failure.is_better_position(*approx_position))
161 {
162 self.best_failure = Some(BestFailure {
163 token: *token,
164 position_in_tokenstream: *approx_position,
165 msg,
166 remaining_matcher: self
167 .remaining_matcher
168 .expect("must have collected matcher already")
169 .clone(),
170 })
171 }
172 }
173 Error(err_sp, msg) => {
174 let span = err_sp.substitute_dummy(self.root_span);
175 let guar = self.dcx.span_err(span, msg.clone());
176 self.result = Some((span, guar));
177 }
178 ErrorReported(guar) => self.result = Some((self.root_span, *guar)),
179 }
180 }
181
182 fn description() -> &'static str {
183 "detailed"
184 }
185
186 fn recovery() -> Recovery {
187 Recovery::Allowed
188 }
189}
190
191impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> {
192 fn new(dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self {
193 Self { dcx, remaining_matcher: None, best_failure: None, root_span, result: None }
194 }
195}
196
197pub(crate) struct FailureForwarder<'matcher> {
200 expected_token: Option<&'matcher Token>,
201}
202
203impl<'matcher> FailureForwarder<'matcher> {
204 pub(crate) fn new() -> Self {
205 Self { expected_token: None }
206 }
207}
208
209impl<'matcher> Tracker<'matcher> for FailureForwarder<'matcher> {
210 type Failure = (Token, u32, &'static str);
211
212 fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure {
213 (tok, position, msg)
214 }
215
216 fn description() -> &'static str {
217 "failure-forwarder"
218 }
219
220 fn set_expected_token(&mut self, tok: &'matcher Token) {
221 self.expected_token = Some(tok);
222 }
223
224 fn get_expected_token(&self) -> Option<&'matcher Token> {
225 self.expected_token
226 }
227}
228
229pub(super) fn emit_frag_parse_err(
230 mut e: Diag<'_>,
231 parser: &Parser<'_>,
232 orig_parser: &mut Parser<'_>,
233 site_span: Span,
234 arm_span: Span,
235 kind: AstFragmentKind,
236) -> ErrorGuaranteed {
237 if parser.token == token::Eof
239 && let DiagMessage::Str(message) = &e.messages[0].0
240 && message.ends_with(", found `<eof>`")
241 {
242 let msg = &e.messages[0];
243 e.messages[0] = (
244 DiagMessage::from(format!(
245 "macro expansion ends with an incomplete expression: {}",
246 message.replace(", found `<eof>`", ""),
247 )),
248 msg.1,
249 );
250 if !e.span.is_dummy() {
251 e.replace_span_with(parser.token.span.shrink_to_hi(), true);
253 }
254 }
255 if e.span.is_dummy() {
256 e.replace_span_with(site_span, true);
258 if !parser.psess.source_map().is_imported(arm_span) {
259 e.span_label(arm_span, "in this macro arm");
260 }
261 } else if parser.psess.source_map().is_imported(parser.token.span) {
262 e.span_label(site_span, "in this macro invocation");
263 }
264 match kind {
265 AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
267 Err(err) => err.cancel(),
268 Ok(_) => {
269 e.note(
270 "the macro call doesn't expand to an expression, but it can expand to a statement",
271 );
272
273 if parser.token == token::Semi {
274 if let Ok(snippet) = parser.psess.source_map().span_to_snippet(site_span) {
275 e.span_suggestion_verbose(
276 site_span,
277 "surround the macro invocation with `{}` to interpret the expansion as a statement",
278 format!("{{ {snippet}; }}"),
279 Applicability::MaybeIncorrect,
280 );
281 }
282 } else {
283 e.span_suggestion_verbose(
284 site_span.shrink_to_hi(),
285 "add `;` to interpret the expansion as a statement",
286 ";",
287 Applicability::MaybeIncorrect,
288 );
289 }
290 }
291 },
292 _ => annotate_err_with_kind(&mut e, kind, site_span),
293 };
294 e.emit()
295}
296
297pub(crate) fn annotate_err_with_kind(err: &mut Diag<'_>, kind: AstFragmentKind, span: Span) {
298 match kind {
299 AstFragmentKind::Ty => {
300 err.span_label(span, "this macro call doesn't expand to a type");
301 }
302 AstFragmentKind::Pat => {
303 err.span_label(span, "this macro call doesn't expand to a pattern");
304 }
305 _ => {}
306 };
307}
308
309#[derive(Subdiagnostic)]
310enum ExplainDocComment {
311 #[label(expand_explain_doc_comment_inner)]
312 Inner {
313 #[primary_span]
314 span: Span,
315 },
316 #[label(expand_explain_doc_comment_outer)]
317 Outer {
318 #[primary_span]
319 span: Span,
320 },
321}
322
323pub(super) fn annotate_doc_comment(err: &mut Diag<'_>, sm: &SourceMap, span: Span) {
324 if let Ok(src) = sm.span_to_snippet(span) {
325 if src.starts_with("///") || src.starts_with("/**") {
326 err.subdiagnostic(ExplainDocComment::Outer { span });
327 } else if src.starts_with("//!") || src.starts_with("/*!") {
328 err.subdiagnostic(ExplainDocComment::Inner { span });
329 }
330 }
331}
332
333pub(super) fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> {
336 if let Some(expected_token) = expected_token {
337 Cow::from(format!("expected {}, found {}", token_descr(expected_token), token_descr(tok)))
338 } else {
339 match tok.kind {
340 token::Eof => Cow::from("unexpected end of macro invocation"),
341 _ => Cow::from(format!("no rules expected {}", token_descr(tok))),
342 }
343 }
344}