Skip to main content

rustc_attr_parsing/attributes/diagnostic/
mod.rs

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