about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs2
-rw-r--r--compiler/rustc_infer/messages.ftl44
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs389
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs448
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/suggest.rs208
-rw-r--r--compiler/rustc_middle/src/ty/sty.rs32
6 files changed, 767 insertions, 356 deletions
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
index b02eae19fce..c97fcc92856 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
@@ -768,7 +768,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             let (provided_ty, provided_span) = provided_arg_tys[*provided_idx];
             let trace =
                 mk_trace(provided_span, formal_and_expected_inputs[*expected_idx], provided_ty);
-            if !matches!(trace.cause.as_failure_code(*e), FailureCode::Error0308(_)) {
+            if !matches!(trace.cause.as_failure_code(*e), FailureCode::Error0308) {
                 self.err_ctxt().report_and_explain_type_error(trace, *e).emit();
                 return true;
             }
diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl
index 1e43644be89..4d4a7880b00 100644
--- a/compiler/rustc_infer/messages.ftl
+++ b/compiler/rustc_infer/messages.ftl
@@ -348,3 +348,47 @@ infer_prlf_known_limitation = this is a known limitation that will be removed in
 
 infer_opaque_captures_lifetime = hidden type for `{$opaque_ty}` captures lifetime that does not appear in bounds
     .label = opaque type defined here
+
+infer_fps_use_ref = consider using a reference
+infer_fps_remove_ref = consider removing the reference
+infer_fps_cast = consider casting to a fn pointer
+infer_fps_items_are_distinct = fn items are distinct from fn pointers
+infer_fps_cast_both = consider casting both fn items to fn pointers using `as {$expected_sig}`
+
+infer_fn_uniq_types = different fn items have unique types, even if their signatures are the same
+infer_fn_consider_casting = consider casting the fn item to a fn pointer: `{$casting}`
+
+infer_sarwa_option = you can convert from `&Option<T>` to `Option<&T>` using `.as_ref()`
+infer_sarwa_result = you can convert from `&Result<T, E>` to `Result<&T, &E>` using `.as_ref()`
+
+infer_suggest_accessing_field = you might have meant to use field `{$name}` whose type is `{$ty}`
+
+infer_sbfrit_change_return_type = you could change the return type to be a boxed trait object
+infer_sbfrit_box_return_expr = if you change the return type to expect trait objects, box the returned expressions
+
+infer_stp_wrap_one = try wrapping the pattern in `{$variant}`
+infer_stp_wrap_many = try wrapping the pattern in a variant of `{$path}`
+
+infer_tuple_trailing_comma = use a trailing comma to create a tuple with one element
+
+infer_oc_method_compat = method not compatible with trait
+infer_oc_type_compat = type not compatible with trait
+infer_oc_const_compat = const not compatible with trait
+infer_oc_try_compat = `?` operator has incompatible types
+infer_oc_match_compat = `match` arms have incompatible types
+infer_oc_if_else_different = `if` and `else` have incompatible types
+infer_oc_no_else = `if` may be missing an `else` clause
+infer_oc_no_diverge = `else` clause of `let...else` does not diverge
+infer_oc_fn_main_correct_type = `main` function has wrong type
+infer_oc_fn_start_correct_type = `#[start]` function has wrong type
+infer_oc_intristic_correct_type = intrinsic has wrong type
+infer_oc_method_correct_type = mismatched `self` parameter type
+infer_oc_closure_selfref = closure/generator type that references itself
+infer_oc_cant_coerce = cannot coerce intrinsics to function pointers
+infer_oc_generic = mismatched types
+
+infer_meant_byte_literal = if you meant to write a byte literal, prefix with `b`
+infer_meant_char_literal = if you meant to write a `char` literal, use single quotes
+infer_meant_str_literal = if you meant to write a `str` literal, use double quotes
+infer_consider_specifying_length = consider specifying the actual array length
+infer_try_cannot_convert = `?` operator cannot convert from `{$found}` to `{$expected}`
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index 6bbd3fd3e6e..b129621130d 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -184,18 +184,6 @@ pub enum SourceKindMultiSuggestion<'a> {
     },
 }
 
