about summary refs log tree commit diff
diff options
context:
space:
mode:
authormejrs <59372212+mejrs@users.noreply.github.com>2025-03-27 15:39:23 +0100
committermejrs <59372212+mejrs@users.noreply.github.com>2025-04-10 17:28:23 +0200
commit2007c8994db6d46533f84f4697fea5734c6df53d (patch)
tree0b18d56589d061b2847ffbadbce7f8934ed5d4cf
parent199ee4084349361ae44c6e4b0cbc9a5e72913b20 (diff)
downloadrust-2007c8994db6d46533f84f4697fea5734c6df53d.tar.gz
rust-2007c8994db6d46533f84f4697fea5734c6df53d.zip
Write the format string parserand split it from conditions parser
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs1
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs399
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs64
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs344
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr48
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr80
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr8
-rw-r--r--tests/ui/on-unimplemented/bad-annotation.rs4
-rw-r--r--tests/ui/on-unimplemented/bad-annotation.stderr22
-rw-r--r--tests/ui/on-unimplemented/impl-substs.stderr2
11 files changed, 613 insertions, 360 deletions
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 31847ae3b46..0c458db0b23 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -372,6 +372,7 @@ symbols! {
         SyncUnsafeCell,
         T,
         Target,
+        This,
         ToOwned,
         ToString,
         TokenStream,
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 7fd1c0d2743..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,7 @@ 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 9914e828a93..6a7f3223421 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,7 +1,7 @@
 use std::iter;
 use std::path::PathBuf;
 
-use rustc_ast::MetaItemInner;
+use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::codes::*;
 use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
@@ -10,35 +10,21 @@ use rustc_hir::{AttrArgs, Attribute};
 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_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
-use rustc_span::{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;
 use crate::error_reporting::traits::on_unimplemented_format::errors::*;
+use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatString};
 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,
@@ -275,6 +261,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             }
         }));
 
+        flags.push((sym::This, Some(self.tcx.def_path_str(trait_pred.trait_ref.def_id))));
+
         if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
             command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file)
         } else {
@@ -283,19 +271,23 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
     }
 }
 
+/// 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>,
@@ -332,12 +324,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)
@@ -359,7 +351,7 @@ impl<'tcx> OnUnimplementedDirective {
                 }
                 true
             });
-            Some(cond.clone())
+            Some(Condition { inner: cond.clone() })
         };
 
         let mut message = None;
@@ -369,24 +361,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
@@ -479,15 +483,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(
@@ -561,13 +565,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,
@@ -643,27 +650,7 @@ impl<'tcx> OnUnimplementedDirective {
         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, options, &options_map)
             {
                 debug!("evaluate: skipping {:?} due to condition", command);
                 continue;
@@ -687,8 +674,8 @@ 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)),
+            label: label.map(|l| l.1.format(tcx, trait_ref, &options_map, long_ty_file)),
+            message: message.map(|m| m.1.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))
@@ -705,144 +692,65 @@ 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
+        let trait_def_id = if tcx.is_trait(item_def_id) { item_def_id } else { 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())
-                    {
+
+        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, .. }) => {
+                for w in warnings {
+                    w.emit_warning(tcx, trait_def_id)
+                }
+            }
+            // 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(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,
+                                WrappedParserError { description: e.description, label: e.label },
                             );
                         }
-                    }
-                    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());
-                                    }
-                                }
-                            }
-                        }
-                        // `{: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 {
-                                let reported = struct_span_code_err!(
-                                    tcx.dcx(),
-                                    self.span,
-                                    E0231,
-                                    "only named generic parameters are allowed"
-                                )
-                                .emit();
-                                result = Err(reported);
-                            }
-                        }
+                    } else {
+                        let reported = struct_span_code_err!(
+                            tcx.dcx(),
+                            self.span,
+                            E0231,
+                            "{}",
+                            e.description,
+                        )
+                        .emit();
+                        result = Err(reported);
                     }
                 }
             }
         }
-        // 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 },
-                    );
-                }
-            } else {
-                let reported =
-                    struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,).emit();
-                result = Err(reported);
-            }
-        }
 
         result
     }
