about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2023-02-15 21:30:55 +0100
committerGitHub <noreply@github.com>2023-02-15 21:30:55 +0100
commit1fdf0e1334a61b8c7e3d6e91c83c19bb3a27e28f (patch)
tree1f226f714e5ec5047354f454ef98215da2e67d9e
parent2d14db321b043ffc579a7461464c88d7e3f54f83 (diff)
parent58939b9520e71b6caa2ccc5ca28071e6379d39c0 (diff)
downloadrust-1fdf0e1334a61b8c7e3d6e91c83c19bb3a27e28f.tar.gz
rust-1fdf0e1334a61b8c7e3d6e91c83c19bb3a27e28f.zip
Rollup merge of #107034 - IntQuant:issue-100717-infer-5, r=oli-obk
Migrating rustc_infer to session diagnostics (part 4)

`@rustbot` label +A-translation
r? rust-lang/diagnostics
cc https://github.com/rust-lang/rust/issues/100717
-rw-r--r--compiler/rustc_error_messages/locales/en-US/infer.ftl39
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs213
-rw-r--r--compiler/rustc_infer/src/errors/note_and_explain.rs44
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs14
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs66
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/note.rs194
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/suggest.rs147
7 files changed, 486 insertions, 231 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/infer.ftl b/compiler/rustc_error_messages/locales/en-US/infer.ftl
index bcc1d9002df..c5b2b6c2d73 100644
--- a/compiler/rustc_error_messages/locales/en-US/infer.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/infer.ftl
@@ -140,6 +140,18 @@ infer_lifetime_param_suggestion_elided = each elided lifetime in input position
 
 infer_region_explanation = {$pref_kind ->
     *[should_not_happen] [{$pref_kind}]
+    [ref_valid_for] ...the reference is valid for
+    [content_valid_for] ...but the borrowed content is only valid for
+    [type_obj_valid_for] object type is valid for
+    [source_pointer_valid_for] source pointer is only valid for
+    [type_satisfy] type must satisfy
+    [type_outlive] type must outlive
+    [lf_param_instantiated_with] lifetime parameter instantiated with
+    [lf_param_must_outlive] but lifetime parameter must outlive
+    [lf_instantiated_with] lifetime instantiated with
+    [lf_must_outlive] but lifetime must outlive
+    [pointer_valid_for] the pointer is valid for
+    [data_valid_for] but the referenced data is only valid for
     [empty] {""}
 }{$pref_kind ->
     [empty] {""}
@@ -148,7 +160,6 @@ infer_region_explanation = {$pref_kind ->
     *[should_not_happen] [{$desc_kind}]
     [restatic] the static lifetime
     [revar] lifetime {$desc_arg}
-
     [as_defined] the lifetime `{$desc_arg}` as defined here
     [as_defined_anon] the anonymous lifetime as defined here
     [defined_here] the anonymous lifetime defined here
@@ -158,8 +169,16 @@ infer_region_explanation = {$pref_kind ->
     *[should_not_happen] [{$suff_kind}]
     [empty]{""}
     [continues] ...
+    [req_by_binding] {" "}as required by this binding
 }
 
+infer_outlives_content = lifetime of reference outlives lifetime of borrowed content...
+infer_outlives_bound = lifetime of the source pointer does not outlive lifetime bound of the object type
+infer_fullfill_req_lifetime = the type `{$ty}` does not fulfill the required lifetime
+infer_lf_bound_not_satisfied = lifetime bound not satisfied
+infer_borrowed_too_long = a value of type `{$ty}` is borrowed for too long
+infer_ref_longer_than_data = in type `{$ty}`, reference has a longer lifetime than the data it references
+
 infer_mismatched_static_lifetime = incompatible lifetime on type
 infer_does_not_outlive_static_from_impl = ...does not necessarily outlive the static lifetime introduced by the compatible `impl`
 infer_implicit_static_lifetime_note = this has an implicit `'static` lifetime requirement
@@ -308,3 +327,21 @@ infer_ril_introduced_here = `'static` requirement introduced here
 infer_ril_introduced_by = requirement introduced by this return type
 infer_ril_because_of = because of this returned expression
 infer_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type
+
+infer_where_remove = remove the `where` clause
+infer_where_copy_predicates = copy the `where` clause predicates from the trait
+
+infer_srs_remove_and_box = consider removing this semicolon and boxing the expressions
+infer_srs_remove = consider removing this semicolon
+infer_srs_add = consider returning the local binding `{$ident}`
+infer_srs_add_one = consider returning one of these bindings
+
+infer_await_both_futures = consider `await`ing on both `Future`s
+infer_await_future = consider `await`ing on the `Future`
+infer_await_note = calling an async function returns a future
+
+infer_prlf_defined_with_sub = the lifetime `{$sub_symbol}` defined here...
+infer_prlf_defined_without_sub = the lifetime defined here...
+infer_prlf_must_oultive_with_sup = ...must outlive the lifetime `{$sup_symbol}` defined here
+infer_prlf_must_oultive_without_sup = ...must outlive the lifetime defined here
+infer_prlf_known_limitation = this is a known limitation that will be removed in the future (see issue #100013 <https://github.com/rust-lang/rust/issues/100013> for more information)
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index 8bf3a160abb..0c2713fb1a7 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -933,3 +933,216 @@ pub struct ButNeedsToSatisfy {
     pub has_lifetime: bool,
     pub lifetime: String,
 }
+
+#[derive(Diagnostic)]
+#[diag(infer_outlives_content, code = "E0312")]
+pub struct OutlivesContent<'a> {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub notes: Vec<note_and_explain::RegionExplanation<'a>>,
+}
+
+#[derive(Diagnostic)]
+#[diag(infer_outlives_bound, code = "E0476")]
+pub struct OutlivesBound<'a> {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub notes: Vec<note_and_explain::RegionExplanation<'a>>,
+}
+
+#[derive(Diagnostic)]
+#[diag(infer_fullfill_req_lifetime, code = "E0477")]
+pub struct FullfillReqLifetime<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'a>,
+    #[subdiagnostic]
+    pub note: Option<note_and_explain::RegionExplanation<'a>>,
+}
+
+#[derive(Diagnostic)]
+#[diag(infer_lf_bound_not_satisfied, code = "E0478")]
+pub struct LfBoundNotSatisfied<'a> {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub notes: Vec<note_and_explain::RegionExplanation<'a>>,
+}
+
+#[derive(Diagnostic)]
+#[diag(infer_ref_longer_than_data, code = "E0491")]
+pub struct RefLongerThanData<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'a>,
+    #[subdiagnostic]
+    pub notes: Vec<note_and_explain::RegionExplanation<'a>>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum WhereClauseSuggestions {
+    #[suggestion(
+        infer_where_remove,
+        code = "",
+        applicability = "machine-applicable",
+        style = "verbose"
+    )]
+    Remove {
+        #[primary_span]
+        span: Span,
+    },
+    #[suggestion(
+        infer_where_copy_predicates,
+        code = "{space}where {trait_predicates}",
+        applicability = "machine-applicable",
+        style = "verbose"
+    )]
+    CopyPredicates {
+        #[primary_span]
+        span: Span,
+        space: &'static str,
+        trait_predicates: String,
+    },
+}
+
+#[derive(Subdiagnostic)]
+pub enum SuggestRemoveSemiOrReturnBinding {
+    #[multipart_suggestion(infer_srs_remove_and_box, applicability = "machine-applicable")]
+    RemoveAndBox {
+        #[suggestion_part(code = "Box::new(")]
+        first_lo: Span,
+        #[suggestion_part(code = ")")]
+        first_hi: Span,
+        #[suggestion_part(code = "Box::new(")]
+        second_lo: Span,
+        #[suggestion_part(code = ")")]
+        second_hi: Span,
+        #[suggestion_part(code = "")]
+        sp: Span,
+    },
+    #[suggestion(
+        infer_srs_remove,
+        style = "short",
+        code = "",
+        applicability = "machine-applicable"
+    )]
+    Remove {
+        #[primary_span]
+        sp: Span,
+    },
+    #[suggestion(
+        infer_srs_add,
+        style = "verbose",
+        code = "{code}",
+        applicability = "maybe-incorrect"
+    )]
+    Add {
+        #[primary_span]
+        sp: Span,
+        code: String,
+        ident: Ident,
+    },
+    #[note(infer_srs_add_one)]
+    AddOne {
+        #[primary_span]
+        spans: MultiSpan,
+    },
+}
+
+#[derive(Subdiagnostic)]
+pub enum ConsiderAddingAwait {
+    #[help(infer_await_both_futures)]
+    BothFuturesHelp,
+    #[multipart_suggestion(infer_await_both_futures, applicability = "maybe-incorrect")]
+    BothFuturesSugg {
+        #[suggestion_part(code = ".await")]
+        first: Span,
+        #[suggestion_part(code = ".await")]
+        second: Span,
+    },
+    #[suggestion(
+        infer_await_future,
+        code = ".await",
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    FutureSugg {
+        #[primary_span]
+        span: Span,
+    },
+    #[note(infer_await_note)]
+    FutureSuggNote {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(
+        infer_await_future,
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    FutureSuggMultiple {
+        #[suggestion_part(code = ".await")]
+        spans: Vec<Span>,
+    },
+}
+
+#[derive(Diagnostic)]
+pub enum PlaceholderRelationLfNotSatisfied {
+    #[diag(infer_lf_bound_not_satisfied)]
+    HasBoth {
+        #[primary_span]
+        span: Span,
+        #[note(infer_prlf_defined_with_sub)]
+        sub_span: Span,
+        #[note(infer_prlf_must_oultive_with_sup)]
+        sup_span: Span,
+        sub_symbol: Symbol,
+        sup_symbol: Symbol,
+        #[note(infer_prlf_known_limitation)]
+        note: (),
+    },
+    #[diag(infer_lf_bound_not_satisfied)]
+    HasSub {
+        #[primary_span]
+        span: Span,
+        #[note(infer_prlf_defined_with_sub)]
+        sub_span: Span,
+        #[note(infer_prlf_must_oultive_without_sup)]
+        sup_span: Span,
+        sub_symbol: Symbol,
+        #[note(infer_prlf_known_limitation)]
+        note: (),
+    },
+    #[diag(infer_lf_bound_not_satisfied)]
+    HasSup {
+        #[primary_span]
+        span: Span,
+        #[note(infer_prlf_defined_without_sub)]
+        sub_span: Span,
+        #[note(infer_prlf_must_oultive_with_sup)]
+        sup_span: Span,
+        sup_symbol: Symbol,
+        #[note(infer_prlf_known_limitation)]
+        note: (),
+    },
+    #[diag(infer_lf_bound_not_satisfied)]
+    HasNone {
+        #[primary_span]
+        span: Span,
+        #[note(infer_prlf_defined_without_sub)]
+        sub_span: Span,
+        #[note(infer_prlf_must_oultive_without_sup)]
+        sup_span: Span,
+        #[note(infer_prlf_known_limitation)]
+        note: (),
+    },
+    #[diag(infer_lf_bound_not_satisfied)]
+    OnlyPrimarySpan {
+        #[primary_span]
+        span: Span,
+        #[note(infer_prlf_known_limitation)]
+        note: (),
+    },
+}
diff --git a/compiler/rustc_infer/src/errors/note_and_explain.rs b/compiler/rustc_infer/src/errors/note_and_explain.rs
index 5d861a78af8..cb96aeec5f3 100644
--- a/compiler/rustc_infer/src/errors/note_and_explain.rs
+++ b/compiler/rustc_infer/src/errors/note_and_explain.rs
@@ -121,16 +121,42 @@ impl<'a> DescriptionCtx<'a> {
 
 pub enum PrefixKind {
     Empty,
+    RefValidFor,
+    ContentValidFor,
+    TypeObjValidFor,
+    SourcePointerValidFor,
+    TypeSatisfy,
+    TypeOutlive,
+    LfParamInstantiatedWith,
+    LfParamMustOutlive,
+    LfInstantiatedWith,
+    LfMustOutlive,
+    PointerValidFor,
+    DataValidFor,
 }
 
 pub enum SuffixKind {
+    Empty,
     Continues,
+    ReqByBinding,
 }
 
 impl IntoDiagnosticArg for PrefixKind {
     fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
         let kind = match self {
             Self::Empty => "empty",
+            Self::RefValidFor => "ref_valid_for",
+            Self::ContentValidFor => "content_valid_for",
+            Self::TypeObjValidFor => "type_obj_valid_for",
+            Self::SourcePointerValidFor => "source_pointer_valid_for",
+            Self::TypeSatisfy => "type_satisfy",
+            Self::TypeOutlive => "type_outlive",
+            Self::LfParamInstantiatedWith => "lf_param_instantiated_with",
+            Self::LfParamMustOutlive => "lf_param_must_outlive",
+            Self::LfInstantiatedWith => "lf_instantiated_with",
+            Self::LfMustOutlive => "lf_must_outlive",
+            Self::PointerValidFor => "pointer_valid_for",
+            Self::DataValidFor => "data_valid_for",
         }
         .into();
         rustc_errors::DiagnosticArgValue::Str(kind)
@@ -140,7 +166,9 @@ impl IntoDiagnosticArg for PrefixKind {
 impl IntoDiagnosticArg for SuffixKind {
     fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
         let kind = match self {
+            Self::Empty => "empty",
             Self::Continues => "continues",
+            Self::ReqByBinding => "req_by_binding",
         }
         .into();
         rustc_errors::DiagnosticArgValue::Str(kind)
@@ -166,17 +194,19 @@ impl RegionExplanation<'_> {
 }
 
 impl AddToDiagnostic for RegionExplanation<'_> {
-    fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
+    fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, f: F)
     where
         F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
     {
-        if let Some(span) = self.desc.span {
-            diag.span_note(span, fluent::infer_region_explanation);
-        } else {
-            diag.note(fluent::infer_region_explanation);
-        }
-        self.desc.add_to(diag);
         diag.set_arg("pref_kind", self.prefix);
         diag.set_arg("suff_kind", self.suffix);
+        let desc_span = self.desc.span;
+        self.desc.add_to(diag);
+        let msg = f(diag, fluent::infer_region_explanation.into());
+        if let Some(span) = desc_span {
+            diag.span_note(span, msg);
+        } else {
+            diag.note(msg);
+        }
     }
 }
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 1d3fcf7571e..bb7947e3141 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -751,15 +751,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     };
                     let msg = "`match` arms have incompatible types";
                     err.span_label(outer, msg);
-                    self.suggest_remove_semi_or_return_binding(
-                        err,
+                    if let Some(subdiag) = self.suggest_remove_semi_or_return_binding(
                         prior_arm_block_id,
                         prior_arm_ty,
                         prior_arm_span,
                         arm_block_id,
                         arm_ty,
                         arm_span,
-                    );
+                    ) {
+                        err.subdiagnostic(subdiag);
+                    }
                     if let Some(ret_sp) = opt_suggest_box_span {
                         // Get return type span and point to it.
                         self.suggest_boxing_for_return_impl_trait(
@@ -784,15 +785,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 if let Some(sp) = outer_span {
                     err.span_label(sp, "`if` and `else` have incompatible types");
                 }
-                self.suggest_remove_semi_or_return_binding(
-                    err,
+                if let Some(subdiag) = self.suggest_remove_semi_or_return_binding(
                     Some(then_id),
                     then_ty,
                     then_span,
                     Some(else_id),
                     else_ty,
                     else_span,
-                );
+                ) {
+                    err.subdiagnostic(subdiag);
+                }
                 if let Some(ret_sp) = opt_suggest_box_span {
                     self.suggest_boxing_for_return_impl_trait(
                         err,
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs
index 9534bce54ef..e8d94f0c04e 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs
@@ -1,5 +1,8 @@
-use crate::infer::{
-    error_reporting::nice_region_error::NiceRegionError, RegionResolutionError, SubregionOrigin,
+use crate::{
+    errors::PlaceholderRelationLfNotSatisfied,
+    infer::{
+        error_reporting::nice_region_error::NiceRegionError, RegionResolutionError, SubregionOrigin,
+    },
 };
 use rustc_data_structures::intern::Interned;
 use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
@@ -16,8 +19,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> {
                 Region(Interned(RePlaceholder(ty::Placeholder { name: sub_name, .. }), _)),
                 Region(Interned(RePlaceholder(ty::Placeholder { name: sup_name, .. }), _)),
             )) => {
-                let msg = "lifetime bound not satisfied";
-                let mut err = self.tcx().sess.struct_span_err(*span, msg);
+                let span = *span;
                 let (sub_span, sub_symbol) = match sub_name {
                     ty::BrNamed(def_id, symbol) => {
                         (Some(self.tcx().def_span(def_id)), Some(symbol))
@@ -32,41 +34,47 @@ impl<'tcx> NiceRegionError<'_, 'tcx> {
                     ty::BrAnon(_, span) => (*span, None),
                     ty::BrEnv => (None, None),
                 };
-                match (sub_span, sup_span, sub_symbol, sup_symbol) {
-                    (Some(sub_span), Some(sup_span), Some(sub_symbol), Some(sup_symbol)) => {
-                        err.span_note(
+                let diag = match (sub_span, sup_span, sub_symbol, sup_symbol) {
+                    (Some(sub_span), Some(sup_span), Some(&sub_symbol), Some(&sup_symbol)) => {
+                        PlaceholderRelationLfNotSatisfied::HasBoth {
+                            span,
                             sub_span,
-                            format!("the lifetime `{sub_symbol}` defined here..."),
-                        );
-                        err.span_note(
                             sup_span,
-                            format!("...must outlive the lifetime `{sup_symbol}` defined here"),
-                        );
+                            sub_symbol,
+                            sup_symbol,
+                            note: (),
+                        }
                     }
-                    (Some(sub_span), Some(sup_span), _, Some(sup_symbol)) => {
-                        err.span_note(sub_span, "the lifetime defined here...");
-                        err.span_note(
+                    (Some(sub_span), Some(sup_span), _, Some(&sup_symbol)) => {
+                        PlaceholderRelationLfNotSatisfied::HasSup {
+                            span,
+                            sub_span,
                             sup_span,
-                            format!("...must outlive the lifetime `{sup_symbol}` defined here"),
-                        );
+                            sup_symbol,
+                            note: (),
+                        }
                     }
-                    (Some(sub_span), Some(sup_span), Some(sub_symbol), _) => {
-                        err.span_note(
+                    (Some(sub_span), Some(sup_span), Some(&sub_symbol), _) => {
+                        PlaceholderRelationLfNotSatisfied::HasSub {
+                            span,
                             sub_span,
-                            format!("the lifetime `{sub_symbol}` defined here..."),
-                        );
-                        err.span_note(sup_span, "...must outlive the lifetime defined here");
+                            sup_span,
+                            sub_symbol,
+                            note: (),
+                        }
                     }
                     (Some(sub_span), Some(sup_span), _, _) => {
-                        err.span_note(sub_span, "the lifetime defined here...");
-                        err.span_note(sup_span, "...must outlive the lifetime defined here");
+                        PlaceholderRelationLfNotSatisfied::HasNone {
+                            span,
+                            sub_span,
+                            sup_span,
+                            note: (),
+                        }
                     }
-                    _ => {}
-                }
-                err.note("this is a known limitation that will be removed in the future (see issue #100013 <https://github.com/rust-lang/rust/issues/100013> for more information)");
-                Some(err)
+                    _ => PlaceholderRelationLfNotSatisfied::OnlyPrimarySpan { span, note: () },
+                };
+                Some(self.tcx().sess.create_err(diag))
             }
-
             _ => None,
         }
     }
diff --git a/compiler/rustc_infer/src/infer/error_reporting/note.rs b/compiler/rustc_infer/src/infer/error_reporting/note.rs
index bdd09a995dc..e0e89158a58 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/note.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/note.rs
@@ -1,9 +1,11 @@
-use crate::errors::RegionOriginNote;
+use crate::errors::{
+    note_and_explain, FullfillReqLifetime, LfBoundNotSatisfied, OutlivesBound, OutlivesContent,
+    RefLongerThanData, RegionOriginNote, WhereClauseSuggestions,
+};
 use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt};
 use crate::infer::{self, SubregionOrigin};
 use rustc_errors::{
-    fluent, struct_span_err, AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder,
-    ErrorGuaranteed,
+    fluent, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic,
 };
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::traits::ObligationCauseCode;
@@ -119,130 +121,105 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 err
             }
             infer::Reborrow(span) => {
-                let mut err = struct_span_err!(
-                    self.tcx.sess,
-                    span,
-                    E0312,
-                    "lifetime of reference outlives lifetime of borrowed content..."
-                );
-                note_and_explain_region(
+                let reference_valid = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "...the reference is valid for ",
                     sub,
-                    "...",
                     None,
+                    note_and_explain::PrefixKind::RefValidFor,
+                    note_and_explain::SuffixKind::Continues,
                 );
-                note_and_explain_region(
+                let content_valid = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "...but the borrowed content is only valid for ",
                     sup,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::ContentValidFor,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                err
+                OutlivesContent {
+                    span,
+                    notes: reference_valid.into_iter().chain(content_valid).collect(),
+                }
+                .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic)
             }
             infer::RelateObjectBound(span) => {
-                let mut err = struct_span_err!(
-                    self.tcx.sess,
-                    span,
-                    E0476,
-                    "lifetime of the source pointer does not outlive lifetime bound of the \
-                     object type"
-                );
-                note_and_explain_region(
+                let object_valid = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "object type is valid for ",
                     sub,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::TypeObjValidFor,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                note_and_explain_region(
+                let pointer_valid = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "source pointer is only valid for ",
                     sup,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::SourcePointerValidFor,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                err
+                OutlivesBound {
+                    span,
+                    notes: object_valid.into_iter().chain(pointer_valid).collect(),
+                }
+                .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic)
             }
             infer::RelateParamBound(span, ty, opt_span) => {
-                let mut err = struct_span_err!(
-                    self.tcx.sess,
-                    span,
-                    E0477,
-                    "the type `{}` does not fulfill the required lifetime",
-                    self.ty_to_string(ty)
+                let prefix = match *sub {
+                    ty::ReStatic => note_and_explain::PrefixKind::TypeSatisfy,
+                    _ => note_and_explain::PrefixKind::TypeOutlive,
+                };
+                let suffix = if opt_span.is_some() {
+                    note_and_explain::SuffixKind::ReqByBinding
+                } else {
+                    note_and_explain::SuffixKind::Empty
+                };
+                let note = note_and_explain::RegionExplanation::new(
+                    self.tcx, sub, opt_span, prefix, suffix,
                 );
-                match *sub {
-                    ty::ReStatic => note_and_explain_region(
-                        self.tcx,
-                        &mut err,
-                        "type must satisfy ",
-                        sub,
-                        if opt_span.is_some() { " as required by this binding" } else { "" },
-                        opt_span,
-                    ),
-                    _ => note_and_explain_region(
-                        self.tcx,
-                        &mut err,
-                        "type must outlive ",
-                        sub,
-                        if opt_span.is_some() { " as required by this binding" } else { "" },
-                        opt_span,
-                    ),
-                }
-                err
+                FullfillReqLifetime { span, ty: self.resolve_vars_if_possible(ty), note }
+                    .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic)
             }
             infer::RelateRegionParamBound(span) => {
-                let mut err =
-                    struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
-                note_and_explain_region(
+                let param_instantiated = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "lifetime parameter instantiated with ",
                     sup,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::LfParamInstantiatedWith,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                note_and_explain_region(
+                let param_must_outlive = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "but lifetime parameter must outlive ",
                     sub,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::LfParamMustOutlive,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                err
+                LfBoundNotSatisfied {
+                    span,
+                    notes: param_instantiated.into_iter().chain(param_must_outlive).collect(),
+                }
+                .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic)
             }
             infer::ReferenceOutlivesReferent(ty, span) => {
-                let mut err = struct_span_err!(
-                    self.tcx.sess,
-                    span,
-                    E0491,
-                    "in type `{}`, reference has a longer lifetime than the data it references",
-                    self.ty_to_string(ty)
-                );
-                note_and_explain_region(
+                let pointer_valid = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "the pointer is valid for ",
                     sub,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::PointerValidFor,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                note_and_explain_region(
+                let data_valid = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "but the referenced data is only valid for ",
                     sup,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::DataValidFor,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                err
+                RefLongerThanData {
+                    span,
+                    ty: self.resolve_vars_if_possible(ty),
+                    notes: pointer_valid.into_iter().chain(data_valid).collect(),
+                }
+                .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic)
             }
             infer::CompareImplItemObligation { span, impl_item_def_id, trait_item_def_id } => {
                 let mut err = self.report_extra_impl_obligation(
@@ -279,25 +256,25 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 err
             }
             infer::AscribeUserTypeProvePredicate(span) => {
-                let mut err =
-                    struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
-                note_and_explain_region(
+                let instantiated = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "lifetime instantiated with ",
                     sup,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::LfInstantiatedWith,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                note_and_explain_region(
+                let must_outlive = note_and_explain::RegionExplanation::new(
                     self.tcx,
-                    &mut err,
-                    "but lifetime must outlive ",
                     sub,
-                    "",
                     None,
+                    note_and_explain::PrefixKind::LfMustOutlive,
+                    note_and_explain::SuffixKind::Empty,
                 );
-                err
+                LfBoundNotSatisfied {
+                    span,
+                    notes: instantiated.into_iter().chain(must_outlive).collect(),
+                }
+                .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic)
             }
         };
         if sub.is_error() || sup.is_error() {
@@ -347,22 +324,17 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
 
         let Some(generics) = self.tcx.hir().get_generics(impl_item_def_id) else { return; };
 
-        if trait_predicates.is_empty() {
-            err.span_suggestion_verbose(
-                generics.where_clause_span,
-                "remove the `where` clause",
-                String::new(),
-                Applicability::MachineApplicable,
-            );
+        let suggestion = if trait_predicates.is_empty() {
+            WhereClauseSuggestions::Remove { span: generics.where_clause_span }
         } else {
             let space = if generics.where_clause_span.is_empty() { " " } else { "" };
-            err.span_suggestion_verbose(
-                generics.where_clause_span,
-                "copy the `where` clause predicates from the trait",
-                format!("{space}where {}", trait_predicates.join(", ")),
-                Applicability::MachineApplicable,
-            );
-        }
+            WhereClauseSuggestions::CopyPredicates {
+                span: generics.where_clause_span,
+                space,
+                trait_predicates: trait_predicates.join(", "),
+            }
+        };
+        err.subdiagnostic(suggestion);
     }
 
     pub(super) fn report_placeholder_failure(
diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
index 7d9a53d1c02..18c5097a262 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
@@ -11,21 +11,22 @@ use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TypeVisitable};
 use rustc_span::{sym, BytePos, Span};
 
-use crate::errors::SuggAddLetForLetChains;
+use crate::errors::{
+    ConsiderAddingAwait, SuggAddLetForLetChains, SuggestRemoveSemiOrReturnBinding,
+};
 
 use super::TypeErrCtxt;
 
 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
     pub(super) fn suggest_remove_semi_or_return_binding(
         &self,
-        err: &mut Diagnostic,
         first_id: Option<hir::HirId>,
         first_ty: Ty<'tcx>,
         first_span: Span,
         second_id: Option<hir::HirId>,
         second_ty: Ty<'tcx>,
         second_span: Span,
-    ) {
+    ) -> Option<SuggestRemoveSemiOrReturnBinding> {
         let remove_semicolon = [
             (first_id, self.resolve_vars_if_possible(second_ty)),
             (second_id, self.resolve_vars_if_possible(first_ty)),
@@ -37,35 +38,29 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         });
         match remove_semicolon {
             Some((sp, StatementAsExpression::NeedsBoxing)) => {
-                err.multipart_suggestion(
-                    "consider removing this semicolon and boxing the expressions",
-                    vec![
-                        (first_span.shrink_to_lo(), "Box::new(".to_string()),
-                        (first_span.shrink_to_hi(), ")".to_string()),
-                        (second_span.shrink_to_lo(), "Box::new(".to_string()),
-                        (second_span.shrink_to_hi(), ")".to_string()),
-                        (sp, String::new()),
-                    ],
-                    Applicability::MachineApplicable,
-                );
+                Some(SuggestRemoveSemiOrReturnBinding::RemoveAndBox {
+                    first_lo: first_span.shrink_to_lo(),
+                    first_hi: first_span.shrink_to_hi(),
+                    second_lo: second_span.shrink_to_lo(),
+                    second_hi: second_span.shrink_to_hi(),
+                    sp,
+                })
             }
             Some((sp, StatementAsExpression::CorrectType)) => {
-                err.span_suggestion_short(
-                    sp,
-                    "consider removing this semicolon",
-                    "",
-                    Applicability::MachineApplicable,
-                );
+                Some(SuggestRemoveSemiOrReturnBinding::Remove { sp })
             }
             None => {
+                let mut ret = None;
                 for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
                     if let Some(id) = id
                         && let hir::Node::Block(blk) = self.tcx.hir().get(id)
-                        && self.consider_returning_binding(blk, ty, err)
+                        && let Some(diag) = self.consider_returning_binding_diag(blk, ty)
                     {
+                        ret = Some(diag);
                         break;
                     }
                 }
+                ret
             }
         }
     }
@@ -198,7 +193,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             return;
         }
 
-        match (
+        let subdiag = match (
             self.get_impl_future_output_ty(exp_found.expected),
             self.get_impl_future_output_ty(exp_found.found),
         ) {
@@ -207,65 +202,56 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             {
                 ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
                     let then_span = self.find_block_span_from_hir_id(*then_id);
-                    diag.multipart_suggestion(
-                        "consider `await`ing on both `Future`s",
-                        vec![
-                            (then_span.shrink_to_hi(), ".await".to_string()),
-                            (exp_span.shrink_to_hi(), ".await".to_string()),
-                        ],
-                        Applicability::MaybeIncorrect,
-                    );
+                    Some(ConsiderAddingAwait::BothFuturesSugg {
+                        first: then_span.shrink_to_hi(),
+                        second: exp_span.shrink_to_hi(),
+                    })
                 }
                 ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
                     prior_arms,
                     ..
                 }) => {
                     if let [.., arm_span] = &prior_arms[..] {
-                        diag.multipart_suggestion(
-                            "consider `await`ing on both `Future`s",
-                            vec![
-                                (arm_span.shrink_to_hi(), ".await".to_string()),
-                                (exp_span.shrink_to_hi(), ".await".to_string()),
-                            ],
-                            Applicability::MaybeIncorrect,
-                        );
+                        Some(ConsiderAddingAwait::BothFuturesSugg {
+                            first: arm_span.shrink_to_hi(),
+                            second: exp_span.shrink_to_hi(),
+                        })
                     } else {
-                        diag.help("consider `await`ing on both `Future`s");
+                        Some(ConsiderAddingAwait::BothFuturesHelp)
                     }
                 }
-                _ => {
-                    diag.help("consider `await`ing on both `Future`s");
-                }
+                _ => Some(ConsiderAddingAwait::BothFuturesHelp),
             },
             (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
-                self.suggest_await_on_future(diag, exp_span);
-                diag.span_note(exp_span, "calling an async function returns a future");
+                // FIXME: Seems like we can't have a suggestion and a note with different spans in a single subdiagnostic
+                diag.subdiagnostic(ConsiderAddingAwait::FutureSugg {
+                    span: exp_span.shrink_to_hi(),
+                });
+                Some(ConsiderAddingAwait::FutureSuggNote { span: exp_span })
             }
             (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
             {
                 ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
-                    self.suggest_await_on_future(diag, then_span.shrink_to_hi());
+                    Some(ConsiderAddingAwait::FutureSugg { span: then_span.shrink_to_hi() })
                 }
                 ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
                     let then_span = self.find_block_span_from_hir_id(*then_id);
-                    self.suggest_await_on_future(diag, then_span.shrink_to_hi());
+                    Some(ConsiderAddingAwait::FutureSugg { span: then_span.shrink_to_hi() })
                 }
                 ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
                     ref prior_arms,
                     ..
-                }) => {
-                    diag.multipart_suggestion_verbose(
-                        "consider `await`ing on the `Future`",
-                        prior_arms
-                            .iter()
-                            .map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
-                            .collect(),
-                        Applicability::MaybeIncorrect,
-                    );
-                }
-                _ => {}
+                }) => Some({
+                    ConsiderAddingAwait::FutureSuggMultiple {
+                        spans: prior_arms.iter().map(|arm| arm.shrink_to_hi()).collect(),
+                    }
+                }),
+                _ => None,
             },
-            _ => {}
+            _ => None,
+        };
+        if let Some(subdiag) = subdiag {
+            diag.subdiagnostic(subdiag);
         }
     }
 