-#[derive(Subdiagnostic)]
-#[suggestion(
-    infer_suggest_add_let_for_letchains,
-    style = "verbose",
-    applicability = "machine-applicable",
-    code = "let "
-)]
-pub(crate) struct SuggAddLetForLetChains {
-    #[primary_span]
-    pub span: Span,
-}
-
 impl<'a> SourceKindMultiSuggestion<'a> {
     pub fn new_fully_qualified(
         span: Span,
@@ -1157,3 +1145,380 @@ pub struct OpaqueCapturesLifetime<'tcx> {
     pub opaque_ty_span: Span,
     pub opaque_ty: Ty<'tcx>,
 }
+
+#[derive(Subdiagnostic)]
+pub enum FunctionPointerSuggestion<'a> {
+    #[suggestion(
+        infer_fps_use_ref,
+        code = "&{fn_name}",
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    UseRef {
+        #[primary_span]
+        span: Span,
+        #[skip_arg]
+        fn_name: String,
+    },
+    #[suggestion(
+        infer_fps_remove_ref,
+        code = "{fn_name}",
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    RemoveRef {
+        #[primary_span]
+        span: Span,
+        #[skip_arg]
+        fn_name: String,
+    },
+    #[suggestion(
+        infer_fps_cast,
+        code = "&({fn_name} as {sig})",
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    CastRef {
+        #[primary_span]
+        span: Span,
+        #[skip_arg]
+        fn_name: String,
+        #[skip_arg]
+        sig: Binder<'a, FnSig<'a>>,
+    },
+    #[suggestion(
+        infer_fps_cast,
+        code = "{fn_name} as {sig}",
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    Cast {
+        #[primary_span]
+        span: Span,
+        #[skip_arg]
+        fn_name: String,
+        #[skip_arg]
+        sig: Binder<'a, FnSig<'a>>,
+    },
+    #[suggestion(
+        infer_fps_cast_both,
+        code = "{fn_name} as {found_sig}",
+        style = "hidden",
+        applicability = "maybe-incorrect"
+    )]
+    CastBoth {
+        #[primary_span]
+        span: Span,
+        #[skip_arg]
+        fn_name: String,
+        #[skip_arg]
+        found_sig: Binder<'a, FnSig<'a>>,
+        expected_sig: Binder<'a, FnSig<'a>>,
+    },
+    #[suggestion(
+        infer_fps_cast_both,
+        code = "&({fn_name} as {found_sig})",
+        style = "hidden",
+        applicability = "maybe-incorrect"
+    )]
+    CastBothRef {
+        #[primary_span]
+        span: Span,
+        #[skip_arg]
+        fn_name: String,
+        #[skip_arg]
+        found_sig: Binder<'a, FnSig<'a>>,
+        expected_sig: Binder<'a, FnSig<'a>>,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[note(infer_fps_items_are_distinct)]
+pub struct FnItemsAreDistinct;
+
+#[derive(Subdiagnostic)]
+#[note(infer_fn_uniq_types)]
+pub struct FnUniqTypes;
+
+#[derive(Subdiagnostic)]
+#[help(infer_fn_consider_casting)]
+pub struct FnConsiderCasting {
+    pub casting: String,
+}
+
+#[derive(Subdiagnostic)]
+pub enum SuggestAsRefWhereAppropriate<'a> {
+    #[suggestion(
+        infer_sarwa_option,
+        code = "{snippet}.as_ref()",
+        applicability = "machine-applicable"
+    )]
+    Option {
+        #[primary_span]
+        span: Span,
+        snippet: &'a str,
+    },
+    #[suggestion(
+        infer_sarwa_result,
+        code = "{snippet}.as_ref()",
+        applicability = "machine-applicable"
+    )]
+    Result {
+        #[primary_span]
+        span: Span,
+        snippet: &'a str,
+    },
+}
+
+#[derive(Subdiagnostic)]
+pub enum SuggestAccessingField<'a> {
+    #[suggestion(
+        infer_suggest_accessing_field,
+        code = "{snippet}.{name}",
+        applicability = "maybe-incorrect"
+    )]
+    Safe {
+        #[primary_span]
+        span: Span,
+        snippet: String,
+        name: Symbol,
+        ty: Ty<'a>,
+    },
+    #[suggestion(
+        infer_suggest_accessing_field,
+        code = "unsafe {{ {snippet}.{name} }}",
+        applicability = "maybe-incorrect"
+    )]
+    Unsafe {
+        #[primary_span]
+        span: Span,
+        snippet: String,
+        name: Symbol,
+        ty: Ty<'a>,
+    },
+}
+
+#[derive(Subdiagnostic)]
+pub enum SuggestBoxingForReturnImplTrait {
+    #[multipart_suggestion(infer_sbfrit_change_return_type, applicability = "maybe-incorrect")]
+    ChangeReturnType {
+        #[suggestion_part(code = "Box<dyn")]
+        start_sp: Span,
+        #[suggestion_part(code = ">")]
+        end_sp: Span,
+    },
+    #[multipart_suggestion(infer_sbfrit_box_return_expr, applicability = "maybe-incorrect")]
+    BoxReturnExpr {
+        #[suggestion_part(code = "Box::new(")]
+        starts: Vec<Span>,
+        #[suggestion_part(code = ")")]
+        ends: Vec<Span>,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(infer_stp_wrap_one, applicability = "maybe-incorrect")]
+pub struct SuggestTuplePatternOne {
+    pub variant: String,
+    #[suggestion_part(code = "{variant}(")]
+    pub span_low: Span,
+    #[suggestion_part(code = ")")]
+    pub span_high: Span,
+}
+
+pub struct SuggestTuplePatternMany {
+    pub path: String,
+    pub cause_span: Span,
+    pub compatible_variants: Vec<String>,
+}
+
+impl AddToDiagnostic for SuggestTuplePatternMany {
+    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, f: F)
+    where
+        F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
+    {
+        diag.set_arg("path", self.path);
+        let message = f(diag, crate::fluent_generated::infer_stp_wrap_many.into());
+        diag.multipart_suggestions(
+            message,
+            self.compatible_variants.into_iter().map(|variant| {
+                vec![
+                    (self.cause_span.shrink_to_lo(), format!("{}(", variant)),
+                    (self.cause_span.shrink_to_hi(), ")".to_string()),
+                ]
+            }),
+            rustc_errors::Applicability::MaybeIncorrect,
+        );
+    }
+}
+
+#[derive(Subdiagnostic)]
+pub enum TypeErrorAdditionalDiags {
+    #[suggestion(
+        infer_meant_byte_literal,
+        code = "b'{code}'",
+        applicability = "machine-applicable"
+    )]
+    MeantByteLiteral {
+        #[primary_span]
+        span: Span,
+        code: String,
+    },
+    #[suggestion(
+        infer_meant_char_literal,
+        code = "'{code}'",
+        applicability = "machine-applicable"
+    )]
+    MeantCharLiteral {
+        #[primary_span]
+        span: Span,
+        code: String,
+    },
+    #[suggestion(
+        infer_meant_str_literal,
+        code = "\"{code}\"",
+        applicability = "machine-applicable"
+    )]
+    MeantStrLiteral {
+        #[primary_span]
+        span: Span,
+        code: String,
+    },
+    #[suggestion(
+        infer_consider_specifying_length,
+        code = "{length}",
+        applicability = "maybe-incorrect"
+    )]
+    ConsiderSpecifyingLength {
+        #[primary_span]
+        span: Span,
+        length: u64,
+    },
+    #[note(infer_try_cannot_convert)]
+    TryCannotConvert { found: String, expected: String },
+    #[suggestion(infer_tuple_trailing_comma, code = ",", applicability = "machine-applicable")]
+    TupleOnlyComma {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(infer_tuple_trailing_comma, applicability = "machine-applicable")]
+    TupleAlsoParentheses {
+        #[suggestion_part(code = "(")]
+        span_low: Span,
+        #[suggestion_part(code = ",)")]
+        span_high: Span,
+    },
+    #[suggestion(
+        infer_suggest_add_let_for_letchains,
+        style = "verbose",
+        applicability = "machine-applicable",
+        code = "let "
+    )]
+    AddLetForLetChains {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(Diagnostic)]
+pub enum ObligationCauseFailureCode {
+    #[diag(infer_oc_method_compat, code = "E0308")]
+    MethodCompat {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_type_compat, code = "E0308")]
+    TypeCompat {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_const_compat, code = "E0308")]
+    ConstCompat {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_try_compat, code = "E0308")]
+    TryCompat {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_match_compat, code = "E0308")]
+    MatchCompat {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_if_else_different, code = "E0308")]
+    IfElseDifferent {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_no_else, code = "E0317")]
+    NoElse {
+        #[primary_span]
+        span: Span,
+    },
+    #[diag(infer_oc_no_diverge, code = "E0308")]
+    NoDiverge {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_fn_main_correct_type, code = "E0580")]
+    FnMainCorrectType {
+        #[primary_span]
+        span: Span,
+    },
+    #[diag(infer_oc_fn_start_correct_type, code = "E0308")]
+    FnStartCorrectType {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_intristic_correct_type, code = "E0308")]
+    IntristicCorrectType {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_method_correct_type, code = "E0308")]
+    MethodCorrectType {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_closure_selfref, code = "E0644")]
+    ClosureSelfref {
+        #[primary_span]
+        span: Span,
+    },
+    #[diag(infer_oc_cant_coerce, code = "E0308")]
+    CantCoerce {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+    #[diag(infer_oc_generic, code = "E0308")]
+    Generic {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    },
+}
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 86fca9797d0..7901bc94021 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -49,11 +49,10 @@ use super::lexical_region_resolve::RegionResolutionError;
 use super::region_constraints::GenericKind;
 use super::{InferCtxt, RegionVariableOrigin, SubregionOrigin, TypeTrace, ValuePairs};
 