@@ -854,95 +762,26 @@ impl<'tcx> OnUnimplementedFormatString {
         options: &FxHashMap<Symbol, String>,
         long_ty_file: &mut Option<PathBuf>,
     ) -> 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(tcx, trait_ref, options, long_ty_file)
         } 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..4c852c1023a
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
@@ -0,0 +1,64 @@
+use rustc_ast::MetaItemInner;
+use rustc_attr_parsing as attr;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_parse_format::{ParseMode, Parser, Piece, Position};
+use rustc_span::{Span, Symbol, sym};
+
+pub static ALLOWED_CONDITION_SYMBOLS: &[Symbol] = &[
+    sym::from_desugaring,
+    sym::direct,
+    sym::cause,
+    sym::integral,
+    sym::integer_,
+    sym::float,
+    sym::_Self,
+    sym::crate_local,
+];
+
+#[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: &[(Symbol, Option<String>)],
+        options_map: &FxHashMap<Symbol, String>,
+    ) -> 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!({
+                    let mut parser = Parser::new(v.as_str(), None, None, false, ParseMode::Format);
+                    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 options_map.get(&s) {
+                                        Some(val) => val.to_string(),
+                                        None => format!("{{{arg}}}"),
+                                    }
+                                }
+                                Position::ArgumentImplicitlyIs(_) => String::from("{}"),
+                                Position::ArgumentIs(idx) => format!("{{{idx}}}"),
+                            },
+                        })
+                        .collect();
+                    constructed_message
+                })
+            });
+
+            options.contains(&(cfg.name, value))
+        })
+    }
+}
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
index 40a47921fc1..21db207496c 100644
--- 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
@@ -1,3 +1,347 @@
+use std::fmt::Write;
+use std::path::PathBuf;
+
+use errors::*;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::span_bug;
+use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt};
+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};
+
+pub struct FormatString {
+    input: Symbol,
+    input_span: Span,
+    pieces: Vec<Piece>,
+    // the formatting string was parsed succesfully but with warnings
+    pub warnings: Vec<FormatWarning>,
+}
+
+enum Piece {
+    Lit(String),
+    Arg(FormatArg),
+}
+
+pub enum FormatArg {
+    // A generic parameter, like `{T}` if we're on the `From<T>` trait.
+    GenericParam { generic_param: Symbol, span: Span },
+    // `{Self}`
+    SelfUpper,
+    This,
+    Trait,
+    ItemContext,
+    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 },
+}
+
+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
+            }
+        }
+    }
+}
+
+impl FormatString {
+    pub fn parse(input: Symbol, input_span: Span, ctx: &Ctx<'_>) -> 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, input_span);
+                    let arg = parse_arg(&arg, ctx, &mut warnings, input_span);
+                    pieces.push(Piece::Arg(arg));
+                }
+            }
+        }
+
+        if parser.errors.is_empty() {
+            Ok(FormatString { input, input_span, pieces, warnings })
+        } else {
+            Err(parser.errors)
+        }
+    }
+
+    pub fn format<'tcx>(
+        &self,
+        tcx: TyCtxt<'tcx>,
+        trait_ref: ty::TraitRef<'tcx>,
+        options: &FxHashMap<Symbol, String>,
+        long_ty_file: &mut Option<PathBuf>,
+    ) -> String {
+        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 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, span }) => {
+                    // Should always be some but we can't raise errors here
+                    if let Some(value) = generic_map.get(&generic_param) {
+                        ret.push_str(value);
+                    } else if cfg!(debug_assertions) {
+                        span_bug!(*span, "invalid generic parameter");
+                    } else {
+                        let _ = ret.write_fmt(format_args!("{{{}}}", generic_param.as_str()));
+                    }
+                }
+                // `{Self}`
+                Piece::Arg(FormatArg::SelfUpper) => {
+                    let Some(slf) = generic_map.get(&kw::SelfUpper) else {
+                        span_bug!(
+                            self.input_span,
+                            "broken format string {:?} for {:?}: \
+                                  no argument matching `Self`",
+                            self.input,
+                            trait_ref,
+                        )
+                    };
+                    ret.push_str(&slf);
+                }
+
+                // It's only `rustc_onunimplemented` from here
+                Piece::Arg(FormatArg::This) => {
+                    let Some(this) = options.get(&sym::This) else {
+                        span_bug!(
+                            self.input_span,
+                            "broken format string {:?} for {:?}: \
+                                      no argument matching This",
+                            self.input,
+                            trait_ref,
+                        )
+                    };
+                    ret.push_str(this);
+                }
+                Piece::Arg(FormatArg::Trait) => {
+                    let Some(this) = options.get(&sym::Trait) else {
+                        span_bug!(
+                            self.input_span,
+                            "broken format string {:?} for {:?}: \
+                                      no argument matching Trait",
+                            self.input,
+                            trait_ref,
+                        )
+                    };
+                    ret.push_str(this);
+                }
+                Piece::Arg(FormatArg::ItemContext) => {
+                    let itemcontext = options.get(&sym::ItemContext);
+                    ret.push_str(itemcontext.unwrap_or(&String::new()));
+                }
+            }
+        }
+        ret
+    }
+}
+
+fn parse_arg(
+    arg: &Argument<'_>,
+    ctx: &Ctx<'_>,
+    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, span }
+            }
+
+            (_, 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() })
+    }
+}
+
+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_middle::ty::TyCtxt;
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr
index 7fd51c7527f..a82a1e78da0 100644
--- a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr
@@ -1,52 +1,52 @@
 warning: unmatched `}` found
