rustc_expand/mbe/
diagnostics.rs

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    // An error occurred, try the expansion again, tracking the expansion closely for better
28    // diagnostics.
29    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        // Nonterminal parser recovery might turn failed matches into successful ones,
35        // but for that it must have emitted an error already
36        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        // An irrecoverable error occurred and has been emitted.
44        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    // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
81    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
106/// The tracker used for the slow error path that collects useful info for diagnostics.
107struct CollectTrackerAndEmitter<'dcx, 'matcher> {
108    dcx: DiagCtxtHandle<'dcx>,
109    remaining_matcher: Option<&'matcher MatcherLoc>,
110    /// Which arm's failure should we report? (the one furthest along)
111    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                // Nonterminal parser recovery might turn failed matches into successful ones,
148                // but for that it must have emitted an error already
149                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
197/// Currently used by macro_rules! compilation to extract a little information from the `Failure`
198/// case.
199pub(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    // FIXME(davidtwco): avoid depending on the error message text
238    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            // early end of macro arm (#52866)
252            e.replace_span_with(parser.token.span.shrink_to_hi(), true);
253        }
254    }
255    if e.span.is_dummy() {
256        // Get around lack of span in error (#30128)
257        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        // Try a statement if an expression is wanted but failed and suggest adding `;` to call.
266        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
333/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
334/// other tokens, this is "unexpected token...".
335pub(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}