-use crate::errors;
+use crate::errors::{self, ObligationCauseFailureCode, TypeErrorAdditionalDiags};
 use crate::infer;
 use crate::infer::error_reporting::nice_region_error::find_anon_type::find_anon_type;
 use crate::infer::ExpectedFound;
-use crate::traits::error_reporting::report_object_safety_error;
 use crate::traits::{
     IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
     PredicateObligation,
@@ -90,6 +89,28 @@ pub use need_type_info::TypeAnnotationNeeded;
 
 pub mod nice_region_error;
 
+/// Makes a valid string literal from a string by escaping special characters (" and \),
+/// unless they are already escaped.
+fn escape_literal(s: &str) -> String {
+    let mut escaped = String::with_capacity(s.len());
+    let mut chrs = s.chars().peekable();
+    while let Some(first) = chrs.next() {
+        match (first, chrs.peek()) {
+            ('\\', Some(&delim @ '"') | Some(&delim @ '\'')) => {
+                escaped.push('\\');
+                escaped.push(delim);
+                chrs.next();
+            }
+            ('"' | '\'', _) => {
+                escaped.push('\\');
+                escaped.push(first)
+            }
+            (c, _) => escaped.push(c),
+        };
+    }
+    escaped
+}
+
 /// A helper for building type related errors. The `typeck_results`
 /// field is only populated during an in-progress typeck.
 /// Get an instance by calling `InferCtxt::err` or `FnCtxt::infer_err`.
@@ -1899,233 +1920,182 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         debug!(?diag);
     }
 
