rustc_trait_selection/error_reporting/traits/
on_unimplemented.rs

1use std::iter;
2use std::path::PathBuf;
3
4use rustc_ast::MetaItemInner;
5use rustc_data_structures::fx::FxHashMap;
6use rustc_errors::codes::*;
7use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
8use rustc_hir::def_id::{DefId, LocalDefId};
9use rustc_hir::{AttrArgs, AttrKind, Attribute};
10use rustc_macros::LintDiagnostic;
11use rustc_middle::bug;
12use rustc_middle::ty::print::PrintTraitRefExt as _;
13use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt};
14use rustc_parse_format::{ParseMode, Parser, Piece, Position};
15use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
16use rustc_span::{Ident, Span, Symbol, kw, sym};
17use tracing::{debug, info};
18use {rustc_attr_parsing as attr, rustc_hir as hir};
19
20use super::{ObligationCauseCode, PredicateObligation};
21use crate::error_reporting::TypeErrCtxt;
22use crate::errors::{
23    EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
24};
25use crate::infer::InferCtxtExt;
26
27/// The symbols which are always allowed in a format string
28static ALLOWED_FORMAT_SYMBOLS: &[Symbol] = &[
29    kw::SelfUpper,
30    sym::ItemContext,
31    sym::from_desugaring,
32    sym::direct,
33    sym::cause,
34    sym::integral,
35    sym::integer_,
36    sym::float,
37    sym::_Self,
38    sym::crate_local,
39    sym::Trait,
40];
41
42impl<'tcx> TypeErrCtxt<'_, 'tcx> {
43    fn impl_similar_to(
44        &self,
45        trait_pred: ty::PolyTraitPredicate<'tcx>,
46        obligation: &PredicateObligation<'tcx>,
47    ) -> Option<(DefId, GenericArgsRef<'tcx>)> {
48        let tcx = self.tcx;
49        let param_env = obligation.param_env;
50        self.enter_forall(trait_pred, |trait_pred| {
51            let trait_self_ty = trait_pred.self_ty();
52
53            let mut self_match_impls = vec![];
54            let mut fuzzy_match_impls = vec![];
55
56            self.tcx.for_each_relevant_impl(trait_pred.def_id(), trait_self_ty, |def_id| {
57                let impl_args = self.fresh_args_for_item(obligation.cause.span, def_id);
58                let impl_trait_ref =
59                    tcx.impl_trait_ref(def_id).unwrap().instantiate(tcx, impl_args);
60
61                let impl_self_ty = impl_trait_ref.self_ty();
62
63                if self.can_eq(param_env, trait_self_ty, impl_self_ty) {
64                    self_match_impls.push((def_id, impl_args));
65
66                    if iter::zip(
67                        trait_pred.trait_ref.args.types().skip(1),
68                        impl_trait_ref.args.types().skip(1),
69                    )
70                    .all(|(u, v)| self.fuzzy_match_tys(u, v, false).is_some())
71                    {
72                        fuzzy_match_impls.push((def_id, impl_args));
73                    }
74                }
75            });
76
77            let impl_def_id_and_args = if let [impl_] = self_match_impls[..] {
78                impl_
79            } else if let [impl_] = fuzzy_match_impls[..] {
80                impl_
81            } else {
82                return None;
83            };
84
85            tcx.has_attr(impl_def_id_and_args.0, sym::rustc_on_unimplemented)
86                .then_some(impl_def_id_and_args)
87        })
88    }
89
90    /// Used to set on_unimplemented's `ItemContext`
91    /// to be the enclosing (async) block/function/closure
92    fn describe_enclosure(&self, def_id: LocalDefId) -> Option<&'static str> {
93        match self.tcx.hir_node_by_def_id(def_id) {
94            hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn { .. }, .. }) => Some("a function"),
95            hir::Node::TraitItem(hir::TraitItem { kind: hir::TraitItemKind::Fn(..), .. }) => {
96                Some("a trait method")
97            }
98            hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Fn(..), .. }) => {
99                Some("a method")
100            }
101            hir::Node::Expr(hir::Expr {
102                kind: hir::ExprKind::Closure(hir::Closure { kind, .. }),
103                ..
104            }) => Some(self.describe_closure(*kind)),
105            _ => None,
106        }
107    }
108
109    pub fn on_unimplemented_note(
110        &self,
111        trait_pred: ty::PolyTraitPredicate<'tcx>,
112        obligation: &PredicateObligation<'tcx>,
113        long_ty_file: &mut Option<PathBuf>,
114    ) -> OnUnimplementedNote {
115        if trait_pred.polarity() != ty::PredicatePolarity::Positive {
116            return OnUnimplementedNote::default();
117        }
118
119        let (def_id, args) = self
120            .impl_similar_to(trait_pred, obligation)
121            .unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args));
122        let trait_pred = trait_pred.skip_binder();
123
124        let mut flags = vec![];
125        // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs,
126        // but I guess we could synthesize one here. We don't see any errors that rely on
127        // that yet, though.
128        let enclosure = self.describe_enclosure(obligation.cause.body_id).map(|t| t.to_owned());
129        flags.push((sym::ItemContext, enclosure));
130
131        match obligation.cause.code() {
132            ObligationCauseCode::BuiltinDerived(..)
133            | ObligationCauseCode::ImplDerived(..)
134            | ObligationCauseCode::WellFormedDerived(..) => {}
135            _ => {
136                // this is a "direct", user-specified, rather than derived,
137                // obligation.
138                flags.push((sym::direct, None));
139            }
140        }
141
142        if let Some(k) = obligation.cause.span.desugaring_kind() {
143            flags.push((sym::from_desugaring, None));
144            flags.push((sym::from_desugaring, Some(format!("{k:?}"))));
145        }
146
147        if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
148            flags.push((sym::cause, Some("MainFunctionType".to_string())));
149        }
150
151        flags.push((sym::Trait, Some(trait_pred.trait_ref.print_trait_sugared().to_string())));
152
153        // Add all types without trimmed paths or visible paths, ensuring they end up with
154        // their "canonical" def path.
155        ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({
156            let generics = self.tcx.generics_of(def_id);
157            let self_ty = trait_pred.self_ty();
158            // This is also included through the generics list as `Self`,
159            // but the parser won't allow you to use it
160            flags.push((sym::_Self, Some(self_ty.to_string())));
161            if let Some(def) = self_ty.ty_adt_def() {
162                // We also want to be able to select self's original
163                // signature with no type arguments resolved
164                flags.push((
165                    sym::_Self,
166                    Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
167                ));
168            }
169
170            for param in generics.own_params.iter() {
171                let value = match param.kind {
172                    GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
173                        args[param.index as usize].to_string()
174                    }
175                    GenericParamDefKind::Lifetime => continue,
176                };
177                let name = param.name;
178                flags.push((name, Some(value)));
179
180                if let GenericParamDefKind::Type { .. } = param.kind {
181                    let param_ty = args[param.index as usize].expect_ty();
182                    if let Some(def) = param_ty.ty_adt_def() {
183                        // We also want to be able to select the parameter's
184                        // original signature with no type arguments resolved
185                        flags.push((
186                            name,
187                            Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
188                        ));
189                    }
190                }
191            }
192
193            if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) {
194                flags.push((sym::crate_local, None));
195            }
196
197            // Allow targeting all integers using `{integral}`, even if the exact type was resolved
198            if self_ty.is_integral() {
199                flags.push((sym::_Self, Some("{integral}".to_owned())));
200            }
201
202            if self_ty.is_array_slice() {
203                flags.push((sym::_Self, Some("&[]".to_owned())));
204            }
205
206            if self_ty.is_fn() {
207                let fn_sig = self_ty.fn_sig(self.tcx);
208                let shortname = if let ty::FnDef(def_id, _) = self_ty.kind()
209                    && self.tcx.codegen_fn_attrs(def_id).safe_target_features
210                {
211                    "#[target_feature] fn"
212                } else {
213                    match fn_sig.safety() {
214                        hir::Safety::Safe => "fn",
215                        hir::Safety::Unsafe => "unsafe fn",
216                    }
217                };
218                flags.push((sym::_Self, Some(shortname.to_owned())));
219            }
220
221            // Slices give us `[]`, `[{ty}]`
222            if let ty::Slice(aty) = self_ty.kind() {
223                flags.push((sym::_Self, Some("[]".to_string())));
224                if let Some(def) = aty.ty_adt_def() {
225                    // We also want to be able to select the slice's type's original
226                    // signature with no type arguments resolved
227                    flags.push((
228                        sym::_Self,
229                        Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())),
230                    ));
231                }
232                if aty.is_integral() {
233                    flags.push((sym::_Self, Some("[{integral}]".to_string())));
234                }
235            }
236
237            // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]`
238            if let ty::Array(aty, len) = self_ty.kind() {
239                flags.push((sym::_Self, Some("[]".to_string())));
240                let len = len.try_to_target_usize(self.tcx);
241                flags.push((sym::_Self, Some(format!("[{aty}; _]"))));
242                if let Some(n) = len {
243                    flags.push((sym::_Self, Some(format!("[{aty}; {n}]"))));
244                }
245                if let Some(def) = aty.ty_adt_def() {
246                    // We also want to be able to select the array's type's original
247                    // signature with no type arguments resolved
248                    let def_ty = self.tcx.type_of(def.did()).instantiate_identity();
249                    flags.push((sym::_Self, Some(format!("[{def_ty}; _]"))));
250                    if let Some(n) = len {
251                        flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]"))));
252                    }
253                }
254                if aty.is_integral() {
255                    flags.push((sym::_Self, Some("[{integral}; _]".to_string())));
256                    if let Some(n) = len {
257                        flags.push((sym::_Self, Some(format!("[{{integral}}; {n}]"))));
258                    }
259                }
260            }
261            if let ty::Dynamic(traits, _, _) = self_ty.kind() {
262                for t in traits.iter() {
263                    if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() {
264                        flags.push((sym::_Self, Some(self.tcx.def_path_str(trait_ref.def_id))))
265                    }
266                }
267            }
268
269            // `&[{integral}]` - `FromIterator` needs that.
270            if let ty::Ref(_, ref_ty, rustc_ast::Mutability::Not) = self_ty.kind()
271                && let ty::Slice(sty) = ref_ty.kind()
272                && sty.is_integral()
273            {
274                flags.push((sym::_Self, Some("&[{integral}]".to_owned())));
275            }
276        }));
277
278        if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
279            command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file)
280        } else {
281            OnUnimplementedNote::default()
282        }
283    }
284}
285
286#[derive(Clone, Debug)]
287pub struct OnUnimplementedFormatString {
288    symbol: Symbol,
289    span: Span,
290    is_diagnostic_namespace_variant: bool,
291}
292
293#[derive(Debug)]
294pub struct OnUnimplementedDirective {
295    pub condition: Option<MetaItemInner>,
296    pub subcommands: Vec<OnUnimplementedDirective>,
297    pub message: Option<OnUnimplementedFormatString>,
298    pub label: Option<OnUnimplementedFormatString>,
299    pub notes: Vec<OnUnimplementedFormatString>,
300    pub parent_label: Option<OnUnimplementedFormatString>,
301    pub append_const_msg: Option<AppendConstMessage>,
302}
303
304/// For the `#[rustc_on_unimplemented]` attribute
305#[derive(Default)]
306pub struct OnUnimplementedNote {
307    pub message: Option<String>,
308    pub label: Option<String>,
309    pub notes: Vec<String>,
310    pub parent_label: Option<String>,
311    // If none, should fall back to a generic message
312    pub append_const_msg: Option<AppendConstMessage>,
313}
314
315/// Append a message for `~const Trait` errors.
316#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
317pub enum AppendConstMessage {
318    #[default]
319    Default,
320    Custom(Symbol, Span),
321}
322
323#[derive(LintDiagnostic)]
324#[diag(trait_selection_malformed_on_unimplemented_attr)]
325#[help]
326pub struct MalformedOnUnimplementedAttrLint {
327    #[label]
328    pub span: Span,
329}
330
331impl MalformedOnUnimplementedAttrLint {
332    fn new(span: Span) -> Self {
333        Self { span }
334    }
335}
336
337#[derive(LintDiagnostic)]
338#[diag(trait_selection_missing_options_for_on_unimplemented_attr)]
339#[help]
340pub struct MissingOptionsForOnUnimplementedAttr;
341
342#[derive(LintDiagnostic)]
343#[diag(trait_selection_ignored_diagnostic_option)]
344pub struct IgnoredDiagnosticOption {
345    pub option_name: &'static str,
346    #[label]
347    pub span: Span,
348    #[label(trait_selection_other_label)]
349    pub prev_span: Span,
350}
351
352impl IgnoredDiagnosticOption {
353    fn maybe_emit_warning<'tcx>(
354        tcx: TyCtxt<'tcx>,
355        item_def_id: DefId,
356        new: Option<Span>,
357        old: Option<Span>,
358        option_name: &'static str,
359    ) {
360        if let (Some(new_item), Some(old_item)) = (new, old) {
361            if let Some(item_def_id) = item_def_id.as_local() {
362                tcx.emit_node_span_lint(
363                    UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
364                    tcx.local_def_id_to_hir_id(item_def_id),
365                    new_item,
366                    IgnoredDiagnosticOption { span: new_item, prev_span: old_item, option_name },
367                );
368            }
369        }
370    }
371}
372
373#[derive(LintDiagnostic)]
374#[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
375#[help]
376pub struct UnknownFormatParameterForOnUnimplementedAttr {
377    argument_name: Symbol,
378    trait_name: Ident,
379}
380
381#[derive(LintDiagnostic)]
382#[diag(trait_selection_disallowed_positional_argument)]
383#[help]
384pub struct DisallowedPositionalArgument;
385
386#[derive(LintDiagnostic)]
387#[diag(trait_selection_invalid_format_specifier)]
388#[help]
389pub struct InvalidFormatSpecifier;
390
391#[derive(LintDiagnostic)]
392#[diag(trait_selection_wrapped_parser_error)]
393pub struct WrappedParserError {
394    description: String,
395    label: String,
396}
397
398impl<'tcx> OnUnimplementedDirective {
399    fn parse(
400        tcx: TyCtxt<'tcx>,
401        item_def_id: DefId,
402        items: &[MetaItemInner],
403        span: Span,
404        is_root: bool,
405        is_diagnostic_namespace_variant: bool,
406    ) -> Result<Option<Self>, ErrorGuaranteed> {
407        let mut errored = None;
408        let mut item_iter = items.iter();
409
410        let parse_value = |value_str, value_span| {
411            OnUnimplementedFormatString::try_parse(
412                tcx,
413                item_def_id,
414                value_str,
415                value_span,
416                is_diagnostic_namespace_variant,
417            )
418            .map(Some)
419        };
420
421        let condition = if is_root {
422            None
423        } else {
424            let cond = item_iter
425                .next()
426                .ok_or_else(|| tcx.dcx().emit_err(EmptyOnClauseInOnUnimplemented { span }))?
427                .meta_item_or_bool()
428                .ok_or_else(|| tcx.dcx().emit_err(InvalidOnClauseInOnUnimplemented { span }))?;
429            attr::eval_condition(cond, &tcx.sess, Some(tcx.features()), &mut |cfg| {
430                if let Some(value) = cfg.value
431                    && let Err(guar) = parse_value(value, cfg.span)
432                {
433                    errored = Some(guar);
434                }
435                true
436            });
437            Some(cond.clone())
438        };
439
440        let mut message = None;
441        let mut label = None;
442        let mut notes = Vec::new();
443        let mut parent_label = None;
444        let mut subcommands = vec![];
445        let mut append_const_msg = None;
446
447        for item in item_iter {
448            if item.has_name(sym::message) && message.is_none() {
449                if let Some(message_) = item.value_str() {
450                    message = parse_value(message_, item.span())?;
451                    continue;
452                }
453            } else if item.has_name(sym::label) && label.is_none() {
454                if let Some(label_) = item.value_str() {
455                    label = parse_value(label_, item.span())?;
456                    continue;
457                }
458            } else if item.has_name(sym::note) {
459                if let Some(note_) = item.value_str() {
460                    if let Some(note) = parse_value(note_, item.span())? {
461                        notes.push(note);
462                        continue;
463                    }
464                }
465            } else if item.has_name(sym::parent_label)
466                && parent_label.is_none()
467                && !is_diagnostic_namespace_variant
468            {
469                if let Some(parent_label_) = item.value_str() {
470                    parent_label = parse_value(parent_label_, item.span())?;
471                    continue;
472                }
473            } else if item.has_name(sym::on)
474                && is_root
475                && message.is_none()
476                && label.is_none()
477                && notes.is_empty()
478                && !is_diagnostic_namespace_variant
479            // FIXME(diagnostic_namespace): disallow filters for now
480            {
481                if let Some(items) = item.meta_item_list() {
482                    match Self::parse(
483                        tcx,
484                        item_def_id,
485                        items,
486                        item.span(),
487                        false,
488                        is_diagnostic_namespace_variant,
489                    ) {
490                        Ok(Some(subcommand)) => subcommands.push(subcommand),
491                        Ok(None) => bug!(
492                            "This cannot happen for now as we only reach that if `is_diagnostic_namespace_variant` is false"
493                        ),
494                        Err(reported) => errored = Some(reported),
495                    };
496                    continue;
497                }
498            } else if item.has_name(sym::append_const_msg)
499                && append_const_msg.is_none()
500                && !is_diagnostic_namespace_variant
501            {
502                if let Some(msg) = item.value_str() {
503                    append_const_msg = Some(AppendConstMessage::Custom(msg, item.span()));
504                    continue;
505                } else if item.is_word() {
506                    append_const_msg = Some(AppendConstMessage::Default);
507                    continue;
508                }
509            }
510
511            if is_diagnostic_namespace_variant {
512                if let Some(def_id) = item_def_id.as_local() {
513                    tcx.emit_node_span_lint(
514                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
515                        tcx.local_def_id_to_hir_id(def_id),
516                        vec![item.span()],
517                        MalformedOnUnimplementedAttrLint::new(item.span()),
518                    );
519                }
520            } else {
521                // nothing found
522                tcx.dcx().emit_err(NoValueInOnUnimplemented { span: item.span() });
523            }
524        }
525
526        if let Some(reported) = errored {
527            if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) }
528        } else {
529            Ok(Some(OnUnimplementedDirective {
530                condition,
531                subcommands,
532                message,
533                label,
534                notes,
535                parent_label,
536                append_const_msg,
537            }))
538        }
539    }
540
541    pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
542        if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) {
543            return Self::parse_attribute(attr, false, tcx, item_def_id);
544        } else {
545            tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented])
546                .filter_map(|attr| Self::parse_attribute(attr, true, tcx, item_def_id).transpose())
547                .try_fold(None, |aggr: Option<Self>, directive| {
548                    let directive = directive?;
549                    if let Some(aggr) = aggr {
550                        let mut subcommands = aggr.subcommands;
551                        subcommands.extend(directive.subcommands);
552                        let mut notes = aggr.notes;
553                        notes.extend(directive.notes);
554                        IgnoredDiagnosticOption::maybe_emit_warning(
555                            tcx,
556                            item_def_id,
557                            directive.message.as_ref().map(|f| f.span),
558                            aggr.message.as_ref().map(|f| f.span),
559                            "message",
560                        );
561                        IgnoredDiagnosticOption::maybe_emit_warning(
562                            tcx,
563                            item_def_id,
564                            directive.label.as_ref().map(|f| f.span),
565                            aggr.label.as_ref().map(|f| f.span),
566                            "label",
567                        );
568                        IgnoredDiagnosticOption::maybe_emit_warning(
569                            tcx,
570                            item_def_id,
571                            directive.condition.as_ref().map(|i| i.span()),
572                            aggr.condition.as_ref().map(|i| i.span()),
573                            "condition",
574                        );
575                        IgnoredDiagnosticOption::maybe_emit_warning(
576                            tcx,
577                            item_def_id,
578                            directive.parent_label.as_ref().map(|f| f.span),
579                            aggr.parent_label.as_ref().map(|f| f.span),
580                            "parent_label",
581                        );
582                        IgnoredDiagnosticOption::maybe_emit_warning(
583                            tcx,
584                            item_def_id,
585                            directive.append_const_msg.as_ref().and_then(|c| {
586                                if let AppendConstMessage::Custom(_, s) = c {
587                                    Some(*s)
588                                } else {
589                                    None
590                                }
591                            }),
592                            aggr.append_const_msg.as_ref().and_then(|c| {
593                                if let AppendConstMessage::Custom(_, s) = c {
594                                    Some(*s)
595                                } else {
596                                    None
597                                }
598                            }),
599                            "append_const_msg",
600                        );
601
602                        Ok(Some(Self {
603                            condition: aggr.condition.or(directive.condition),
604                            subcommands,
605                            message: aggr.message.or(directive.message),
606                            label: aggr.label.or(directive.label),
607                            notes,
608                            parent_label: aggr.parent_label.or(directive.parent_label),
609                            append_const_msg: aggr.append_const_msg.or(directive.append_const_msg),
610                        }))
611                    } else {
612                        Ok(Some(directive))
613                    }
614                })
615        }
616    }
617
618    fn parse_attribute(
619        attr: &Attribute,
620        is_diagnostic_namespace_variant: bool,
621        tcx: TyCtxt<'tcx>,
622        item_def_id: DefId,
623    ) -> Result<Option<Self>, ErrorGuaranteed> {
624        let result = if let Some(items) = attr.meta_item_list() {
625            Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
626        } else if let Some(value) = attr.value_str() {
627            if !is_diagnostic_namespace_variant {
628                Ok(Some(OnUnimplementedDirective {
629                    condition: None,
630                    message: None,
631                    subcommands: vec![],
632                    label: Some(OnUnimplementedFormatString::try_parse(
633                        tcx,
634                        item_def_id,
635                        value,
636                        attr.span,
637                        is_diagnostic_namespace_variant,
638                    )?),
639                    notes: Vec::new(),
640                    parent_label: None,
641                    append_const_msg: None,
642                }))
643            } else {
644                let item = attr.get_normal_item();
645                let report_span = match &item.args {
646                    AttrArgs::Empty => item.path.span,
647                    AttrArgs::Delimited(args) => args.dspan.entire(),
648                    AttrArgs::Eq { eq_span, expr } => eq_span.to(expr.span),
649                };
650
651                if let Some(item_def_id) = item_def_id.as_local() {
652                    tcx.emit_node_span_lint(
653                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
654                        tcx.local_def_id_to_hir_id(item_def_id),
655                        report_span,
656                        MalformedOnUnimplementedAttrLint::new(report_span),
657                    );
658                }
659                Ok(None)
660            }
661        } else if is_diagnostic_namespace_variant {
662            match &attr.kind {
663                AttrKind::Normal(p) if !matches!(p.args, AttrArgs::Empty) => {
664                    if let Some(item_def_id) = item_def_id.as_local() {
665                        tcx.emit_node_span_lint(
666                            UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
667                            tcx.local_def_id_to_hir_id(item_def_id),
668                            attr.span,
669                            MalformedOnUnimplementedAttrLint::new(attr.span),
670                        );
671                    }
672                }
673                _ => {
674                    if let Some(item_def_id) = item_def_id.as_local() {
675                        tcx.emit_node_span_lint(
676                            UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
677                            tcx.local_def_id_to_hir_id(item_def_id),
678                            attr.span,
679                            MissingOptionsForOnUnimplementedAttr,
680                        )
681                    }
682                }
683            };
684
685            Ok(None)
686        } else {
687            let reported = tcx.dcx().delayed_bug("of_item: neither meta_item_list nor value_str");
688            return Err(reported);
689        };
690        debug!("of_item({:?}) = {:?}", item_def_id, result);
691        result
692    }
693
694    pub fn evaluate(
695        &self,
696        tcx: TyCtxt<'tcx>,
697        trait_ref: ty::TraitRef<'tcx>,
698        options: &[(Symbol, Option<String>)],
699        long_ty_file: &mut Option<PathBuf>,
700    ) -> OnUnimplementedNote {
701        let mut message = None;
702        let mut label = None;
703        let mut notes = Vec::new();
704        let mut parent_label = None;
705        let mut append_const_msg = None;
706        info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
707
708        let options_map: FxHashMap<Symbol, String> =
709            options.iter().filter_map(|(k, v)| v.clone().map(|v| (*k, v))).collect();
710
711        for command in self.subcommands.iter().chain(Some(self)).rev() {
712            debug!(?command);
713            if let Some(ref condition) = command.condition
714                && !attr::eval_condition(condition, &tcx.sess, Some(tcx.features()), &mut |cfg| {
715                    let value = cfg.value.map(|v| {
716                        // `with_no_visible_paths` is also used when generating the options,
717                        // so we need to match it here.
718                        ty::print::with_no_visible_paths!(
719                            OnUnimplementedFormatString {
720                                symbol: v,
721                                span: cfg.span,
722                                is_diagnostic_namespace_variant: false
723                            }
724                            .format(
725                                tcx,
726                                trait_ref,
727                                &options_map,
728                                long_ty_file
729                            )
730                        )
731                    });
732
733                    options.contains(&(cfg.name, value))
734                })
735            {
736                debug!("evaluate: skipping {:?} due to condition", command);
737                continue;
738            }
739            debug!("evaluate: {:?} succeeded", command);
740            if let Some(ref message_) = command.message {
741                message = Some(message_.clone());
742            }
743
744            if let Some(ref label_) = command.label {
745                label = Some(label_.clone());
746            }
747
748            notes.extend(command.notes.clone());
749
750            if let Some(ref parent_label_) = command.parent_label {
751                parent_label = Some(parent_label_.clone());
752            }
753
754            append_const_msg = command.append_const_msg;
755        }
756
757        OnUnimplementedNote {
758            label: label.map(|l| l.format(tcx, trait_ref, &options_map, long_ty_file)),
759            message: message.map(|m| m.format(tcx, trait_ref, &options_map, long_ty_file)),
760            notes: notes
761                .into_iter()
762                .map(|n| n.format(tcx, trait_ref, &options_map, long_ty_file))
763                .collect(),
764            parent_label: parent_label
765                .map(|e_s| e_s.format(tcx, trait_ref, &options_map, long_ty_file)),
766            append_const_msg,
767        }
768    }
769}
770
771impl<'tcx> OnUnimplementedFormatString {
772    fn try_parse(
773        tcx: TyCtxt<'tcx>,
774        item_def_id: DefId,
775        from: Symbol,
776        value_span: Span,
777        is_diagnostic_namespace_variant: bool,
778    ) -> Result<Self, ErrorGuaranteed> {
779        let result = OnUnimplementedFormatString {
780            symbol: from,
781            span: value_span,
782            is_diagnostic_namespace_variant,
783        };
784        result.verify(tcx, item_def_id)?;
785        Ok(result)
786    }
787
788    fn verify(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<(), ErrorGuaranteed> {
789        let trait_def_id = if tcx.is_trait(item_def_id) {
790            item_def_id
791        } else {
792            tcx.trait_id_of_impl(item_def_id)
793                .expect("expected `on_unimplemented` to correspond to a trait")
794        };
795        let trait_name = tcx.item_ident(trait_def_id);
796        let generics = tcx.generics_of(item_def_id);
797        let s = self.symbol.as_str();
798        let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
799        let mut result = Ok(());
800        for token in &mut parser {
801            match token {
802                Piece::Lit(_) => (), // Normal string, no need to check it
803                Piece::NextArgument(a) => {
804                    let format_spec = a.format;
805                    if self.is_diagnostic_namespace_variant
806                        && (format_spec.ty_span.is_some()
807                            || format_spec.width_span.is_some()
808                            || format_spec.precision_span.is_some()
809                            || format_spec.fill_span.is_some())
810                    {
811                        if let Some(item_def_id) = item_def_id.as_local() {
812                            tcx.emit_node_span_lint(
813                                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
814                                tcx.local_def_id_to_hir_id(item_def_id),
815                                self.span,
816                                InvalidFormatSpecifier,
817                            );
818                        }
819                    }
820                    match a.position {
821                        Position::ArgumentNamed(s) => {
822                            match Symbol::intern(s) {
823                                // `{ThisTraitsName}` is allowed
824                                s if s == trait_name.name
825                                    && !self.is_diagnostic_namespace_variant =>
826                                {
827                                    ()
828                                }
829                                s if ALLOWED_FORMAT_SYMBOLS.contains(&s)
830                                    && !self.is_diagnostic_namespace_variant =>
831                                {
832                                    ()
833                                }
834                                // So is `{A}` if A is a type parameter
835                                s if generics.own_params.iter().any(|param| param.name == s) => (),
836                                s => {
837                                    if self.is_diagnostic_namespace_variant {
838                                        if let Some(item_def_id) = item_def_id.as_local() {
839                                            tcx.emit_node_span_lint(
840                                                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
841                                                tcx.local_def_id_to_hir_id(item_def_id),
842                                                self.span,
843                                                UnknownFormatParameterForOnUnimplementedAttr {
844                                                    argument_name: s,
845                                                    trait_name,
846                                                },
847                                            );
848                                        }
849                                    } else {
850                                        result = Err(struct_span_code_err!(
851                                            tcx.dcx(),
852                                            self.span,
853                                            E0230,
854                                            "there is no parameter `{}` on {}",
855                                            s,
856                                            if trait_def_id == item_def_id {
857                                                format!("trait `{trait_name}`")
858                                            } else {
859                                                "impl".to_string()
860                                            }
861                                        )
862                                        .emit());
863                                    }
864                                }
865                            }
866                        }
867                        // `{:1}` and `{}` are not to be used
868                        Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => {
869                            if self.is_diagnostic_namespace_variant {
870                                if let Some(item_def_id) = item_def_id.as_local() {
871                                    tcx.emit_node_span_lint(
872                                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
873                                        tcx.local_def_id_to_hir_id(item_def_id),
874                                        self.span,
875                                        DisallowedPositionalArgument,
876                                    );
877                                }
878                            } else {
879                                let reported = struct_span_code_err!(
880                                    tcx.dcx(),
881                                    self.span,
882                                    E0231,
883                                    "only named generic parameters are allowed"
884                                )
885                                .emit();
886                                result = Err(reported);
887                            }
888                        }
889                    }
890                }
891            }
892        }
893        // we cannot return errors from processing the format string as hard error here
894        // as the diagnostic namespace guarantees that malformed input cannot cause an error
895        //
896        // if we encounter any error while processing we nevertheless want to show it as warning
897        // so that users are aware that something is not correct
898        for e in parser.errors {
899            if self.is_diagnostic_namespace_variant {
900                if let Some(item_def_id) = item_def_id.as_local() {
901                    tcx.emit_node_span_lint(
902                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
903                        tcx.local_def_id_to_hir_id(item_def_id),
904                        self.span,
905                        WrappedParserError { description: e.description, label: e.label },
906                    );
907                }
908            } else {
909                let reported =
910                    struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,).emit();
911                result = Err(reported);
912            }
913        }
914
915        result
916    }
917
918    pub fn format(
919        &self,
920        tcx: TyCtxt<'tcx>,
921        trait_ref: ty::TraitRef<'tcx>,
922        options: &FxHashMap<Symbol, String>,
923        long_ty_file: &mut Option<PathBuf>,
924    ) -> String {
925        let name = tcx.item_name(trait_ref.def_id);
926        let trait_str = tcx.def_path_str(trait_ref.def_id);
927        let generics = tcx.generics_of(trait_ref.def_id);
928        let generic_map = generics
929            .own_params
930            .iter()
931            .filter_map(|param| {
932                let value = match param.kind {
933                    GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
934                        if let Some(ty) = trait_ref.args[param.index as usize].as_type() {
935                            tcx.short_string(ty, long_ty_file)
936                        } else {
937                            trait_ref.args[param.index as usize].to_string()
938                        }
939                    }
940                    GenericParamDefKind::Lifetime => return None,
941                };
942                let name = param.name;
943                Some((name, value))
944            })
945            .collect::<FxHashMap<Symbol, String>>();
946        let empty_string = String::new();
947
948        let s = self.symbol.as_str();
949        let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
950        let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string);
951        let constructed_message = (&mut parser)
952            .map(|p| match p {
953                Piece::Lit(s) => s.to_owned(),
954                Piece::NextArgument(a) => match a.position {
955                    Position::ArgumentNamed(arg) => {
956                        let s = Symbol::intern(arg);
957                        match generic_map.get(&s) {
958                            Some(val) => val.to_string(),
959                            None if self.is_diagnostic_namespace_variant => {
960                                format!("{{{arg}}}")
961                            }
962                            None if s == name => trait_str.clone(),
963                            None => {
964                                if let Some(val) = options.get(&s) {
965                                    val.clone()
966                                } else if s == sym::from_desugaring {
967                                    // don't break messages using these two arguments incorrectly
968                                    String::new()
969                                } else if s == sym::ItemContext
970                                    && !self.is_diagnostic_namespace_variant
971                                {
972                                    item_context.clone()
973                                } else if s == sym::integral {
974                                    String::from("{integral}")
975                                } else if s == sym::integer_ {
976                                    String::from("{integer}")
977                                } else if s == sym::float {
978                                    String::from("{float}")
979                                } else {
980                                    bug!(
981                                        "broken on_unimplemented {:?} for {:?}: \
982                                      no argument matching {:?}",
983                                        self.symbol,
984                                        trait_ref,
985                                        s
986                                    )
987                                }
988                            }
989                        }
990                    }
991                    Position::ArgumentImplicitlyIs(_) if self.is_diagnostic_namespace_variant => {
992                        String::from("{}")
993                    }
994                    Position::ArgumentIs(idx) if self.is_diagnostic_namespace_variant => {
995                        format!("{{{idx}}}")
996                    }
997                    _ => bug!("broken on_unimplemented {:?} - bad format arg", self.symbol),
998                },
999            })
1000            .collect();
1001        // we cannot return errors from processing the format string as hard error here
1002        // as the diagnostic namespace guarantees that malformed input cannot cause an error
1003        //
1004        // if we encounter any error while processing the format string
1005        // we don't want to show the potentially half assembled formatted string,
1006        // therefore we fall back to just showing the input string in this case
1007        //
1008        // The actual parser errors are emitted earlier
1009        // as lint warnings in OnUnimplementedFormatString::verify
1010        if self.is_diagnostic_namespace_variant && !parser.errors.is_empty() {
1011            String::from(s)
1012        } else {
1013            constructed_message
1014        }
1015    }
1016}