Skip to main content

rustc_expand/mbe/
diagnostics.rs

1use std::borrow::Cow;
2
3use rustc_ast::token::{self, Token};
4use rustc_ast::tokenstream::TokenStream;
5use rustc_data_structures::fx::FxHashSet;
6use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage, pluralize};
7use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs};
8use rustc_macros::Subdiagnostic;
9use rustc_middle::bug;
10use rustc_parse::parser::{Parser, Recovery, token_descr};
11use rustc_session::parse::ParseSess;
12use rustc_span::source_map::SourceMap;
13use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span};
14use tracing::debug;
15
16use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
17use crate::expand::{AstFragmentKind, parse_ast_fragment};
18use crate::mbe::macro_parser::ParseResult::*;
19use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
20use crate::mbe::macro_rules::{
21    Tracker, WhichMatcher, try_match_macro, try_match_macro_attr, try_match_macro_derive,
22};
23
24pub(super) enum FailedMacro<'a> {
25    Func,
26    Attr(&'a TokenStream),
27    Derive,
28}
29
30pub(super) fn failed_to_match_macro(
31    psess: &ParseSess,
32    sp: Span,
33    def_span: Span,
34    name: Ident,
35    args: FailedMacro<'_>,
36    body: &TokenStream,
37    rules: &[MacroRule],
38    on_unmatched_args: Option<&Directive>,
39) -> (Span, ErrorGuaranteed) {
40    {
    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:40",
                        "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(40u32),
                        ::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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("failed to match macro")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("failed to match macro");
41    let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
42        psess.source_map().guess_head_span(def_span)
43    } else {
44        DUMMY_SP
45    };
46
47    // An error occurred, try the expansion again, tracking the expansion closely for better
48    // diagnostics.
49    let mut tracker = CollectTrackerAndEmitter::new(name, psess.dcx(), sp);
50
51    let try_success_result = match args {
52        FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker),
53        FailedMacro::Attr(attr_args) => {
54            try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
55        }
56        FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker),
57    };
58
59    if try_success_result.is_ok() {
60        // Nonterminal parser recovery might turn failed matches into successful ones,
61        // but for that it must have emitted an error already
62        if !tracker.dcx.has_errors().is_some() {
    {
        ::core::panicking::panic_fmt(format_args!("Macro matching returned a success on the second try"));
    }
};assert!(
63            tracker.dcx.has_errors().is_some(),
64            "Macro matching returned a success on the second try"
65        );
66    }
67
68    if let Some(result) = tracker.result {
69        // An irrecoverable error occurred and has been emitted.
70        return result;
71    }
72
73    let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
74    else {
75        return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro"));
76    };
77
78    let span = token.span.substitute_dummy(sp);
79    let CustomDiagnostic {
80        message: custom_message, label: custom_label, notes: custom_notes, ..
81    } = {
82        on_unmatched_args
83            .map(|directive| directive.eval(None, &FormatArgs { this: name.to_string(), .. }))
84            .unwrap_or_default()
85    };
86
87    let mut err = match custom_message {
88        Some(message) => psess.dcx().struct_span_err(span, message),
89        None => psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)),
90    };
91    err.span_label(span, custom_label.unwrap_or_else(|| label.to_string()));
92    if !def_head_span.is_dummy() {
93        err.span_label(def_head_span, "when calling this macro");
94    }
95
96    annotate_doc_comment(&mut err, psess.source_map(), span);
97
98    if let Some(span) = remaining_matcher.span() {
99        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}"));
100    } else {
101        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}"));
102    }
103    for note in custom_notes {
104        err.note(note);
105    }
106
107    if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
108        && (#[allow(non_exhaustive_omitted_patterns)] match expected_token.kind {
    token::OpenInvisible(_) => true,
    _ => false,
}matches!(expected_token.kind, token::OpenInvisible(_))
109            || #[allow(non_exhaustive_omitted_patterns)] match token.kind {
    token::OpenInvisible(_) => true,
    _ => false,
}matches!(token.kind, token::OpenInvisible(_)))
110    {
111        err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
112        err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
113
114        if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
115            err.help("try using `:tt` instead in the macro definition");
116        }
117    }
118
119    // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
120    if let FailedMacro::Func = args
121        && let Some((body, comma_span)) = body.add_comma()
122    {
123        for rule in rules {
124            let MacroRule::Func { lhs, .. } = rule else { continue };
125            let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed);
126            let mut tt_parser = TtParser::new();
127
128            if let Success(_) =
129                tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
130            {
131                if comma_span.is_dummy() {
132                    err.note("you might be missing a comma");
133                } else {
134                    err.span_suggestion_short(
135                        comma_span,
136                        "missing comma here",
137                        ", ",
138                        Applicability::MachineApplicable,
139                    );
140                }
141            }
142        }
143    }
144    let guar = err.emit();
145    (sp, guar)
146}
147
148/// The tracker used for the slow error path that collects useful info for diagnostics.
149struct CollectTrackerAndEmitter<'dcx, 'matcher> {
150    macro_name: Ident,
151    dcx: DiagCtxtHandle<'dcx>,
152
153    /// The matcher currently being parsed.
154    //
155    // FIXME: Factor out a per-arm `Tracker` so that the `Option` is unnecessary.
156    current: Option<(WhichMatcher, &'matcher [MatcherLoc])>,
157
158    /// Matches of [`MatcherLoc`]s that successfully consumed input from the parser.
159    ///
160    /// This accumulates all calls to [`Tracker::matched_one()`]. It is used to identify all
161    /// competing matches for ambiguity errors.
162    matches: FxHashSet<SuccessfulMatch>,
163
164    remaining_matcher: Option<&'matcher MatcherLoc>,
165    /// Which arm's failure should we report? (the one furthest along)
166    best_failure: Option<BestFailure>,
167    root_span: Span,
168    result: Option<(Span, ErrorGuaranteed)>,
169}
170
171#[derive(#[automatically_derived]
impl ::core::marker::Copy for SuccessfulMatch { }Copy, #[automatically_derived]
impl ::core::clone::Clone for SuccessfulMatch {
    #[inline]
    fn clone(&self) -> SuccessfulMatch {
        let _: ::core::clone::AssertParamIsClone<u32>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for SuccessfulMatch {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "SuccessfulMatch", "input_pos", &self.input_pos, "loc_index",
            &&self.loc_index)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for SuccessfulMatch {
    #[inline]
    fn eq(&self, other: &SuccessfulMatch) -> bool {
        self.input_pos == other.input_pos && self.loc_index == other.loc_index
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SuccessfulMatch {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u32>;
    }
}Eq, #[automatically_derived]
impl ::core::cmp::PartialOrd for SuccessfulMatch {
    #[inline]
    fn partial_cmp(&self, other: &SuccessfulMatch)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for SuccessfulMatch {
    #[inline]
    fn cmp(&self, other: &SuccessfulMatch) -> ::core::cmp::Ordering {
        match ::core::cmp::Ord::cmp(&self.input_pos, &other.input_pos) {
            ::core::cmp::Ordering::Equal =>
                ::core::cmp::Ord::cmp(&self.loc_index, &other.loc_index),
            cmp => cmp,
        }
    }
}Ord, #[automatically_derived]
impl ::core::hash::Hash for SuccessfulMatch {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.input_pos, state);
        ::core::hash::Hash::hash(&self.loc_index, state)
    }
}Hash)]
172struct SuccessfulMatch {
173    /// The position in the parser.
174    ///
175    /// As per [`Parser::approx_token_stream_pos()`].
176    input_pos: u32,
177
178    /// The index of the [`MatcherLoc`].
179    loc_index: u32,
180}
181
182struct BestFailure {
183    token: Token,
184
185    /// The matcher in which the failure occurred.
186    matcher: WhichMatcher,
187
188    /// The approximate (parser) position of the failure.
189    ///
190    /// This is relative to [`Self::matcher`].
191    position: u32,
192
193    msg: &'static str,
194    remaining_matcher: MatcherLoc,
195}
196
197impl BestFailure {
198    fn is_better_position(&self, matcher: WhichMatcher, position: u32) -> bool {
199        (matcher, position) > (self.matcher, self.position)
200    }
201}
202
203impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> {
204    fn prepare(&mut self, which_matcher: WhichMatcher, matcher: &'matcher [MatcherLoc]) {
205        if self.current.is_some() {
206            ::rustc_middle::util::bug::bug_fmt(format_args!("`Self::after_arm()` was not called to clean up context"));bug!("`Self::after_arm()` was not called to clean up context");
207        }
208
209        self.current = Some((which_matcher, matcher));
210    }
211
212    fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
213        if self.remaining_matcher.is_none()
214            || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
215        {
216            self.remaining_matcher = Some(matcher);
217        }
218    }
219
220    fn matched_one(&mut self, parser: &Parser<'_>, loc_index: usize) {
221        let input_pos = parser.approx_token_stream_pos();
222        let loc_index: u32 = loc_index.try_into().unwrap();
223        let m = SuccessfulMatch { input_pos, loc_index };
224        self.matches.insert(m);
225    }
226
227    fn after_arm(&mut self, result: &NamedParseResult) {
228        match *result {
229            Success(_) => {
230                // Nonterminal parser recovery might turn failed matches into successful ones,
231                // but for that it must have emitted an error already
232                self.dcx.span_delayed_bug(
233                    self.root_span,
234                    "should not collect detailed info for successful macro match",
235                );
236            }
237            Failure => {
238                if self.best_failure.is_none() {
239                    ::rustc_middle::util::bug::bug_fmt(format_args!("A matching failure occurred but `Self::failure()` was not called"));bug!("A matching failure occurred but `Self::failure()` was not called");
240                }
241            }
242            Ambiguity => {
243                if self.result.is_none() {
244                    ::rustc_middle::util::bug::bug_fmt(format_args!("An ambiguity error occurred but `Self::ambiguity()` was not called"));bug!("An ambiguity error occurred but `Self::ambiguity()` was not called");
245                }
246            }
247            ErrorReported(guar) => self.result = Some((self.root_span, guar)),
248        }
249
250        self.current = None;
251        self.matches.clear();
252    }
253
254    fn failure(&mut self, parser: &Parser<'_>) {
255        let Some((which_matcher, _)) = self.current else {
256            ::rustc_middle::util::bug::bug_fmt(format_args!("`Self::prepare()` was not called to initialize context"));bug!("`Self::prepare()` was not called to initialize context");
257        };
258
259        let mut token = parser.token;
260        let approx_position = parser.approx_token_stream_pos();
261        let msg = if token.kind == token::Eof {
262            // FIXME: Can this be factored out of the EOF case?
263            if !token.span.is_dummy() {
264                token.span = token.span.shrink_to_hi();
265            }
266            "missing tokens in macro arguments"
267        } else {
268            "no rules expected this token in macro call"
269        };
270
271        {
    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:271",
                        "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(271u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_expand::mbe::diagnostics"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("token")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("token");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("msg")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("msg");
                                            NAME.as_str()
                                        }], ::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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("a new failure of an arm")
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&token)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&msg)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!(?token, ?msg, "a new failure of an arm");
272
273        if self
274            .best_failure
275            .as_ref()
276            .is_none_or(|failure| failure.is_better_position(which_matcher, approx_position))
277        {
278            self.best_failure = Some(BestFailure {
279                token,
280                matcher: which_matcher,
281                position: approx_position,
282                msg,
283                remaining_matcher: self
284                    .remaining_matcher
285                    .expect("must have collected matcher already")
286                    .clone(),
287            })
288        }
289    }
290
291    fn ambiguity(&mut self, parser: &Parser<'_>) {
292        let Some((_, matcher)) = self.current else {
293            ::rustc_middle::util::bug::bug_fmt(format_args!("`Self::prepare()` was not called to initialize context"));bug!("`Self::prepare()` was not called to initialize context");
294        };
295
296        #[expect(
297            rustc::potential_query_instability,
298            reason = "sorting the results deterministically afterwards"
299        )]
300        let (mut bb_locs, mut next_locs) = self
301            .matches
302            .iter()
303            .filter(|m| m.input_pos == parser.approx_token_stream_pos())
304            .partition::<Vec<&SuccessfulMatch>, _>(|m| {
305                let loc = &matcher[m.loc_index as usize];
306                #[allow(non_exhaustive_omitted_patterns)] match loc {
    MatcherLoc::MetaVarDecl { .. } => true,
    _ => false,
}matches!(loc, MatcherLoc::MetaVarDecl { .. })
307            });
308
309        // Use a reasonable and deterministic ordering for data in the error message.
310        bb_locs.sort_unstable_by_key(|m| m.loc_index);
311        next_locs.sort_unstable_by_key(|m| m.loc_index);
312
313        let span = parser.token.span.substitute_dummy(self.root_span);
314
315        if parser.token == token::Eof {
316            let msg = "ambiguity: multiple successful parses".to_string();
317            let guar = self.dcx.span_err(span, msg);
318            self.result = Some((span, guar));
319            return;
320        }
321
322        let nts = bb_locs
323            .into_iter()
324            .map(|m| {
325                let loc = &matcher[m.loc_index as usize];
326                let MatcherLoc::MetaVarDecl { bind, kind, .. } = loc else { ::core::panicking::panic("internal error: entered unreachable code")unreachable!() };
327                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} (\'{1}\')", kind, bind))
    })format!("{kind} ('{bind}')")
