Skip to main content

rustc_attr_parsing/attributes/diagnostic/
mod.rs

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