about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_passes/messages.ftl43
-rw-r--r--compiler/rustc_passes/src/check_attr.rs32
-rw-r--r--compiler/rustc_passes/src/errors.rs160
-rw-r--r--compiler/rustc_passes/src/lib.rs2
-rw-r--r--compiler/rustc_passes/src/liveness.rs204
-rw-r--r--compiler/rustc_passes/src/stability.rs42
-rw-r--r--tests/rustdoc-ui/doc_cfg_hide.stderr2
-rw-r--r--tests/rustdoc-ui/invalid-doc-attr.rs2
-rw-r--r--tests/rustdoc-ui/invalid-doc-attr.stderr2
-rw-r--r--tests/ui/attributes/invalid-doc-attr.rs2
-rw-r--r--tests/ui/attributes/invalid-doc-attr.stderr2
11 files changed, 313 insertions, 180 deletions
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 055682a1509..40680150601 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -139,7 +139,6 @@ passes_doc_attr_not_crate_level =
 passes_attr_crate_level =
     this attribute can only be applied at the crate level
     .suggestion = to apply to the crate, use an inner attribute
-    .help = to apply to the crate, use an inner attribute
     .note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 
 passes_doc_test_unknown =
@@ -724,3 +723,45 @@ passes_skipping_const_checks = skipping const checks
 passes_invalid_macro_export_arguments = `{$name}` isn't a valid `#[macro_export]` argument
 
 passes_invalid_macro_export_arguments_too_many_items = `#[macro_export]` can only take 1 or 0 arguments
+
+passes_unreachable_due_to_uninhabited = unreachable {$descr}
+    .label = unreachable {$descr}
+    .label_orig = any code following this expression is unreachable
+    .note = this expression has type `{$ty}`, which is uninhabited
+
+passes_unused_var_maybe_capture_ref = unused variable: `{$name}`
+    .help = did you mean to capture by reference instead?
+
+passes_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read
+    .help = did you mean to capture by reference instead?
+
+passes_unused_var_remove_field = unused variable: `{$name}`
+passes_unused_var_remove_field_suggestion = try removing the field
+
+passes_unused_var_assigned_only = variable `{$name}` is assigned to, but never used
+    .note = consider using `_{$name}` instead
+
+passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
+
+passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been partially stabilized since {$since} and is succeeded by the feature `{$implies}`
+    .suggestion = if you are using features which are still unstable, change to using `{$implies}`
+    .suggestion_remove = if you are using features which are now stable, remove this line
+
+passes_ineffective_unstable_impl = an `#[unstable]` annotation here has no effect
+    .note = see issue #55436 <https://github.com/rust-lang/rust/issues/55436> for more information
+
+passes_unused_assign = value assigned to `{$name}` is never read
+    .help = maybe it is overwritten before being read?
+
+passes_unused_assign_passed = value passed to `{$name}` is never read
+    .help = maybe it is overwritten before being read?
+
+passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal
+passes_string_interpolation_only_works = string interpolation only works in `format!` invocations
+
+passes_unused_variable_try_prefix = unused variable: `{$name}`
+    .label = unused variable
+    .suggestion = if this is intentional, prefix it with an underscore
+
+passes_unused_variable_try_ignore = unused variable: `{$name}`
+    .suggestion = try ignoring the field
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 085a28626ea..3f28ac26f86 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -29,7 +29,7 @@ use rustc_session::lint::builtin::{
 };
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::{kw, sym, Symbol};
-use rustc_span::{Span, DUMMY_SP};
+use rustc_span::{BytePos, Span, DUMMY_SP};
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs};
 use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