-    pub fn report_and_explain_type_error(
+    pub fn type_error_additional_suggestions(
         &self,
-        trace: TypeTrace<'tcx>,
+        trace: &TypeTrace<'tcx>,
         terr: TypeError<'tcx>,
-    ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+    ) -> Vec<TypeErrorAdditionalDiags> {
         use crate::traits::ObligationCauseCode::MatchExpressionArm;
-
-        debug!("report_and_explain_type_error(trace={:?}, terr={:?})", trace, terr);
-
+        let mut suggestions = Vec::new();
         let span = trace.cause.span();
-        let failure_code = trace.cause.as_failure_code(terr);
-        let mut diag = match failure_code {
-            FailureCode::Error0038(did) => {
-                let violations = self.tcx.object_safety_violations(did);
-                report_object_safety_error(self.tcx, span, did, violations)
-            }
-            FailureCode::Error0317(failure_str) => {
-                struct_span_err!(self.tcx.sess, span, E0317, "{}", failure_str)
-            }
-            FailureCode::Error0580(failure_str) => {
-                struct_span_err!(self.tcx.sess, span, E0580, "{}", failure_str)
-            }
-            FailureCode::Error0308(failure_str) => {
-                fn escape_literal(s: &str) -> String {
-                    let mut escaped = String::with_capacity(s.len());
-                    let mut chrs = s.chars().peekable();
-                    while let Some(first) = chrs.next() {
-                        match (first, chrs.peek()) {
-                            ('\\', Some(&delim @ '"') | Some(&delim @ '\'')) => {
-                                escaped.push('\\');
-                                escaped.push(delim);
-                                chrs.next();
-                            }
-                            ('"' | '\'', _) => {
-                                escaped.push('\\');
-                                escaped.push(first)
-                            }
-                            (c, _) => escaped.push(c),
-                        };
+        let values = self.resolve_vars_if_possible(trace.values);
+        if let Some((expected, found)) = values.ty() {
+            match (expected.kind(), found.kind()) {
+                (ty::Tuple(_), ty::Tuple(_)) => {}
+                // If a tuple of length one was expected and the found expression has
+                // parentheses around it, perhaps the user meant to write `(expr,)` to
+                // build a tuple (issue #86100)
+                (ty::Tuple(fields), _) => {
+                    suggestions.extend(self.suggest_wrap_to_build_a_tuple( span, found, fields))
+                }
+                // If a byte was expected and the found expression is a char literal
+                // containing a single ASCII character, perhaps the user meant to write `b'c'` to
+                // specify a byte literal
+                (ty::Uint(ty::UintTy::U8), ty::Char) => {
+                    if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
+                        && let Some(code) = code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
+                        && !code.starts_with("\\u") // forbid all Unicode escapes
+                        && code.chars().next().map_or(false, |c| c.is_ascii()) // forbids literal Unicode characters beyond ASCII
+                    {
+                        suggestions.push(TypeErrorAdditionalDiags::MeantByteLiteral { span, code: escape_literal(code) })
                     }
-                    escaped
                 }
-                let mut err = struct_span_err!(self.tcx.sess, span, E0308, "{}", failure_str);
-                let values = self.resolve_vars_if_possible(trace.values);
-                if let Some((expected, found)) = values.ty() {
-                    match (expected.kind(), found.kind()) {
-                        (ty::Tuple(_), ty::Tuple(_)) => {}
-                        // If a tuple of length one was expected and the found expression has
-                        // parentheses around it, perhaps the user meant to write `(expr,)` to
-                        // build a tuple (issue #86100)
-                        (ty::Tuple(fields), _) => {
-                            self.emit_tuple_wrap_err(&mut err, span, found, fields)
-                        }
-                        // If a byte was expected and the found expression is a char literal
-                        // containing a single ASCII character, perhaps the user meant to write `b'c'` to
-                        // specify a byte literal
-                        (ty::Uint(ty::UintTy::U8), ty::Char) => {
-                            if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
-                                && let Some(code) = code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
-                                && !code.starts_with("\\u") // forbid all Unicode escapes
-                                && code.chars().next().map_or(false, |c| c.is_ascii()) // forbids literal Unicode characters beyond ASCII
-                            {
-                                err.span_suggestion(
-                                    span,
-                                    "if you meant to write a byte literal, prefix with `b`",
-                                    format!("b'{}'", escape_literal(code)),
-                                    Applicability::MachineApplicable,
-                                );
-                            }
-                        }
-                        // If a character was expected and the found expression is a string literal
-                        // containing a single character, perhaps the user meant to write `'c'` to
-                        // specify a character literal (issue #92479)
-                        (ty::Char, ty::Ref(_, r, _)) if r.is_str() => {
-                            if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
-                                && let Some(code) = code.strip_prefix('"').and_then(|s| s.strip_suffix('"'))
-                                && code.chars().count() == 1
-                            {
-                                err.span_suggestion(
-                                    span,
-                                    "if you meant to write a `char` literal, use single quotes",
-                                    format!("'{}'", escape_literal(code)),
-                                    Applicability::MachineApplicable,
-                                );
-                            }
-                        }
-                        // If a string was expected and the found expression is a character literal,
-                        // perhaps the user meant to write `"s"` to specify a string literal.
-                        (ty::Ref(_, r, _), ty::Char) if r.is_str() => {
-                            if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) {
-                                if let Some(code) =
-                                    code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
-                                {
-                                    err.span_suggestion(
-                                        span,
-                                        "if you meant to write a `str` literal, use double quotes",
-                                        format!("\"{}\"", escape_literal(code)),
-                                        Applicability::MachineApplicable,
-                                    );
-                                }
-                            }
-                        }
-                        // For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`,
-                        // we try to suggest to add the missing `let` for `if let Some(..) = expr`
-                        (ty::Bool, ty::Tuple(list)) => if list.len() == 0 {
-                            self.suggest_let_for_letchains(&mut err, &trace.cause, span);
-                        }
-                        (ty::Array(_, _), ty::Array(_, _)) => 'block: {
-                            let hir = self.tcx.hir();
-                            let TypeError::FixedArraySize(sz) = terr else {
-                                break 'block;
-                            };
-                            let tykind = match hir.find_by_def_id(trace.cause.body_id) {
-                                Some(hir::Node::Item(hir::Item {
-                                    kind: hir::ItemKind::Fn(_, _, body_id),
-                                    ..
-                                })) => {
-                                    let body = hir.body(*body_id);
-                                    struct LetVisitor<'v> {
-                                        span: Span,
-                                        result: Option<&'v hir::Ty<'v>>,
-                                    }
-                                    impl<'v> Visitor<'v> for LetVisitor<'v> {
-                                        fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
-                                            if self.result.is_some() {
-                                                return;
-                                            }
-                                            // Find a local statement where the initializer has
-                                            // the same span as the error and the type is specified.
-                                            if let hir::Stmt {
-                                                kind: hir::StmtKind::Local(hir::Local {
-                                                    init: Some(hir::Expr {
-                                                        span: init_span,
-                                                        ..
-                                                    }),
-                                                    ty: Some(array_ty),
-                                                    ..
-                                                }),
-                                                ..
-                                            } = s
-                                            && init_span == &self.span {
-                                                self.result = Some(*array_ty);
-                                            }
-                                        }
-                                    }
-                                    let mut visitor = LetVisitor {span, result: None};
-                                    visitor.visit_body(body);
-                                    visitor.result.map(|r| &r.peel_refs().kind)
-                                }
-                                Some(hir::Node::Item(hir::Item {
-                                    kind: hir::ItemKind::Const(ty, _),
-                                    ..
-                                })) => {
-                                    Some(&ty.peel_refs().kind)
-                                }
-                                _ => None
-                            };
-
-                            if let Some(tykind) = tykind
-                                && let hir::TyKind::Array(_, length) = tykind
-                                && let hir::ArrayLen::Body(hir::AnonConst { hir_id, .. }) = length
-                                && let Some(span) = self.tcx.hir().opt_span(*hir_id)
-                            {
-                                err.span_suggestion(
-                                    span,
-                                    "consider specifying the actual array length",
-                                    sz.found,
-                                    Applicability::MaybeIncorrect,
-                                );
-                            }
+                // If a character was expected and the found expression is a string literal
+                // containing a single character, perhaps the user meant to write `'c'` to
+                // specify a character literal (issue #92479)
+                (ty::Char, ty::Ref(_, r, _)) if r.is_str() => {
+                    if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
+                        && let Some(code) = code.strip_prefix('"').and_then(|s| s.strip_suffix('"'))
+                        && code.chars().count() == 1
+                    {
+                        suggestions.push(TypeErrorAdditionalDiags::MeantCharLiteral { span, code: escape_literal(code) })
+                    }
+                }
+                // If a string was expected and the found expression is a character literal,
+                // perhaps the user meant to write `"s"` to specify a string literal.
+                (ty::Ref(_, r, _), ty::Char) if r.is_str() => {
+                    if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) {
+                        if let Some(code) =
+                            code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
+                        {
+                            suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral { span, code: escape_literal(code) })
                         }
-                        _ => {}
                     }
                 }
-                let code = trace.cause.code();
-                if let &MatchExpressionArm(box MatchExpressionArmCause { source, .. }) = code
+                // For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`,
+                // we try to suggest to add the missing `let` for `if let Some(..) = expr`
+                (ty::Bool, ty::Tuple(list)) => if list.len() == 0 {
+                    suggestions.extend(self.suggest_let_for_letchains(&trace.cause, span));
+                }
+                (ty::Array(_, _), ty::Array(_, _)) => suggestions.extend(self.suggest_specify_actual_length(terr, trace, span)),
+                _ => {}
+            }
+        }
+        let code = trace.cause.code();
+        if let &MatchExpressionArm(box MatchExpressionArmCause { source, .. }) = code
                     && let hir::MatchSource::TryDesugar = source
                     && let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values)
                 {
-                    err.note(&format!(
-                        "`?` operator cannot convert from `{}` to `{}`",
-                        found_ty.content(),
-                        expected_ty.content(),
-                    ));
+                    suggestions.push(TypeErrorAdditionalDiags::TryCannotConvert { found: found_ty.content(), expected: expected_ty.content() });
                 }
-                err
+        suggestions
+    }
+
+    fn suggest_specify_actual_length(
+        &self,
+        terr: TypeError<'_>,
+        trace: &TypeTrace<'_>,
+        span: Span,
+    ) -> Option<TypeErrorAdditionalDiags> {
+        let hir = self.tcx.hir();
+        let TypeError::FixedArraySize(sz) = terr else {
+            return None;
+        };
+        let tykind = match hir.find_by_def_id(trace.cause.body_id) {
+            Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body_id), .. })) => {
+                let body = hir.body(*body_id);
+                struct LetVisitor<'v> {
+                    span: Span,
+                    result: Option<&'v hir::Ty<'v>>,
+                }
+                impl<'v> Visitor<'v> for LetVisitor<'v> {
+                    fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
+                        if self.result.is_some() {
+                            return;
+                        }
+                        // Find a local statement where the initializer has
+                        // the same span as the error and the type is specified.
+                        if let hir::Stmt {
+                            kind: hir::StmtKind::Local(hir::Local {
+                                init: Some(hir::Expr {
+                                    span: init_span,
+                                    ..
+                                }),
+                                ty: Some(array_ty),
+                                ..
+                            }),
+                            ..
+                        } = s
+                        && init_span == &self.span {
+                            self.result = Some(*array_ty);
+                        }
+                    }
+                }
+                let mut visitor = LetVisitor { span, result: None };
+                visitor.visit_body(body);
+                visitor.result.map(|r| &r.peel_refs().kind)
             }
