about summary refs log tree commit diff
path: root/compiler/rustc_trait_selection/src
diff options
context:
space:
mode:
authorThe rustc-dev-guide Cronjob Bot <github-actions@github.com>2025-04-21 04:03:09 +0000
committerThe rustc-dev-guide Cronjob Bot <github-actions@github.com>2025-04-21 04:03:09 +0000
commitd12c1f581f97ca0fe67d1498fa2c34b61a8bd189 (patch)
treecad0adefe81f03e6b1727edbe2ff7cd35f33ebf2 /compiler/rustc_trait_selection/src
parent49b62eeacc0b57c01464577c5f3437f2f29f9683 (diff)
parentb8005bff3248cfc6e327faf4fa631ac49bb49ba9 (diff)
downloadrust-d12c1f581f97ca0fe67d1498fa2c34b61a8bd189.tar.gz
rust-d12c1f581f97ca0fe67d1498fa2c34b61a8bd189.zip
Merge from rustc
Diffstat (limited to 'compiler/rustc_trait_selection/src')
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs2
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs621
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs120
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs414
4 files changed, 789 insertions, 368 deletions
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs
index 8ff7030717a..78f9287b407 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs
@@ -2,6 +2,8 @@ pub mod ambiguity;
 pub mod call_kind;
 mod fulfillment_errors;
 pub mod on_unimplemented;
+pub mod on_unimplemented_condition;
+pub mod on_unimplemented_format;
 mod overflow;
 pub mod suggestions;
 
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
index f0c6e51f2a4..4c4491269b7 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
@@ -1,44 +1,31 @@
 use std::iter;
 use std::path::PathBuf;
 
-use rustc_ast::MetaItemInner;
-use rustc_data_structures::fx::FxHashMap;
+use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_errors::codes::*;
 use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::{AttrArgs, Attribute};
 use rustc_macros::LintDiagnostic;
 use rustc_middle::bug;
-use rustc_middle::ty::print::PrintTraitRefExt as _;
-use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt};
-use rustc_parse_format::{ParseMode, Parser, Piece, Position};
+use rustc_middle::ty::print::PrintTraitRefExt;
+use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt};
 use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
-use rustc_span::{Ident, Span, Symbol, kw, sym};
+use rustc_span::{Span, Symbol, sym};
 use tracing::{debug, info};
 use {rustc_attr_parsing as attr, rustc_hir as hir};
 
 use super::{ObligationCauseCode, PredicateObligation};
 use crate::error_reporting::TypeErrCtxt;
+use crate::error_reporting::traits::on_unimplemented_condition::{Condition, ConditionOptions};
+use crate::error_reporting::traits::on_unimplemented_format::{
+    Ctx, FormatArgs, FormatString, FormatWarning,
+};
 use crate::errors::{
     EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
 };
 use crate::infer::InferCtxtExt;
 
-/// The symbols which are always allowed in a format string
-static ALLOWED_FORMAT_SYMBOLS: &[Symbol] = &[
-    kw::SelfUpper,
-    sym::ItemContext,
-    sym::from_desugaring,
-    sym::direct,
-    sym::cause,
-    sym::integral,
-    sym::integer_,
-    sym::float,
-    sym::_Self,
-    sym::crate_local,
-    sym::Trait,
-];
-
 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
     fn impl_similar_to(
         &self,
@@ -121,86 +108,78 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             .unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args));
         let trait_pred = trait_pred.skip_binder();
 
-        let mut flags = vec![];
+        let mut self_types = vec![];
+        let mut generic_args: Vec<(Symbol, String)> = vec![];
+        let mut crate_local = false;
         // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs,
         // but I guess we could synthesize one here. We don't see any errors that rely on
         // that yet, though.
-        let enclosure = self.describe_enclosure(obligation.cause.body_id).map(|t| t.to_owned());
-        flags.push((sym::ItemContext, enclosure));
+        let item_context = self.describe_enclosure(obligation.cause.body_id).unwrap_or("");
 
-        match obligation.cause.code() {
+        let direct = match obligation.cause.code() {
             ObligationCauseCode::BuiltinDerived(..)
             | ObligationCauseCode::ImplDerived(..)
-            | ObligationCauseCode::WellFormedDerived(..) => {}
+            | ObligationCauseCode::WellFormedDerived(..) => false,
             _ => {
                 // this is a "direct", user-specified, rather than derived,
                 // obligation.
-                flags.push((sym::direct, None));
+                true
             }
-        }
-
-        if let Some(k) = obligation.cause.span.desugaring_kind() {
-            flags.push((sym::from_desugaring, None));
-            flags.push((sym::from_desugaring, Some(format!("{k:?}"))));
-        }
+        };
 
-        if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
-            flags.push((sym::cause, Some("MainFunctionType".to_string())));
-        }
+        let from_desugaring = obligation.cause.span.desugaring_kind();
 