@@ -927,30 +927,18 @@ impl CheckAttrVisitor<'_> {
         hir_id: HirId,
     ) -> bool {
         if hir_id != CRATE_HIR_ID {
-            self.tcx.struct_span_lint_hir(
+            // insert a bang between `#` and `[...`
+            let bang_span = attr.span.lo() + BytePos(1);
+            let sugg = (attr.style == AttrStyle::Outer
+                && self.tcx.hir().get_parent_item(hir_id) == CRATE_OWNER_ID)
+                .then_some(errors::AttrCrateLevelOnlySugg {
+                    attr: attr.span.with_lo(bang_span).with_hi(bang_span),
+                });
+            self.tcx.emit_spanned_lint(
                 INVALID_DOC_ATTRIBUTES,
                 hir_id,
                 meta.span(),
-                fluent::passes_attr_crate_level,
-                |err| {
-                    if attr.style == AttrStyle::Outer
-                        && self.tcx.hir().get_parent_item(hir_id) == CRATE_OWNER_ID
-                    {
-                        if let Ok(mut src) = self.tcx.sess.source_map().span_to_snippet(attr.span) {
-                            src.insert(1, '!');
-                            err.span_suggestion_verbose(
-                                attr.span,
-                                fluent::passes_suggestion,
-                                src,
-                                Applicability::MaybeIncorrect,
-                            );
-                        } else {
-                            err.span_help(attr.span, fluent::passes_help);
-                        }
-                    }
-                    err.note(fluent::passes_note);
-                    err
-                },
+                errors::AttrCrateLevelOnly { sugg },
             );
             return false;
         }
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index e8603b3a2f1..99fc69d1bec 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -6,7 +6,8 @@ use std::{
 use crate::fluent_generated as fluent;
 use rustc_ast::Label;
 use rustc_errors::{
-    error_code, Applicability, DiagnosticSymbolList, ErrorGuaranteed, IntoDiagnostic, MultiSpan,
+    error_code, AddToDiagnostic, Applicability, Diagnostic, DiagnosticSymbolList, ErrorGuaranteed,
+    IntoDiagnostic, MultiSpan,
 };
 use rustc_hir::{self as hir, ExprKind, Target};
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
@@ -1555,3 +1556,160 @@ pub struct SkippingConstChecks {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unreachable_due_to_uninhabited)]
+pub struct UnreachableDueToUninhabited<'desc, 'tcx> {
+    pub descr: &'desc str,
+    #[label]
+    pub expr: Span,
+    #[label(passes_label_orig)]
+    #[note]
+    pub orig: Span,
+    pub ty: Ty<'tcx>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_var_maybe_capture_ref)]
+#[help]
+pub struct UnusedVarMaybeCaptureRef {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_capture_maybe_capture_ref)]
+#[help]
+pub struct UnusedCaptureMaybeCaptureRef {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_var_remove_field)]
+pub struct UnusedVarRemoveField {
+    pub name: String,
+    #[subdiagnostic]
+    pub sugg: UnusedVarRemoveFieldSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    passes_unused_var_remove_field_suggestion,
+    applicability = "machine-applicable"
+)]
+pub struct UnusedVarRemoveFieldSugg {
+    #[suggestion_part(code = "")]
+    pub spans: Vec<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_var_assigned_only)]
+#[note]
+pub struct UnusedVarAssignedOnly {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unnecessary_stable_feature)]
+pub struct UnnecessaryStableFeature {
+    pub feature: Symbol,
+    pub since: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unnecessary_partial_stable_feature)]
+pub struct UnnecessaryPartialStableFeature {
+    #[suggestion(code = "{implies}", applicability = "maybe-incorrect")]
+    pub span: Span,
+    #[suggestion(passes_suggestion_remove, code = "", applicability = "maybe-incorrect")]
+    pub line: Span,
+    pub feature: Symbol,
+    pub since: Symbol,
+    pub implies: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_ineffective_unstable_impl)]
+#[note]
+pub struct IneffectiveUnstableImpl;
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_assign)]
+#[help]
+pub struct UnusedAssign {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_assign_passed)]
+#[help]
+pub struct UnusedAssignPassed {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_variable_try_prefix)]
+pub struct UnusedVariableTryPrefix {
+    #[label]
+    pub label: Option<Span>,
+    #[subdiagnostic]
+    pub string_interp: Vec<UnusedVariableStringInterp>,
+    #[subdiagnostic]
+    pub sugg: UnusedVariableTryPrefixSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(passes_suggestion, applicability = "machine-applicable")]
+pub struct UnusedVariableTryPrefixSugg {
+    #[suggestion_part(code = "_{name}")]
+    pub spans: Vec<Span>,
+    pub name: String,
+}
+
+pub struct UnusedVariableStringInterp {
+    pub lit: Span,
+    pub lo: Span,
+    pub hi: Span,
+}
+
+impl AddToDiagnostic for UnusedVariableStringInterp {
+    fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F) {
+        diag.span_label(self.lit, crate::fluent_generated::passes_maybe_string_interpolation);
+        diag.multipart_suggestion(
+            crate::fluent_generated::passes_string_interpolation_only_works,
+            vec![(self.lo, String::from("format!(")), (self.hi, String::from(")"))],
+            Applicability::MachineApplicable,
+        );
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_unused_variable_try_ignore)]
+pub struct UnusedVarTryIgnore {
+    #[subdiagnostic]
+    pub sugg: UnusedVarTryIgnoreSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(passes_suggestion, applicability = "machine-applicable")]
+pub struct UnusedVarTryIgnoreSugg {
+    #[suggestion_part(code = "{name}: _")]
+    pub shorthands: Vec<Span>,
+    #[suggestion_part(code = "_")]
+    pub non_shorthands: Vec<Span>,
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_attr_crate_level)]
+#[note]
+pub struct AttrCrateLevelOnly {
+    #[subdiagnostic]
+    pub sugg: Option<AttrCrateLevelOnlySugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")]
+pub struct AttrCrateLevelOnlySugg {
+    #[primary_span]
+    pub attr: Span,
+}
diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs
index eca3bae9a1c..8b7338e29aa 100644
--- a/compiler/rustc_passes/src/lib.rs
+++ b/compiler/rustc_passes/src/lib.rs
@@ -12,6 +12,8 @@
 #![feature(min_specialization)]
 #![feature(try_blocks)]
 #![recursion_limit = "256"]
+#![deny(rustc::untranslatable_diagnostic)]
+#![deny(rustc::diagnostic_outside_of_impl)]
 
 #[macro_use]
 extern crate rustc_middle;
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index 7d8f6add632..6758024419d 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -81,13 +81,13 @@
 //! We generate various special nodes for various, well, special purposes.
 //! These are described in the `Liveness` struct.
 
+use crate::errors;
+
 use self::LiveNodeKind::*;
 use self::VarKind::*;
 
 use rustc_ast::InlineAsmOptions;
 use rustc_data_structures::fx::FxIndexMap;
-use rustc_errors::Applicability;
-use rustc_errors::Diagnostic;
 use rustc_hir as hir;
 use rustc_hir::def::*;
 use rustc_hir::def_id::LocalDefId;
@@ -1297,13 +1297,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
         self.exit_ln
     }
 