-            FailureCode::Error0644(failure_str) => {
-                struct_span_err!(self.tcx.sess, span, E0644, "{}", failure_str)
+            Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _), .. })) => {
+                Some(&ty.peel_refs().kind)
             }
+            _ => None,
         };
+        if let Some(tykind) = tykind
+            && let hir::TyKind::Array(_, length) = tykind
+            && let hir::ArrayLen::Body(hir::AnonConst { hir_id, .. }) = length
+            && let Some(span) = self.tcx.hir().opt_span(*hir_id)
+        {
+            Some(TypeErrorAdditionalDiags::ConsiderSpecifyingLength { span, length: sz.found })
+        } else {
+            None
+        }
+    }
+
+    pub fn report_and_explain_type_error(
+        &self,
+        trace: TypeTrace<'tcx>,
+        terr: TypeError<'tcx>,
+    ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+        debug!("report_and_explain_type_error(trace={:?}, terr={:?})", trace, terr);
+
+        let span = trace.cause.span();
+        let failure_code = trace.cause.as_failure_code_diag(
+            terr,
+            span,
+            self.type_error_additional_suggestions(&trace, terr),
+        );
+        let mut diag = self.tcx.sess.create_err(failure_code);
         self.note_type_err(&mut diag, &trace.cause, None, Some(trace.values), terr, false, false);
         diag
     }
 
