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