328            })
329            .collect::<Vec<String>>()
330            .join(" or ");
331
332        let msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("local ambiguity when calling macro `{0}`: multiple parsing options: {1}",
                self.macro_name,
                match next_locs.len() {
                    0 =>
                        ::alloc::__export::must_use({
                                ::alloc::fmt::format(format_args!("built-in NTs {0}.", nts))
                            }),
                    n =>
                        ::alloc::__export::must_use({
                                ::alloc::fmt::format(format_args!("built-in NTs {1} or {2} other option{0}.",
                                        if n == 1 { "" } else { "s" }, nts, n))
                            }),
                }))
    })format!(
333            "local ambiguity when calling macro `{}`: multiple parsing options: {}",
334            self.macro_name,
335            match next_locs.len() {
336                0 => format!("built-in NTs {nts}."),
337                n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)),
338            }
339        );
340
341        let guar = self.dcx.span_err(span, msg);
342        self.result = Some((span, guar));
343    }
344
345    fn description() -> &'static str {
346        "detailed"
347    }
348
349    fn recovery() -> Recovery {
350        Recovery::Allowed
351    }
352}
353
354impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> {
355    fn new(macro_name: Ident, dcx: DiagCtxtHandle<'dcx>, root_span: Span) -> Self {
356        Self {
357            macro_name,
358            dcx,
359            current: None,
360            matches: FxHashSet::default(),
361            remaining_matcher: None,
362            best_failure: None,
363            root_span,
364            result: None,
365        }
366    }
367}
368
369pub(super) fn emit_frag_parse_err(
370    mut e: Diag<'_>,
371    parser: &mut Parser<'_>,
372    orig_parser: &mut Parser<'_>,
373    site_span: Span,
374    arm_span: Span,
375    kind: AstFragmentKind,
376    bindings: &[MacroRule],
377    matched_rule_bindings: &[MatcherLoc],
378) -> ErrorGuaranteed {
379    // FIXME(davidtwco): avoid depending on the error message text
380    if parser.token == token::Eof
381        && let DiagMessage::Str(message) = &e.messages[0].0
382        && message.ends_with(", found `<eof>`")
383    {
384        let msg = &e.messages[0];
385        e.messages[0] = (
386            DiagMessage::from(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("macro expansion ends with an incomplete expression: {0}",
                message.replace(", found `<eof>`", "")))
    })format!(
387                "macro expansion ends with an incomplete expression: {}",
388                message.replace(", found `<eof>`", ""),
389            )),
390            msg.1,
391        );
392        if !e.span.is_dummy() {
393            // early end of macro arm (#52866)
394            e.replace_span_with(parser.token.span.shrink_to_hi(), true);
395        }
396    }
397    if e.span.is_dummy() {
398        // Get around lack of span in error (#30128)
399        e.replace_span_with(site_span, true);
400        if !parser.psess.source_map().is_imported(arm_span) {
401            e.span_label(arm_span, "in this macro arm");
402        }
403    } else if parser.psess.source_map().is_imported(parser.token.span) {
404        e.span_label(site_span, "in this macro invocation");
405    }
406    match kind {
407        // Try a statement if an expression is wanted but failed and suggest adding `;` to call.
408        AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
409            Err(err) => err.cancel(),
410            Ok(_) => {
411                e.note(
412                    "the macro call doesn't expand to an expression, but it can expand to a statement",
413                );
414
415                if parser.token == token::Semi {
416                    if let Ok(snippet) = parser.psess.source_map().span_to_snippet(site_span) {
417                        e.span_suggestion_verbose(
418                            site_span,
419                            "surround the macro invocation with `{}` to interpret the expansion as a statement",
420                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{ {0}; }}", snippet))
    })format!("{{ {snippet}; }}"),
421                            Applicability::MaybeIncorrect,
422                        );
423                    }
424                } else {
425                    e.span_suggestion_verbose(
426                        site_span.shrink_to_hi(),
427                        "add `;` to interpret the expansion as a statement",
428                        ";",
429                        Applicability::MaybeIncorrect,
430                    );
431                }
432            }
433        },
434        _ => annotate_err_with_kind(&mut e, kind, site_span),
435    };
436
437    if parser.token.kind == token::Dollar {
438        let dollar_span = parser.token.span;
439        parser.bump();
440        if let token::Ident(name, _) = parser.token.kind {
441            let metavar_span = dollar_span.to(parser.token.span);
442            let mut bindings_names = ::alloc::vec::Vec::new()vec![];
443            for rule in bindings {
444                let MacroRule::Func { lhs, .. } = rule else { continue };
445                for param in lhs {
446                    let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
447                    bindings_names.push(bind.name);
448                }
449            }
450
451            let mut matched_rule_bindings_names = ::alloc::vec::Vec::new()vec![];
452            for param in matched_rule_bindings {
453                let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
454                matched_rule_bindings_names.push(bind.name);
455            }
456
457            // Report the unbound metavariable as the primary error up front, so every
458            // case is consistent regardless of which suggestion (if any) we attach below.
459            e.primary_message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot find macro parameter `${0}` in this scope",
                name))
    })format!("cannot find macro parameter `${name}` in this scope"));