-    fn emit_tuple_wrap_err(
+    fn suggest_wrap_to_build_a_tuple(
         &self,
-        err: &mut Diagnostic,
         span: Span,
         found: Ty<'tcx>,
         expected_fields: &List<Ty<'tcx>>,
-    ) {
-        let [expected_tup_elem] = expected_fields[..] else { return };
+    ) -> Option<TypeErrorAdditionalDiags> {
+        let [expected_tup_elem] = expected_fields[..] else { return None};
 
         if !self.same_type_modulo_infer(expected_tup_elem, found) {
-            return;
+            return None;
         }
 
         let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
-            else { return };
+            else { return None };
 
-        let msg = "use a trailing comma to create a tuple with one element";
-        if code.starts_with('(') && code.ends_with(')') {
+        let sugg = if code.starts_with('(') && code.ends_with(')') {
             let before_close = span.hi() - BytePos::from_u32(1);
-            err.span_suggestion(
-                span.with_hi(before_close).shrink_to_hi(),
-                msg,
-                ",",
-                Applicability::MachineApplicable,
-            );
+            TypeErrorAdditionalDiags::TupleOnlyComma {
+                span: span.with_hi(before_close).shrink_to_hi(),
+            }
         } else {
-            err.multipart_suggestion(
-                msg,
-                vec![(span.shrink_to_lo(), "(".into()), (span.shrink_to_hi(), ",)".into())],
-                Applicability::MachineApplicable,
-            );
-        }
+            TypeErrorAdditionalDiags::TupleAlsoParentheses {
+                span_low: span.shrink_to_lo(),
+                span_high: span.shrink_to_hi(),
+            }
+        };
+        Some(sugg)
     }
 
     fn values_str(
@@ -2828,15 +2798,21 @@ impl<'tcx> InferCtxt<'tcx> {
 }
 
 pub enum FailureCode {
-    Error0038(DefId),
-    Error0317(&'static str),
-    Error0580(&'static str),
-    Error0308(&'static str),
-    Error0644(&'static str),
+    Error0317,
+    Error0580,
+    Error0308,
+    Error0644,
 }
 
 pub trait ObligationCauseExt<'tcx> {
     fn as_failure_code(&self, terr: TypeError<'tcx>) -> FailureCode;
+
+    fn as_failure_code_diag(
+        &self,
+        terr: TypeError<'tcx>,
+        span: Span,
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    ) -> ObligationCauseFailureCode;
     fn as_requirement_str(&self) -> &'static str;
 }
 
@@ -2845,40 +2821,68 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> {
         use self::FailureCode::*;
         use crate::traits::ObligationCauseCode::*;
         match self.code() {
+            IfExpressionWithNoElse => Error0317,
+            MainFunctionType => Error0580,
+            CompareImplItemObligation { .. }
+            | MatchExpressionArm(_)
+            | IfExpression { .. }
+            | LetElse
+            | StartFunctionType
+            | IntrinsicType
+            | MethodReceiver => Error0308,
+
+            // In the case where we have no more specific thing to
+            // say, also take a look at the error code, maybe we can
+            // tailor to that.
+            _ => match terr {
+                TypeError::CyclicTy(ty) if ty.is_closure() || ty.is_generator() => Error0644,
+                TypeError::IntrinsicCast => Error0308,
+                _ => Error0308,
+            },
+        }
+    }
+    fn as_failure_code_diag(
+        &self,
+        terr: TypeError<'tcx>,
+        span: Span,
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+    ) -> ObligationCauseFailureCode {
+        use crate::traits::ObligationCauseCode::*;
+        match self.code() {
             CompareImplItemObligation { kind: ty::AssocKind::Fn, .. } => {
-                Error0308("method not compatible with trait")
+                ObligationCauseFailureCode::MethodCompat { span, subdiags }
             }
             CompareImplItemObligation { kind: ty::AssocKind::Type, .. } => {
-                Error0308("type not compatible with trait")
+                ObligationCauseFailureCode::TypeCompat { span, subdiags }
             }
             CompareImplItemObligation { kind: ty::AssocKind::Const, .. } => {
-                Error0308("const not compatible with trait")
-            }
-            MatchExpressionArm(box MatchExpressionArmCause { source, .. }) => {
-                Error0308(match source {
-                    hir::MatchSource::TryDesugar => "`?` operator has incompatible types",
-                    _ => "`match` arms have incompatible types",
-                })
-            }
-            IfExpression { .. } => Error0308("`if` and `else` have incompatible types"),
-            IfExpressionWithNoElse => Error0317("`if` may be missing an `else` clause"),
-            LetElse => Error0308("`else` clause of `let...else` does not diverge"),
-            MainFunctionType => Error0580("`main` function has wrong type"),
-            StartFunctionType => Error0308("`#[start]` function has wrong type"),
-            IntrinsicType => Error0308("intrinsic has wrong type"),
-            MethodReceiver => Error0308("mismatched `self` parameter type"),
+                ObligationCauseFailureCode::ConstCompat { span, subdiags }
+            }
+            MatchExpressionArm(box MatchExpressionArmCause { source, .. }) => match source {
+                hir::MatchSource::TryDesugar => {
+                    ObligationCauseFailureCode::TryCompat { span, subdiags }
+                }
+                _ => ObligationCauseFailureCode::MatchCompat { span, subdiags },
+            },
+            IfExpression { .. } => ObligationCauseFailureCode::IfElseDifferent { span, subdiags },
+            IfExpressionWithNoElse => ObligationCauseFailureCode::NoElse { span },
+            LetElse => ObligationCauseFailureCode::NoDiverge { span, subdiags },
+            MainFunctionType => ObligationCauseFailureCode::FnMainCorrectType { span },
+            StartFunctionType => ObligationCauseFailureCode::FnStartCorrectType { span, subdiags },
+            IntrinsicType => ObligationCauseFailureCode::IntristicCorrectType { span, subdiags },
+            MethodReceiver => ObligationCauseFailureCode::MethodCorrectType { span, subdiags },
 
             // In the case where we have no more specific thing to
             // say, also take a look at the error code, maybe we can
             // tailor to that.
             _ => match terr {
                 TypeError::CyclicTy(ty) if ty.is_closure() || ty.is_generator() => {
-                    Error0644("closure/generator type that references itself")
+                    ObligationCauseFailureCode::ClosureSelfref { span }
                 }
                 TypeError::IntrinsicCast => {
-                    Error0308("cannot coerce intrinsics to function pointers")
+                    ObligationCauseFailureCode::CantCoerce { span, subdiags }
                 }
-                _ => Error0308("mismatched types"),
+                _ => ObligationCauseFailureCode::Generic { span, subdiags },
             },
         }
     }
diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
index 8ad143247e8..b5aeca12a1f 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
@@ -1,7 +1,7 @@
 use hir::def::CtorKind;
 use hir::intravisit::{walk_expr, walk_stmt, Visitor};
 use rustc_data_structures::fx::FxIndexSet;
-use rustc_errors::{Applicability, Diagnostic};
+use rustc_errors::Diagnostic;
 use rustc_hir as hir;
 use rustc_middle::traits::{
     IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
@@ -13,11 +13,20 @@ use rustc_span::{sym, BytePos, Span};
 use rustc_target::abi::FieldIdx;
 
 use crate::errors::{
-    ConsiderAddingAwait, SuggAddLetForLetChains, SuggestRemoveSemiOrReturnBinding,
+    ConsiderAddingAwait, FnConsiderCasting, FnItemsAreDistinct, FnUniqTypes,
+    FunctionPointerSuggestion, SuggestAccessingField, SuggestAsRefWhereAppropriate,
+    SuggestBoxingForReturnImplTrait, SuggestRemoveSemiOrReturnBinding, SuggestTuplePatternMany,
+    SuggestTuplePatternOne, TypeErrorAdditionalDiags,
 };
 
 use super::TypeErrCtxt;
 
+#[derive(Clone, Copy)]
+pub enum SuggestAsRefKind {
+    Option,
+    Result,
+}
+
 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
     pub(super) fn suggest_remove_semi_or_return_binding(
         &self,
@@ -72,25 +81,20 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         return_sp: Span,
         arm_spans: impl Iterator<Item = Span>,
     ) {
-        err.multipart_suggestion(
-            "you could change the return type to be a boxed trait object",
-            vec![
-                (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
-                (return_sp.shrink_to_hi(), ">".to_string()),
-            ],
-            Applicability::MaybeIncorrect,
-        );
-        let sugg = arm_spans
-            .flat_map(|sp| {
-                [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
-                    .into_iter()
-            })
-            .collect::<Vec<_>>();
-        err.multipart_suggestion(
-            "if you change the return type to expect trait objects, box the returned expressions",
-            sugg,
-            Applicability::MaybeIncorrect,
-        );
+        let sugg = SuggestBoxingForReturnImplTrait::ChangeReturnType {
+            start_sp: return_sp.with_hi(return_sp.lo() + BytePos(4)),
+            end_sp: return_sp.shrink_to_hi(),
+        };
+        err.subdiagnostic(sugg);
+
+        let mut starts = Vec::new();
+        let mut ends = Vec::new();
+        for span in arm_spans {
+            starts.push(span.shrink_to_lo());
+            ends.push(span.shrink_to_hi());
+        }
+        let sugg = SuggestBoxingForReturnImplTrait::BoxReturnExpr { starts, ends };
+        err.subdiagnostic(sugg);
     }
 
     pub(super) fn suggest_tuple_pattern(
@@ -130,30 +134,21 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 match &compatible_variants[..] {
                     [] => {}
                     [variant] => {
-                        diag.multipart_suggestion_verbose(
-                            &format!("try wrapping the pattern in `{}`", variant),
-                            vec![
-                                (cause.span.shrink_to_lo(), format!("{}(", variant)),
-                                (cause.span.shrink_to_hi(), ")".to_string()),
-                            ],
-                            Applicability::MaybeIncorrect,
-                        );
+                        let sugg = SuggestTuplePatternOne {
+                            variant: variant.to_owned(),
+                            span_low: cause.span.shrink_to_lo(),
+                            span_high: cause.span.shrink_to_hi(),
+                        };
+                        diag.subdiagnostic(sugg);
                     }
                     _ => {
                         // More than one matching variant.
-                        diag.multipart_suggestions(
-                            &format!(
-                                "try wrapping the pattern in a variant of `{}`",
-                                self.tcx.def_path_str(expected_adt.did())
-                            ),
-                            compatible_variants.into_iter().map(|variant| {
-                                vec![
-                                    (cause.span.shrink_to_lo(), format!("{}(", variant)),
-                                    (cause.span.shrink_to_hi(), ")".to_string()),
-                                ]
-                            }),
-                            Applicability::MaybeIncorrect,
-                        );
+                        let sugg = SuggestTuplePatternMany {
+                            path: self.tcx.def_path_str(expected_adt.did()),
+                            cause_span: cause.span,
+                            compatible_variants,
+                        };
+                        diag.subdiagnostic(sugg);
                     }
                 }
             }
@@ -256,15 +251,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         }
     }
 
-    pub fn suggest_await_on_future(&self, diag: &mut Diagnostic, sp: Span) {
-        diag.span_suggestion_verbose(
-            sp.shrink_to_hi(),
-            "consider `await`ing on the `Future`",
-            ".await",
-            Applicability::MaybeIncorrect,
-        );
-    }
-
     pub(super) fn suggest_accessing_field_where_appropriate(
         &self,
         cause: &ObligationCause<'tcx>,
@@ -291,21 +277,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
                     if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
                         let suggestion = if expected_def.is_struct() {
-                            format!("{}.{}", snippet, name)
+                            SuggestAccessingField::Safe { span, snippet, name, ty }
                         } else if expected_def.is_union() {
-                            format!("unsafe {{ {}.{} }}", snippet, name)
+                            SuggestAccessingField::Unsafe { span, snippet, name, ty }
                         } else {
                             return;
                         };
-                        diag.span_suggestion(
-                            span,
-                            &format!(
-                                "you might have meant to use field `{}` whose type is `{}`",
-                                name, ty
-                            ),
-                            suggestion,
-                            Applicability::MaybeIncorrect,
-                        );
+                        diag.subdiagnostic(suggestion);
                     }
                 }
             }
@@ -321,15 +299,15 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         diag: &mut Diagnostic,
     ) {
         if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
-            && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
+            && let Some(msg) = self.should_suggest_as_ref_kind(exp_found.expected, exp_found.found)
         {
-            diag.span_suggestion(
-                span,
-                msg,
-                // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
-                format!("{}.as_ref()", snippet.trim_start_matches('&')),
-                Applicability::MachineApplicable,
-            );
+            // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
+            let snippet = snippet.trim_start_matches('&');
+            let subdiag = match msg {
+                SuggestAsRefKind::Option => SuggestAsRefWhereAppropriate::Option { span, snippet },
+                SuggestAsRefKind::Result => SuggestAsRefWhereAppropriate::Result { span, snippet },
+            };
+            diag.subdiagnostic(subdiag);
         }
     }
 
@@ -362,31 +340,19 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     return;
                 }
 
-                let (msg, sug) = match (expected.is_ref(), found.is_ref()) {
-                    (true, false) => {
-                        let msg = "consider using a reference";
-                        let sug = format!("&{fn_name}");
-                        (msg, sug)
-                    }
-                    (false, true) => {
-                        let msg = "consider removing the reference";
-                        let sug = format!("{fn_name}");
-                        (msg, sug)
-                    }
+                let sugg = match (expected.is_ref(), found.is_ref()) {
+                    (true, false) => FunctionPointerSuggestion::UseRef { span, fn_name },
+                    (false, true) => FunctionPointerSuggestion::RemoveRef { span, fn_name },
                     (true, true) => {
-                        diag.note("fn items are distinct from fn pointers");
-                        let msg = "consider casting to a fn pointer";
-                        let sug = format!("&({fn_name} as {sig})");
-                        (msg, sug)
+                        diag.subdiagnostic(FnItemsAreDistinct);
+                        FunctionPointerSuggestion::CastRef { span, fn_name, sig: *sig }
                     }
                     (false, false) => {
-                        diag.note("fn items are distinct from fn pointers");
-                        let msg = "consider casting to a fn pointer";
-                        let sug = format!("{fn_name} as {sig}");
-                        (msg, sug)
+                        diag.subdiagnostic(FnItemsAreDistinct);
+                        FunctionPointerSuggestion::Cast { span, fn_name, sig: *sig }
                     }
                 };
-                diag.span_suggestion_verbose(span, msg, sug, Applicability::MaybeIncorrect);
+                diag.subdiagnostic(sugg);
             }
             (ty::FnDef(did1, substs1), ty::FnDef(did2, substs2)) => {
                 let expected_sig =
@@ -395,7 +361,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     &(self.normalize_fn_sig)(self.tcx.fn_sig(*did2).subst(self.tcx, substs2));
 
                 if self.same_type_modulo_infer(*expected_sig, *found_sig) {
-                    diag.note("different fn items have unique types, even if their signatures are the same");
+                    diag.subdiagnostic(FnUniqTypes);
                 }
 
                 if !self.same_type_modulo_infer(*found_sig, *expected_sig)
@@ -409,16 +375,22 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
 
                 let fn_name = self.tcx.def_path_str_with_substs(*did2, substs2);
                 let sug = if found.is_ref() {
-                    format!("&({fn_name} as {found_sig})")
+                    FunctionPointerSuggestion::CastBothRef {
+                        span,
+                        fn_name,
+                        found_sig: *found_sig,
+                        expected_sig: *expected_sig,
+                    }
                 } else {
-                    format!("{fn_name} as {found_sig}")
+                    FunctionPointerSuggestion::CastBoth {
+                        span,
+                        fn_name,
+                        found_sig: *found_sig,
+                        expected_sig: *expected_sig,
+                    }
                 };
 
-                let msg = format!(
-                    "consider casting both fn items to fn pointers using `as {expected_sig}`"
-                );
-
-                diag.span_suggestion_hidden(span, msg, sug, Applicability::MaybeIncorrect);
+                diag.subdiagnostic(sug);
             }
             (ty::FnDef(did, substs), ty::FnPtr(sig)) => {
                 let expected_sig =
@@ -437,7 +409,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     format!("{fn_name} as {found_sig}")
                 };
 
-                diag.help(&format!("consider casting the fn item to a fn pointer: `{}`", casting));
+                diag.subdiagnostic(FnConsiderCasting { casting });
             }
             _ => {
                 return;
@@ -445,23 +417,19 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         };
     }
 
