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