460            e.span(metavar_span);
461            e.span_label(metavar_span, "not found in this scope");
462            if parser.psess.source_map().is_imported(metavar_span) {
463                e.span_label(site_span, "in this macro invocation");
464            }
465
466            if let Some(matched_name) = rustc_span::edit_distance::find_best_match_for_name(
467                &matched_rule_bindings_names[..],
468                name,
469                None,
470            ) {
471                e.span_suggestion_verbose(
472                    parser.token.span,
473                    "there is a macro metavariable with a similar name",
474                    matched_name,
475                    Applicability::MaybeIncorrect,
476                );
477            } else if bindings_names.contains(&name) {
478                e.span_label(
479                    parser.token.span,
480                    "there is an macro metavariable with this name in another macro matcher",
481                );
482            } else if let Some(matched_name) =
483                rustc_span::edit_distance::find_best_match_for_name(&bindings_names[..], name, None)
484            {
485                e.span_suggestion_verbose(
486                    parser.token.span,
487                    "there is a macro metavariable with a similar name in another macro matcher",
488                    matched_name,
489                    Applicability::MaybeIncorrect,
490                );
491            } else if !matched_rule_bindings_names.is_empty() {
492                let msg = matched_rule_bindings_names
493                    .iter()
494                    .map(|sym| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("${0}", sym))
    })format!("${}", sym))