-    pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
+    pub fn should_suggest_as_ref_kind(
+        &self,
+        expected: Ty<'tcx>,
+        found: Ty<'tcx>,
+    ) -> Option<SuggestAsRefKind> {
         if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
             (expected.kind(), found.kind())
         {
             if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
                 if exp_def == &found_def {
                     let have_as_ref = &[
-                        (
-                            sym::Option,
-                            "you can convert from `&Option<T>` to `Option<&T>` using \
-                        `.as_ref()`",
-                        ),
-                        (
-                            sym::Result,
-                            "you can convert from `&Result<T, E>` to \
-                        `Result<&T, &E>` using `.as_ref()`",
-                        ),
+                        (sym::Option, SuggestAsRefKind::Option),
+                        (sym::Result, SuggestAsRefKind::Result),
                     ];
                     if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
                         self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
@@ -495,15 +463,28 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         None
     }
 
+    // FIXME: Remove once `rustc_hir_typeck` is migrated to diagnostic structs
+    pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
+        match self.should_suggest_as_ref_kind(expected, found) {
+            Some(SuggestAsRefKind::Option) => Some(
+                "you can convert from `&Option<T>` to `Option<&T>` using \
+            `.as_ref()`",
+            ),
+            Some(SuggestAsRefKind::Result) => Some(
+                "you can convert from `&Result<T, E>` to \
+            `Result<&T, &E>` using `.as_ref()`",
+            ),
+            None => None,
+        }
+    }
     /// Try to find code with pattern `if Some(..) = expr`
     /// use a `visitor` to mark the `if` which its span contains given error span,
     /// and then try to find a assignment in the `cond` part, which span is equal with error span
     pub(super) fn suggest_let_for_letchains(
         &self,
-        err: &mut Diagnostic,
         cause: &ObligationCause<'_>,
         span: Span,
-    ) {
+    ) -> Option<TypeErrorAdditionalDiags> {
         let hir = self.tcx.hir();
         if let Some(node) = self.tcx.hir().find_by_def_id(cause.body_id) &&
             let hir::Node::Item(hir::Item {
@@ -550,9 +531,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
         visitor.visit_body(&body);
         if visitor.result {
-                err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
+                return Some(TypeErrorAdditionalDiags::AddLetForLetChains{span: span.shrink_to_lo()});
             }
         }
+        None
     }
 }
 
diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs
index 5bbd396d6f3..24cbe8e8281 100644
--- a/compiler/rustc_middle/src/ty/sty.rs
+++ b/compiler/rustc_middle/src/ty/sty.rs
@@ -15,6 +15,7 @@ use hir::def::DefKind;
 use polonius_engine::Atom;
 use rustc_data_structures::captures::Captures;
 use rustc_data_structures::intern::Interned;
+use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::LangItem;
@@ -864,8 +865,8 @@ impl<'tcx> PolyTraitRef<'tcx> {
     }
 }
 
