Skip to main content

rustc_attr_parsing/attributes/diagnostic/
mod.rs

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