495                    .collect::<Vec<_>>()
496                    .join(", ");
497                e.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("available metavariable names are: {0}",
                msg))
    })format!("available metavariable names are: {msg}"));
498            }
499        }
500    }
501    e.emit()
502}
503
504pub(crate) fn annotate_err_with_kind(err: &mut Diag<'_>, kind: AstFragmentKind, span: Span) {
505    match kind {
506        AstFragmentKind::Ty => {
507            err.span_label(span, "this macro call doesn't expand to a type");
508        }
509        AstFragmentKind::Pat => {
510            err.span_label(span, "this macro call doesn't expand to a pattern");
511        }
512        _ => {}
513    };
514}
515
516#[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)]
517enum ExplainDocComment {
518    #[label(
519        "inner doc comments expand to `#![doc = \"...\"]`, which is what this macro attempted to match"
520    )]
521    Inner {
522        #[primary_span]
523        span: Span,
524    },
525    #[label(
526        "outer doc comments expand to `#[doc = \"...\"]`, which is what this macro attempted to match"
527    )]
528    Outer {
529        #[primary_span]
530        span: Span,
531    },
532}
533
534fn annotate_doc_comment(err: &mut Diag<'_>, sm: &SourceMap, span: Span) {
535    if let Ok(src) = sm.span_to_snippet(span) {
536        if src.starts_with("///") || src.starts_with("/**") {
537            err.subdiagnostic(ExplainDocComment::Outer { span });
538        } else if src.starts_with("//!") || src.starts_with("/*!") {
539            err.subdiagnostic(ExplainDocComment::Inner { span });
540        }
541    }
542}
543
544/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
545/// other tokens, this is "unexpected token...".
546fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> {
547    if let Some(expected_token) = expected_token {
548        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)))
549    } else {
550        match tok.kind {
551            token::Eof => Cow::from("unexpected end of macro invocation"),
552            _ => 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))),
553        }
554    }
555}