-impl rustc_errors::IntoDiagnosticArg for PolyTraitRef<'_> {
-    fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
+impl<'tcx> IntoDiagnosticArg for TraitRef<'tcx> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         self.to_string().into_diagnostic_arg()
     }
 }
@@ -910,6 +911,12 @@ impl<'tcx> ExistentialTraitRef<'tcx> {
     }
 }
 
+impl<'tcx> IntoDiagnosticArg for ExistentialTraitRef<'tcx> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.to_string().into_diagnostic_arg()
+    }
+}
+
 pub type PolyExistentialTraitRef<'tcx> = Binder<'tcx, ExistentialTraitRef<'tcx>>;
 
 impl<'tcx> PolyExistentialTraitRef<'tcx> {
@@ -926,12 +933,6 @@ impl<'tcx> PolyExistentialTraitRef<'tcx> {
     }
 }
 
-impl rustc_errors::IntoDiagnosticArg for PolyExistentialTraitRef<'_> {
-    fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
-        self.to_string().into_diagnostic_arg()
-    }
-}
-
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
 #[derive(HashStable)]
 pub enum BoundVariableKind {
@@ -1146,6 +1147,15 @@ impl<'tcx, T: IntoIterator> Binder<'tcx, T> {
     }
 }
 
+impl<'tcx, T> IntoDiagnosticArg for Binder<'tcx, T>
+where
+    T: IntoDiagnosticArg,
+{
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.0.into_diagnostic_arg()
+    }
+}
+
 struct SkipBindersAt<'tcx> {
     tcx: TyCtxt<'tcx>,
     index: ty::DebruijnIndex,
@@ -1362,6 +1372,12 @@ impl<'tcx> FnSig<'tcx> {
     }
 }
 
+impl<'tcx> IntoDiagnosticArg for FnSig<'tcx> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.to_string().into_diagnostic_arg()
+    }
+}
+
 pub type PolyFnSig<'tcx> = Binder<'tcx, FnSig<'tcx>>;
 
 impl<'tcx> PolyFnSig<'tcx> {