-        flags.push((sym::Trait, Some(trait_pred.trait_ref.print_trait_sugared().to_string())));
+        let cause = if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
+            Some("MainFunctionType".to_string())
+        } else {
+            None
+        };
 
         // Add all types without trimmed paths or visible paths, ensuring they end up with
         // their "canonical" def path.
         ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({
             let generics = self.tcx.generics_of(def_id);
             let self_ty = trait_pred.self_ty();
-            // This is also included through the generics list as `Self`,
-            // but the parser won't allow you to use it
-            flags.push((sym::_Self, Some(self_ty.to_string())));
+            self_types.push(self_ty.to_string());
             if let Some(def) = self_ty.ty_adt_def() {
                 // We also want to be able to select self's original
                 // signature with no type arguments resolved
-                flags.push((
-                    sym::_Self,
-                    Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
-                ));
+                self_types.push(self.tcx.type_of(def.did()).instantiate_identity().to_string());
             }
 
-            for param in generics.own_params.iter() {
-                let value = match param.kind {
+            for GenericParamDef { name, kind, index, .. } in generics.own_params.iter() {
+                let value = match kind {
                     GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
-                        args[param.index as usize].to_string()
+                        args[*index as usize].to_string()
                     }
                     GenericParamDefKind::Lifetime => continue,
                 };
-                let name = param.name;
-                flags.push((name, Some(value)));
+                generic_args.push((*name, value));
 
-                if let GenericParamDefKind::Type { .. } = param.kind {
-                    let param_ty = args[param.index as usize].expect_ty();
+                if let GenericParamDefKind::Type { .. } = kind {
+                    let param_ty = args[*index as usize].expect_ty();
                     if let Some(def) = param_ty.ty_adt_def() {
                         // We also want to be able to select the parameter's
                         // original signature with no type arguments resolved
-                        flags.push((
-                            name,
-                            Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
+                        generic_args.push((
+                            *name,
+                            self.tcx.type_of(def.did()).instantiate_identity().to_string(),
                         ));
                     }
                 }
             }
 
             if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) {
-                flags.push((sym::crate_local, None));
+                crate_local = true;
             }
 
             // Allow targeting all integers using `{integral}`, even if the exact type was resolved
             if self_ty.is_integral() {
-                flags.push((sym::_Self, Some("{integral}".to_owned())));
+                self_types.push("{integral}".to_owned());
             }
 
             if self_ty.is_array_slice() {
-                flags.push((sym::_Self, Some("&[]".to_owned())));
+                self_types.push("&[]".to_owned());
             }
 
             if self_ty.is_fn() {
@@ -215,53 +194,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                         hir::Safety::Unsafe => "unsafe fn",
                     }
                 };
-                flags.push((sym::_Self, Some(shortname.to_owned())));
+                self_types.push(shortname.to_owned());
             }
 
             // Slices give us `[]`, `[{ty}]`
             if let ty::Slice(aty) = self_ty.kind() {
-                flags.push((sym::_Self, Some("[]".to_string())));
+                self_types.push("[]".to_owned());
                 if let Some(def) = aty.ty_adt_def() {
                     // We also want to be able to select the slice's type's original
                     // signature with no type arguments resolved
-                    flags.push((
-                        sym::_Self,
-                        Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())),
-                    ));
+                    self_types
+                        .push(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity()));
                 }
                 if aty.is_integral() {
-                    flags.push((sym::_Self, Some("[{integral}]".to_string())));
+                    self_types.push("[{integral}]".to_string());
                 }
             }
 
             // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]`
             if let ty::Array(aty, len) = self_ty.kind() {
-                flags.push((sym::_Self, Some("[]".to_string())));
+                self_types.push("[]".to_string());
                 let len = len.try_to_target_usize(self.tcx);
-                flags.push((sym::_Self, Some(format!("[{aty}; _]"))));
+                self_types.push(format!("[{aty}; _]"));
                 if let Some(n) = len {
-                    flags.push((sym::_Self, Some(format!("[{aty}; {n}]"))));
+                    self_types.push(format!("[{aty}; {n}]"));
                 }
                 if let Some(def) = aty.ty_adt_def() {
                     // We also want to be able to select the array's type's original
                     // signature with no type arguments resolved
                     let def_ty = self.tcx.type_of(def.did()).instantiate_identity();
-                    flags.push((sym::_Self, Some(format!("[{def_ty}; _]"))));
+                    self_types.push(format!("[{def_ty}; _]"));
                     if let Some(n) = len {
-                        flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]"))));
+                        self_types.push(format!("[{def_ty}; {n}]"));
                     }
                 }
                 if aty.is_integral() {
-                    flags.push((sym::_Self, Some("[{integral}; _]".to_string())));
+                    self_types.push("[{integral}; _]".to_string());
                     if let Some(n) = len {
-                        flags.push((sym::_Self, Some(format!("[{{integral}}; {n}]"))));
+                        self_types.push(format!("[{{integral}}; {n}]"));
                     }
                 }
             }
             if let ty::Dynamic(traits, _, _) = self_ty.kind() {
                 for t in traits.iter() {
                     if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() {
-                        flags.push((sym::_Self, Some(self.tcx.def_path_str(trait_ref.def_id))))
+                        self_types.push(self.tcx.def_path_str(trait_ref.def_id));
                     }
                 }
             }
@@ -271,31 +248,76 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 && let ty::Slice(sty) = ref_ty.kind()
                 && sty.is_integral()
             {
-                flags.push((sym::_Self, Some("&[{integral}]".to_owned())));
+                self_types.push("&[{integral}]".to_owned());
             }
         }));
 
+        let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id);
+        let trait_sugared = trait_pred.trait_ref.print_trait_sugared();
+
+        let condition_options = ConditionOptions {
+            self_types,
+            from_desugaring,
+            cause,
+            crate_local,
+            direct,
+            generic_args,
+        };
+
+        // Unlike the generic_args earlier,
+        // this one is *not* collected under `with_no_trimmed_paths!`
+        // for printing the type to the user
+        //
+        // This includes `Self`, as it is the first parameter in `own_params`.
+        let generic_args = self
+            .tcx
+            .generics_of(trait_pred.trait_ref.def_id)
+            .own_params
+            .iter()
+            .filter_map(|param| {
+                let value = match param.kind {
+                    GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
+                        if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type()
+                        {
+                            self.tcx.short_string(ty, long_ty_file)
+                        } else {
+                            trait_pred.trait_ref.args[param.index as usize].to_string()
+                        }
+                    }
+                    GenericParamDefKind::Lifetime => return None,
+                };
+                let name = param.name;
+                Some((name, value))
+            })
+            .collect();
+
+        let format_args = FormatArgs { this, trait_sugared, generic_args, item_context };
+
         if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
-            command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file)
+            command.evaluate(self.tcx, trait_pred.trait_ref, &condition_options, &format_args)
         } else {
             OnUnimplementedNote::default()
         }
     }
 }
 
