about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-03-06 22:02:46 +0100
committerGitHub <noreply@github.com>2024-03-06 22:02:46 +0100
commitefe9deace861039956aa182d8c17c38ef9ef2408 (patch)
treeb9854ba5f0310cbbc3dc518fadb2bb3d27785643
parentc7fca03240dfba93363505bc9c6839a224cc8ca6 (diff)
parent3591e77b3566772023b7e35e96224106243ef214 (diff)
downloadrust-efe9deace861039956aa182d8c17c38ef9ef2408.tar.gz
rust-efe9deace861039956aa182d8c17c38ef9ef2408.zip
Rollup merge of #121382 - nnethercote:rework-untranslatable_diagnostic-lint, r=davidtwco
Rework `untranslatable_diagnostic` lint

Currently it only checks calls to functions marked with `#[rustc_lint_diagnostics]`. This PR changes it to check calls to any function with an `impl Into<{D,Subd}iagnosticMessage>` parameter. This greatly improves its coverage and doesn't rely on people remembering to add `#[rustc_lint_diagnostics]`. It also lets us add `#[rustc_lint_diagnostics]` to a number of functions that don't have an `impl Into<{D,Subd}iagnosticMessage>`, such as `Diag::span`.

r? ``@davidtwco``
-rw-r--r--compiler/rustc_abi/src/layout.rs7
-rw-r--r--compiler/rustc_ast_lowering/src/asm.rs1
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs1
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs1
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs4
-rw-r--r--compiler/rustc_attr/src/builtin.rs3
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mod.rs6
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_errors.rs13
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs4
-rw-r--r--compiler/rustc_driver_impl/src/args.rs1
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs1
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs31
-rw-r--r--compiler/rustc_errors/src/lib.rs40
-rw-r--r--compiler/rustc_expand/src/base.rs2
-rw-r--r--compiler/rustc_expand/src/config.rs4
-rw-r--r--compiler/rustc_expand/src/expand.rs3
-rw-r--r--compiler/rustc_expand/src/lib.rs1
-rw-r--r--compiler/rustc_expand/src/proc_macro_server.rs3
-rw-r--r--compiler/rustc_expand/src/tests.rs2
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs4
-rw-r--r--compiler/rustc_interface/src/interface.rs1
-rw-r--r--compiler/rustc_interface/src/util.rs3
-rw-r--r--compiler/rustc_lint/src/context.rs7
-rw-r--r--compiler/rustc_lint/src/internal.rs165
-rw-r--r--compiler/rustc_lint/src/let_underscore.rs1
-rw-r--r--compiler/rustc_lint/src/levels.rs1
-rw-r--r--compiler/rustc_metadata/src/creader.rs1
-rw-r--r--compiler/rustc_metadata/src/native_libs.rs1
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs3
-rw-r--r--compiler/rustc_middle/src/ty/sty.rs4
-rw-r--r--compiler/rustc_passes/src/check_attr.rs3
-rw-r--r--compiler/rustc_passes/src/check_const.rs1
-rw-r--r--compiler/rustc_passes/src/entry.rs1
-rw-r--r--compiler/rustc_session/src/config.rs6
-rw-r--r--compiler/rustc_session/src/options.rs1
-rw-r--r--compiler/rustc_session/src/parse.rs1
-rw-r--r--compiler/rustc_session/src/search_paths.rs1
-rw-r--r--compiler/rustc_session/src/session.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/layout.rs5
-rw-r--r--src/tools/rustfmt/src/parse/session.rs1
-rw-r--r--tests/ui-fulldeps/internal-lints/diagnostics.rs33
-rw-r--r--tests/ui-fulldeps/internal-lints/diagnostics.stderr14
42 files changed, 302 insertions, 86 deletions
diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs
index 28e148bddb2..1c6d0495ecf 100644
--- a/compiler/rustc_abi/src/layout.rs
+++ b/compiler/rustc_abi/src/layout.rs
@@ -1,6 +1,9 @@
+use std::borrow::{Borrow, Cow};
+use std::cmp;
 use std::fmt::{self, Write};
+use std::iter;
+use std::ops::Bound;
 use std::ops::Deref;
-use std::{borrow::Borrow, cmp, iter, ops::Bound};
 
 use rustc_index::Idx;
 use tracing::debug;