@@ -655,16 +641,15 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
 
     /// Suggest returning a local binding with a compatible type if the block
     /// has no return expression.
-    pub fn consider_returning_binding(
+    pub fn consider_returning_binding_diag(
         &self,
         blk: &'tcx hir::Block<'tcx>,
         expected_ty: Ty<'tcx>,
-        err: &mut Diagnostic,
-    ) -> bool {
+    ) -> Option<SuggestRemoveSemiOrReturnBinding> {
         let blk = blk.innermost_block();
         // Do not suggest if we have a tail expr.
         if blk.expr.is_some() {
-            return false;
+            return None;
         }
         let mut shadowed = FxIndexSet::default();
         let mut candidate_idents = vec![];
@@ -733,7 +718,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         match &candidate_idents[..] {
             [(ident, _ty)] => {
                 let sm = self.tcx.sess.source_map();
-                if let Some(stmt) = blk.stmts.last() {
+                let (span, sugg) = if let Some(stmt) = blk.stmts.last() {
                     let stmt_span = sm.stmt_span(stmt.span, blk.span);
                     let sugg = if sm.is_multiline(blk.span)
                         && let Some(spacing) = sm.indentation_before(stmt_span)
@@ -742,12 +727,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     } else {
                         format!(" {ident}")
                     };
-                    err.span_suggestion_verbose(
-                        stmt_span.shrink_to_hi(),
-                        format!("consider returning the local binding `{ident}`"),
-                        sugg,
-                        Applicability::MaybeIncorrect,
-                    );
+                    (stmt_span.shrink_to_hi(), sugg)
                 } else {
                     let sugg = if sm.is_multiline(blk.span)
                         && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
@@ -757,21 +737,34 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                         format!(" {ident} ")
                     };
                     let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
-                    err.span_suggestion_verbose(
+                    (
                         sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
-                        format!("consider returning the local binding `{ident}`"),
                         sugg,
-                        Applicability::MaybeIncorrect,
-                    );
-                }
-                true
+                    )
+                };
+                Some(SuggestRemoveSemiOrReturnBinding::Add { sp: span, code: sugg, ident: *ident })
             }
             values if (1..3).contains(&values.len()) => {
                 let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
-                err.span_note(spans, "consider returning one of these bindings");
+                Some(SuggestRemoveSemiOrReturnBinding::AddOne { spans: spans.into() })
+            }
+            _ => None,
+        }
+    }
+
+    pub fn consider_returning_binding(
+        &self,
+        blk: &'tcx hir::Block<'tcx>,
+        expected_ty: Ty<'tcx>,
+        err: &mut Diagnostic,
+    ) -> bool {
+        let diag = self.consider_returning_binding_diag(blk, expected_ty);
+        match diag {
+            Some(diag) => {
+                err.subdiagnostic(diag);
                 true
             }
-            _ => false,
+            None => false,
         }
     }
 }