+/// Represents a format string in a on_unimplemented attribute,
+/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]`
 #[derive(Clone, Debug)]
 pub struct OnUnimplementedFormatString {
-    symbol: Symbol,
-    span: Span,
-    is_diagnostic_namespace_variant: bool,
+    /// Symbol of the format string, i.e. `"content"`
+    pub symbol: Symbol,
+    ///The span of the format string, i.e. `"content"`
+    pub span: Span,
+    pub is_diagnostic_namespace_variant: bool,
 }
 
 #[derive(Debug)]
 pub struct OnUnimplementedDirective {
-    pub condition: Option<MetaItemInner>,
+    pub condition: Option<Condition>,
     pub subcommands: Vec<OnUnimplementedDirective>,
-    pub message: Option<OnUnimplementedFormatString>,
-    pub label: Option<OnUnimplementedFormatString>,
+    pub message: Option<(Span, OnUnimplementedFormatString)>,
+    pub label: Option<(Span, OnUnimplementedFormatString)>,
     pub notes: Vec<OnUnimplementedFormatString>,
     pub parent_label: Option<OnUnimplementedFormatString>,
     pub append_const_msg: Option<AppendConstMessage>,
@@ -329,7 +351,7 @@ pub struct MalformedOnUnimplementedAttrLint {
 }
 
 impl MalformedOnUnimplementedAttrLint {
-    fn new(span: Span) -> Self {
+    pub fn new(span: Span) -> Self {
         Self { span }
     }
 }
@@ -350,7 +372,7 @@ pub struct IgnoredDiagnosticOption {
 }
 
 impl IgnoredDiagnosticOption {
-    fn maybe_emit_warning<'tcx>(
+    pub fn maybe_emit_warning<'tcx>(
         tcx: TyCtxt<'tcx>,
         item_def_id: DefId,
         new: Option<Span>,
@@ -371,28 +393,10 @@ impl IgnoredDiagnosticOption {
 }
 
 #[derive(LintDiagnostic)]
-#[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
-#[help]
-pub struct UnknownFormatParameterForOnUnimplementedAttr {
-    argument_name: Symbol,
-    trait_name: Ident,
-}
-
-#[derive(LintDiagnostic)]
-#[diag(trait_selection_disallowed_positional_argument)]
-#[help]
-pub struct DisallowedPositionalArgument;
-
-#[derive(LintDiagnostic)]
-#[diag(trait_selection_invalid_format_specifier)]
-#[help]
-pub struct InvalidFormatSpecifier;
-
-#[derive(LintDiagnostic)]
 #[diag(trait_selection_wrapped_parser_error)]
 pub struct WrappedParserError {
-    description: String,
-    label: String,
+    pub description: String,
+    pub label: String,
 }
 
 impl<'tcx> OnUnimplementedDirective {
@@ -407,12 +411,12 @@ impl<'tcx> OnUnimplementedDirective {
         let mut errored = None;
         let mut item_iter = items.iter();
 
-        let parse_value = |value_str, value_span| {
+        let parse_value = |value_str, span| {
             OnUnimplementedFormatString::try_parse(
                 tcx,
                 item_def_id,
                 value_str,
-                value_span,
+                span,
                 is_diagnostic_namespace_variant,
             )
             .map(Some)
@@ -434,7 +438,7 @@ impl<'tcx> OnUnimplementedDirective {
                 }
                 true
             });
-            Some(cond.clone())
+            Some(Condition { inner: cond.clone() })
         };
 
         let mut message = None;
@@ -444,24 +448,36 @@ impl<'tcx> OnUnimplementedDirective {
         let mut subcommands = vec![];
         let mut append_const_msg = None;
 
+        let get_value_and_span = |item: &_, key| {
+            if let MetaItemInner::MetaItem(MetaItem {
+                path,
+                kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }),
+                ..
+            }) = item
+                && *path == key
+            {
+                Some((*s, *span))
+            } else {
+                None
+            }
+        };
+
         for item in item_iter {
-            if item.has_name(sym::message) && message.is_none() {
-                if let Some(message_) = item.value_str() {
-                    message = parse_value(message_, item.span())?;
-                    continue;
-                }
-            } else if item.has_name(sym::label) && label.is_none() {
-                if let Some(label_) = item.value_str() {
-                    label = parse_value(label_, item.span())?;
+            if let Some((message_, span)) = get_value_and_span(item, sym::message)
+                && message.is_none()
+            {
+                message = parse_value(message_, span)?.map(|l| (item.span(), l));
+                continue;
+            } else if let Some((label_, span)) = get_value_and_span(item, sym::label)
+                && label.is_none()
+            {
+                label = parse_value(label_, span)?.map(|l| (item.span(), l));
+                continue;
+            } else if let Some((note_, span)) = get_value_and_span(item, sym::note) {
+                if let Some(note) = parse_value(note_, span)? {
+                    notes.push(note);
                     continue;
                 }
-            } else if item.has_name(sym::note) {
-                if let Some(note_) = item.value_str() {
-                    if let Some(note) = parse_value(note_, item.span())? {
-                        notes.push(note);
-                        continue;
-                    }
-                }
             } else if item.has_name(sym::parent_label)
                 && parent_label.is_none()
                 && !is_diagnostic_namespace_variant
@@ -539,6 +555,13 @@ impl<'tcx> OnUnimplementedDirective {
     }
 
     pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
+        if !tcx.is_trait(item_def_id) {
+            // It could be a trait_alias (`trait MyTrait = SomeOtherTrait`)
+            // or an implementation (`impl MyTrait for Foo {}`)
+            //
+            // We don't support those.
+            return Ok(None);
+        }
         if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) {
             return Self::parse_attribute(attr, false, tcx, item_def_id);
         } else {
@@ -554,15 +577,15 @@ impl<'tcx> OnUnimplementedDirective {
                         IgnoredDiagnosticOption::maybe_emit_warning(
                             tcx,
                             item_def_id,
-                            directive.message.as_ref().map(|f| f.span),
-                            aggr.message.as_ref().map(|f| f.span),
+                            directive.message.as_ref().map(|f| f.0),
+                            aggr.message.as_ref().map(|f| f.0),
                             "message",
                         );
                         IgnoredDiagnosticOption::maybe_emit_warning(
                             tcx,
                             item_def_id,
-                            directive.label.as_ref().map(|f| f.span),
-                            aggr.label.as_ref().map(|f| f.span),
+                            directive.label.as_ref().map(|f| f.0),
+                            aggr.label.as_ref().map(|f| f.0),
                             "label",
                         );
                         IgnoredDiagnosticOption::maybe_emit_warning(
@@ -636,13 +659,16 @@ impl<'tcx> OnUnimplementedDirective {
                     condition: None,
                     message: None,
                     subcommands: vec![],
-                    label: Some(OnUnimplementedFormatString::try_parse(
-                        tcx,
-                        item_def_id,
-                        value,
+                    label: Some((
                         attr.span(),
-                        is_diagnostic_namespace_variant,
-                    )?),
+                        OnUnimplementedFormatString::try_parse(
+                            tcx,
+                            item_def_id,
+                            value,
+                            attr.value_span().unwrap_or(attr.span()),
+                            is_diagnostic_namespace_variant,
+                        )?,
+                    )),
                     notes: Vec::new(),
                     parent_label: None,
                     append_const_msg: None,
@@ -702,43 +728,23 @@ impl<'tcx> OnUnimplementedDirective {
         &self,
         tcx: TyCtxt<'tcx>,
         trait_ref: ty::TraitRef<'tcx>,
-        options: &[(Symbol, Option<String>)],
-        long_ty_file: &mut Option<PathBuf>,
+        condition_options: &ConditionOptions,
+        args: &FormatArgs<'tcx>,
     ) -> OnUnimplementedNote {
         let mut message = None;
         let mut label = None;
         let mut notes = Vec::new();
         let mut parent_label = None;
         let mut append_const_msg = None;
-        info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
-
-        let options_map: FxHashMap<Symbol, String> =
-            options.iter().filter_map(|(k, v)| v.clone().map(|v| (*k, v))).collect();
+        info!(
+            "evaluate({:?}, trait_ref={:?}, options={:?}, args ={:?})",
+            self, trait_ref, condition_options, args
+        );
 
         for command in self.subcommands.iter().chain(Some(self)).rev() {
             debug!(?command);
             if let Some(ref condition) = command.condition
-                && !attr::eval_condition(condition, &tcx.sess, Some(tcx.features()), &mut |cfg| {
-                    let value = cfg.value.map(|v| {
-                        // `with_no_visible_paths` is also used when generating the options,
-                        // so we need to match it here.
-                        ty::print::with_no_visible_paths!(
-                            OnUnimplementedFormatString {
-                                symbol: v,
-                                span: cfg.span,
-                                is_diagnostic_namespace_variant: false
-                            }
-                            .format(
-                                tcx,
-                                trait_ref,
-                                &options_map,
-                                long_ty_file
-                            )
-                        )
-                    });
-
-                    options.contains(&(cfg.name, value))
-                })
+                && !condition.matches_predicate(tcx, condition_options)
             {
                 debug!("evaluate: skipping {:?} due to condition", command);
                 continue;
@@ -762,14 +768,10 @@ impl<'tcx> OnUnimplementedDirective {
         }
 
         OnUnimplementedNote {
-            label: label.map(|l| l.format(tcx, trait_ref, &options_map, long_ty_file)),
-            message: message.map(|m| m.format(tcx, trait_ref, &options_map, long_ty_file)),
-            notes: notes
-                .into_iter()
-                .map(|n| n.format(tcx, trait_ref, &options_map, long_ty_file))
-                .collect(),
-            parent_label: parent_label
-                .map(|e_s| e_s.format(tcx, trait_ref, &options_map, long_ty_file)),
+            label: label.map(|l| l.1.format(tcx, trait_ref, args)),
+            message: message.map(|m| m.1.format(tcx, trait_ref, args)),
+            notes: notes.into_iter().map(|n| n.format(tcx, trait_ref, args)).collect(),
+            parent_label: parent_label.map(|e_s| e_s.format(tcx, trait_ref, args)),
             append_const_msg,
         }
     }
@@ -780,142 +782,95 @@ impl<'tcx> OnUnimplementedFormatString {
         tcx: TyCtxt<'tcx>,
         item_def_id: DefId,
         from: Symbol,
-        value_span: Span,
+        span: Span,
         is_diagnostic_namespace_variant: bool,
     ) -> Result<Self, ErrorGuaranteed> {
-        let result = OnUnimplementedFormatString {
-            symbol: from,
-            span: value_span,
-            is_diagnostic_namespace_variant,
-        };
+        let result =
+            OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant };
         result.verify(tcx, item_def_id)?;
         Ok(result)
     }
 
-    fn verify(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<(), ErrorGuaranteed> {
-        let trait_def_id = if tcx.is_trait(item_def_id) {
-            item_def_id
+    fn verify(&self, tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> Result<(), ErrorGuaranteed> {
+        if !tcx.is_trait(trait_def_id) {
+            return Ok(());
+        };
+
+        let ctx = if self.is_diagnostic_namespace_variant {
+            Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }
         } else {
-            tcx.trait_id_of_impl(item_def_id)
-                .expect("expected `on_unimplemented` to correspond to a trait")
+            Ctx::RustcOnUnimplemented { tcx, trait_def_id }
         };
-        let trait_name = tcx.item_ident(trait_def_id);
-        let generics = tcx.generics_of(item_def_id);
-        let s = self.symbol.as_str();
-        let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
+
         let mut result = Ok(());
-        for token in &mut parser {
-            match token {
-                Piece::Lit(_) => (), // Normal string, no need to check it
-                Piece::NextArgument(a) => {
-                    let format_spec = a.format;
-                    if self.is_diagnostic_namespace_variant
-                        && (format_spec.ty_span.is_some()
-                            || format_spec.width_span.is_some()
-                            || format_spec.precision_span.is_some()
-                            || format_spec.fill_span.is_some())
-                    {
-                        if let Some(item_def_id) = item_def_id.as_local() {
-                            tcx.emit_node_span_lint(
-                                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
-                                tcx.local_def_id_to_hir_id(item_def_id),
-                                self.span,
-                                InvalidFormatSpecifier,
-                            );
-                        }
+
+        match FormatString::parse(self.symbol, self.span, &ctx) {
+            // Warnings about format specifiers, deprecated parameters, wrong parameters etc.
+            // In other words we'd like to let the author know, but we can still try to format the string later
+            Ok(FormatString { warnings, .. }) => {
+                if self.is_diagnostic_namespace_variant {
+                    for w in warnings {
+                        w.emit_warning(tcx, trait_def_id)
                     }
-                    match a.position {
-                        Position::ArgumentNamed(s) => {
-                            match Symbol::intern(s) {
-                                // `{ThisTraitsName}` is allowed
-                                s if s == trait_name.name
-                                    && !self.is_diagnostic_namespace_variant =>
-                                {
-                                    ()
-                                }
-                                s if ALLOWED_FORMAT_SYMBOLS.contains(&s)
-                                    && !self.is_diagnostic_namespace_variant =>
-                                {
-                                    ()
-                                }
-                                // So is `{A}` if A is a type parameter
-                                s if generics.own_params.iter().any(|param| param.name == s) => (),
-                                s => {
-                                    if self.is_diagnostic_namespace_variant {
-                                        if let Some(item_def_id) = item_def_id.as_local() {
-                                            tcx.emit_node_span_lint(
-                                                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
-                                                tcx.local_def_id_to_hir_id(item_def_id),
-                                                self.span,
-                                                UnknownFormatParameterForOnUnimplementedAttr {
-                                                    argument_name: s,
-                                                    trait_name,
-                                                },
-                                            );
-                                        }
-                                    } else {
-                                        result = Err(struct_span_code_err!(
-                                            tcx.dcx(),
-                                            self.span,
-                                            E0230,
-                                            "there is no parameter `{}` on {}",
-                                            s,
-                                            if trait_def_id == item_def_id {
-                                                format!("trait `{trait_name}`")
-                                            } else {
-                                                "impl".to_string()
-                                            }
-                                        )
-                                        .emit());
-                                    }
-                                }
+                } else {
+                    for w in warnings {
+                        match w {
+                            FormatWarning::UnknownParam { argument_name, span } => {
+                                let reported = struct_span_code_err!(
+                                    tcx.dcx(),
+                                    span,
+                                    E0230,
+                                    "cannot find parameter {} on this trait",
+                                    argument_name,
+                                )
+                                .emit();
+                                result = Err(reported);
                             }
-                        }
-                        // `{:1}` and `{}` are not to be used
-                        Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => {
-                            if self.is_diagnostic_namespace_variant {
-                                if let Some(item_def_id) = item_def_id.as_local() {
-                                    tcx.emit_node_span_lint(
-                                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
-                                        tcx.local_def_id_to_hir_id(item_def_id),
-                                        self.span,
-                                        DisallowedPositionalArgument,
-                                    );
-                                }
-                            } else {
+                            FormatWarning::PositionalArgument { span, .. } => {
                                 let reported = struct_span_code_err!(
                                     tcx.dcx(),
-                                    self.span,
+                                    span,
                                     E0231,
-                                    "only named generic parameters are allowed"
+                                    "positional format arguments are not allowed here"
                                 )
                                 .emit();
                                 result = Err(reported);
                             }
+                            FormatWarning::InvalidSpecifier { .. }
+                            | FormatWarning::FutureIncompat { .. } => {}
                         }
                     }
                 }
             }
-        }
-        // we cannot return errors from processing the format string as hard error here
-        // as the diagnostic namespace guarantees that malformed input cannot cause an error
-        //
-        // if we encounter any error while processing we nevertheless want to show it as warning
-        // so that users are aware that something is not correct
-        for e in parser.errors {
-            if self.is_diagnostic_namespace_variant {
-                if let Some(item_def_id) = item_def_id.as_local() {
-                    tcx.emit_node_span_lint(
-                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
-                        tcx.local_def_id_to_hir_id(item_def_id),
-                        self.span,
-                        WrappedParserError { description: e.description, label: e.label },
-                    );
+            // Errors from the underlying `rustc_parse_format::Parser`
+            Err(errors) => {
+                // we cannot return errors from processing the format string as hard error here
+                // as the diagnostic namespace guarantees that malformed input cannot cause an error
+                //
+                // if we encounter any error while processing we nevertheless want to show it as warning
+                // so that users are aware that something is not correct
+                for e in errors {
+                    if self.is_diagnostic_namespace_variant {
+                        if let Some(trait_def_id) = trait_def_id.as_local() {
+                            tcx.emit_node_span_lint(
+                                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                                tcx.local_def_id_to_hir_id(trait_def_id),
+                                self.span,
+                                WrappedParserError { description: e.description, label: e.label },
+                            );
+                        }
+                    } else {
+                        let reported = struct_span_code_err!(
+                            tcx.dcx(),
+                            self.span,
+                            E0231,
+                            "{}",
+                            e.description,
+                        )
+                        .emit();
+                        result = Err(reported);
+                    }
                 }
-            } else {
-                let reported =
-                    struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,).emit();
-                result = Err(reported);
             }
         }
 
@@ -926,98 +881,28 @@ impl<'tcx> OnUnimplementedFormatString {
         &self,
         tcx: TyCtxt<'tcx>,
         trait_ref: ty::TraitRef<'tcx>,
-        options: &FxHashMap<Symbol, String>,
-        long_ty_file: &mut Option<PathBuf>,
+        args: &FormatArgs<'tcx>,
     ) -> String {
-        let name = tcx.item_name(trait_ref.def_id);
-        let trait_str = tcx.def_path_str(trait_ref.def_id);
-        let generics = tcx.generics_of(trait_ref.def_id);
-        let generic_map = generics
-            .own_params
-            .iter()
-            .filter_map(|param| {
-                let value = match param.kind {
-                    GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
-                        if let Some(ty) = trait_ref.args[param.index as usize].as_type() {
-                            tcx.short_string(ty, long_ty_file)
-                        } else {
-                            trait_ref.args[param.index as usize].to_string()
-                        }
-                    }
-                    GenericParamDefKind::Lifetime => return None,
-                };
-                let name = param.name;
-                Some((name, value))
-            })
-            .collect::<FxHashMap<Symbol, String>>();
-        let empty_string = String::new();
-
-        let s = self.symbol.as_str();
-        let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
-        let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string);
-        let constructed_message = (&mut parser)
-            .map(|p| match p {
-                Piece::Lit(s) => s.to_owned(),
-                Piece::NextArgument(a) => match a.position {
-                    Position::ArgumentNamed(arg) => {
-                        let s = Symbol::intern(arg);
-                        match generic_map.get(&s) {
-                            Some(val) => val.to_string(),
-                            None if self.is_diagnostic_namespace_variant => {
-                                format!("{{{arg}}}")
-                            }
-                            None if s == name => trait_str.clone(),
-                            None => {
-                                if let Some(val) = options.get(&s) {
-                                    val.clone()
-                                } else if s == sym::from_desugaring {
-                                    // don't break messages using these two arguments incorrectly
-                                    String::new()
-                                } else if s == sym::ItemContext
-                                    && !self.is_diagnostic_namespace_variant
-                                {
-                                    item_context.clone()
-                                } else if s == sym::integral {
-                                    String::from("{integral}")
-                                } else if s == sym::integer_ {
-                                    String::from("{integer}")
-                                } else if s == sym::float {
-                                    String::from("{float}")
-                                } else {
-                                    bug!(
-                                        "broken on_unimplemented {:?} for {:?}: \
-                                      no argument matching {:?}",
-                                        self.symbol,
-                                        trait_ref,
-                                        s
-                                    )
-                                }
-                            }
-                        }
-                    }
-                    Position::ArgumentImplicitlyIs(_) if self.is_diagnostic_namespace_variant => {
-                        String::from("{}")
-                    }
-                    Position::ArgumentIs(idx) if self.is_diagnostic_namespace_variant => {
-                        format!("{{{idx}}}")
-                    }
-                    _ => bug!("broken on_unimplemented {:?} - bad format arg", self.symbol),
-                },
-            })
-            .collect();
-        // we cannot return errors from processing the format string as hard error here
-        // as the diagnostic namespace guarantees that malformed input cannot cause an error
-        //
-        // if we encounter any error while processing the format string
-        // we don't want to show the potentially half assembled formatted string,
-        // therefore we fall back to just showing the input string in this case
-        //
-        // The actual parser errors are emitted earlier
-        // as lint warnings in OnUnimplementedFormatString::verify
-        if self.is_diagnostic_namespace_variant && !parser.errors.is_empty() {
-            String::from(s)
+        let trait_def_id = trait_ref.def_id;
+        let ctx = if self.is_diagnostic_namespace_variant {
+            Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }
+        } else {
+            Ctx::RustcOnUnimplemented { tcx, trait_def_id }
+        };
+
+        if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) {
+            s.format(args)
         } else {
-            constructed_message
+            // we cannot return errors from processing the format string as hard error here
+            // as the diagnostic namespace guarantees that malformed input cannot cause an error
+            //
+            // if we encounter any error while processing the format string
+            // we don't want to show the potentially half assembled formatted string,
+            // therefore we fall back to just showing the input string in this case
+            //
+            // The actual parser errors are emitted earlier
+            // as lint warnings in OnUnimplementedFormatString::verify
+            self.symbol.as_str().into()
         }
     }
 }
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
new file mode 100644
index 00000000000..116cfb01cb6
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
@@ -0,0 +1,120 @@
+use rustc_ast::MetaItemInner;
+use rustc_attr_parsing as attr;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_parse_format::{ParseMode, Parser, Piece, Position};
+use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
+
+/// A predicate in an attribute using on, all, any,
+/// similar to a cfg predicate.
+#[derive(Debug)]
+pub struct Condition {
+    pub inner: MetaItemInner,
+}
+
+impl Condition {
+    pub fn span(&self) -> Span {
+        self.inner.span()
+    }
+
+    pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
+        attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
+            let value = cfg.value.map(|v| {
+                // `with_no_visible_paths` is also used when generating the options,
+                // so we need to match it here.
+                ty::print::with_no_visible_paths!({
+                    Parser::new(v.as_str(), None, None, false, ParseMode::Format)
+                        .map(|p| match p {
+                            Piece::Lit(s) => s.to_owned(),
+                            Piece::NextArgument(a) => match a.position {
+                                Position::ArgumentNamed(arg) => {
+                                    let s = Symbol::intern(arg);
+                                    match options.generic_args.iter().find(|(k, _)| *k == s) {
+                                        Some((_, val)) => val.to_string(),
+                                        None => format!("{{{arg}}}"),
+                                    }
+                                }
+                                Position::ArgumentImplicitlyIs(_) => String::from("{}"),
+                                Position::ArgumentIs(idx) => format!("{{{idx}}}"),
+                            },
+                        })
+                        .collect()
+                })
+            });
+
+            options.contains(cfg.name, &value)
+        })
+    }
+}
+
+/// Used with `Condition::matches_predicate` to test whether the condition applies
+///
+/// For example, given a
+/// ```rust,ignore (just an example)
+/// #[rustc_on_unimplemented(
+///     on(all(from_desugaring = "QuestionMark"),
+///         message = "the `?` operator can only be used in {ItemContext} \
+///                     that returns `Result` or `Option` \
+///                     (or another type that implements `{FromResidual}`)",
+///         label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
+///         parent_label = "this function should return `Result` or `Option` to accept `?`"
+///     ),
+/// )]
+/// pub trait FromResidual<R = <Self as Try>::Residual> {
+///    ...
+/// }
+///
+/// async fn an_async_function() -> u32 {
+///     let x: Option<u32> = None;
+///     x?; //~ ERROR the `?` operator
+///     22
+/// }
+///  ```
+/// it will look like this:
+///
+/// ```rust,ignore (just an example)
+/// ConditionOptions {
+///     self_types: ["u32", "{integral}"],
+///     from_desugaring: Some("QuestionMark"),
+///     cause: None,
+///     crate_local: false,
+///     direct: true,
+///     generic_args: [("Self","u32"),
+///         ("R", "core::option::Option<core::convert::Infallible>"),
+///         ("R", "core::option::Option<T>" ),
+///     ],
+/// }
+/// ```
+#[derive(Debug)]
+pub struct ConditionOptions {
+    /// All the self types that may apply.
+    /// for example
+    pub self_types: Vec<String>,
+    // The kind of compiler desugaring.
+    pub from_desugaring: Option<DesugaringKind>,
+    /// Match on a variant of [rustc_infer::traits::ObligationCauseCode]
+    pub cause: Option<String>,
+    pub crate_local: bool,
+    /// Is the obligation "directly" user-specified, rather than derived?
+    pub direct: bool,
+    // A list of the generic arguments and their reified types
+    pub generic_args: Vec<(Symbol, String)>,
+}
+
+impl ConditionOptions {
+    pub fn contains(&self, key: Symbol, value: &Option<String>) -> bool {
+        match (key, value) {
+            (sym::_Self | kw::SelfUpper, Some(value)) => self.self_types.contains(&value),
+            // from_desugaring as a flag
+            (sym::from_desugaring, None) => self.from_desugaring.is_some(),
+            // from_desugaring as key == value
+            (sym::from_desugaring, Some(v)) if let Some(ds) = self.from_desugaring => ds.matches(v),
+            (sym::cause, Some(value)) => self.cause.as_deref() == Some(value),
+            (sym::crate_local, None) => self.crate_local,
+            (sym::direct, None) => self.direct,
+            (other, Some(value)) => {
+                self.generic_args.iter().any(|(k, v)| *k == other && v == value)
+            }
+            _ => false,
+        }
+    }
+}
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs
new file mode 100644
index 00000000000..f835406122b
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs
@@ -0,0 +1,414 @@
+use std::fmt;
+
+use errors::*;
+use rustc_middle::ty::TyCtxt;
+use rustc_middle::ty::print::TraitRefPrintSugared;
+use rustc_parse_format::{
+    Alignment, Argument, Count, FormatSpec, InnerSpan, ParseError, ParseMode, Parser,
+    Piece as RpfPiece, Position,
+};
+use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
+use rustc_span::def_id::DefId;
+use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym};
+
+/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces",
+/// either as string pieces or dynamic arguments.
+#[derive(Debug)]
+pub struct FormatString {
+    #[allow(dead_code, reason = "Debug impl")]
+    input: Symbol,
+    span: Span,
+    pieces: Vec<Piece>,
+    /// The formatting string was parsed succesfully but with warnings
+    pub warnings: Vec<FormatWarning>,
+}
+
+#[derive(Debug)]
+enum Piece {
+    Lit(String),
+    Arg(FormatArg),
+}
+
+#[derive(Debug)]
+enum FormatArg {
+    // A generic parameter, like `{T}` if we're on the `From<T>` trait.
+    GenericParam {
+        generic_param: Symbol,
+    },
+    // `{Self}`
+    SelfUpper,
+    /// `{This}` or `{TraitName}`
+    This,
+    /// The sugared form of the trait
+    Trait,
+    /// what we're in, like a function, method, closure etc.
+    ItemContext,
+    /// What the user typed, if it doesn't match anything we can use.
+    AsIs(String),
+}
+
+pub enum Ctx<'tcx> {
+    // `#[rustc_on_unimplemented]`
+    RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
+    // `#[diagnostic::...]`
+    DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
+}
+
+#[derive(Debug)]
+pub enum FormatWarning {
+    UnknownParam { argument_name: Symbol, span: Span },
+    PositionalArgument { span: Span, help: String },
+    InvalidSpecifier { name: String, span: Span },
+    FutureIncompat { span: Span, help: String },
+}
+
+impl FormatWarning {
+    pub fn emit_warning<'tcx>(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) {
+        match *self {
+            FormatWarning::UnknownParam { argument_name, span } => {
+                let this = tcx.item_ident(item_def_id);
+                if let Some(item_def_id) = item_def_id.as_local() {
+                    tcx.emit_node_span_lint(
+                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                        tcx.local_def_id_to_hir_id(item_def_id),
+                        span,
+                        UnknownFormatParameterForOnUnimplementedAttr {
+                            argument_name,
+                            trait_name: this,
+                        },
+                    );
+                }
+            }
+            FormatWarning::PositionalArgument { span, .. } => {
+                if let Some(item_def_id) = item_def_id.as_local() {
+                    tcx.emit_node_span_lint(
+                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                        tcx.local_def_id_to_hir_id(item_def_id),
+                        span,
+                        DisallowedPositionalArgument,
+                    );
+                }
+            }
+            FormatWarning::InvalidSpecifier { span, .. } => {
+                if let Some(item_def_id) = item_def_id.as_local() {
+                    tcx.emit_node_span_lint(
+                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                        tcx.local_def_id_to_hir_id(item_def_id),
+                        span,
+                        InvalidFormatSpecifier,
+                    );
+                }
+            }
+            FormatWarning::FutureIncompat { .. } => {
+                // We've never deprecated anything in diagnostic namespace format strings
+                // but if we do we will emit a warning here
+
+                // FIXME(mejrs) in a couple releases, start emitting warnings for
+                // #[rustc_on_unimplemented] deprecated args
+            }
+        }
+    }
+}
+
+/// Arguments to fill a [FormatString] with.
+///
+/// For example, given a
+/// ```rust,ignore (just an example)
+///
+/// #[rustc_on_unimplemented(
+///     on(all(from_desugaring = "QuestionMark"),
+///         message = "the `?` operator can only be used in {ItemContext} \
+///                     that returns `Result` or `Option` \
+///                     (or another type that implements `{FromResidual}`)",
+///         label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
+///         parent_label = "this function should return `Result` or `Option` to accept `?`"
+///     ),
+/// )]
+/// pub trait FromResidual<R = <Self as Try>::Residual> {
+///    ...
+/// }
+///
+/// async fn an_async_function() -> u32 {
+///     let x: Option<u32> = None;
+///     x?; //~ ERROR the `?` operator
+///     22
+/// }
+///  ```
+/// it will look like this:
+///
+/// ```rust,ignore (just an example)
+/// FormatArgs {
+///     this: "FromResidual",
+///     trait_sugared: "FromResidual<Option<Infallible>>",
+///     item_context: "an async function",
+///     generic_args: [("Self", "u32"), ("R", "Option<Infallible>")],
+/// }
+/// ```
+#[derive(Debug)]
+pub struct FormatArgs<'tcx> {
+    pub this: String,
+    pub trait_sugared: TraitRefPrintSugared<'tcx>,
+    pub item_context: &'static str,
+    pub generic_args: Vec<(Symbol, String)>,
+}
+
+impl FormatString {
+    pub fn span(&self) -> Span {
+        self.span
+    }
+
+    pub fn parse<'tcx>(
+        input: Symbol,
+        span: Span,
+        ctx: &Ctx<'tcx>,
+    ) -> Result<Self, Vec<ParseError>> {
+        let s = input.as_str();
+        let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
+        let mut pieces = Vec::new();
+        let mut warnings = Vec::new();
+
+        for piece in &mut parser {
+            match piece {
+                RpfPiece::Lit(lit) => {
+                    pieces.push(Piece::Lit(lit.into()));
+                }
+                RpfPiece::NextArgument(arg) => {
+                    warn_on_format_spec(arg.format, &mut warnings, span);
+                    let arg = parse_arg(&arg, ctx, &mut warnings, span);
+                    pieces.push(Piece::Arg(arg));
+                }
+            }
+        }
+
+        if parser.errors.is_empty() {
+            Ok(FormatString { input, pieces, span, warnings })
+        } else {
+            Err(parser.errors)
+        }
+    }
+
+    pub fn format(&self, args: &FormatArgs<'_>) -> String {
+        let mut ret = String::new();
+        for piece in &self.pieces {
+            match piece {
+                Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s),
+
+                // `A` if we have `trait Trait<A> {}` and `note = "i'm the actual type of {A}"`
+                Piece::Arg(FormatArg::GenericParam { generic_param }) => {
+                    // Should always be some but we can't raise errors here
+                    let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) {
+                        Some((_, val)) => val.to_string(),
+                        None => generic_param.to_string(),
+                    };
+                    ret.push_str(&value);
+                }
+                // `{Self}`
+                Piece::Arg(FormatArg::SelfUpper) => {
+                    let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) {
+                        Some((_, val)) => val.to_string(),
+                        None => "Self".to_string(),
+                    };
+                    ret.push_str(&slf);
+                }
+
+                // It's only `rustc_onunimplemented` from here
+                Piece::Arg(FormatArg::This) => ret.push_str(&args.this),
+                Piece::Arg(FormatArg::Trait) => {
+                    let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared));
+                }
+                Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context),
+            }
+        }
+        ret
+    }
+}
+
+fn parse_arg<'tcx>(
+    arg: &Argument<'_>,
+    ctx: &Ctx<'tcx>,
+    warnings: &mut Vec<FormatWarning>,
+    input_span: Span,
+) -> FormatArg {
+    let (Ctx::RustcOnUnimplemented { tcx, trait_def_id }
+    | Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = ctx;
+    let trait_name = tcx.item_ident(*trait_def_id);
+    let generics = tcx.generics_of(trait_def_id);
+    let span = slice_span(input_span, arg.position_span);
+
+    match arg.position {
+        // Something like "hello {name}"
+        Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
+            // accepted, but deprecated
+            (Ctx::RustcOnUnimplemented { .. }, sym::_Self) => {
+                warnings
+                    .push(FormatWarning::FutureIncompat { span, help: String::from("use {Self}") });
+                FormatArg::SelfUpper
+            }
+            (
+                Ctx::RustcOnUnimplemented { .. },
+                sym::from_desugaring
+                | sym::crate_local
+                | sym::direct
+                | sym::cause
+                | sym::float
+                | sym::integer_
+                | sym::integral,
+            ) => {
+                warnings.push(FormatWarning::FutureIncompat {
+                    span,
+                    help: String::from("don't use this in a format string"),
+                });
+                FormatArg::AsIs(String::new())
+            }
+
+            // Only `#[rustc_on_unimplemented]` can use these
+            (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
+            (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
+            (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
+            // `{ThisTraitsName}`. Some attrs in std use this, but I'd like to change it to the more general `{This}`
+            // because that'll be simpler to parse and extend in the future
+            (Ctx::RustcOnUnimplemented { .. }, name) if name == trait_name.name => {
+                warnings
+                    .push(FormatWarning::FutureIncompat { span, help: String::from("use {This}") });
+                FormatArg::This
+            }
+
+            // Any attribute can use these
+            (
+                Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
+                kw::SelfUpper,
+            ) => FormatArg::SelfUpper,
+            (
+                Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
+                generic_param,
+            ) if generics.own_params.iter().any(|param| param.name == generic_param) => {
+                FormatArg::GenericParam { generic_param }
+            }
+
+            (_, argument_name) => {
+                warnings.push(FormatWarning::UnknownParam { argument_name, span });
+                FormatArg::AsIs(format!("{{{}}}", argument_name.as_str()))
+            }
+        },
+
+        // `{:1}` and `{}` are ignored
+        Position::ArgumentIs(idx) => {
+            warnings.push(FormatWarning::PositionalArgument {
+                span,
+                help: format!("use `{{{idx}}}` to print a number in braces"),
+            });
+            FormatArg::AsIs(format!("{{{idx}}}"))
+        }
+        Position::ArgumentImplicitlyIs(_) => {
+            warnings.push(FormatWarning::PositionalArgument {
+                span,
+                help: String::from("use `{{}}` to print empty braces"),
+            });
+            FormatArg::AsIs(String::from("{}"))
+        }
+    }
+}
+
+/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
+/// with specifiers, so emit a warning if they are used.
+fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec<FormatWarning>, input_span: Span) {
+    if !matches!(
+        spec,
+        FormatSpec {
+            fill: None,
+            fill_span: None,
+            align: Alignment::AlignUnknown,
+            sign: None,
+            alternate: false,
+            zero_pad: false,
+            debug_hex: None,
+            precision: Count::CountImplied,
+            precision_span: None,
+            width: Count::CountImplied,
+            width_span: None,
+            ty: _,
+            ty_span: _,
+        },
+    ) {
+        let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span);
+        warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
+    }
+}
+
+/// Helper function because `Span` and `rustc_parse_format::InnerSpan` don't know about each other
+fn slice_span(input: Span, inner: InnerSpan) -> Span {
+    let InnerSpan { start, end } = inner;
+    let span = input.data();
+
+    Span::new(
+        span.lo + BytePos::from_usize(start),
+        span.lo + BytePos::from_usize(end),
+        span.ctxt,
+        span.parent,
+    )
+}
+
+pub mod errors {
+    use rustc_macros::LintDiagnostic;
+    use rustc_span::Ident;
+
+    use super::*;
+
+    #[derive(LintDiagnostic)]
+    #[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
+    #[help]
+    pub struct UnknownFormatParameterForOnUnimplementedAttr {
+        pub argument_name: Symbol,
+        pub trait_name: Ident,
+    }
+
+    #[derive(LintDiagnostic)]
+    #[diag(trait_selection_disallowed_positional_argument)]
+    #[help]
+    pub struct DisallowedPositionalArgument;
+
+    #[derive(LintDiagnostic)]
+    #[diag(trait_selection_invalid_format_specifier)]
+    #[help]
+    pub struct InvalidFormatSpecifier;
+
+    #[derive(LintDiagnostic)]
+    #[diag(trait_selection_missing_options_for_on_unimplemented_attr)]
+    #[help]
+    pub struct MissingOptionsForOnUnimplementedAttr;
+
+    #[derive(LintDiagnostic)]
+    #[diag(trait_selection_ignored_diagnostic_option)]
+    pub struct IgnoredDiagnosticOption {
+        pub option_name: &'static str,
+        #[label]
+        pub span: Span,
+        #[label(trait_selection_other_label)]
+        pub prev_span: Span,
+    }
+
+    impl IgnoredDiagnosticOption {
+        pub fn maybe_emit_warning<'tcx>(
+            tcx: TyCtxt<'tcx>,
+            item_def_id: DefId,
+            new: Option<Span>,
+            old: Option<Span>,
+            option_name: &'static str,
+        ) {
+            if let (Some(new_item), Some(old_item)) = (new, old) {
+                if let Some(item_def_id) = item_def_id.as_local() {
+                    tcx.emit_node_span_lint(
+                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                        tcx.local_def_id_to_hir_id(item_def_id),
+                        new_item,
+                        IgnoredDiagnosticOption {
+                            span: new_item,
+                            prev_span: old_item,
+                            option_name,
+                        },
+                    );
+                }
+            }
+        }
+    }
+}