Skip to main content

rustc_attr_parsing/attributes/diagnostic/
mod.rs

1use std::ops::Range;
2
3use rustc_hir::attrs::diagnostic::{
4    Directive, Filter, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name,
5    NameValue, Piece, Predicate,
6};
7use rustc_parse_format::{
8    Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
9};
10use rustc_session::lint::builtin::{
11    MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
12};
13use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym};
14use thin_vec::{ThinVec, thin_vec};
15
16use crate::context::AcceptContext;
17use crate::diagnostics::{
18    DupesNotAllowed, FormatWarning, IgnoredDiagnosticOption, InvalidOnClause,
19    MalFormedDiagnosticAttributeLint, MissingOptionsForDiagnosticAttribute,
20    NonMetaItemDiagnosticAttribute, WrappedParserError,
21};
22use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser};
23
24pub(crate) mod do_not_recommend;
25pub(crate) mod on_const;
26pub(crate) mod on_move;
27pub(crate) mod on_type_error;
28pub(crate) mod on_unimplemented;
29pub(crate) mod on_unknown;
30pub(crate) mod on_unmatched_args;
31pub(crate) mod opaque;
32
33#[derive(#[automatically_derived]
impl ::core::marker::Copy for Mode { }Copy, #[automatically_derived]
impl ::core::clone::Clone for Mode {
    #[inline]
    fn clone(&self) -> Mode { *self }
}Clone)]
34pub(crate) enum Mode {
35    /// `#[rustc_on_unimplemented]`
36    RustcOnUnimplemented,
37    /// `#[diagnostic::on_unimplemented]`
38    DiagnosticOnUnimplemented,
39    /// `#[diagnostic::on_const]`
40    DiagnosticOnConst,
41    /// `#[diagnostic::on_move]`
42    DiagnosticOnMove,
43    /// `#[diagnostic::on_unknown]`
44    DiagnosticOnUnknown,
45    /// `#[diagnostic::on_unmatched_args]`
46    DiagnosticOnUnmatchedArgs,
47    /// `#[diagnostic::on_type_error]`
48    DiagnosticOnTypeError,
49}
50
51impl Mode {
52    fn as_str(&self) -> &'static str {
53        match self {
54            Self::RustcOnUnimplemented => "rustc_on_unimplemented",
55            Self::DiagnosticOnUnimplemented => "diagnostic::on_unimplemented",
56            Self::DiagnosticOnConst => "diagnostic::on_const",
57            Self::DiagnosticOnMove => "diagnostic::on_move",
58            Self::DiagnosticOnUnknown => "diagnostic::on_unknown",
59            Self::DiagnosticOnUnmatchedArgs => "diagnostic::on_unmatched_args",
60            Self::DiagnosticOnTypeError => "diagnostic::on_type_error",
61        }
62    }
63
64    fn expected_options(&self) -> &'static str {
65        const DEFAULT: &str =
66            "at least one of the `message`, `note` and `label` options are expected";
67        const DIAGNOSTIC_ON_TYPE_ERROR_EXPECTED_OPTIONS: &str =
68            "at least a single `note` option is expected";
69        match self {
70            Self::RustcOnUnimplemented => {
71                "see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
72            }
73            Self::DiagnosticOnUnimplemented => DEFAULT,
74            Self::DiagnosticOnConst => DEFAULT,
75            Self::DiagnosticOnMove => DEFAULT,
76            Self::DiagnosticOnUnknown => DEFAULT,
77            Self::DiagnosticOnUnmatchedArgs => DEFAULT,
78            Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_EXPECTED_OPTIONS,
79        }
80    }
81
82    fn allowed_options(&self) -> &'static str {
83        const DEFAULT: &str = "only `message`, `note` and `label` are allowed as options";
84        const DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS: &str =
85            "only `note` is allowed as option for `diagnostic::on_type_error`";
86        match self {
87            Self::RustcOnUnimplemented => {
88                "see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented>"
89            }
90            Self::DiagnosticOnUnimplemented => DEFAULT,
91            Self::DiagnosticOnConst => DEFAULT,
92            Self::DiagnosticOnMove => DEFAULT,
93            Self::DiagnosticOnUnknown => DEFAULT,
94            Self::DiagnosticOnUnmatchedArgs => DEFAULT,
95            Self::DiagnosticOnTypeError => DIAGNOSTIC_ON_TYPE_ERROR_ALLOWED_OPTIONS,
96        }
97    }
98
99    fn allowed_format_arguments(&self) -> &'static str {
100        match self {
101            Self::RustcOnUnimplemented => {
102                "see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented> for allowed format arguments"
103            }
104            Self::DiagnosticOnUnimplemented => {
105                "only `Self` and generics of the trait are allowed as a format argument"
106            }
107            Self::DiagnosticOnConst => {
108                "only `Self` and generics of the implementation are allowed as a format argument"
109            }
110            Self::DiagnosticOnMove => {
111                "only `This`, `Self` and generics of the type are allowed as a format argument"
112            }
113            Self::DiagnosticOnUnknown => {
114                "only `This` is allowed as a format argument, referring to the failed import"
115            }
116            Self::DiagnosticOnUnmatchedArgs => {
117                "only `This` is allowed as a format argument, referring to the macro's name"
118            }
119            Self::DiagnosticOnTypeError => {
120                "only `note` is allowed as option for `diagnostic::on_type_error`"
121            }
122        }
123    }
124}
125
126fn merge_directives(
127    cx: &mut AcceptContext<'_, '_>,
128    first: &mut Option<(Span, Directive)>,
129    later: (Span, Directive),
130) {
131    if let Some((_, first)) = first {
132        if first.is_rustc_attr || later.1.is_rustc_attr {
133            cx.emit_err(DupesNotAllowed);
134        }
135
136        merge(cx, &mut first.message, later.1.message, sym::message);
137        merge(cx, &mut first.label, later.1.label, sym::label);
138        first.notes.extend(later.1.notes);
139    } else {
140        *first = Some(later);
141    }
142}
143
144fn merge<T>(
145    cx: &mut AcceptContext<'_, '_>,
146    first: &mut Option<(Span, T)>,
147    later: Option<(Span, T)>,
148    option_name: Symbol,
149) {
150    match (first, later) {
151        (Some(_) | None, None) => {}
152        (Some((first_span, _)), Some((later_span, _))) => {
153            let first_span = *first_span;
154            cx.emit_lint(
155                MALFORMED_DIAGNOSTIC_ATTRIBUTES,
156                IgnoredDiagnosticOption { first_span, later_span, option_name },
157                later_span,
158            );
159        }
160        (first @ None, Some(later)) => {
161            first.get_or_insert(later);
162        }
163    }
164}
165
166fn parse_list<'p>(
167    cx: &mut AcceptContext<'_, '_>,
168    args: &'p ArgParser,
169    mode: Mode,
170) -> Option<&'p MetaItemListParser> {
171    let span = cx.attr_span;
172    match args {
173        ArgParser::List(items) if items.len() != 0 => return Some(items),
174        ArgParser::List(list) => {
175            // We're dealing with `#[diagnostic::attr()]`.
176            // This can be because that is what the user typed, but that's also what we'd see
177            // if the user used non-metaitem syntax. See `ArgParser::from_attr_args`.
178            cx.emit_lint(
179                MALFORMED_DIAGNOSTIC_ATTRIBUTES,
180                NonMetaItemDiagnosticAttribute,
181                list.span,
182            );
183        }
184        ArgParser::NoArgs => {
185            cx.emit_lint(
186                MALFORMED_DIAGNOSTIC_ATTRIBUTES,
187                MissingOptionsForDiagnosticAttribute {
188                    attribute: mode.as_str(),
189                    options: mode.expected_options(),
190                },
191                span,
192            );
193        }
194        ArgParser::NameValue(_) => {
195            cx.emit_lint(
196                MALFORMED_DIAGNOSTIC_ATTRIBUTES,
197                MalFormedDiagnosticAttributeLint {
198                    attribute: mode.as_str(),
199                    options: mode.allowed_options(),
200                    span,
201                },
202                span,
203            );
204        }
205    }
206    None
207}
208
209fn parse_directive_items<'p>(
210    cx: &mut AcceptContext<'_, '_>,
211    mode: Mode,
212    items: impl Iterator<Item = &'p MetaItemOrLitParser>,
213    is_root: bool,
214) -> Option<Directive> {
215    let mut message: Option<(Span, _)> = None;
216    let mut label: Option<(Span, _)> = None;
217    let mut notes = ThinVec::new();
218    let mut parent_label = None;
219    let mut filters = ThinVec::new();
220
221    for item in items {
222        let span = item.span();
223
224        macro malformed() {{
225            cx.emit_lint(
226                MALFORMED_DIAGNOSTIC_ATTRIBUTES,
227                MalFormedDiagnosticAttributeLint {
228                    attribute: mode.as_str(),
229                    options: mode.allowed_options(),
230                    span,
231                },
232                span,
233            );
234            continue;
235        }}
236
237        macro or_malformed($($code:tt)*) {{
238            let Some(ret) = (
239                try {
240                    $($code)*
241                }
242            ) else {
243                malformed!()
244            };
245            ret
246        }}
247
248        macro duplicate($name: ident, $($first_span:tt)*) {{
249            let first_span = $($first_span)*;
250            cx.emit_lint(
251                MALFORMED_DIAGNOSTIC_ATTRIBUTES,
252                IgnoredDiagnosticOption {
253                    first_span,
254                    later_span: span,
255                    option_name: $name,
256                },
257                span,
258            );
259        }}
260
261        let item: &MetaItemParser = {
    let Some(ret) =
        (try {
                item.meta_item()?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(item.meta_item()?);
262        let name = {
    let Some(ret) =
        (try {
                item.ident()?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(item.ident()?).name;
263
264        // Currently, as of April 2026, all arguments of all diagnostic attrs
265        // must have a value, like `message = "message"`. Thus in a well-formed
266        // diagnostic attribute this is never `None`.
267        //
268        // But we don't assert its presence yet because we don't want to mention it
269        // if someone does something like `#[diagnostic::on_unimplemented(doesnt_exist)]`.
270        // That happens in the big `match` below.
271        let value: Option<Ident> = match item.args().as_name_value() {
272            Some(nv) => Some({
    let Some(ret) =
        (try {
                nv.value_as_ident()?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(nv.value_as_ident()?)),
273            None => None,
274        };
275
276        let mut parse_format = |input: Ident| {
277            let snippet = cx.sess.source_map().span_to_snippet(input.span).ok();
278            let is_snippet = snippet.is_some();
279            match parse_format_string(input.name, snippet, input.span, mode) {
280                Ok((f, warnings)) => {
281                    for warning in warnings {
282                        let (FormatWarning::InvalidSpecifier { span }
283                        | FormatWarning::PositionalArgument { span }
284                        | FormatWarning::IndexedArgument { span }
285                        | FormatWarning::DisallowedPlaceholder { span, .. }) = warning;
286                        cx.emit_lint(MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, warning, span);
287                    }
288
289                    f
290                }
291                Err(e) => {
292                    cx.emit_lint(
293                        MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
294                        WrappedParserError {
295                            description: e.description,
296                            label: e.label,
297                            span: slice_span(input.span, e.span.clone(), is_snippet),
298                        },
299                        input.span,
300                    );
301                    // We could not parse the input, just use it as-is.
302                    FormatString {
303                        input: input.name,
304                        span: input.span,
305                        pieces: {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(Piece::Lit(input.name));
    vec
}thin_vec![Piece::Lit(input.name)],
306                    }
307                }
308            }
309        };
310        match (mode, name) {
311            (
312                Mode::RustcOnUnimplemented
313                | Mode::DiagnosticOnUnimplemented
314                | Mode::DiagnosticOnConst
315                | Mode::DiagnosticOnMove
316                | Mode::DiagnosticOnUnknown
317                | Mode::DiagnosticOnUnmatchedArgs,
318                sym::message,
319            ) => {
320                let value = {
    let Some(ret) =
        (try {
                value?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(value?);
321                if let Some(message) = &message {
322                    {
    let first_span = message.0;
    cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
        IgnoredDiagnosticOption {
            first_span,
            later_span: span,
            option_name: name,
        }, span);
}duplicate!(name, message.0)
323                } else {
324                    message = Some((item.span(), parse_format(value)));
325                }
326            }
327            (
328                Mode::RustcOnUnimplemented
329                | Mode::DiagnosticOnUnimplemented
330                | Mode::DiagnosticOnConst
331                | Mode::DiagnosticOnMove
332                | Mode::DiagnosticOnUnknown
333                | Mode::DiagnosticOnUnmatchedArgs,
334                sym::label,
335            ) => {
336                let value = {
    let Some(ret) =
        (try {
                value?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(value?);
337                if let Some(label) = &label {
338                    {
    let first_span = label.0;
    cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
        IgnoredDiagnosticOption {
            first_span,
            later_span: span,
            option_name: name,
        }, span);
}duplicate!(name, label.0)
339                } else {
340                    label = Some((item.span(), parse_format(value)));
341                }
342            }
343            (_, sym::note) => {
344                let value = {
    let Some(ret) =
        (try {
                value?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(value?);
345                notes.push(parse_format(value))
346            }
347            (Mode::RustcOnUnimplemented, sym::parent_label) => {
348                let value = {
    let Some(ret) =
        (try {
                value?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(value?);
349                if parent_label.is_none() {
350                    parent_label = Some(parse_format(value));
351                } else {
352                    {
    let first_span = span;
    cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
        IgnoredDiagnosticOption {
            first_span,
            later_span: span,
            option_name: name,
        }, span);
}duplicate!(name, span)
353                }
354            }
355            (Mode::RustcOnUnimplemented, sym::on) => {
356                if is_root {
357                    let items = {
    let Some(ret) =
        (try {
                item.args().as_list()?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(item.args().as_list()?);
358                    let mut iter = items.mixed();
359                    let filter: &MetaItemOrLitParser = match iter.next() {
360                        Some(c) => c,
361                        None => {
362                            cx.emit_err(InvalidOnClause::Empty { span });
363                            continue;
364                        }
365                    };
366
367                    let filter = parse_filter(filter);
368
369                    if items.len() < 2 {
370                        // Something like `#[rustc_on_unimplemented(on(.., /* nothing */))]`
371                        // There's a filter but no directive behind it, this is a mistake.
372                        {
    cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
        MalFormedDiagnosticAttributeLint {
            attribute: mode.as_str(),
            options: mode.allowed_options(),
            span,
        }, span);
    continue;
};malformed!();
373                    }
374
375                    match filter {
376                        Ok(filter) => {
377                            let directive =
378                                {
    let Some(ret) =
        (try {
                parse_directive_items(cx, mode, iter, false)?
            }) else {
            {
                cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                    MalFormedDiagnosticAttributeLint {
                        attribute: mode.as_str(),
                        options: mode.allowed_options(),
                        span,
                    }, span);
                continue;
            }
        };
    ret
}or_malformed!(parse_directive_items(cx, mode, iter, false)?);
379                            filters.push((filter, directive));
380                        }
381                        Err(e) => {
382                            cx.emit_err(e);
383                        }
384                    }
385                } else {
386                    {
    cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
        MalFormedDiagnosticAttributeLint {
            attribute: mode.as_str(),
            options: mode.allowed_options(),
            span,
        }, span);
    continue;
};malformed!();
387                }
388            }
389            _other => {
390                {
    cx.emit_lint(MALFORMED_DIAGNOSTIC_ATTRIBUTES,
        MalFormedDiagnosticAttributeLint {
            attribute: mode.as_str(),
            options: mode.allowed_options(),
            span,
        }, span);
    continue;
};malformed!();
391            }
392        }
393    }
394
395    Some(Directive {
396        is_rustc_attr: #[allow(non_exhaustive_omitted_patterns)] match mode {
    Mode::RustcOnUnimplemented => true,
    _ => false,
}matches!(mode, Mode::RustcOnUnimplemented),
397        filters,
398        message,
399        label,
400        notes,
401        parent_label,
402    })
403}
404
405pub(crate) fn parse_format_string(
406    input: Symbol,
407    snippet: Option<String>,
408    span: Span,
409    mode: Mode,
410) -> Result<(FormatString, Vec<FormatWarning>), ParseError> {
411    let s = input.as_str();
412    let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic);
413    let pieces: Vec<_> = parser.by_ref().collect();
414
415    if let Some(err) = parser.errors.into_iter().next() {
416        return Err(err);
417    }
418    let mut warnings = Vec::new();
419
420    let pieces = pieces
421        .into_iter()
422        .map(|piece| match piece {
423            RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)),
424            RpfPiece::NextArgument(arg) => {
425                Piece::Arg(parse_arg(&arg, mode, &mut warnings, span, parser.is_source_literal))
426            }
427        })
428        .collect();
429
430    Ok((FormatString { input, pieces, span }, warnings))
431}
432
433fn parse_arg(
434    arg: &Argument<'_>,
435    mode: Mode,
436    warnings: &mut Vec<FormatWarning>,
437    input_span: Span,
438    is_source_literal: bool,
439) -> FormatArg {
440    let span = slice_span(input_span, arg.position_span.clone(), is_source_literal);
441
442    let mut check_format = true;
443
444    let ret = match arg.position {
445        // Something like "hello {name}"
446        Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) {
447            (Mode::RustcOnUnimplemented, sym::ItemContext) => FormatArg::ItemContext,
448
449            // `{This:ty}`
450            (Mode::RustcOnUnimplemented, sym::This) => match arg.format.ty {
451                "resolved" => {
452                    check_format = false;
453                    FormatArg::ThisResolved
454                }
455                "path" => {
456                    check_format = false;
457                    FormatArg::ThisPath
458                }
459                _ => FormatArg::This,
460            },
461
462            (Mode::DiagnosticOnTypeError, sym::Found) => FormatArg::Found,
463            (Mode::DiagnosticOnTypeError, sym::Expected) => FormatArg::Expected,
464            (Mode::DiagnosticOnUnknown, sym::Unresolved) => FormatArg::Unresolved,
465
466            // Some diagnostic attributes can use `{This}` to refer to the annotated item.
467            // For those that don't, we continue and maybe use it as a generic parameter.
468            //
469            // FIXME(mejrs) `DiagnosticOnUnimplemented` is intentionally not here;
470            // that requires lang approval which is best kept for a standalone PR.
471            (
472                Mode::DiagnosticOnUnknown
473                | Mode::DiagnosticOnMove
474                | Mode::DiagnosticOnUnmatchedArgs
475                | Mode::DiagnosticOnTypeError,
476                sym::This,
477            ) => FormatArg::This,
478
479            // `{Self}`; the self type.
480            // - For trait declaration attributes that's the type that does not implement it.
481            // - for trait impl attributes, the implemented for type.
482            // - For ADT attributes, that's the type (which will be identical to `{This}`)
483            // - For everything else it doesn't make sense.
484            (
485                Mode::RustcOnUnimplemented
486                | Mode::DiagnosticOnUnimplemented
487                | Mode::DiagnosticOnMove
488                | Mode::DiagnosticOnConst,
489                kw::SelfUpper,
490            ) => FormatArg::SelfUpper,
491
492            // Generic parameters.
493            // FIXME(mejrs) unfortunately, all the "special" symbols above might fall through,
494            // but at this time we are not aware of what generic parameters the trait actually has.
495            // If we find `ItemContext` or something we have to assume that's a generic parameter.
496            // We lint against that in `check_attr.rs` though.
497            (
498                Mode::RustcOnUnimplemented
499                | Mode::DiagnosticOnUnimplemented
500                | Mode::DiagnosticOnMove
501                | Mode::DiagnosticOnConst
502                | Mode::DiagnosticOnTypeError,
503                generic_param,
504            ) => FormatArg::GenericParam { generic_param, span },
505
506            // Generics are explicitly not allowed, we print those back as is.
507            (Mode::DiagnosticOnUnknown | Mode::DiagnosticOnUnmatchedArgs, as_is) => {
508                warnings.push(FormatWarning::DisallowedPlaceholder {
509                    span,
510                    attr: mode.as_str(),
511                    allowed: mode.allowed_format_arguments(),
512                });
513                FormatArg::AsIs(Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{{0}}}", as_is))
    })format!("{{{as_is}}}")))
514            }
515        },
516
517        // `{1}` and `{}` are ignored
518        Position::ArgumentIs(idx) => {
519            warnings.push(FormatWarning::IndexedArgument { span });
520            FormatArg::AsIs(Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{{0}}}", idx))
    })format!("{{{idx}}}")))
521        }
522        Position::ArgumentImplicitlyIs(_) => {
523            warnings.push(FormatWarning::PositionalArgument { span });
524            FormatArg::AsIs(sym::empty_braces)
525        }
526    };
527    if check_format {
528        warn_on_format_spec(&arg.format, warnings, input_span, is_source_literal);
529    }
530    ret
531}
532
533/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
534/// with specifiers, so emit a warning if they are used.
535fn warn_on_format_spec(
536    spec: &FormatSpec<'_>,
537    warnings: &mut Vec<FormatWarning>,
538    input_span: Span,
539    is_source_literal: bool,
540) {
541    if let Some(ty_span) = &spec.ty_span {
542        let span = slice_span(input_span, ty_span.clone(), is_source_literal);
543        warnings.push(FormatWarning::InvalidSpecifier { span })
544    }
545}
546
547fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span {
548    if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
549}
550
551pub(crate) fn parse_filter(input: &MetaItemOrLitParser) -> Result<Filter, InvalidOnClause> {
552    let span = input.span();
553    let pred = parse_predicate(input)?;
554    Ok(Filter { span, pred })
555}
556
557fn parse_predicate(input: &MetaItemOrLitParser) -> Result<Predicate, InvalidOnClause> {
558    let Some(meta_item) = input.meta_item() else {
559        return Err(InvalidOnClause::UnsupportedLiteral { span: input.span() });
560    };
561
562    let Some(predicate) = meta_item.ident() else {
563        return Err(InvalidOnClause::ExpectedIdentifier {
564            span: meta_item.path().span(),
565            path: meta_item.path().get_attribute_path(),
566        });
567    };
568
569    match meta_item.args() {
570        ArgParser::List(mis) => match predicate.name {
571            sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis)?)),
572            sym::all => Ok(Predicate::All(parse_predicate_sequence(mis)?)),
573            sym::not => {
574                if let Some(single) = mis.as_single() {
575                    Ok(Predicate::Not(Box::new(parse_predicate(single)?)))
576                } else {
577                    Err(InvalidOnClause::ExpectedOnePredInNot { span: mis.span })
578                }
579            }
580            invalid_pred => {
581                Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred })
582            }
583        },
584        ArgParser::NameValue(p) => {
585            let Some(value) = p.value_as_ident() else {
586                return Err(InvalidOnClause::UnsupportedLiteral { span: p.args_span() });
587            };
588            let name = parse_name(predicate.name);
589            let value = parse_filter_format(value.name);
590            let kv = NameValue { name, value };
591            Ok(Predicate::Match(kv))
592        }
593        ArgParser::NoArgs => {
594            let flag = parse_flag(predicate)?;
595            Ok(Predicate::Flag(flag))
596        }
597    }
598}
599
600fn parse_predicate_sequence(
601    sequence: &MetaItemListParser,
602) -> Result<ThinVec<Predicate>, InvalidOnClause> {
603    sequence.mixed().map(parse_predicate).collect()
604}
605
606fn parse_flag(Ident { name, span }: Ident) -> Result<Flag, InvalidOnClause> {
607    match name {
608        sym::crate_local => Ok(Flag::CrateLocal),
609        sym::direct => Ok(Flag::Direct),
610        sym::from_desugaring => Ok(Flag::FromDesugaring),
611        invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }),
612    }
613}
614
615fn parse_name(name: Symbol) -> Name {
616    match name {
617        kw::SelfUpper => Name::SelfUpper,
618        sym::from_desugaring => Name::FromDesugaring,
619        sym::cause => Name::Cause,
620        generic => Name::GenericArg(generic),
621    }
622}
623
624fn parse_filter_format(input: Symbol) -> FilterFormatString {
625    let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic)
626        .map(|p| match p {
627            RpfPiece::Lit(s) => LitOrArg::Lit(Symbol::intern(s)),
628            // We just ignore formatspecs here
629            RpfPiece::NextArgument(a) => match a.position {
630                // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even
631                // if the integer type has been resolved, to allow targeting all integers.
632                // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet,
633                // from the `Display` impl of `InferTy` to be precise.
634                // `"{union|enum|struct}"` is used as a special selector for ADTs.
635                //
636                // Don't try to format these later!
637                Position::ArgumentNamed(
638                    arg @ ("integer" | "integral" | "float" | "union" | "enum" | "struct"),
639                ) => LitOrArg::Lit(Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{{0}}}", arg))
    })format!("{{{arg}}}"))),
640
641                Position::ArgumentNamed(arg) => LitOrArg::Arg(Symbol::intern(arg)),
642                Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(sym::empty_braces),
643                Position::ArgumentIs(idx) => LitOrArg::Lit(Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{{0}}}", idx))
    })format!("{{{idx}}}"))),
644            },
645        })
646        .collect();
647    FilterFormatString { pieces }
648}