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