-  --> $DIR/broken_format.rs:2:32
+  --> $DIR/broken_format.rs:2:42
    |
 LL | #[diagnostic::on_unimplemented(message = "{{Test } thing")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^^
    |
    = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
 
 warning: positional format arguments are not allowed here
-  --> $DIR/broken_format.rs:7:32
+  --> $DIR/broken_format.rs:7:49
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {}")]
-   |                                ^^^^^^^^^^^^^^^^^^^
+   |                                                 ^
    |
    = help: only named format arguments with the name of one of the generic types are allowed in this context
 
 warning: positional format arguments are not allowed here
-  --> $DIR/broken_format.rs:12:32
+  --> $DIR/broken_format.rs:12:49
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {1:}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^
+   |                                                 ^
    |
    = help: only named format arguments with the name of one of the generic types are allowed in this context
 
 warning: invalid format specifier
-  --> $DIR/broken_format.rs:17:32
+  --> $DIR/broken_format.rs:17:42
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^^^
    |
    = help: no format specifier are supported in this position
 
 warning: expected `}`, found `!`
-  --> $DIR/broken_format.rs:22:32
+  --> $DIR/broken_format.rs:22:42
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^
 
 warning: unmatched `}` found
-  --> $DIR/broken_format.rs:22:32
+  --> $DIR/broken_format.rs:22:42
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^
 
 warning: unmatched `}` found
-  --> $DIR/broken_format.rs:2:32
+  --> $DIR/broken_format.rs:2:42
    |
 LL | #[diagnostic::on_unimplemented(message = "{{Test } thing")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^^
    |
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
@@ -70,10 +70,10 @@ LL | fn check_1(_: impl ImportantTrait1) {}
    |                    ^^^^^^^^^^^^^^^ required by this bound in `check_1`
 
 warning: positional format arguments are not allowed here
-  --> $DIR/broken_format.rs:7:32
+  --> $DIR/broken_format.rs:7:49
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {}")]
-   |                                ^^^^^^^^^^^^^^^^^^^
+   |                                                 ^
    |
    = help: only named format arguments with the name of one of the generic types are allowed in this context
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
@@ -98,10 +98,10 @@ LL | fn check_2(_: impl ImportantTrait2) {}
    |                    ^^^^^^^^^^^^^^^ required by this bound in `check_2`
 
 warning: positional format arguments are not allowed here
-  --> $DIR/broken_format.rs:12:32
+  --> $DIR/broken_format.rs:12:49
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {1:}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^
+   |                                                 ^
    |
    = help: only named format arguments with the name of one of the generic types are allowed in this context
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
@@ -126,10 +126,10 @@ LL | fn check_3(_: impl ImportantTrait3) {}
    |                    ^^^^^^^^^^^^^^^ required by this bound in `check_3`
 
 warning: invalid format specifier
-  --> $DIR/broken_format.rs:17:32
+  --> $DIR/broken_format.rs:17:42
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^^^
    |
    = help: no format specifier are supported in this position
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
@@ -154,18 +154,18 @@ LL | fn check_4(_: impl ImportantTrait4) {}
    |                    ^^^^^^^^^^^^^^^ required by this bound in `check_4`
 
 warning: expected `}`, found `!`
-  --> $DIR/broken_format.rs:22:32
+  --> $DIR/broken_format.rs:22:42
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^
    |
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: unmatched `}` found
-  --> $DIR/broken_format.rs:22:32
+  --> $DIR/broken_format.rs:22:42
    |
 LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^^^^^^^^^^^
    |
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr
index bb455d92940..88816a98dcf 100644
--- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr
@@ -39,82 +39,82 @@ LL | #[diagnostic::on_unimplemented = "Message"]
    = help: only `message`, `note` and `label` are allowed as options
 
 warning: there is no parameter `from_desugaring` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:17
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                 ^^^^^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `direct` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:34
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                  ^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `cause` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:42
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `integral` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:49
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                 ^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `integer` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:59
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                           ^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `float` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:15
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |               ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `_Self` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:22
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                      ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `crate_local` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:29
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                             ^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `Trait` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:42
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
 warning: there is no parameter `ItemContext` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:49
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                 ^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
@@ -191,91 +191,91 @@ LL | fn takes_bar(_: impl Bar) {}
    |                      ^^^ required by this bound in `takes_bar`
 
 warning: there is no parameter `from_desugaring` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:17
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                 ^^^^^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `direct` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:34
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                  ^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `cause` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:42
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `integral` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:49
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                 ^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `integer` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:59
    |
 LL |     message = "{from_desugaring}{direct}{cause}{integral}{integer}",
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                           ^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `float` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:15
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |               ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `_Self` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:22
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                      ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `crate_local` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:29
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                             ^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `Trait` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:42
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                          ^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
 warning: there is no parameter `ItemContext` on trait `Baz`