@@ -32,7 +35,7 @@ where
 pub trait LayoutCalculator {
     type TargetDataLayoutRef: Borrow<TargetDataLayout>;
 
-    fn delayed_bug(&self, txt: String);
+    fn delayed_bug(&self, txt: impl Into<Cow<'static, str>>);
     fn current_data_layout(&self) -> Self::TargetDataLayoutRef;
 
     fn scalar_pair<FieldIdx: Idx, VariantIdx: Idx>(
diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs
index fd717e82d26..7fd419f62e4 100644
--- a/compiler/rustc_ast_lowering/src/asm.rs
+++ b/compiler/rustc_ast_lowering/src/asm.rs
@@ -22,6 +22,7 @@ use std::collections::hash_map::Entry;
 use std::fmt::Write;
 
 impl<'a, 'hir> LoweringContext<'a, 'hir> {
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     pub(crate) fn lower_inline_asm(
         &mut self,
         sp: Span,
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 85b599902b9..41f7418ddde 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -1513,6 +1513,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
         }
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn lower_expr_yield(&mut self, span: Span, opt_expr: Option<&Expr>) -> hir::ExprKind<'hir> {
         let yielded =
             opt_expr.as_ref().map(|x| self.lower_expr(x)).unwrap_or_else(|| self.expr_unit(span));
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 087d240b0d5..37ee9a5140a 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -2272,6 +2272,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         self.expr_block(block)
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn lower_array_length(&mut self, c: &AnonConst) -> hir::ArrayLen {
         match c.value.kind {
             ExprKind::Underscore => {
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index a28fcb00779..d9e3f028697 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -17,6 +17,7 @@ use crate::errors;
 macro_rules! gate {
     ($visitor:expr, $feature:ident, $span:expr, $explain:expr) => {{
         if !$visitor.features.$feature && !$span.allows_unstable(sym::$feature) {
+            #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
             feature_err(&$visitor.sess, sym::$feature, $span, $explain).emit();
         }
     }};
@@ -34,6 +35,7 @@ macro_rules! gate {
 macro_rules! gate_alt {
     ($visitor:expr, $has_feature:expr, $name:expr, $span:expr, $explain:expr) => {{
         if !$has_feature && !$span.allows_unstable($name) {
+            #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
             feature_err(&$visitor.sess, $name, $span, $explain).emit();
         }
     }};
@@ -73,6 +75,7 @@ struct PostExpansionVisitor<'a> {
 }
 
 impl<'a> PostExpansionVisitor<'a> {
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn check_abi(&self, abi: ast::StrLit, constness: ast::Const) {
         let ast::StrLit { symbol_unescaped, span, .. } = abi;
 
@@ -579,6 +582,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
                 if let Ok(snippet) = sm.span_to_snippet(span)
                     && snippet == "!"
                 {
+                    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
                     feature_err(sess, sym::never_patterns, span, "`!` patterns are experimental")
                         .emit();
                 } else {
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index eb25b910c7b..814104ec78c 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -516,6 +516,7 @@ pub struct Condition {
 }
 
 /// Tests if a cfg-pattern matches the cfg set
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 pub fn cfg_matches(
     cfg: &ast::MetaItem,
     sess: &Session,
@@ -566,6 +567,7 @@ fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Feat
     }
 }
 
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
     let (cfg, feature, has_feature) = gated_cfg;
     if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
@@ -592,6 +594,7 @@ fn parse_version(s: Symbol) -> Option<RustcVersion> {
 
 /// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
 /// evaluate individual items.
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 pub fn eval_condition(
     cfg: &ast::MetaItem,
     sess: &Session,
diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs
index 53e8ac121bb..560928d3941 100644
--- a/compiler/rustc_borrowck/src/diagnostics/mod.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs
@@ -76,6 +76,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
     /// LL |         for (key, value) in dict {
     ///    |                             ^^^^
     /// ```
+    #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
     pub(super) fn add_moved_or_invoked_closure_note(
         &self,
         location: Location,
@@ -585,6 +586,7 @@ impl UseSpans<'_> {
     }
 
     /// Add a span label to the arguments of the closure, if it exists.
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub(super) fn args_subdiag(
         self,
         dcx: &rustc_errors::DiagCtxt,
@@ -598,6 +600,7 @@ impl UseSpans<'_> {
 
     /// Add a span label to the use of the captured variable, if it exists.
     /// only adds label to the `path_span`
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub(super) fn var_path_only_subdiag(
         self,
         dcx: &rustc_errors::DiagCtxt,
@@ -635,6 +638,7 @@ impl UseSpans<'_> {
     }
 
     /// Add a subdiagnostic to the use of the captured variable, if it exists.
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub(super) fn var_subdiag(
         self,
         dcx: &rustc_errors::DiagCtxt,
@@ -1008,6 +1012,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
         self.borrow_spans(span, borrow.reserve_location)
     }
 
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn explain_captures(
         &mut self,
         err: &mut Diag<'_>,
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
index 3765dfe5db5..c06bf94a6fd 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
@@ -201,6 +201,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
     // For generic associated types (GATs) which implied 'static requirement
     // from higher-ranked trait bounds (HRTB). Try to locate span of the trait
     // and the span which bounded to the trait for adding 'static lifetime suggestion
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn suggest_static_lifetime_for_gat_from_hrtb(
         &self,
         diag: &mut Diag<'_>,
@@ -254,9 +256,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
 
         hrtb_bounds.iter().for_each(|bound| {
             let Trait(PolyTraitRef { trait_ref, span: trait_span, .. }, _) = bound else { return; };
-            // FIXME: make this translatable
-            #[allow(rustc::diagnostic_outside_of_impl)]
-            #[allow(rustc::untranslatable_diagnostic)]
             diag.span_note(
                 *trait_span,
                 "due to current limitations in the borrow checker, this implies a `'static` lifetime"
@@ -580,6 +579,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
     ///            executing...
     ///    = note: ...therefore, returned references to captured variables will escape the closure
     /// ```
+    #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
     fn report_fnmut_error(
         &self,
         errci: &ErrorConstraintInfo<'tcx>,
@@ -761,6 +761,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
     ///    |     ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it
     ///    |                    is returning data with lifetime `'b`
     /// ```
+    #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
     fn report_general_error(&self, errci: &ErrorConstraintInfo<'tcx>) -> Diag<'tcx> {
         let ErrorConstraintInfo {
             fr,
@@ -822,6 +823,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
     /// LL |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a {
     ///    |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     /// ```
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn add_static_impl_trait_suggestion(
         &self,
         diag: &mut Diag<'_>,
@@ -972,6 +975,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
         self.suggest_constrain_dyn_trait_in_impl(diag, &visitor.0, ident, self_ty);
     }
 
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     #[instrument(skip(self, err), level = "debug")]
     fn suggest_constrain_dyn_trait_in_impl(
         &self,
@@ -1034,6 +1039,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
         suggest_adding_lifetime_params(self.infcx.tcx, sub, ty_sup, ty_sub, diag);
     }
 
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn suggest_move_on_borrowing_closure(&self, diag: &mut Diag<'_>) {
         let map = self.infcx.tcx.hir();
         let body_id = map.body_owned_by(self.mir_def_id());
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index 1107b894ab3..15720f25c5c 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -62,6 +62,7 @@ impl<'tcx> NonConstOp<'tcx> for FloatingPointOp {
         }
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
         feature_err(
             &ccx.tcx.sess,
@@ -556,6 +557,7 @@ impl<'tcx> NonConstOp<'tcx> for RawMutPtrDeref {
         Status::Unstable(sym::const_mut_refs)
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
         feature_err(
             &ccx.tcx.sess,
@@ -589,6 +591,7 @@ impl<'tcx> NonConstOp<'tcx> for StaticAccess {
         }
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
         let mut err = feature_err(
             &ccx.tcx.sess,
@@ -632,6 +635,7 @@ pub mod ty {
             }
         }
 
+        #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
         fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
             feature_err(
                 &ccx.tcx.sess,
diff --git a/compiler/rustc_driver_impl/src/args.rs b/compiler/rustc_driver_impl/src/args.rs
index 5dfd37a6da4..8b6fb5fd660 100644
--- a/compiler/rustc_driver_impl/src/args.rs
+++ b/compiler/rustc_driver_impl/src/args.rs
@@ -97,6 +97,7 @@ impl Expander {
 /// **Note:** This function doesn't interpret argument 0 in any special way.
 /// If this function is intended to be used with command line arguments,
 /// `argv[0]` must be removed prior to calling it manually.
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 pub fn arg_expand_all(early_dcx: &EarlyDiagCtxt, at_args: &[String]) -> Vec<String> {
     let mut expander = Expander::default();
     for arg in at_args {
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index 85be97ea24f..3b6bf0005a3 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -4,6 +4,7 @@
 //!
 //! This API is completely unstable and subject to change.
 
+#![allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![doc(rust_logo)]
 #![feature(rustdoc_internals)]
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 5390dda9b21..c186d5b284f 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -598,6 +598,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     ///
     /// In the meantime, though, callsites are required to deal with the "bug"
     /// locally in whichever way makes the most sense.
+    #[rustc_lint_diagnostics]
     #[track_caller]
     pub fn downgrade_to_delayed_bug(&mut self) {
         assert!(
@@ -631,6 +632,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     with_fn! { with_span_labels,
     /// Labels all the given spans with the provided label.
     /// See [`Self::span_label()`] for more information.
+    #[rustc_lint_diagnostics]
     pub fn span_labels(&mut self, spans: impl IntoIterator<Item = Span>, label: &str) -> &mut Self {
         for span in spans {
             self.span_label(span, label.to_string());
@@ -638,6 +640,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self
     } }
 
+    #[rustc_lint_diagnostics]
     pub fn replace_span_with(&mut self, after: Span, keep_label: bool) -> &mut Self {
         let before = self.span.clone();
         self.span(after);
@@ -653,6 +656,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self
     }
 
+    #[rustc_lint_diagnostics]
     pub fn note_expected_found(
         &mut self,
         expected_label: &dyn fmt::Display,
@@ -663,6 +667,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self.note_expected_found_extra(expected_label, expected, found_label, found, &"", &"")
     }
 
+    #[rustc_lint_diagnostics]
     pub fn note_expected_found_extra(
         &mut self,
         expected_label: &dyn fmt::Display,
@@ -705,6 +710,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self
     }
 
+    #[rustc_lint_diagnostics]
     pub fn note_trait_signature(&mut self, name: Symbol, signature: String) -> &mut Self {
         self.highlighted_note(vec![
             StringPart::normal(format!("`{name}` from trait: `")),
@@ -722,12 +728,14 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self
     } }
 
+    #[rustc_lint_diagnostics]
     fn highlighted_note(&mut self, msg: Vec<StringPart>) -> &mut Self {
         self.sub_with_highlights(Level::Note, msg, MultiSpan::new());
         self
     }
 
     /// This is like [`Diag::note()`], but it's only printed once.
+    #[rustc_lint_diagnostics]
     pub fn note_once(&mut self, msg: impl Into<SubdiagMessage>) -> &mut Self {
         self.sub(Level::OnceNote, msg, MultiSpan::new());
         self
@@ -748,6 +756,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     /// Prints the span with a note above it.
     /// This is like [`Diag::note_once()`], but it gets its own span.
+    #[rustc_lint_diagnostics]
     pub fn span_note_once<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
@@ -786,12 +795,14 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     } }
 
     /// This is like [`Diag::help()`], but it's only printed once.
+    #[rustc_lint_diagnostics]
     pub fn help_once(&mut self, msg: impl Into<SubdiagMessage>) -> &mut Self {
         self.sub(Level::OnceHelp, msg, MultiSpan::new());
         self
     }
 
     /// Add a help message attached to this diagnostic with a customizable highlighted message.
+    #[rustc_lint_diagnostics]
     pub fn highlighted_help(&mut self, msg: Vec<StringPart>) -> &mut Self {
         self.sub_with_highlights(Level::Help, msg, MultiSpan::new());
         self
@@ -812,12 +823,14 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// Disallow attaching suggestions this diagnostic.
     /// Any suggestions attached e.g. with the `span_suggestion_*` methods
     /// (before and after the call to `disable_suggestions`) will be ignored.
+    #[rustc_lint_diagnostics]
     pub fn disable_suggestions(&mut self) -> &mut Self {
         self.suggestions = Err(SuggestionsDisabled);
         self
     }
 
     /// Helper for pushing to `self.suggestions`, if available (not disable).
+    #[rustc_lint_diagnostics]
     fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
         for subst in &suggestion.substitutions {
             for part in &subst.parts {
@@ -838,6 +851,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     with_fn! { with_multipart_suggestion,
     /// Show a suggestion that has multiple parts to it.
     /// In other words, multiple changes need to be applied as part of this suggestion.
+    #[rustc_lint_diagnostics]
     pub fn multipart_suggestion(
         &mut self,
         msg: impl Into<SubdiagMessage>,
@@ -854,6 +868,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     /// Show a suggestion that has multiple parts to it, always as it's own subdiagnostic.
     /// In other words, multiple changes need to be applied as part of this suggestion.
+    #[rustc_lint_diagnostics]
     pub fn multipart_suggestion_verbose(
         &mut self,
         msg: impl Into<SubdiagMessage>,
@@ -869,6 +884,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     }
 
     /// [`Diag::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
+    #[rustc_lint_diagnostics]
     pub fn multipart_suggestion_with_style(
         &mut self,
         msg: impl Into<SubdiagMessage>,
@@ -911,6 +927,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// be from the message, showing the span label inline would be visually unpleasant
     /// (marginally overlapping spans or multiline spans) and showing the snippet window wouldn't
     /// improve understandability.
+    #[rustc_lint_diagnostics]
     pub fn tool_only_multipart_suggestion(
         &mut self,
         msg: impl Into<SubdiagMessage>,
@@ -943,6 +960,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// * may contain a name of a function, variable, or type, but not whole expressions
     ///
     /// See `CodeSuggestion` for more information.
+    #[rustc_lint_diagnostics]
     pub fn span_suggestion(
         &mut self,
         sp: Span,
@@ -961,6 +979,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     } }
 
     /// [`Diag::span_suggestion()`] but you can set the [`SuggestionStyle`].
+    #[rustc_lint_diagnostics]
     pub fn span_suggestion_with_style(
         &mut self,
         sp: Span,
@@ -986,6 +1005,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     with_fn! { with_span_suggestion_verbose,
     /// Always show the suggested change.
+    #[rustc_lint_diagnostics]
     pub fn span_suggestion_verbose(
         &mut self,
         sp: Span,
@@ -1006,6 +1026,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     with_fn! { with_span_suggestions,
     /// Prints out a message with multiple suggested edits of the code.
     /// See also [`Diag::span_suggestion()`].
+    #[rustc_lint_diagnostics]
     pub fn span_suggestions(
         &mut self,
         sp: Span,
@@ -1022,6 +1043,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         )
     } }
 
+    #[rustc_lint_diagnostics]
     pub fn span_suggestions_with_style(
         &mut self,
         sp: Span,
@@ -1052,6 +1074,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// Prints out a message with multiple suggested edits of the code, where each edit consists of
     /// multiple parts.
     /// See also [`Diag::multipart_suggestion()`].
+    #[rustc_lint_diagnostics]
     pub fn multipart_suggestions(
         &mut self,
         msg: impl Into<SubdiagMessage>,
@@ -1098,6 +1121,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// inline, it will only show the message and not the suggestion.
     ///
     /// See `CodeSuggestion` for more information.
+    #[rustc_lint_diagnostics]
     pub fn span_suggestion_short(
         &mut self,
         sp: Span,
@@ -1121,6 +1145,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// be from the message, showing the span label inline would be visually unpleasant
     /// (marginally overlapping spans or multiline spans) and showing the snippet window wouldn't
     /// improve understandability.
+    #[rustc_lint_diagnostics]
     pub fn span_suggestion_hidden(
         &mut self,
         sp: Span,
@@ -1165,6 +1190,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
     /// [rustc_macros::Subdiagnostic]). Performs eager translation of any translatable messages
     /// used in the subdiagnostic, so suitable for use with repeated messages (i.e. re-use of
     /// interpolated variables).
+    #[rustc_lint_diagnostics]
     pub fn subdiagnostic(
         &mut self,
         dcx: &crate::DiagCtxt,
@@ -1180,6 +1206,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     with_fn! { with_span,
     /// Add a span.
+    #[rustc_lint_diagnostics]
     pub fn span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self {
         self.span = sp.into();
         if let Some(span) = self.span.primary_span() {
@@ -1188,6 +1215,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self
     } }
 
+    #[rustc_lint_diagnostics]
     pub fn is_lint(&mut self, name: String, has_future_breakage: bool) -> &mut Self {
         self.is_lint = Some(IsLint { name, has_future_breakage });
         self
@@ -1195,6 +1223,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     with_fn! { with_code,
     /// Add an error code.
+    #[rustc_lint_diagnostics]
     pub fn code(&mut self, code: ErrCode) -> &mut Self {
         self.code = Some(code);
         self
@@ -1202,6 +1231,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     with_fn! { with_primary_message,
     /// Add a primary message.
+    #[rustc_lint_diagnostics]
     pub fn primary_message(&mut self, msg: impl Into<DiagMessage>) -> &mut Self {
         self.messages[0] = (msg.into(), Style::NoStyle);
         self
@@ -1209,6 +1239,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
 
     with_fn! { with_arg,
     /// Add an argument.
+    #[rustc_lint_diagnostics]
     pub fn arg(
         &mut self,
         name: impl Into<DiagArgName>,
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 0caa8fa9e8a..76b44f73f47 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -1101,32 +1101,36 @@ impl DiagCtxt {
 // Functions beginning with `struct_`/`create_` create a diagnostic. Other
 // functions create and emit a diagnostic all in one go.
 impl DiagCtxt {
-    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    // No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't
+    // user-facing.
     #[track_caller]
-    pub fn struct_bug(&self, msg: impl Into<DiagMessage>) -> Diag<'_, BugAbort> {
-        Diag::new(self, Bug, msg)
+    pub fn struct_bug(&self, msg: impl Into<Cow<'static, str>>) -> Diag<'_, BugAbort> {
+        Diag::new(self, Bug, msg.into())
     }
 
-    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    // No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't
+    // user-facing.
     #[track_caller]
-    pub fn bug(&self, msg: impl Into<DiagMessage>) -> ! {
+    pub fn bug(&self, msg: impl Into<Cow<'static, str>>) -> ! {
         self.struct_bug(msg).emit()
     }
 
-    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    // No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't
+    // user-facing.
     #[track_caller]
     pub fn struct_span_bug(
         &self,
         span: impl Into<MultiSpan>,
-        msg: impl Into<DiagMessage>,
+        msg: impl Into<Cow<'static, str>>,
     ) -> Diag<'_, BugAbort> {
         self.struct_bug(msg).with_span(span)
     }
 
-    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    // No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't
+    // user-facing.
     #[track_caller]
-    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagMessage>) -> ! {
-        self.struct_span_bug(span, msg).emit()
+    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<Cow<'static, str>>) -> ! {
+        self.struct_span_bug(span, msg.into()).emit()
     }
 
     #[track_caller]
@@ -1240,24 +1244,28 @@ impl DiagCtxt {
     }
 
     /// Ensures that an error is printed. See `Level::DelayedBug`.
-    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    //
+    // No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't
+    // user-facing.
     #[track_caller]
-    pub fn delayed_bug(&self, msg: impl Into<DiagMessage>) -> ErrorGuaranteed {
-        Diag::<ErrorGuaranteed>::new(self, DelayedBug, msg).emit()
+    pub fn delayed_bug(&self, msg: impl Into<Cow<'static, str>>) -> ErrorGuaranteed {
+        Diag::<ErrorGuaranteed>::new(self, DelayedBug, msg.into()).emit()
     }
 
     /// Ensures that an error is printed. See `Level::DelayedBug`.
     ///
     /// Note: this function used to be called `delay_span_bug`. It was renamed
     /// to match similar functions like `span_err`, `span_warn`, etc.
-    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    //
+    // No `#[rustc_lint_diagnostics]` and no `impl Into<DiagMessage>` because bug messages aren't
+    // user-facing.
     #[track_caller]
     pub fn span_delayed_bug(
         &self,
         sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagMessage>,
+        msg: impl Into<Cow<'static, str>>,
     ) -> ErrorGuaranteed {
-        Diag::<ErrorGuaranteed>::new(self, DelayedBug, msg).with_span(sp).emit()
+        Diag::<ErrorGuaranteed>::new(self, DelayedBug, msg.into()).with_span(sp).emit()
     }
 
     #[rustc_lint_diagnostics]
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 0beba2a5504..69dfb48919c 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1493,6 +1493,8 @@ fn pretty_printing_compatibility_hack(item: &Item, sess: &Session) -> bool {
                             };
 
                             if crate_matches {
+                                // FIXME: make this translatable
+                                #[allow(rustc::untranslatable_diagnostic)]
                                 sess.psess.buffer_lint_with_diagnostic(
                                         PROC_MACRO_BACK_COMPAT,
                                         item.ident.span,
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 921fea14312..c95d7cdeb73 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -239,6 +239,7 @@ impl<'a> StripUnconfigured<'a> {
     /// Gives a compiler warning when the `cfg_attr` contains no attributes and
     /// is in the original source file. Gives a compiler error if the syntax of
     /// the attribute is incorrect.
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     pub(crate) fn expand_cfg_attr(&self, attr: &Attribute, recursive: bool) -> Vec<Attribute> {
         let Some((cfg_predicate, expanded_attrs)) =
             rustc_parse::parse_cfg_attr(attr, &self.sess.psess)
@@ -273,6 +274,7 @@ impl<'a> StripUnconfigured<'a> {
         }
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn expand_cfg_attr_item(
         &self,
         attr: &Attribute,
@@ -371,6 +373,7 @@ impl<'a> StripUnconfigured<'a> {
     }
 
     /// If attributes are not allowed on expressions, emit an error for `attr`
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     #[instrument(level = "trace", skip(self))]
     pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
         if self.features.is_some_and(|features| !features.stmt_expr_attributes)
@@ -384,7 +387,6 @@ impl<'a> StripUnconfigured<'a> {
             );
 
             if attr.is_doc_comment() {
-                #[allow(rustc::untranslatable_diagnostic)]
                 err.help("`///` is for documentation comments. For a plain comment, use `//`.");
             }
 
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 169973200aa..fcc439e71f9 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -783,6 +783,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         })
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn gate_proc_macro_attr_item(&self, span: Span, item: &Annotatable) {
         let kind = match item {
             Annotatable::Item(_)
@@ -825,6 +826,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         }
 
         impl<'ast, 'a> Visitor<'ast> for GateProcMacroInput<'a> {
+            #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
             fn visit_item(&mut self, item: &'ast ast::Item) {
                 match &item.kind {
                     ItemKind::Mod(_, mod_kind)
@@ -1689,6 +1691,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
 
     // Detect use of feature-gated or invalid attributes on macro invocations
     // since they will not be detected after macro expansion.
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn check_attributes(&self, attrs: &[ast::Attribute], call: &ast::MacCall) {
         let features = self.cx.ecfg.features;
         let mut attrs = attrs.iter().peekable();
diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs
index 0e73abc9ed8..e550f7242c3 100644
--- a/compiler/rustc_expand/src/lib.rs
+++ b/compiler/rustc_expand/src/lib.rs
@@ -5,6 +5,7 @@
 #![feature(associated_type_defaults)]
 #![feature(if_let_guard)]
 #![feature(let_chains)]
+#![feature(lint_reasons)]
 #![feature(macro_metavar_expr)]
 #![feature(map_try_insert)]
 #![feature(proc_macro_diagnostic)]
diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs
index 32a083a72f0..ddc685c9d07 100644
--- a/compiler/rustc_expand/src/proc_macro_server.rs
+++ b/compiler/rustc_expand/src/proc_macro_server.rs
@@ -517,6 +517,9 @@ impl server::FreeFunctions for Rustc<'_, '_> {
             Diag::new(&self.psess().dcx, diagnostic.level.to_internal(), message);
         diag.span(MultiSpan::from_spans(diagnostic.spans));
         for child in diagnostic.children {
+            // This message comes from another diagnostic, and we are just reconstructing the
+            // diagnostic, so there's no need for translation.
+            #[allow(rustc::untranslatable_diagnostic)]
             diag.sub(child.level.to_internal(), child.message, MultiSpan::from_spans(child.spans));
         }
         diag.emit();
diff --git a/compiler/rustc_expand/src/tests.rs b/compiler/rustc_expand/src/tests.rs
index 8c47b759453..a3510dc9bff 100644
--- a/compiler/rustc_expand/src/tests.rs
+++ b/compiler/rustc_expand/src/tests.rs
@@ -178,6 +178,7 @@ impl<T: Write> Write for Shared<T> {
     }
 }
 
+#[allow(rustc::untranslatable_diagnostic)] // no translation needed for tests
 fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) {
     create_default_session_globals_then(|| {
         let (handler, source_map, output) = create_test_handler();
@@ -192,7 +193,6 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
             println!("text: {:?}", source_map.span_to_snippet(span));
         }
 
-        #[allow(rustc::untranslatable_diagnostic)]
         handler.span_err(msp, "foo");
 
         assert!(
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 3f971aad042..0d51e1e46e0 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -695,8 +695,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     // Used by the `rustc::potential_query_instability` lint to warn methods which
     // might not be stable during incremental compilation.
     rustc_attr!(rustc_lint_query_instability, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
-    // Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints
-    // to assist in changes to diagnostic APIs.
+    // Used by the `rustc::diagnostic_outside_of_impl` lints to assist in changes to diagnostic
+    // APIs. Any function with this attribute will be checked by that lint.
     rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
     // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions`
     // types (as well as any others in future).
diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs
index b2ec8e53429..284d965979e 100644
--- a/compiler/rustc_interface/src/interface.rs
+++ b/compiler/rustc_interface/src/interface.rs
@@ -320,6 +320,7 @@ pub struct Config {
 
 // JUSTIFICATION: before session exists, only config
 #[allow(rustc::bad_opt_access)]
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R {
     trace!("run_compiler");
 
diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs
index bf3beb677aa..0d50200133c 100644
--- a/compiler/rustc_interface/src/util.rs
+++ b/compiler/rustc_interface/src/util.rs
@@ -160,6 +160,7 @@ pub(crate) fn run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>(
     })
 }
 
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 fn load_backend_from_dylib(early_dcx: &EarlyDiagCtxt, path: &Path) -> MakeBackendFn {
     match unsafe { load_symbol_from_dylib::<MakeBackendFn>(path, "__rustc_codegen_backend") } {
         Ok(backend_sym) => backend_sym,
@@ -227,6 +228,7 @@ fn get_rustc_path_inner(bin_path: &str) -> Option<PathBuf> {
     })
 }
 
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 fn get_codegen_sysroot(
     early_dcx: &EarlyDiagCtxt,
     maybe_sysroot: &Option<PathBuf>,
@@ -319,6 +321,7 @@ fn get_codegen_sysroot(
     }
 }
 
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 pub(crate) fn check_attr_crate_type(
     sess: &Session,
     attrs: &[ast::Attribute],
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index f043a312c71..89cdde11726 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -621,12 +621,13 @@ pub trait LintContext {
     /// Note that this function should only be called for [`LintExpectationId`]s
     /// retrieved from the current lint pass. Buffered or manually created ids can
     /// cause ICEs.
-    #[rustc_lint_diagnostics]
     fn fulfill_expectation(&self, expectation: LintExpectationId) {
         // We need to make sure that submitted expectation ids are correctly fulfilled suppressed
         // and stored between compilation sessions. To not manually do these steps, we simply create
-        // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal
-        // expected lint diagnostic.
+        // a dummy diagnostic and emit it as usual, which will be suppressed and stored like a
+        // normal expected lint diagnostic.
+        #[allow(rustc::diagnostic_outside_of_impl)]
+        #[allow(rustc::untranslatable_diagnostic)]
         self.sess()
             .dcx()
             .struct_expect(
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
index fdcd0192e6a..3a8c8e79b4f 100644
--- a/compiler/rustc_lint/src/internal.rs
+++ b/compiler/rustc_lint/src/internal.rs
@@ -10,7 +10,7 @@ use rustc_ast as ast;
 use rustc_hir::def::Res;
 use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
 use rustc_hir::{BinOp, BinOpKind, HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
-use rustc_middle::ty;
+use rustc_middle::ty::{self, Ty as MiddleTy};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::symbol::{kw, sym, Symbol};
@@ -338,10 +338,11 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
 }
 
 declare_tool_lint! {
-    /// The `untranslatable_diagnostic` lint detects diagnostics created
-    /// without using translatable Fluent strings.
+    /// The `untranslatable_diagnostic` lint detects messages passed to functions with `impl
+    /// Into<{D,Subd}iagMessage` parameters without using translatable Fluent strings.
     ///
-    /// More details on translatable diagnostics can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
+    /// More details on translatable diagnostics can be found
+    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
     pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
     Deny,
     "prevent creation of diagnostics which cannot be translated",
@@ -349,11 +350,13 @@ declare_tool_lint! {
 }
 
 declare_tool_lint! {
-    /// The `diagnostic_outside_of_impl` lint detects diagnostics created manually,
-    /// and inside an `IntoDiagnostic`/`AddToDiagnostic` implementation,
-    /// or a `#[derive(Diagnostic)]`/`#[derive(Subdiagnostic)]` expansion.
+    /// The `diagnostic_outside_of_impl` lint detects calls to functions annotated with
+    /// `#[rustc_lint_diagnostics]` that are outside an `IntoDiagnostic`, `AddToDiagnostic`, or
+    /// `DecorateLint` impl, or a `#[derive(Diagnostic)]`, `#[derive(Subdiagnostic)]`,
+    /// `#[derive(DecorateLint)]` expansion.
     ///
-    /// More details on diagnostics implementations can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
+    /// More details on diagnostics implementations can be found
+    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
     pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
     Deny,
     "prevent creation of diagnostics outside of `IntoDiagnostic`/`AddToDiagnostic` impls",
@@ -364,54 +367,130 @@ declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE
 
 impl LateLintPass<'_> for Diagnostics {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return };
-        debug!(?span, ?def_id, ?args);
-        let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args)
+        // Only check function calls and method calls.
+        let (span, def_id, fn_gen_args, call_tys) = match expr.kind {
+            ExprKind::Call(callee, args) => {
+                match cx.typeck_results().node_type(callee.hir_id).kind() {
+                    &ty::FnDef(def_id, fn_gen_args) => {
+                        let call_tys: Vec<_> =
+                            args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
+                        (callee.span, def_id, fn_gen_args, call_tys)
+                    }
+                    _ => return, // occurs for fns passed as args
+                }
+            }
+            ExprKind::MethodCall(segment, _recv, args, _span) => {
+                let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+                let fn_gen_args = cx.typeck_results().node_args(expr.hir_id);
+                let mut call_tys: Vec<_> =
+                    args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
+                call_tys.insert(0, cx.tcx.types.self_param); // dummy inserted for `self`
+                (segment.ident.span, def_id, fn_gen_args, call_tys)
+            }
+            _ => return,
+        };
+
+        // Is the callee marked with `#[rustc_lint_diagnostics]`?
+        let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, fn_gen_args)
             .ok()
             .flatten()
             .is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics));
-        if !has_attr {
-            return;
-        }
 
-        let mut found_parent_with_attr = false;
-        let mut found_impl = false;
-        for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
-            if let Some(owner_did) = hir_id.as_owner() {
-                found_parent_with_attr = found_parent_with_attr
-                    || cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics);
+        // Closure: is the type `{D,Subd}iagMessage`?
+        let is_diag_message = |ty: MiddleTy<'_>| {
+            if let Some(adt_def) = ty.ty_adt_def()
+                && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
+                && matches!(name, sym::DiagMessage | sym::SubdiagMessage)
+            {
+                true
+            } else {
+                false
             }
+        };
 
-            debug!(?parent);
-            if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
-                && let Impl { of_trait: Some(of_trait), .. } = impl_
-                && let Some(def_id) = of_trait.trait_def_id()
-                && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
-                && matches!(name, sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint)
-            {
-                found_impl = true;
-                break;
+        // Does the callee have a `impl Into<{D,Subd}iagMessage>` parameter? (There should be at
+        // most one.)
+        let mut impl_into_diagnostic_message_param = None;
+        let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
+        let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
+        for (i, &param_ty) in fn_sig.inputs().iter().enumerate() {
+            if let ty::Param(p) = param_ty.kind() {
+                // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
+                for pred in predicates.iter() {
+                    if let Some(trait_pred) = pred.as_trait_clause()
+                        && let trait_ref = trait_pred.skip_binder().trait_ref
+                        && trait_ref.self_ty() == param_ty // correct predicate for the param?
+                        && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
+                        && let ty1 = trait_ref.args.type_at(1)
+                        && is_diag_message(ty1)
+                    {
+                        if impl_into_diagnostic_message_param.is_some() {
+                            cx.tcx.dcx().span_bug(
+                                span,
+                                "can't handle multiple `impl Into<{D,Sub}iagMessage>` params",
+                            );
+                        }
+                        impl_into_diagnostic_message_param = Some((i, p.name));
+                    }
+                }
             }
         }
-        debug!(?found_impl);
-        if !found_parent_with_attr && !found_impl {
-            cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
+
+        // Is the callee interesting?
+        if !has_attr && impl_into_diagnostic_message_param.is_none() {
+            return;
         }
 
-        let mut found_diagnostic_message = false;
-        for ty in args.types() {
-            debug!(?ty);
-            if let Some(adt_def) = ty.ty_adt_def()
-                && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
-                && matches!(name, sym::DiagMessage | sym::SubdiagMessage)
+        // Is the parent method marked with `#[rustc_lint_diagnostics]`?
+        let mut parent_has_attr = false;
+        for (hir_id, _parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
+            if let Some(owner_did) = hir_id.as_owner()
+                && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
             {
-                found_diagnostic_message = true;
+                parent_has_attr = true;
                 break;
             }
         }
-        debug!(?found_diagnostic_message);
-        if !found_parent_with_attr && !found_diagnostic_message {
-            cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
+
+        // Calls to `#[rustc_lint_diagnostics]`-marked functions should only occur:
+        // - inside an impl of `IntoDiagnostic`, `AddToDiagnostic`, or `DecorateLint`, or
+        // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
+        //
+        // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
+        if has_attr && !parent_has_attr {
+            let mut is_inside_appropriate_impl = false;
+            for (_hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
+                debug!(?parent);
+                if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
+                    && let Impl { of_trait: Some(of_trait), .. } = impl_
+                    && let Some(def_id) = of_trait.trait_def_id()
+                    && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
+                    && matches!(
+                        name,
+                        sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint
+                    )
+                {
+                    is_inside_appropriate_impl = true;
+                    break;
+                }
+            }
+            debug!(?is_inside_appropriate_impl);
+            if !is_inside_appropriate_impl {
+                cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
+            }
+        }
+
+        // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
+        // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
+        // `UNTRANSLATABLE_DIAGNOSTIC` lint.
+        if let Some((param_i, param_i_p_name)) = impl_into_diagnostic_message_param {
+            // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
+            let arg_ty = call_tys[param_i];
+            let is_translatable = is_diag_message(arg_ty)
+                || matches!(arg_ty.kind(), ty::Param(p) if p.name == param_i_p_name);
+            if !is_translatable {
+                cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
+            }
         }
     }
 }
@@ -425,7 +504,7 @@ declare_tool_lint! {
     report_in_external_macro: true
 }
 
-declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
+declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
 
 impl LateLintPass<'_> for BadOptAccess {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs
index c552b983255..de642f373b5 100644
--- a/compiler/rustc_lint/src/let_underscore.rs
+++ b/compiler/rustc_lint/src/let_underscore.rs
@@ -104,6 +104,7 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
 ];
 
 impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
         if matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
             return;
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 906cd1d1283..2f08cd53b75 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -724,6 +724,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         };
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option<HirId>) {
         let sess = self.sess;
         for (attr_index, attr) in attrs.iter().enumerate() {
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 0d37c0feb0c..72757d90e42 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -947,6 +947,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
         }
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn report_unused_deps(&mut self, krate: &ast::Crate) {
         // Make a point span rather than covering the whole file
         let span = krate.spans.inner_span.shrink_to_lo();
diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs
index cdcc586b09e..baa2e1ff602 100644
--- a/compiler/rustc_metadata/src/native_libs.rs
+++ b/compiler/rustc_metadata/src/native_libs.rs
@@ -93,6 +93,7 @@ struct Collector<'tcx> {
 }
 
 impl<'tcx> Collector<'tcx> {
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn process_module(&mut self, module: &ForeignModule) {
         let ForeignModule { def_id, abi, ref foreign_items } = *module;
         let def_id = def_id.expect_local();
diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs
index dda41f70f06..b1bfd2f1105 100644
--- a/compiler/rustc_middle/src/ty/layout.rs
+++ b/compiler/rustc_middle/src/ty/layout.rs
@@ -17,6 +17,7 @@ use rustc_target::abi::call::FnAbi;
 use rustc_target::abi::*;
 use rustc_target::spec::{abi::Abi as SpecAbi, HasTargetSpec, PanicStrategy, Target};
 
+use std::borrow::Cow;
 use std::cmp;
 use std::fmt;
 use std::num::NonZero;
@@ -268,7 +269,7 @@ pub struct LayoutCx<'tcx, C> {
 impl<'tcx> LayoutCalculator for LayoutCx<'tcx, TyCtxt<'tcx>> {
     type TargetDataLayoutRef = &'tcx TargetDataLayout;
 
-    fn delayed_bug(&self, txt: String) {
+    fn delayed_bug(&self, txt: impl Into<Cow<'static, str>>) {
         self.tcx.dcx().delayed_bug(txt);
     }
 
diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs
index a0baaa3ce28..427c0f04bd1 100644
--- a/compiler/rustc_middle/src/ty/sty.rs
+++ b/compiler/rustc_middle/src/ty/sty.rs
@@ -13,7 +13,7 @@ use crate::ty::{GenericArg, GenericArgs, GenericArgsRef};
 use crate::ty::{List, ParamEnv};
 use hir::def::DefKind;
 use rustc_data_structures::captures::Captures;
-use rustc_errors::{DiagArgValue, DiagMessage, ErrorGuaranteed, IntoDiagnosticArg, MultiSpan};
+use rustc_errors::{DiagArgValue, ErrorGuaranteed, IntoDiagnosticArg, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::LangItem;
@@ -1543,7 +1543,7 @@ impl<'tcx> Ty<'tcx> {
     pub fn new_error_with_message<S: Into<MultiSpan>>(
         tcx: TyCtxt<'tcx>,
         span: S,
-        msg: impl Into<DiagMessage>,
+        msg: impl Into<Cow<'static, str>>,
     ) -> Ty<'tcx> {
         let reported = tcx.dcx().span_delayed_bug(span, msg);
         Ty::new(tcx, Error(reported))
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 04c0cf7436e..a99db9cb8cc 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -1074,6 +1074,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     /// of one item. Read the documentation of [`check_doc_inline`] for more information.
     ///
     /// [`check_doc_inline`]: Self::check_doc_inline
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn check_doc_attrs(
         &self,
         attr: &Attribute,
@@ -1756,6 +1757,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if the `#[repr]` attributes on `item` are valid.
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn check_repr(
         &self,
         attrs: &[Attribute],
@@ -2328,6 +2330,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
             let hir_sig = tcx.hir().fn_sig_by_hir_id(hir_id);
             if let Some(hir_sig) = hir_sig {
+                #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
                 match terr {
                     TypeError::ArgumentMutability(idx) | TypeError::ArgumentSorts(_, idx) => {
                         if let Some(ty) = hir_sig.decl.inputs.get(idx) {
diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs
index 02792491f5e..30a65d69c4e 100644
--- a/compiler/rustc_passes/src/check_const.rs
+++ b/compiler/rustc_passes/src/check_const.rs
@@ -77,6 +77,7 @@ impl<'tcx> CheckConstVisitor<'tcx> {
     }
 
     /// Emits an error when an unsupported expression is found in a const context.
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn const_check_violated(&self, expr: NonConstExpr, span: Span) {
         let Self { tcx, def_id, const_kind } = *self;
 
diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs
index 97c70e327f0..5246389248e 100644
--- a/compiler/rustc_passes/src/entry.rs
+++ b/compiler/rustc_passes/src/entry.rs
@@ -114,6 +114,7 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
     }
 }
 
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
     if let Some((def_id, _)) = visitor.start_fn {
         Some((def_id.to_def_id(), EntryFnType::Start))
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 10243ec3a34..61a220428b0 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1,6 +1,8 @@
 //! Contains infrastructure for configuring the compiler, including parsing
 //! command-line options.
 
+#![allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+
 pub use crate::options::*;
 
 use crate::errors::FileWriteFail;
@@ -2468,9 +2470,7 @@ pub fn parse_externs(
             ));
             let adjusted_name = name.replace('-', "_");
             if is_ascii_ident(&adjusted_name) {
-                // FIXME: make this translatable
-                #[allow(rustc::diagnostic_outside_of_impl)]
-                #[allow(rustc::untranslatable_diagnostic)]
+                #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
                 error.help(format!(
                     "consider replacing the dashes with underscores: `{adjusted_name}`"
                 ));
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 139bc8059d9..93bef82e4ba 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -320,6 +320,7 @@ macro_rules! redirect_field {
 type OptionSetter<O> = fn(&mut O, v: Option<&str>) -> bool;
 type OptionDescrs<O> = &'static [(&'static str, OptionSetter<O>, &'static str, &'static str)];
 
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 fn build_options<O: Default>(
     early_dcx: &EarlyDiagCtxt,
     matches: &getopts::Matches,
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index 506bd5d5dbd..398138d7e1f 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -168,6 +168,7 @@ pub fn add_feature_diagnostics<G: EmissionGuarantee>(
 /// This variant allows you to control whether it is a library or language feature.
 /// Almost always, you want to use this for a language feature. If so, prefer
 /// `add_feature_diagnostics`.
+#[allow(rustc::diagnostic_outside_of_impl)] // FIXME
 pub fn add_feature_diagnostics_for_issue<G: EmissionGuarantee>(
     err: &mut Diag<'_, G>,
     sess: &Session,
diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs
index 9b913c76998..32d5e430717 100644
--- a/compiler/rustc_session/src/search_paths.rs
+++ b/compiler/rustc_session/src/search_paths.rs
@@ -61,6 +61,7 @@ impl SearchPath {
             (PathKind::All, path)
         };
         if path.is_empty() {
+            #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
             early_dcx.early_fatal("empty search path given via `-L`");
         }
 
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 71235f5b171..299a159e577 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -312,6 +312,7 @@ impl Session {
     ) -> Diag<'a> {
         let mut err = self.dcx().create_err(err);
         if err.code.is_none() {
+            #[allow(rustc::diagnostic_outside_of_impl)]
             err.code(E0658);
         }
         add_feature_diagnostics(&mut err, self, feature);
@@ -1022,6 +1023,7 @@ fn default_emitter(
 
 // JUSTIFICATION: literally session construction
 #[allow(rustc::bad_opt_access)]
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
 pub fn build_session(
     early_dcx: EarlyDiagCtxt,
     sopts: config::Options,
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs
index be1c8d9094b..a1be6018083 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs
@@ -1,5 +1,6 @@
 //! Compute the binary representation of a type
 
+use std::borrow::Cow;
 use std::fmt;
 
 use base_db::salsa::Cycle;
@@ -114,8 +115,8 @@ struct LayoutCx<'a> {
 impl<'a> LayoutCalculator for LayoutCx<'a> {
     type TargetDataLayoutRef = &'a TargetDataLayout;
 
-    fn delayed_bug(&self, txt: String) {
-        never!("{}", txt);
+    fn delayed_bug(&self, txt: impl Into<Cow<'static, str>>) {
+        never!("{}", txt.into());
     }
 
     fn current_data_layout(&self) -> &'a TargetDataLayout {
diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs
index 60a89a57536..cb46e65999d 100644
--- a/src/tools/rustfmt/src/parse/session.rs
+++ b/src/tools/rustfmt/src/parse/session.rs
@@ -360,6 +360,7 @@ mod tests {
         }
 
         fn build_diagnostic(level: DiagnosticLevel, span: Option<MultiSpan>) -> DiagInner {
+            #[allow(rustc::untranslatable_diagnostic)] // no translation needed for empty string
             let mut diag = DiagInner::new(level, "");
             diag.messages.clear();
             if let Some(span) = span {
diff --git a/tests/ui-fulldeps/internal-lints/diagnostics.rs b/tests/ui-fulldeps/internal-lints/diagnostics.rs
index 6a978dabe62..f73fdc0882f 100644
--- a/tests/ui-fulldeps/internal-lints/diagnostics.rs
+++ b/tests/ui-fulldeps/internal-lints/diagnostics.rs
@@ -14,8 +14,8 @@ extern crate rustc_session;
 extern crate rustc_span;
 
 use rustc_errors::{
-    AddToDiagnostic, Diag, EmissionGuarantee, DiagCtxt, IntoDiagnostic, Level,
-    SubdiagMessageOp,
+    AddToDiagnostic, DecorateLint, Diag, DiagCtxt, DiagInner, DiagMessage, EmissionGuarantee,
+    IntoDiagnostic, Level, SubdiagMessageOp,
 };
 use rustc_macros::{Diagnostic, Subdiagnostic};
 use rustc_span::Span;
@@ -78,6 +78,31 @@ impl AddToDiagnostic for TranslatableInAddToDiagnostic {
     }
 }
 
+pub struct UntranslatableInDecorateLint;
+
+impl<'a> DecorateLint<'a, ()> for UntranslatableInDecorateLint {
+    fn decorate_lint<'b, >(self, diag: &'b mut Diag<'a, ()>) {
+        diag.note("untranslatable diagnostic");
+        //~^ ERROR diagnostics should be created using translatable messages
+    }
+
+    fn msg(&self) -> DiagMessage {
+        unreachable!();
+    }
+}
+
+pub struct TranslatableInDecorateLint;
+
+impl<'a> DecorateLint<'a, ()> for TranslatableInDecorateLint {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.note(crate::fluent_generated::no_crate_note);
+    }
+
+    fn msg(&self) -> DiagMessage {
+        unreachable!();
+    }
+}
+
 pub fn make_diagnostics<'a>(dcx: &'a DiagCtxt) {
     let _diag = dcx.struct_err(crate::fluent_generated::no_crate_example);
     //~^ ERROR diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
@@ -87,9 +112,11 @@ pub fn make_diagnostics<'a>(dcx: &'a DiagCtxt) {
     //~^^ ERROR diagnostics should be created using translatable messages
 }
 
-// Check that `rustc_lint_diagnostics`-annotated functions aren't themselves linted.
+// Check that `rustc_lint_diagnostics`-annotated functions aren't themselves linted for
+// `diagnostic_outside_of_impl`.
 
 #[rustc_lint_diagnostics]
 pub fn skipped_because_of_annotation<'a>(dcx: &'a DiagCtxt) {
+    #[allow(rustc::untranslatable_diagnostic)]
     let _diag = dcx.struct_err("untranslatable diagnostic"); // okay!
 }
diff --git a/tests/ui-fulldeps/internal-lints/diagnostics.stderr b/tests/ui-fulldeps/internal-lints/diagnostics.stderr
index 5ddfdf32149..b8fbee1ff00 100644
--- a/tests/ui-fulldeps/internal-lints/diagnostics.stderr
+++ b/tests/ui-fulldeps/internal-lints/diagnostics.stderr
@@ -16,8 +16,14 @@ error: diagnostics should be created using translatable messages
 LL |         diag.note("untranslatable diagnostic");
    |              ^^^^
 
+error: diagnostics should be created using translatable messages
+  --> $DIR/diagnostics.rs:85:14
+   |
+LL |         diag.note("untranslatable diagnostic");
+   |              ^^^^
+
 error: diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
-  --> $DIR/diagnostics.rs:82:21
+  --> $DIR/diagnostics.rs:107:21
    |
 LL |     let _diag = dcx.struct_err(crate::fluent_generated::no_crate_example);
    |                     ^^^^^^^^^^
@@ -29,16 +35,16 @@ LL | #![deny(rustc::diagnostic_outside_of_impl)]
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
-  --> $DIR/diagnostics.rs:85:21
+  --> $DIR/diagnostics.rs:110:21
    |
 LL |     let _diag = dcx.struct_err("untranslatable diagnostic");
    |                     ^^^^^^^^^^
 
 error: diagnostics should be created using translatable messages
-  --> $DIR/diagnostics.rs:85:21
+  --> $DIR/diagnostics.rs:110:21
    |
 LL |     let _diag = dcx.struct_err("untranslatable diagnostic");
    |                     ^^^^^^^^^^
 
-error: aborting due to 5 previous errors
+error: aborting due to 6 previous errors