-    fn warn_about_unreachable(
+    fn warn_about_unreachable<'desc>(
         &mut self,
         orig_span: Span,
         orig_ty: Ty<'tcx>,
         expr_span: Span,
         expr_id: HirId,
-        descr: &str,
+        descr: &'desc str,
     ) {
         if !orig_ty.is_never() {
             // Unreachable code warnings are already emitted during type checking.
@@ -1316,22 +1316,15 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
             // that we do not emit the same warning twice if the uninhabited type
             // is indeed `!`.
 
-            let msg = format!("unreachable {}", descr);
-            self.ir.tcx.struct_span_lint_hir(
+            self.ir.tcx.emit_spanned_lint(
                 lint::builtin::UNREACHABLE_CODE,
                 expr_id,
                 expr_span,
-                &msg,
-                |diag| {
-                    diag.span_label(expr_span, &msg)
-                        .span_label(orig_span, "any code following this expression is unreachable")
-                        .span_note(
-                            orig_span,
-                            &format!(
-                                "this expression has type `{}`, which is uninhabited",
-                                orig_ty
-                            ),
-                        )
+                errors::UnreachableDueToUninhabited {
+                    expr: expr_span,
+                    orig: orig_span,
+                    descr,
+                    ty: orig_ty,
                 },
             );
         }
@@ -1483,23 +1476,21 @@ impl<'tcx> Liveness<'_, 'tcx> {
                 if self.used_on_entry(entry_ln, var) {
                     if !self.live_on_entry(entry_ln, var) {
                         if let Some(name) = self.should_warn(var) {
-                            self.ir.tcx.struct_span_lint_hir(
+                            self.ir.tcx.emit_spanned_lint(
                                 lint::builtin::UNUSED_ASSIGNMENTS,
                                 var_hir_id,
                                 vec![span],
-                                format!("value captured by `{}` is never read", name),
-                                |lint| lint.help("did you mean to capture by reference instead?"),
+                                errors::UnusedCaptureMaybeCaptureRef { name },
                             );
                         }
                     }
                 } else {
                     if let Some(name) = self.should_warn(var) {
-                        self.ir.tcx.struct_span_lint_hir(
+                        self.ir.tcx.emit_spanned_lint(
                             lint::builtin::UNUSED_VARIABLES,
                             var_hir_id,
                             vec![span],
-                            format!("unused variable: `{}`", name),
-                            |lint| lint.help("did you mean to capture by reference instead?"),
+                            errors::UnusedVarMaybeCaptureRef { name },
                         );
                     }
                 }
@@ -1514,11 +1505,14 @@ impl<'tcx> Liveness<'_, 'tcx> {
                 Some(entry_ln),
                 Some(body),
                 |spans, hir_id, ln, var| {
-                    if !self.live_on_entry(ln, var) {
-                        self.report_unused_assign(hir_id, spans, var, |name| {
-                            format!("value passed to `{}` is never read", name)
-                        });
-                    }
+                    if !self.live_on_entry(ln, var)
+                        && let Some(name) = self.should_warn(var) {
+                            self.ir.tcx.emit_spanned_lint(
+                                lint::builtin::UNUSED_ASSIGNMENTS,
+                                hir_id,
+                                spans,
+                                errors::UnusedAssignPassed { name },
+                            );                    }
                 },
             );
         }
@@ -1587,39 +1581,35 @@ impl<'tcx> Liveness<'_, 'tcx> {
                 if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) };
 
             if is_assigned {
-                self.ir.tcx.struct_span_lint_hir(
+                self.ir.tcx.emit_spanned_lint(
                     lint::builtin::UNUSED_VARIABLES,
                     first_hir_id,
                     hir_ids_and_spans
                         .into_iter()
                         .map(|(_, _, ident_span)| ident_span)
                         .collect::<Vec<_>>(),
-                    format!("variable `{}` is assigned to, but never used", name),
-                    |lint| lint.note(&format!("consider using `_{}` instead", name)),
+                    errors::UnusedVarAssignedOnly { name },
                 )
             } else if can_remove {
-                self.ir.tcx.struct_span_lint_hir(
+                let spans = hir_ids_and_spans
+                    .iter()
+                    .map(|(_, pat_span, _)| {
+                        let span = self
+                            .ir
+                            .tcx
+                            .sess
+                            .source_map()
+                            .span_extend_to_next_char(*pat_span, ',', true);
+                        span.with_hi(BytePos(span.hi().0 + 1))
+                    })
+                    .collect();
+                self.ir.tcx.emit_spanned_lint(
                     lint::builtin::UNUSED_VARIABLES,
                     first_hir_id,
                     hir_ids_and_spans.iter().map(|(_, pat_span, _)| *pat_span).collect::<Vec<_>>(),
-                    format!("unused variable: `{}`", name),
-                    |lint| {
-                        lint.multipart_suggestion(
-                            "try removing the field",
-                            hir_ids_and_spans
-                                .iter()
-                                .map(|(_, pat_span, _)| {
-                                    let span = self
-                                        .ir
-                                        .tcx
-                                        .sess
-                                        .source_map()
-                                        .span_extend_to_next_char(*pat_span, ',', true);
-                                    (span.with_hi(BytePos(span.hi().0 + 1)), String::new())
-                                })
-                                .collect(),
-                            Applicability::MachineApplicable,
-                        )
+                    errors::UnusedVarRemoveField {
+                        name,
+                        sugg: errors::UnusedVarRemoveFieldSugg { spans },
                     },
                 );
             } else {
@@ -1633,55 +1623,46 @@ impl<'tcx> Liveness<'_, 'tcx> {
                 // the field" message, and suggest `_` for the non-shorthands. If we only
                 // have non-shorthand, then prefix with an underscore instead.
                 if !shorthands.is_empty() {
-                    let shorthands = shorthands
-                        .into_iter()
-                        .map(|(_, pat_span, _)| (pat_span, format!("{}: _", name)))
-                        .chain(
-                            non_shorthands
-                                .into_iter()
-                                .map(|(_, pat_span, _)| (pat_span, "_".to_string())),
-                        )
-                        .collect::<Vec<_>>();
+                    let shorthands =
+                        shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect();
+                    let non_shorthands =
+                        non_shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect();
 
-                    self.ir.tcx.struct_span_lint_hir(
+                    self.ir.tcx.emit_spanned_lint(
                         lint::builtin::UNUSED_VARIABLES,
                         first_hir_id,
                         hir_ids_and_spans
                             .iter()
                             .map(|(_, pat_span, _)| *pat_span)
                             .collect::<Vec<_>>(),
-                        format!("unused variable: `{}`", name),
-                        |lint| {
-                            lint.multipart_suggestion(
-                                "try ignoring the field",
+                        errors::UnusedVarTryIgnore {
+                            sugg: errors::UnusedVarTryIgnoreSugg {
                                 shorthands,
-                                Applicability::MachineApplicable,
-                            )
+                                non_shorthands,
+                                name,
+                            },
                         },
                     );
                 } else {
                     let non_shorthands = non_shorthands
                         .into_iter()
-                        .map(|(_, _, ident_span)| (ident_span, format!("_{}", name)))
+                        .map(|(_, _, ident_span)| ident_span)
                         .collect::<Vec<_>>();
-
-                    self.ir.tcx.struct_span_lint_hir(
+                    let suggestions = self.string_interp_suggestions(&name, opt_body);
+                    self.ir.tcx.emit_spanned_lint(
                         lint::builtin::UNUSED_VARIABLES,
                         first_hir_id,
                         hir_ids_and_spans
                             .iter()
                             .map(|(_, _, ident_span)| *ident_span)
                             .collect::<Vec<_>>(),
-                        format!("unused variable: `{}`", name),
-                        |lint| {
-                            if self.has_added_lit_match_name_span(&name, opt_body, lint) {
-                                lint.span_label(pat.span, "unused variable");
-                            }
-                            lint.multipart_suggestion(
-                                "if this is intentional, prefix it with an underscore",
-                                non_shorthands,
-                                Applicability::MachineApplicable,
-                            )
+                        errors::UnusedVariableTryPrefix {
+                            label: if !suggestions.is_empty() { Some(pat.span) } else { None },
+                            sugg: errors::UnusedVariableTryPrefixSugg {
+                                spans: non_shorthands,
+                                name,
+                            },
+                            string_interp: suggestions,
                         },
                     );
                 }
@@ -1689,65 +1670,40 @@ impl<'tcx> Liveness<'_, 'tcx> {
         }
     }
 
-    fn has_added_lit_match_name_span(
+    fn string_interp_suggestions(
         &self,
         name: &str,
         opt_body: Option<&hir::Body<'_>>,
-        err: &mut Diagnostic,
-    ) -> bool {
-        let mut has_litstring = false;
-        let Some(opt_body) = opt_body else {return false;};
+    ) -> Vec<errors::UnusedVariableStringInterp> {
+        let mut suggs = Vec::new();
+        let Some(opt_body) = opt_body else { return suggs; };
         let mut visitor = CollectLitsVisitor { lit_exprs: vec![] };
         intravisit::walk_body(&mut visitor, opt_body);
         for lit_expr in visitor.lit_exprs {
             let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue };
             let rustc_ast::LitKind::Str(syb, _) = litx.node else{ continue; };
             let name_str: &str = syb.as_str();
-            let mut name_pa = String::from("{");
-            name_pa.push_str(&name);
-            name_pa.push('}');
+            let name_pa = format!("{{{name}}}");
             if name_str.contains(&name_pa) {
-                err.span_label(
-                    lit_expr.span,
-                    "you might have meant to use string interpolation in this string literal",
-                );
-                err.multipart_suggestion(
-                    "string interpolation only works in `format!` invocations",
-                    vec![
-                        (lit_expr.span.shrink_to_lo(), "format!(".to_string()),
-                        (lit_expr.span.shrink_to_hi(), ")".to_string()),
-                    ],
-                    Applicability::MachineApplicable,
-                );
-                has_litstring = true;
+                suggs.push(errors::UnusedVariableStringInterp {
+                    lit: lit_expr.span,
+                    lo: lit_expr.span.shrink_to_lo(),
+                    hi: lit_expr.span.shrink_to_hi(),
+                });
             }
         }
-        has_litstring
+        suggs
     }
 
     fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
-        if !self.live_on_exit(ln, var) {
-            self.report_unused_assign(hir_id, spans, var, |name| {
-                format!("value assigned to `{}` is never read", name)
-            });
-        }
-    }
-
-    fn report_unused_assign(
-        &self,
-        hir_id: HirId,
-        spans: Vec<Span>,
-        var: Variable,
-        message: impl Fn(&str) -> String,
-    ) {
-        if let Some(name) = self.should_warn(var) {
-            self.ir.tcx.struct_span_lint_hir(
-                lint::builtin::UNUSED_ASSIGNMENTS,
-                hir_id,
-                spans,
-                message(&name),
-                |lint| lint.help("maybe it is overwritten before being read?"),
-            )
-        }
+        if !self.live_on_exit(ln, var)
+            && let Some(name) = self.should_warn(var) {
+                self.ir.tcx.emit_spanned_lint(
+                    lint::builtin::UNUSED_ASSIGNMENTS,
+                    hir_id,
+                    spans,
+                    errors::UnusedAssign { name },
+                );
+            }
     }
 }
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index 4a35c679466..9615f283ff4 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -7,7 +7,6 @@ use rustc_attr::{
     UnstableReason, VERSION_PLACEHOLDER,
 };
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
-use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
@@ -759,12 +758,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
                         // do not lint when the trait isn't resolved, since resolution error should
                         // be fixed first
                         if t.path.res != Res::Err && c.fully_stable {
-                            self.tcx.struct_span_lint_hir(
+                            self.tcx.emit_spanned_lint(
                                 INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
                                 item.hir_id(),
                                 span,
-                                "an `#[unstable]` annotation here has no effect",
-                                |lint| lint.note("see issue #55436 <https://github.com/rust-lang/rust/issues/55436> for more information")
+                                errors::IneffectiveUnstableImpl,
                             );
                         }
                     }
@@ -1095,29 +1093,16 @@ fn unnecessary_partially_stable_feature_lint(
     implies: Symbol,
     since: Symbol,
 ) {
-    tcx.struct_span_lint_hir(
+    tcx.emit_spanned_lint(
         lint::builtin::STABLE_FEATURES,
         hir::CRATE_HIR_ID,
         span,
-        format!(
-            "the feature `{feature}` has been partially stabilized since {since} and is succeeded \
-             by the feature `{implies}`"
-        ),
-        |lint| {
-            lint.span_suggestion(
-                span,
-                &format!(
-                "if you are using features which are still unstable, change to using `{implies}`"
-            ),
-                implies,
-                Applicability::MaybeIncorrect,
-            )
-            .span_suggestion(
-                tcx.sess.source_map().span_extend_to_line(span),
-                "if you are using features which are now stable, remove this line",
-                "",
-                Applicability::MaybeIncorrect,
-            )
+        errors::UnnecessaryPartialStableFeature {
+            span,
+            line: tcx.sess.source_map().span_extend_to_line(span),
+            feature,
+            since,
+            implies,
         },
     );
 }
@@ -1131,7 +1116,10 @@ fn unnecessary_stable_feature_lint(
     if since.as_str() == VERSION_PLACEHOLDER {
         since = rust_version_symbol();
     }
-    tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, format!("the feature `{feature}` has been stable since {since} and no longer requires an attribute to enable"), |lint| {
-        lint
-    });
+    tcx.emit_spanned_lint(
+        lint::builtin::STABLE_FEATURES,
+        hir::CRATE_HIR_ID,
+        span,
+        errors::UnnecessaryStableFeature { feature, since },
+    );
 }
diff --git a/tests/rustdoc-ui/doc_cfg_hide.stderr b/tests/rustdoc-ui/doc_cfg_hide.stderr
index 03623368cd0..b7e8870fdf5 100644
--- a/tests/rustdoc-ui/doc_cfg_hide.stderr
+++ b/tests/rustdoc-ui/doc_cfg_hide.stderr
@@ -16,7 +16,7 @@ LL | #![deny(warnings)]
 help: to apply to the crate, use an inner attribute
    |
 LL | #![doc(cfg_hide(doc))]
-   | ~~~~~~~~~~~~~~~~~~~~~~
+   |  +
 
 error: `#[doc(cfg_hide(...)]` takes a list of attributes
   --> $DIR/doc_cfg_hide.rs:4:8
diff --git a/tests/rustdoc-ui/invalid-doc-attr.rs b/tests/rustdoc-ui/invalid-doc-attr.rs
index de004b41e27..c231e43b35c 100644
--- a/tests/rustdoc-ui/invalid-doc-attr.rs
+++ b/tests/rustdoc-ui/invalid-doc-attr.rs
@@ -5,7 +5,7 @@
 //~^ ERROR can only be applied at the crate level
 //~| WARN is being phased out
 //~| HELP to apply to the crate, use an inner attribute
-//~| SUGGESTION #![doc(test(no_crate_inject))]
+//~| SUGGESTION !
 #[doc(inline)]
 //~^ ERROR can only be applied to a `use` item
 //~| WARN is being phased out
diff --git a/tests/rustdoc-ui/invalid-doc-attr.stderr b/tests/rustdoc-ui/invalid-doc-attr.stderr
index 3c66e587b47..b23b8ded867 100644
--- a/tests/rustdoc-ui/invalid-doc-attr.stderr
+++ b/tests/rustdoc-ui/invalid-doc-attr.stderr
@@ -16,7 +16,7 @@ LL | #![deny(warnings)]
 help: to apply to the crate, use an inner attribute
    |
 LL | #![doc(test(no_crate_inject))]
-   |
+   |  +
 
 error: this attribute can only be applied to a `use` item
   --> $DIR/invalid-doc-attr.rs:9:7
diff --git a/tests/ui/attributes/invalid-doc-attr.rs b/tests/ui/attributes/invalid-doc-attr.rs
index de004b41e27..c231e43b35c 100644
--- a/tests/ui/attributes/invalid-doc-attr.rs
+++ b/tests/ui/attributes/invalid-doc-attr.rs
@@ -5,7 +5,7 @@
 //~^ ERROR can only be applied at the crate level
 //~| WARN is being phased out
 //~| HELP to apply to the crate, use an inner attribute
-//~| SUGGESTION #![doc(test(no_crate_inject))]
+//~| SUGGESTION !
 #[doc(inline)]
 //~^ ERROR can only be applied to a `use` item
 //~| WARN is being phased out
diff --git a/tests/ui/attributes/invalid-doc-attr.stderr b/tests/ui/attributes/invalid-doc-attr.stderr
index 3c66e587b47..b23b8ded867 100644
--- a/tests/ui/attributes/invalid-doc-attr.stderr
+++ b/tests/ui/attributes/invalid-doc-attr.stderr
@@ -16,7 +16,7 @@ LL | #![deny(warnings)]
 help: to apply to the crate, use an inner attribute
    |
 LL | #![doc(test(no_crate_inject))]
-   |
+   |  +
 
 error: this attribute can only be applied to a `use` item
   --> $DIR/invalid-doc-attr.rs:9:7