-  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5
+  --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:49
    |
 LL |     label = "{float}{_Self}{crate_local}{Trait}{ItemContext}"
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                 ^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr
index 11263580b15..4dd8c1afca0 100644
--- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr
@@ -47,10 +47,10 @@ LL | #[diagnostic::on_unimplemented]
    = help: at least one of the `message`, `note` and `label` options are expected
 
 warning: there is no parameter `DoesNotExist` on trait `Test`
-  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:32
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:44
    |
 LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                            ^^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
 
@@ -167,10 +167,10 @@ LL | fn take_whatever(_: impl Whatever) {}
    |                          ^^^^^^^^ required by this bound in `take_whatever`
 
 warning: there is no parameter `DoesNotExist` on trait `Test`
-  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:32
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:44
    |
 LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")]
-   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                            ^^^^^^^^^^^^
    |
    = help: expect either a generic argument name or `{Self}` as format argument
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
diff --git a/tests/ui/on-unimplemented/bad-annotation.rs b/tests/ui/on-unimplemented/bad-annotation.rs
index 3f0f69749bf..0936b25847c 100644
--- a/tests/ui/on-unimplemented/bad-annotation.rs
+++ b/tests/ui/on-unimplemented/bad-annotation.rs
@@ -20,12 +20,12 @@ trait BadAnnotation1
 {}
 
 #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"]
-//~^ ERROR there is no parameter `C` on trait `BadAnnotation2`
+//~^ WARNING there is no parameter `C` on trait `BadAnnotation2`
 trait BadAnnotation2<A,B>
 {}
 
 #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
-//~^ ERROR only named generic parameters are allowed
+//~^ ERROR positional format arguments are not allowed here
 trait BadAnnotation3<A,B>
 {}
 
diff --git a/tests/ui/on-unimplemented/bad-annotation.stderr b/tests/ui/on-unimplemented/bad-annotation.stderr
index 4ceea779b29..1fefd93aa7e 100644
--- a/tests/ui/on-unimplemented/bad-annotation.stderr
+++ b/tests/ui/on-unimplemented/bad-annotation.stderr
@@ -11,17 +11,22 @@ LL | #[rustc_on_unimplemented = "message"]
 LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...")]
    |                         ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-error[E0230]: there is no parameter `C` on trait `BadAnnotation2`
-  --> $DIR/bad-annotation.rs:22:1
+warning: there is no parameter `C` on trait `BadAnnotation2`
+  --> $DIR/bad-annotation.rs:22:90
    |
 LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                                                          ^
+   |
+   = help: expect either a generic argument name or `{Self}` as format argument
+   = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
 
-error[E0231]: only named generic parameters are allowed
-  --> $DIR/bad-annotation.rs:27:1
+warning: positional format arguments are not allowed here
+  --> $DIR/bad-annotation.rs:27:90
    |
 LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                                                                                          ^
+   |
+   = help: only named format arguments with the name of one of the generic types are allowed in this context
 
 error[E0232]: this attribute must have a valid value
   --> $DIR/bad-annotation.rs:32:26
@@ -77,7 +82,6 @@ LL | #[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message
    |
    = note: eg `#[rustc_on_unimplemented(message="foo")]`
 
-error: aborting due to 10 previous errors
+error: aborting due to 8 previous errors; 2 warnings emitted
 
-Some errors have detailed explanations: E0230, E0231, E0232.
-For more information about an error, try `rustc --explain E0230`.
+For more information about this error, try `rustc --explain E0232`.
diff --git a/tests/ui/on-unimplemented/impl-substs.stderr b/tests/ui/on-unimplemented/impl-substs.stderr
index b85d45eba5b..0eabe971492 100644
--- a/tests/ui/on-unimplemented/impl-substs.stderr
+++ b/tests/ui/on-unimplemented/impl-substs.stderr
@@ -2,7 +2,7 @@ error[E0277]: the trait bound `(i32, i32, i32): Foo<usize>` is not satisfied
   --> $DIR/impl-substs.rs:13:23
    |
 LL |     Foo::<usize>::foo((1i32, 1i32, 1i32));
-   |     ----------------- ^^^^^^^^^^^^^^^^^^ an impl did not match: usize _ _
+   |     ----------------- ^^^^^^^^^^^^^^^^^^ an impl did not match: usize {B} {C}
    |     |
    |     required by a bound introduced by this call
    |