about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-08-31 07:57:09 +0000
committerbors <bors@rust-lang.org>2022-08-31 07:57:09 +0000
commit12e4fd0755d7d976d4ee0f2004dc938290752ff7 (patch)
tree2a88229f6a91bb1741d22d923002d1b253346689 /compiler
parent7f442f8ba174fd4233a14ef4d7b577aa907db594 (diff)
parent49ed325759ca51034fb965b161a06edf9a33e73c (diff)
downloadrust-12e4fd0755d7d976d4ee0f2004dc938290752ff7.tar.gz
rust-12e4fd0755d7d976d4ee0f2004dc938290752ff7.zip
Auto merge of #101225 - matthiaskrgr:rollup-9s1chas, r=matthiaskrgr
Rollup of 8 pull requests

Successful merges:

 - #100970 (Allow deriving multipart suggestions)
 - #100984 (Reinstate preloading of some dll imports)
 - #101011 (Use getentropy when possible on all Apple platforms)
 - #101025 (Add tier-3 support for powerpc64 and riscv64 openbsd)
 - #101049 (Remove span fatal from ast lowering)
 - #101100 (Make call suggestions more general and more accurate)
 - #101171 (Fix UB from misalignment and provenance widening in `std::sys::windows`)
 - #101185 (Tweak `WellFormedLoc`s a bit)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_ast_lowering/src/errors.rs7
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs12
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs2
-rw-r--r--compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl2
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs17
-rw-r--r--compiler/rustc_errors/src/lib.rs19
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs3
-rw-r--r--compiler/rustc_llvm/build.rs4
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs700
-rw-r--r--compiler/rustc_macros/src/lib.rs5
-rw-r--r--compiler/rustc_middle/src/ty/structural_impls.rs6
-rw-r--r--compiler/rustc_target/src/spec/mod.rs4
-rw-r--r--compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs17
-rw-r--r--compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs18
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs102
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs269
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs55
-rw-r--r--compiler/rustc_typeck/src/check/mod.rs29
-rw-r--r--compiler/rustc_typeck/src/check/op.rs128
-rw-r--r--compiler/rustc_typeck/src/check/wfcheck.rs21
-rw-r--r--compiler/rustc_typeck/src/hir_wf_check.rs4
21 files changed, 873 insertions, 551 deletions
diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs
index 59f1b7180e4..4adeaef9bbf 100644
--- a/compiler/rustc_ast_lowering/src/errors.rs
+++ b/compiler/rustc_ast_lowering/src/errors.rs
@@ -327,3 +327,10 @@ pub struct ArbitraryExpressionInPattern {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(SessionDiagnostic, Clone, Copy)]
+#[diag(ast_lowering::inclusive_range_with_no_end)]
+pub struct InclusiveRangeWithNoEnd {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 61f8c0216f1..17604761688 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -1,8 +1,8 @@
 use super::errors::{
     AsyncGeneratorsNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks,
     BaseExpressionDoubleDot, ClosureCannotBeStatic, FunctionalRecordUpdateDestructuringAssignemnt,
-    GeneratorTooManyParameters, NotSupportedForLifetimeBinderAsyncClosure, RustcBoxAttributeError,
-    UnderscoreExprLhsAssign,
+    GeneratorTooManyParameters, InclusiveRangeWithNoEnd, NotSupportedForLifetimeBinderAsyncClosure,
+    RustcBoxAttributeError, UnderscoreExprLhsAssign,
 };
 use super::ResolverAstLoweringExt;
 use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs};
@@ -1264,7 +1264,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
             (Some(..), Some(..), HalfOpen) => hir::LangItem::Range,
             (None, Some(..), Closed) => hir::LangItem::RangeToInclusive,
             (Some(..), Some(..), Closed) => unreachable!(),
-            (_, None, Closed) => self.diagnostic().span_fatal(span, "inclusive range with no end"),
+            (start, None, Closed) => {
+                self.tcx.sess.emit_err(InclusiveRangeWithNoEnd { span });
+                match start {
+                    Some(..) => hir::LangItem::RangeFrom,
+                    None => hir::LangItem::RangeFull,
+                }
+            }
         };
 
         let fields = self.arena.alloc_from_iter(
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 5e9e8aa553d..30a87740c7f 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -36,6 +36,8 @@
 #![feature(never_type)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
+#![deny(rustc::untranslatable_diagnostic)]
+#![deny(rustc::diagnostic_outside_of_impl)]
 
 #[macro_use]
 extern crate tracing;
diff --git a/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl b/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl
index dcb1e2b0830..f2790531aba 100644
--- a/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl
@@ -129,3 +129,5 @@ ast_lowering_not_supported_for_lifetime_binder_async_closure =
 
 ast_lowering_arbitrary_expression_in_pattern =
     arbitrary expressions aren't allowed in patterns
+
+ast_lowering_inclusive_range_with_no_end = inclusive range with no end
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 506198df4d8..f75e2596f36 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -686,19 +686,12 @@ impl Diagnostic {
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
-        assert!(!suggestion.is_empty());
-        self.push_suggestion(CodeSuggestion {
-            substitutions: vec![Substitution {
-                parts: suggestion
-                    .into_iter()
-                    .map(|(span, snippet)| SubstitutionPart { snippet, span })
-                    .collect(),
-            }],
-            msg: self.subdiagnostic_message_to_diagnostic_message(msg),
-            style: SuggestionStyle::CompletelyHidden,
+        self.multipart_suggestion_with_style(
+            msg,
+            suggestion,
             applicability,
-        });
-        self
+            SuggestionStyle::CompletelyHidden,
+        )
     }
 
     /// Prints out a message with a suggested edit of the code.
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 7b0f4354afd..68abdd0bad1 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -1249,9 +1249,13 @@ impl HandlerInner {
     }
 
     fn treat_err_as_bug(&self) -> bool {
-        self.flags
-            .treat_err_as_bug
-            .map_or(false, |c| self.err_count() + self.lint_err_count >= c.get())
+        self.flags.treat_err_as_bug.map_or(false, |c| {
+            self.err_count()
+                + self.lint_err_count
+                + self.delayed_span_bugs.len()
+                + self.delayed_good_path_bugs.len()
+                >= c.get()
+        })
     }
 
     fn print_error_count(&mut self, registry: &Registry) {
@@ -1407,7 +1411,14 @@ impl HandlerInner {
         // This is technically `self.treat_err_as_bug()` but `delay_span_bug` is called before
         // incrementing `err_count` by one, so we need to +1 the comparing.
         // FIXME: Would be nice to increment err_count in a more coherent way.
-        if self.flags.treat_err_as_bug.map_or(false, |c| self.err_count() + 1 >= c.get()) {
+        if self.flags.treat_err_as_bug.map_or(false, |c| {
+            self.err_count()
+                + self.lint_err_count
+                + self.delayed_span_bugs.len()
+                + self.delayed_good_path_bugs.len()
+                + 1
+                >= c.get()
+        }) {
             // FIXME: don't abort here if report_delayed_bugs is off
             self.span_bug(sp, msg);
         }
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 9e7cbba9511..9c6530c8a08 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -41,7 +41,8 @@ macro_rules! pluralize {
 /// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion
 /// to determine whether it should be automatically applied or if the user should be consulted
 /// before applying the suggestion.
-#[derive(Copy, Clone, Debug, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize)]
+#[derive(Copy, Clone, Debug, Hash, Encodable, Decodable, Serialize, Deserialize)]
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
 pub enum Applicability {
     /// The suggestion is definitely what the user intended, or maintains the exact meaning of the code.
     /// This suggestion should be automatically applied.
diff --git a/compiler/rustc_llvm/build.rs b/compiler/rustc_llvm/build.rs
index abb68f3afe5..28e092c1eb7 100644
--- a/compiler/rustc_llvm/build.rs
+++ b/compiler/rustc_llvm/build.rs
@@ -342,10 +342,10 @@ fn main() {
     };
 
     // RISC-V GCC erroneously requires libatomic for sub-word
-    // atomic operations. FreeBSD uses Clang as its system
+    // atomic operations. Some BSD uses Clang as its system
     // compiler and provides no libatomic in its base system so
     // does not want this.
-    if !target.contains("freebsd") && target.starts_with("riscv") {
+    if target.starts_with("riscv") && !target.contains("freebsd") && !target.contains("openbsd") {
         println!("cargo:rustc-link-lib=atomic");
     }
 
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 666dbc23c28..8b40e295bd8 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -12,7 +12,7 @@ use quote::{format_ident, quote};
 use std::collections::HashMap;
 use std::fmt;
 use std::str::FromStr;
-use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path};
+use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path};
 use synstructure::{BindingInfo, Structure, VariantInfo};
 
 /// Which kind of suggestion is being created?
@@ -28,8 +28,41 @@ enum SubdiagnosticSuggestionKind {
     Verbose,
 }
 
+impl FromStr for SubdiagnosticSuggestionKind {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "" => Ok(SubdiagnosticSuggestionKind::Normal),
+            "_short" => Ok(SubdiagnosticSuggestionKind::Short),
+            "_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden),
+            "_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose),
+            _ => Err(()),
+        }
+    }
+}
+
+impl SubdiagnosticSuggestionKind {
+    pub fn to_suggestion_style(&self) -> TokenStream {
+        match self {
+            SubdiagnosticSuggestionKind::Normal => {
+                quote! { rustc_errors::SuggestionStyle::ShowCode }
+            }
+            SubdiagnosticSuggestionKind::Short => {
+                quote! { rustc_errors::SuggestionStyle::HideCodeInline }
+            }
+            SubdiagnosticSuggestionKind::Hidden => {
+                quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
+            }
+            SubdiagnosticSuggestionKind::Verbose => {
+                quote! { rustc_errors::SuggestionStyle::ShowAlways }
+            }
+        }
+    }
+}
+
 /// Which kind of subdiagnostic is being created from a variant?
-#[derive(Clone, Copy)]
+#[derive(Clone)]
 enum SubdiagnosticKind {
     /// `#[label(...)]`
     Label,
@@ -40,31 +73,9 @@ enum SubdiagnosticKind {
     /// `#[warning(...)]`
     Warn,
     /// `#[suggestion{,_short,_hidden,_verbose}]`
-    Suggestion(SubdiagnosticSuggestionKind),
-}
-
-impl FromStr for SubdiagnosticKind {
-    type Err = ();
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s {
-            "label" => Ok(SubdiagnosticKind::Label),
-            "note" => Ok(SubdiagnosticKind::Note),
-            "help" => Ok(SubdiagnosticKind::Help),
-            "warning" => Ok(SubdiagnosticKind::Warn),
-            "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
-            "suggestion_short" => {
-                Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
-            }
-            "suggestion_hidden" => {
-                Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden))
-            }
-            "suggestion_verbose" => {
-                Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose))
-            }
-            _ => Err(()),
-        }
-    }
+    Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream },
+    /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
+    MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind },
 }
 
 impl quote::IdentFragment for SubdiagnosticKind {
@@ -74,17 +85,9 @@ impl quote::IdentFragment for SubdiagnosticKind {
             SubdiagnosticKind::Note => write!(f, "note"),
             SubdiagnosticKind::Help => write!(f, "help"),
             SubdiagnosticKind::Warn => write!(f, "warn"),
-            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => {
-                write!(f, "suggestion")
-            }
-            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => {
-                write!(f, "suggestion_short")
-            }
-            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => {
-                write!(f, "suggestion_hidden")
-            }
-            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => {
-                write!(f, "suggestion_verbose")
+            SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
+            SubdiagnosticKind::MultipartSuggestion { .. } => {
+                write!(f, "multipart_suggestion_with_style")
             }
         }
     }
@@ -148,11 +151,9 @@ impl<'a> SessionSubdiagnosticDerive<'a> {
                     variant,
                     span,
                     fields: fields_map,
-                    kind: None,
-                    slug: None,
-                    code: None,
                     span_field: None,
                     applicability: None,
+                    has_suggestion_parts: false,
                 };
                 builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
             });
@@ -193,21 +194,15 @@ struct SessionSubdiagnosticDeriveBuilder<'a> {
     /// derive builder.
     fields: HashMap<String, TokenStream>,
 
-    /// Subdiagnostic kind of the type/variant.
-    kind: Option<(SubdiagnosticKind, proc_macro::Span)>,
-
-    /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
-    /// `#[kind(slug)]` attribute on the type or variant.
-    slug: Option<(Path, proc_macro::Span)>,
-    /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
-    /// attribute on the type or variant.
-    code: Option<(TokenStream, proc_macro::Span)>,
-
     /// Identifier for the binding to the `#[primary_span]` field.
     span_field: Option<(proc_macro2::Ident, proc_macro::Span)>,
     /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
     /// `rustc_errors::Applicability::*` variant directly.
     applicability: Option<(TokenStream, proc_macro::Span)>,
+
+    /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
+    /// during finalization if still `false`.
+    has_suggestion_parts: bool,
 }
 
 impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
@@ -217,7 +212,11 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
 }
 
 impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
-    fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> {
+    fn identify_kind(
+        &mut self,
+    ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
+        let mut kind_slug = None;
+
         for attr in self.variant.ast().attrs {
             let span = attr.span().unwrap();
 
@@ -225,116 +224,121 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
             let name = name.as_str();
 
             let meta = attr.parse_meta()?;
-            let kind = match meta {
-                Meta::List(MetaList { ref nested, .. }) => {
-                    let mut nested_iter = nested.into_iter();
-                    if let Some(nested_attr) = nested_iter.next() {
-                        match nested_attr {
-                            NestedMeta::Meta(Meta::Path(path)) => {
-                                self.slug.set_once((path.clone(), span));
-                            }
-                            NestedMeta::Meta(meta @ Meta::NameValue(_))
-                                if matches!(
-                                    meta.path().segments.last().unwrap().ident.to_string().as_str(),
-                                    "code" | "applicability"
-                                ) =>
-                            {
-                                // don't error for valid follow-up attributes
-                            }
-                            nested_attr => {
-                                throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                                    diag.help(
-                                        "first argument of the attribute should be the diagnostic \
-                                         slug",
-                                    )
-                                })
-                            }
-                        };
-                    }
+            let Meta::List(MetaList { ref nested, .. }) = meta else {
+                throw_invalid_attr!(attr, &meta);
+            };
 
-                    for nested_attr in nested_iter {
-                        let meta = match nested_attr {
-                            NestedMeta::Meta(ref meta) => meta,
-                            _ => throw_invalid_nested_attr!(attr, &nested_attr),
-                        };
-
-                        let span = meta.span().unwrap();
-                        let nested_name = meta.path().segments.last().unwrap().ident.to_string();
-                        let nested_name = nested_name.as_str();
-
-                        match meta {
-                            Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
-                                match nested_name {
-                                    "code" => {
-                                        let formatted_str = self.build_format(&s.value(), s.span());
-                                        self.code.set_once((formatted_str, span));
-                                    }
-                                    "applicability" => {
-                                        let value = match Applicability::from_str(&s.value()) {
-                                            Ok(v) => v,
-                                            Err(()) => {
-                                                span_err(span, "invalid applicability").emit();
-                                                Applicability::Unspecified
-                                            }
-                                        };
-                                        self.applicability.set_once((quote! { #value }, span));
-                                    }
-                                    _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                                        diag.help(
-                                            "only `code` and `applicability` are valid nested \
-                                             attributes",
-                                        )
-                                    }),
-                                }
-                            }
-                            _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                                if matches!(meta, Meta::Path(_)) {
-                                    diag.help(
-                                        "a diagnostic slug must be the first argument to the \
-                                         attribute",
-                                    )
-                                } else {
-                                    diag
-                                }
-                            }),
-                        }
+            let mut kind = match name {
+                "label" => SubdiagnosticKind::Label,
+                "note" => SubdiagnosticKind::Note,
+                "help" => SubdiagnosticKind::Help,
+                "warning" => SubdiagnosticKind::Warn,
+                _ => {
+                    if let Some(suggestion_kind) =
+                        name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
+                    {
+                        SubdiagnosticKind::Suggestion { suggestion_kind, code: TokenStream::new() }
+                    } else if let Some(suggestion_kind) =
+                        name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
+                    {
+                        SubdiagnosticKind::MultipartSuggestion { suggestion_kind }
+                    } else {
+                        throw_invalid_attr!(attr, &meta);
                     }
-
-                    let Ok(kind) = SubdiagnosticKind::from_str(name) else {
-                        throw_invalid_attr!(attr, &meta)
-                    };
-
-                    kind
                 }
-                _ => throw_invalid_attr!(attr, &meta),
             };
 
-            if matches!(
-                kind,
-                SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
-            ) && self.code.is_some()
-            {
-                throw_span_err!(
-                    span,
-                    &format!("`code` is not a valid nested attribute of a `{}` attribute", name)
-                );
+            let mut slug = None;
+            let mut code = None;
+
+            let mut nested_iter = nested.into_iter();
+            if let Some(nested_attr) = nested_iter.next() {
+                match nested_attr {
+                    NestedMeta::Meta(Meta::Path(path)) => {
+                        slug.set_once((path.clone(), span));
+                    }
+                    NestedMeta::Meta(meta @ Meta::NameValue(_))
+                        if matches!(
+                            meta.path().segments.last().unwrap().ident.to_string().as_str(),
+                            "code" | "applicability"
+                        ) =>
+                    {
+                        // Don't error for valid follow-up attributes.
+                    }
+                    nested_attr => {
+                        throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                            diag.help(
+                                "first argument of the attribute should be the diagnostic \
+                                 slug",
+                            )
+                        })
+                    }
+                };
             }
 
-            if matches!(
-                kind,
-                SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
-            ) && self.applicability.is_some()
-            {
-                throw_span_err!(
-                    span,
-                    &format!(
-                        "`applicability` is not a valid nested attribute of a `{}` attribute",
-                        name
-                    )
-                );
+            for nested_attr in nested_iter {
+                let meta = match nested_attr {
+                    NestedMeta::Meta(ref meta) => meta,
+                    _ => throw_invalid_nested_attr!(attr, &nested_attr),
+                };
+
+                let span = meta.span().unwrap();
+                let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+                let nested_name = nested_name.as_str();
+
+                let value = match meta {
+                    Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
+                    Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                        diag.help("a diagnostic slug must be the first argument to the attribute")
+                    }),
+                    _ => throw_invalid_nested_attr!(attr, &nested_attr),
+                };
+
+                match nested_name {
+                    "code" => {
+                        if matches!(kind, SubdiagnosticKind::Suggestion { .. }) {
+                            let formatted_str = self.build_format(&value.value(), value.span());
+                            code.set_once((formatted_str, span));
+                        } else {
+                            span_err(
+                                span,
+                                &format!(
+                                    "`code` is not a valid nested attribute of a `{}` attribute",
+                                    name
+                                ),
+                            )
+                            .emit();
+                        }
+                    }
+                    "applicability" => {
+                        if matches!(
+                            kind,
+                            SubdiagnosticKind::Suggestion { .. }
+                                | SubdiagnosticKind::MultipartSuggestion { .. }
+                        ) {
+                            let value =
+                                Applicability::from_str(&value.value()).unwrap_or_else(|()| {
+                                    span_err(span, "invalid applicability").emit();
+                                    Applicability::Unspecified
+                                });
+                            self.applicability.set_once((quote! { #value }, span));
+                        } else {
+                            span_err(
+                                span,
+                                &format!(
+                                    "`applicability` is not a valid nested attribute of a `{}` attribute",
+                                    name
+                                )
+                            ).emit();
+                        }
+                    }
+                    _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                        diag.help("only `code` and `applicability` are valid nested attributes")
+                    }),
+                }
             }
 
-            if self.slug.is_none() {
+            let Some((slug, _)) = slug else {
                 throw_span_err!(
                     span,
                     &format!(
@@ -342,150 +346,338 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                         name
                     )
                 );
+            };
+
+            match kind {
+                SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
+                    let Some((code, _)) = code else {
+                        throw_span_err!(span, "suggestion without `code = \"...\"`");
+                    };
+                    *code_field = code;
+                }
+                SubdiagnosticKind::Label
+                | SubdiagnosticKind::Note
+                | SubdiagnosticKind::Help
+                | SubdiagnosticKind::Warn
+                | SubdiagnosticKind::MultipartSuggestion { .. } => {}
             }
 
-            self.kind.set_once((kind, span));
+            kind_slug.set_once(((kind, slug), span))
         }
 
-        Ok(())
+        Ok(kind_slug.map(|(kind_slug, _)| kind_slug))
+    }
+
+    /// Generates the code for a field with no attributes.
+    fn generate_field_set_arg(&mut self, binding: &BindingInfo<'_>) -> TokenStream {
+        let ast = binding.ast();
+        assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg");
+
+        let diag = &self.diag;
+        let ident = ast.ident.as_ref().unwrap();
+        quote! {
+            #diag.set_arg(
+                stringify!(#ident),
+                #binding
+            );
+        }
     }
 
-    fn generate_field_code(
+    /// Generates the necessary code for all attributes on a field.
+    fn generate_field_attr_code(
         &mut self,
         binding: &BindingInfo<'_>,
-        is_suggestion: bool,
-    ) -> Result<TokenStream, DiagnosticDeriveError> {
+        kind: &SubdiagnosticKind,
+    ) -> TokenStream {
         let ast = binding.ast();
+        assert!(ast.attrs.len() > 0, "field without attributes generating attr code");
 
+        // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will
+        // apply the generated code on each element in the `Vec` or `Option`.
         let inner_ty = FieldInnerTy::from_type(&ast.ty);
-        let info = FieldInfo {
-            binding: binding,
-            ty: inner_ty.inner_type().unwrap_or(&ast.ty),
-            span: &ast.span(),
-        };
+        ast.attrs
+            .iter()
+            .map(|attr| {
+                let info = FieldInfo {
+                    binding,
+                    ty: inner_ty.inner_type().unwrap_or(&ast.ty),
+                    span: &ast.span(),
+                };
 
-        for attr in &ast.attrs {
-            let name = attr.path.segments.last().unwrap().ident.to_string();
-            let name = name.as_str();
-            let span = attr.span().unwrap();
+                let generated = self
+                    .generate_field_code_inner(kind, attr, info)
+                    .unwrap_or_else(|v| v.to_compile_error());
 
-            let meta = attr.parse_meta()?;
-            match meta {
-                Meta::Path(_) => match name {
-                    "primary_span" => {
-                        report_error_if_not_applied_to_span(attr, &info)?;
-                        self.span_field.set_once((binding.binding.clone(), span));
-                        return Ok(quote! {});
-                    }
-                    "applicability" if is_suggestion => {
-                        report_error_if_not_applied_to_applicability(attr, &info)?;
-                        let binding = binding.binding.clone();
-                        self.applicability.set_once((quote! { #binding }, span));
-                        return Ok(quote! {});
-                    }
-                    "applicability" => {
-                        span_err(span, "`#[applicability]` is only valid on suggestions").emit();
-                        return Ok(quote! {});
-                    }
-                    "skip_arg" => {
-                        return Ok(quote! {});
-                    }
-                    _ => throw_invalid_attr!(attr, &meta, |diag| {
+                inner_ty.with(binding, generated)
+            })
+            .collect()
+    }
+
+    fn generate_field_code_inner(
+        &mut self,
+        kind: &SubdiagnosticKind,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+    ) -> Result<TokenStream, DiagnosticDeriveError> {
+        let meta = attr.parse_meta()?;
+        match meta {
+            Meta::Path(path) => self.generate_field_code_inner_path(kind, attr, info, path),
+            Meta::List(list @ MetaList { .. }) => {
+                self.generate_field_code_inner_list(kind, attr, info, list)
+            }
+            _ => throw_invalid_attr!(attr, &meta),
+        }
+    }
+
+    /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`).
+    fn generate_field_code_inner_path(
+        &mut self,
+        kind: &SubdiagnosticKind,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+        path: Path,
+    ) -> Result<TokenStream, DiagnosticDeriveError> {
+        let span = attr.span().unwrap();
+        let ident = &path.segments.last().unwrap().ident;
+        let name = ident.to_string();
+        let name = name.as_str();
+
+        match name {
+            "skip_arg" => Ok(quote! {}),
+            "primary_span" => {
+                if matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) {
+                    throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
                         diag.help(
-                            "only `primary_span`, `applicability` and `skip_arg` are valid field \
-                             attributes",
+                            "multipart suggestions use one or more `#[suggestion_part]`s rather \
+                            than one `#[primary_span]`",
                         )
-                    }),
-                },
-                _ => throw_invalid_attr!(attr, &meta),
+                    })
+                }
+
+                report_error_if_not_applied_to_span(attr, &info)?;
+
+                let binding = info.binding.binding.clone();
+                self.span_field.set_once((binding, span));
+
+                Ok(quote! {})
             }
+            "suggestion_part" => {
+                self.has_suggestion_parts = true;
+
+                match kind {
+                    SubdiagnosticKind::MultipartSuggestion { .. } => {
+                        span_err(
+                            span,
+                            "`#[suggestion_part(...)]` attribute without `code = \"...\"`",
+                        )
+                        .emit();
+                        Ok(quote! {})
+                    }
+                    SubdiagnosticKind::Label
+                    | SubdiagnosticKind::Note
+                    | SubdiagnosticKind::Help
+                    | SubdiagnosticKind::Warn
+                    | SubdiagnosticKind::Suggestion { .. } => {
+                        throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
+                            diag.help(
+                                "`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead",
+                            )
+                        });
+                    }
+                }
+            }
+            "applicability" => {
+                if let SubdiagnosticKind::Suggestion { .. }
+                | SubdiagnosticKind::MultipartSuggestion { .. } = kind
+                {
+                    report_error_if_not_applied_to_applicability(attr, &info)?;
+
+                    let binding = info.binding.binding.clone();
+                    self.applicability.set_once((quote! { #binding }, span));
+                } else {
+                    span_err(span, "`#[applicability]` is only valid on suggestions").emit();
+                }
+
+                Ok(quote! {})
+            }
+            _ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
+                let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
+                    "suggestion_part"
+                } else {
+                    "primary_span"
+                };
+                diag.help(format!(
+                    "only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes",
+                ))
+            }),
         }
+    }
 
-        let ident = ast.ident.as_ref().unwrap();
+    /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g.
+    /// `#[suggestion_part(code = "...")]`).
+    fn generate_field_code_inner_list(
+        &mut self,
+        kind: &SubdiagnosticKind,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+        list: MetaList,
+    ) -> Result<TokenStream, DiagnosticDeriveError> {
+        let span = attr.span().unwrap();
+        let ident = &list.path.segments.last().unwrap().ident;
+        let name = ident.to_string();
+        let name = name.as_str();
+
+        match name {
+            "suggestion_part" => {
+                if !matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) {
+                    throw_invalid_attr!(attr, &Meta::List(list), |diag| {
+                        diag.help(
+                            "`#[suggestion_part(...)]` is only valid in multipart suggestions",
+                        )
+                    })
+                }
 
-        let diag = &self.diag;
-        let generated = quote! {
-            #diag.set_arg(
-                stringify!(#ident),
-                #binding
-            );
-        };
+                self.has_suggestion_parts = true;
+
+                report_error_if_not_applied_to_span(attr, &info)?;
+
+                let mut code = None;
+                for nested_attr in list.nested.iter() {
+                    let NestedMeta::Meta(ref meta) = nested_attr else {
+                        throw_invalid_nested_attr!(attr, &nested_attr);
+                    };
+
+                    let span = meta.span().unwrap();
+                    let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+                    let nested_name = nested_name.as_str();
+
+                    let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) = meta else {
+                        throw_invalid_nested_attr!(attr, &nested_attr);
+                    };
+
+                    match nested_name {
+                        "code" => {
+                            let formatted_str = self.build_format(&value.value(), value.span());
+                            code.set_once((formatted_str, span));
+                        }
+                        _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                            diag.help("`code` is the only valid nested attribute")
+                        }),
+                    }
+                }
+
+                let Some((code, _)) = code else {
+                    span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
+                        .emit();
+                    return Ok(quote! {});
+                };
+                let binding = info.binding;
 
-        Ok(inner_ty.with(binding, generated))
+                Ok(quote! { suggestions.push((#binding, #code)); })
+            }
+            _ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
+                let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
+                    "suggestion_part"
+                } else {
+                    "primary_span"
+                };
+                diag.help(format!(
+                    "only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes",
+                ))
+            }),
+        }
     }
 
-    fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
-        self.identify_kind()?;
-        let Some(kind) = self.kind.map(|(kind, _)| kind) else {
+    pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
+        let Some((kind, slug)) = self.identify_kind()? else {
             throw_span_err!(
                 self.variant.ast().ident.span().unwrap(),
                 "subdiagnostic kind not specified"
             );
         };
 
-        let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_));
-
-        let mut args = TokenStream::new();
-        for binding in self.variant.bindings() {
-            let arg = self
-                .generate_field_code(binding, is_suggestion)
-                .unwrap_or_else(|v| v.to_compile_error());
-            args.extend(arg);
-        }
-
-        // Missing slug errors will already have been reported.
-        let slug = self
-            .slug
-            .as_ref()
-            .map(|(slug, _)| slug.clone())
-            .unwrap_or_else(|| parse_quote! { you::need::to::specify::a::slug });
-        let code = match self.code.as_ref() {
-            Some((code, _)) => Some(quote! { #code }),
-            None if is_suggestion => {
-                span_err(self.span, "suggestion without `code = \"...\"`").emit();
-                Some(quote! { /* macro error */ "..." })
+        let init = match &kind {
+            SubdiagnosticKind::Label
+            | SubdiagnosticKind::Note
+            | SubdiagnosticKind::Help
+            | SubdiagnosticKind::Warn
+            | SubdiagnosticKind::Suggestion { .. } => quote! {},
+            SubdiagnosticKind::MultipartSuggestion { .. } => {
+                quote! { let mut suggestions = Vec::new(); }
             }
-            None => None,
         };
 
+        let attr_args: TokenStream = self
+            .variant
+            .bindings()
+            .iter()
+            .filter(|binding| !binding.ast().attrs.is_empty())
+            .map(|binding| self.generate_field_attr_code(binding, &kind))
+            .collect();
+
         let span_field = self.span_field.as_ref().map(|(span, _)| span);
-        let applicability = match self.applicability.clone() {
-            Some((applicability, _)) => Some(applicability),
-            None if is_suggestion => {
-                span_err(self.span, "suggestion without `applicability`").emit();
-                Some(quote! { rustc_errors::Applicability::Unspecified })
-            }
-            None => None,
-        };
+        let applicability = self.applicability.take().map_or_else(
+            || quote! { rustc_errors::Applicability::Unspecified },
+            |(applicability, _)| applicability,
+        );
 
         let diag = &self.diag;
         let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
         let message = quote! { rustc_errors::fluent::#slug };
-        let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
-            if let Some(span) = span_field {
-                quote! { #diag.#name(#span, #message, #code, #applicability); }
-            } else {
-                span_err(self.span, "suggestion without `#[primary_span]` field").emit();
-                quote! { unreachable!(); }
+        let call = match kind {
+            SubdiagnosticKind::Suggestion { suggestion_kind, code } => {
+                if let Some(span) = span_field {
+                    let style = suggestion_kind.to_suggestion_style();
+
+                    quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
+                } else {
+                    span_err(self.span, "suggestion without `#[primary_span]` field").emit();
+                    quote! { unreachable!(); }
+                }
             }
-        } else if matches!(kind, SubdiagnosticKind::Label) {
-            if let Some(span) = span_field {
-                quote! { #diag.#name(#span, #message); }
-            } else {
-                span_err(self.span, "label without `#[primary_span]` field").emit();
-                quote! { unreachable!(); }
+            SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
+                if !self.has_suggestion_parts {
+                    span_err(
+                        self.span,
+                        "multipart suggestion without any `#[suggestion_part(...)]` fields",
+                    )
+                    .emit();
+                }
+
+                let style = suggestion_kind.to_suggestion_style();
+
+                quote! { #diag.#name(#message, suggestions, #applicability, #style); }
             }
-        } else {
-            if let Some(span) = span_field {
-                quote! { #diag.#name(#span, #message); }
-            } else {
-                quote! { #diag.#name(#message); }
+            SubdiagnosticKind::Label => {
+                if let Some(span) = span_field {
+                    quote! { #diag.#name(#span, #message); }
+                } else {
+                    span_err(self.span, "label without `#[primary_span]` field").emit();
+                    quote! { unreachable!(); }
+                }
+            }
+            _ => {
+                if let Some(span) = span_field {
+                    quote! { #diag.#name(#span, #message); }
+                } else {
+                    quote! { #diag.#name(#message); }
+                }
             }
         };
 
+        let plain_args: TokenStream = self
+            .variant
+            .bindings()
+            .iter()
+            .filter(|binding| binding.ast().attrs.is_empty())
+            .map(|binding| self.generate_field_set_arg(binding))
+            .collect();
+
         Ok(quote! {
+            #init
+            #attr_args
             #call
-            #args
+            #plain_args
         })
     }
 }
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index 8faac8ef36a..20ee5dfc727 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -171,8 +171,13 @@ decl_derive!(
         suggestion_short,
         suggestion_hidden,
         suggestion_verbose,
+        multipart_suggestion,
+        multipart_suggestion_short,
+        multipart_suggestion_hidden,
+        multipart_suggestion_verbose,
         // field attributes
         skip_arg,
         primary_span,
+        suggestion_part,
         applicability)] => diagnostics::session_subdiagnostic_derive
 );
diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs
index 7660a2f3af6..57555433f55 100644
--- a/compiler/rustc_middle/src/ty/structural_impls.rs
+++ b/compiler/rustc_middle/src/ty/structural_impls.rs
@@ -844,6 +844,12 @@ impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Vec<T> {
     }
 }
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for &[T] {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        self.iter().try_for_each(|t| t.visit_with(visitor))
+    }
+}
+
 impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Box<[T]> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         self.try_map_id(|t| t.try_fold_with(folder))
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 8d00129b1b1..aa73625ff81 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -944,9 +944,11 @@ supported_targets! {
 
     ("aarch64-unknown-openbsd", aarch64_unknown_openbsd),
     ("i686-unknown-openbsd", i686_unknown_openbsd),
+    ("powerpc-unknown-openbsd", powerpc_unknown_openbsd),
+    ("powerpc64-unknown-openbsd", powerpc64_unknown_openbsd),
+    ("riscv64gc-unknown-openbsd", riscv64gc_unknown_openbsd),
     ("sparc64-unknown-openbsd", sparc64_unknown_openbsd),
     ("x86_64-unknown-openbsd", x86_64_unknown_openbsd),
-    ("powerpc-unknown-openbsd", powerpc_unknown_openbsd),
 
     ("aarch64-unknown-netbsd", aarch64_unknown_netbsd),
     ("armv6-unknown-netbsd-eabihf", armv6_unknown_netbsd_eabihf),
diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs
new file mode 100644
index 00000000000..9cb3a67dc58
--- /dev/null
+++ b/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs
@@ -0,0 +1,17 @@
+use crate::abi::Endian;
+use crate::spec::{LinkerFlavor, Target, TargetOptions};
+
+pub fn target() -> Target {
+    let mut base = super::openbsd_base::opts();
+    base.cpu = "ppc64".into();
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
+    base.max_atomic_width = Some(64);
+
+    Target {
+        llvm_target: "powerpc64-unknown-openbsd".into(),
+        pointer_width: 64,
+        data_layout: "E-m:e-i64:64-n32:64".into(),
+        arch: "powerpc64".into(),
+        options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base },
+    }
+}
diff --git a/compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs b/compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs
new file mode 100644
index 00000000000..cd10f3afaac
--- /dev/null
+++ b/compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs
@@ -0,0 +1,18 @@
+use crate::spec::{CodeModel, Target, TargetOptions};
+
+pub fn target() -> Target {
+    Target {
+        llvm_target: "riscv64-unknown-openbsd".into(),
+        pointer_width: 64,
+        data_layout: "e-m:e-p:64:64-i64:64-i128:128-n64-S128".into(),
+        arch: "riscv64".into(),
+        options: TargetOptions {
+            code_model: Some(CodeModel::Medium),
+            cpu: "generic-rv64".into(),
+            features: "+m,+a,+f,+d,+c".into(),
+            llvm_abiname: "lp64d".into(),
+            max_atomic_width: Some(64),
+            ..super::openbsd_base::opts()
+        },
+    }
+}
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 33d74249e7b..f6596950c2a 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -21,7 +21,6 @@ use crate::errors::{
 };
 use crate::type_error_struct;
 
-use super::suggest_call_constructor;
 use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
 use rustc_ast as ast;
 use rustc_data_structures::fx::FxHashMap;
@@ -44,7 +43,7 @@ use rustc_middle::middle::stability;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
 use rustc_middle::ty::error::TypeError::FieldMisMatch;
 use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TypeVisitable};
+use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable};
 use rustc_session::parse::feature_err;
 use rustc_span::hygiene::DesugaringKind;
 use rustc_span::lev_distance::find_best_match_for_name;
@@ -2141,15 +2140,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         field: Ident,
     ) -> Ty<'tcx> {
         debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
-        let expr_t = self.check_expr(base);
-        let expr_t = self.structurally_resolved_type(base.span, expr_t);
+        let base_ty = self.check_expr(base);
+        let base_ty = self.structurally_resolved_type(base.span, base_ty);
         let mut private_candidate = None;
-        let mut autoderef = self.autoderef(expr.span, expr_t);
-        while let Some((base_t, _)) = autoderef.next() {
-            debug!("base_t: {:?}", base_t);
-            match base_t.kind() {
+        let mut autoderef = self.autoderef(expr.span, base_ty);
+        while let Some((deref_base_ty, _)) = autoderef.next() {
+            debug!("deref_base_ty: {:?}", deref_base_ty);
+            match deref_base_ty.kind() {
                 ty::Adt(base_def, substs) if !base_def.is_enum() => {
-                    debug!("struct named {:?}", base_t);
+                    debug!("struct named {:?}", deref_base_ty);
                     let (ident, def_scope) =
                         self.tcx.adjust_ident_and_get_scope(field, base_def.did(), self.body_id);
                     let fields = &base_def.non_enum_variant().fields;
@@ -2197,23 +2196,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             // (#90483) apply adjustments to avoid ExprUseVisitor from
             // creating erroneous projection.
             self.apply_adjustments(base, adjustments);
-            self.ban_private_field_access(expr, expr_t, field, did);
+            self.ban_private_field_access(expr, base_ty, field, did);
             return field_ty;
         }
 
         if field.name == kw::Empty {
-        } else if self.method_exists(field, expr_t, expr.hir_id, true) {
-            self.ban_take_value_of_method(expr, expr_t, field);
-        } else if !expr_t.is_primitive_ty() {
-            self.ban_nonexisting_field(field, base, expr, expr_t);
+        } else if self.method_exists(field, base_ty, expr.hir_id, true) {
+            self.ban_take_value_of_method(expr, base_ty, field);
+        } else if !base_ty.is_primitive_ty() {
+            self.ban_nonexisting_field(field, base, expr, base_ty);
         } else {
             let field_name = field.to_string();
             let mut err = type_error_struct!(
                 self.tcx().sess,
                 field.span,
-                expr_t,
+                base_ty,
                 E0610,
-                "`{expr_t}` is a primitive type and therefore doesn't have fields",
+                "`{base_ty}` is a primitive type and therefore doesn't have fields",
             );
             let is_valid_suffix = |field: &str| {
                 if field == "f32" || field == "f64" {
@@ -2251,7 +2250,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     None
                 }
             };
-            if let ty::Infer(ty::IntVar(_)) = expr_t.kind()
+            if let ty::Infer(ty::IntVar(_)) = base_ty.kind()
                 && let ExprKind::Lit(Spanned {
                     node: ast::LitKind::Int(_, ast::LitIntType::Unsuffixed),
                     ..
@@ -2280,35 +2279,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         self.tcx().ty_error()
     }
 
-    fn check_call_constructor(
-        &self,
-        err: &mut Diagnostic,
-        base: &'tcx hir::Expr<'tcx>,
-        def_id: DefId,
-    ) {
-        if let Some(local_id) = def_id.as_local() {
-            let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_id);
-            let node = self.tcx.hir().get(hir_id);
-
-            if let Some(fields) = node.tuple_fields() {
-                let kind = match self.tcx.opt_def_kind(local_id) {
-                    Some(DefKind::Ctor(of, _)) => of,
-                    _ => return,
-                };
-
-                suggest_call_constructor(base.span, kind, fields.len(), err);
-            }
-        } else {
-            // The logic here isn't smart but `associated_item_def_ids`
-            // doesn't work nicely on local.
-            if let DefKind::Ctor(of, _) = self.tcx.def_kind(def_id) {
-                let parent_def_id = self.tcx.parent(def_id);
-                let fields = self.tcx.associated_item_def_ids(parent_def_id);
-                suggest_call_constructor(base.span, of, fields.len(), err);
-            }
-        }
-    }
-
     fn suggest_await_on_field_access(
         &self,
         err: &mut Diagnostic,
@@ -2351,40 +2321,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
     fn ban_nonexisting_field(
         &self,
-        field: Ident,
+        ident: Ident,
         base: &'tcx hir::Expr<'tcx>,
         expr: &'tcx hir::Expr<'tcx>,
-        expr_t: Ty<'tcx>,
+        base_ty: Ty<'tcx>,
     ) {
         debug!(
-            "ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}",
-            field, base, expr, expr_t
+            "ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, base_ty={:?}",
+            ident, base, expr, base_ty
         );
-        let mut err = self.no_such_field_err(field, expr_t, base.hir_id);
+        let mut err = self.no_such_field_err(ident, base_ty, base.hir_id);
 
-        match *expr_t.peel_refs().kind() {
+        match *base_ty.peel_refs().kind() {
             ty::Array(_, len) => {
-                self.maybe_suggest_array_indexing(&mut err, expr, base, field, len);
+                self.maybe_suggest_array_indexing(&mut err, expr, base, ident, len);
             }
             ty::RawPtr(..) => {
-                self.suggest_first_deref_field(&mut err, expr, base, field);
+                self.suggest_first_deref_field(&mut err, expr, base, ident);
             }
             ty::Adt(def, _) if !def.is_enum() => {
-                self.suggest_fields_on_recordish(&mut err, def, field, expr.span);
+                self.suggest_fields_on_recordish(&mut err, def, ident, expr.span);
             }
             ty::Param(param_ty) => {
                 self.point_at_param_definition(&mut err, param_ty);
             }
             ty::Opaque(_, _) => {
-                self.suggest_await_on_field_access(&mut err, field, base, expr_t.peel_refs());
-            }
-            ty::FnDef(def_id, _) => {
-                self.check_call_constructor(&mut err, base, def_id);
+                self.suggest_await_on_field_access(&mut err, ident, base, base_ty.peel_refs());
             }
             _ => {}
         }
 
-        if field.name == kw::Await {
+        self.suggest_fn_call(&mut err, base, base_ty, |output_ty| {
+            if let ty::Adt(def, _) = output_ty.kind() && !def.is_enum() {
+                def.non_enum_variant().fields.iter().any(|field| {
+                    field.ident(self.tcx) == ident
+                        && field.vis.is_accessible_from(expr.hir_id.owner.to_def_id(), self.tcx)
+                })
+            } else if let ty::Tuple(tys) = output_ty.kind()
+                && let Ok(idx) = ident.as_str().parse::<usize>()
+            {
+                idx < tys.len()
+            } else {
+                false
+            }
+        });
+
+        if ident.name == kw::Await {
             // We know by construction that `<expr>.await` is either on Rust 2015
             // or results in `ExprKind::Await`. Suggest switching the edition to 2018.
             err.note("to `.await` a `Future`, switch to Rust 2018 or later");
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
index 64d261285c5..939f4612d44 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
@@ -2,6 +2,7 @@ use super::FnCtxt;
 use crate::astconv::AstConv;
 use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel};
 
+use hir::def_id::DefId;
 use rustc_ast::util::parser::ExprPrecedence;
 use rustc_errors::{Applicability, Diagnostic, MultiSpan};
 use rustc_hir as hir;
@@ -61,70 +62,51 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         pointing_at_return_type
     }
 
-    /// When encountering an fn-like ctor that needs to unify with a value, check whether calling
-    /// the ctor would successfully solve the type mismatch and if so, suggest it:
+    /// When encountering an fn-like type, try accessing the output of the type
+    /// // and suggesting calling it if it satisfies a predicate (i.e. if the
+    /// output has a method or a field):
     /// ```compile_fail,E0308
     /// fn foo(x: usize) -> usize { x }
     /// let x: usize = foo;  // suggest calling the `foo` function: `foo(42)`
     /// ```
-    fn suggest_fn_call(
+    pub(crate) fn suggest_fn_call(
         &self,
         err: &mut Diagnostic,
         expr: &hir::Expr<'_>,
-        expected: Ty<'tcx>,
         found: Ty<'tcx>,
+        can_satisfy: impl FnOnce(Ty<'tcx>) -> bool,
     ) -> bool {
-        let (def_id, output, inputs) = match *found.kind() {
-            ty::FnDef(def_id, _) => {
-                let fn_sig = found.fn_sig(self.tcx);
-                (def_id, fn_sig.output(), fn_sig.inputs().skip_binder().len())
-            }
-            ty::Closure(def_id, substs) => {
-                let fn_sig = substs.as_closure().sig();
-                (def_id, fn_sig.output(), fn_sig.inputs().skip_binder().len() - 1)
-            }
-            ty::Opaque(def_id, substs) => {
-                let sig = self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| {
-                    if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder()
-                    && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
-                    // args tuple will always be substs[1]
-                    && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
-                    {
-                        Some((
-                            pred.kind().rebind(proj.term.ty().unwrap()),
-                            args.len(),
-                        ))
-                    } else {
-                        None
-                    }
-                });
-                if let Some((output, inputs)) = sig {
-                    (def_id, output, inputs)
-                } else {
-                    return false;
-                }
-            }
-            _ => return false,
-        };
-
-        let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output);
-        let output = self.normalize_associated_types_in(expr.span, output);
-        if !output.is_ty_var() && self.can_coerce(output, expected) {
-            let (sugg_call, mut applicability) = match inputs {
+        let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(expr, found)
+            else { return false; };
+        if can_satisfy(output) {
+            let (sugg_call, mut applicability) = match inputs.len() {
                 0 => ("".to_string(), Applicability::MachineApplicable),
                 1..=4 => (
-                    (0..inputs).map(|_| "_").collect::<Vec<_>>().join(", "),
-                    Applicability::MachineApplicable,
+                    inputs
+                        .iter()
+                        .map(|ty| {
+                            if ty.is_suggestable(self.tcx, false) {
+                                format!("/* {ty} */")
+                            } else {
+                                "".to_string()
+                            }
+                        })
+                        .collect::<Vec<_>>()
+                        .join(", "),
+                    Applicability::HasPlaceholders,
                 ),
-                _ => ("...".to_string(), Applicability::HasPlaceholders),
+                _ => ("/* ... */".to_string(), Applicability::HasPlaceholders),
             };
 
-            let msg = match self.tcx.def_kind(def_id) {
-                DefKind::Fn => "call this function",
-                DefKind::Closure | DefKind::OpaqueTy => "call this closure",
-                DefKind::Ctor(CtorOf::Struct, _) => "instantiate this tuple struct",
-                DefKind::Ctor(CtorOf::Variant, _) => "instantiate this tuple variant",
-                _ => "call this function",
+            let msg = match def_id_or_name {
+                DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) {
+                    DefKind::Ctor(CtorOf::Struct, _) => "instantiate this tuple struct".to_string(),
+                    DefKind::Ctor(CtorOf::Variant, _) => {
+                        "instantiate this tuple variant".to_string()
+                    }
+                    kind => format!("call this {}", kind.descr(def_id)),
+                },
+                DefIdOrName::Name(name) => format!("call this {name}"),
             };
 
             let sugg = match expr.kind {
@@ -161,6 +143,179 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         false
     }
 
+    fn extract_callable_info(
+        &self,
+        expr: &Expr<'_>,
+        found: Ty<'tcx>,
+    ) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)> {
+        // Autoderef is useful here because sometimes we box callables, etc.
+        let Some((def_id_or_name, output, inputs)) = self.autoderef(expr.span, found).silence_errors().find_map(|(found, _)| {
+            match *found.kind() {
+                ty::FnPtr(fn_sig) =>
+                    Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())),
+                ty::FnDef(def_id, _) => {
+                    let fn_sig = found.fn_sig(self.tcx);
+                    Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs()))
+                }
+                ty::Closure(def_id, substs) => {
+                    let fn_sig = substs.as_closure().sig();
+                    Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..])))
+                }
+                ty::Opaque(def_id, substs) => {
+                    self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| {
+                        if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder()
+                        && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
+                        // args tuple will always be substs[1]
+                        && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
+                        {
+                            Some((
+                                DefIdOrName::DefId(def_id),
+                                pred.kind().rebind(proj.term.ty().unwrap()),
+                                pred.kind().rebind(args.as_slice()),
+                            ))
+                        } else {
+                            None
+                        }
+                    })
+                }
+                ty::Dynamic(data, _) => {
+                    data.iter().find_map(|pred| {
+                        if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder()
+                        && Some(proj.item_def_id) == self.tcx.lang_items().fn_once_output()
+                        // for existential projection, substs are shifted over by 1
+                        && let ty::Tuple(args) = proj.substs.type_at(0).kind()
+                        {
+                            Some((
+                                DefIdOrName::Name("trait object"),
+                                pred.rebind(proj.term.ty().unwrap()),
+                                pred.rebind(args.as_slice()),
+                            ))
+                        } else {
+                            None
+                        }
+                    })
+                }
+                ty::Param(param) => {
+                    let def_id = self.tcx.generics_of(self.body_id.owner).type_param(&param, self.tcx).def_id;
+                    self.tcx.predicates_of(self.body_id.owner).predicates.iter().find_map(|(pred, _)| {
+                        if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder()
+                        && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
+                        && proj.projection_ty.self_ty() == found
+                        // args tuple will always be substs[1]
+                        && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
+                        {
+                            Some((
+                                DefIdOrName::DefId(def_id),
+                                pred.kind().rebind(proj.term.ty().unwrap()),
+                                pred.kind().rebind(args.as_slice()),
+                            ))
+                        } else {
+                            None
+                        }
+                    })
+                }
+                _ => None,
+            }
+        }) else { return None; };
+
+        let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output);
+        let inputs = inputs
+            .skip_binder()
+            .iter()
+            .map(|ty| {
+                self.replace_bound_vars_with_fresh_vars(
+                    expr.span,
+                    infer::FnCall,
+                    inputs.rebind(*ty),
+                )
+            })
+            .collect();
+
+        // We don't want to register any extra obligations, which should be
+        // implied by wf, but also because that would possibly result in
+        // erroneous errors later on.
+        let infer::InferOk { value: output, obligations: _ } =
+            self.normalize_associated_types_in_as_infer_ok(expr.span, output);
+
+        if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) }
+    }
+
+    pub fn suggest_two_fn_call(
+        &self,
+        err: &mut Diagnostic,
+        lhs_expr: &'tcx hir::Expr<'tcx>,
+        lhs_ty: Ty<'tcx>,
+        rhs_expr: &'tcx hir::Expr<'tcx>,
+        rhs_ty: Ty<'tcx>,
+        can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool,
+    ) -> bool {
+        let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_expr, lhs_ty)
+            else { return false; };
+        let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_expr, rhs_ty)
+            else { return false; };
+
+        if can_satisfy(lhs_output_ty, rhs_output_ty) {
+            let mut sugg = vec![];
+            let mut applicability = Applicability::MachineApplicable;
+
+            for (expr, inputs) in [(lhs_expr, lhs_inputs), (rhs_expr, rhs_inputs)] {
+                let (sugg_call, this_applicability) = match inputs.len() {
+                    0 => ("".to_string(), Applicability::MachineApplicable),
+                    1..=4 => (
+                        inputs
+                            .iter()
+                            .map(|ty| {
+                                if ty.is_suggestable(self.tcx, false) {
+                                    format!("/* {ty} */")
+                                } else {
+                                    "/* value */".to_string()
+                                }
+                            })
+                            .collect::<Vec<_>>()
+                            .join(", "),
+                        Applicability::HasPlaceholders,
+                    ),
+                    _ => ("/* ... */".to_string(), Applicability::HasPlaceholders),
+                };
+
+                applicability = applicability.max(this_applicability);
+
+                match expr.kind {
+                    hir::ExprKind::Call(..)
+                    | hir::ExprKind::Path(..)
+                    | hir::ExprKind::Index(..)
+                    | hir::ExprKind::Lit(..) => {
+                        sugg.extend([(expr.span.shrink_to_hi(), format!("({sugg_call})"))]);
+                    }
+                    hir::ExprKind::Closure { .. } => {
+                        // Might be `{ expr } || { bool }`
+                        applicability = Applicability::MaybeIncorrect;
+                        sugg.extend([
+                            (expr.span.shrink_to_lo(), "(".to_string()),
+                            (expr.span.shrink_to_hi(), format!(")({sugg_call})")),
+                        ]);
+                    }
+                    _ => {
+                        sugg.extend([
+                            (expr.span.shrink_to_lo(), "(".to_string()),
+                            (expr.span.shrink_to_hi(), format!(")({sugg_call})")),
+                        ]);
+                    }
+                }
+            }
+
+            err.multipart_suggestion_verbose(
+                format!("use parentheses to call these"),
+                sugg,
+                applicability,
+            );
+
+            true
+        } else {
+            false
+        }
+    }
+
     pub fn suggest_deref_ref_or_into(
         &self,
         err: &mut Diagnostic,
@@ -178,12 +333,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             } else {
                 err.span_suggestion(sp, &msg, suggestion, applicability);
             }
-        } else if let (ty::FnDef(def_id, ..), true) =
-            (&found.kind(), self.suggest_fn_call(err, expr, expected, found))
+        } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected))
+            && let ty::FnDef(def_id, ..) = &found.kind()
+            && let Some(sp) = self.tcx.hir().span_if_local(*def_id)
         {
-            if let Some(sp) = self.tcx.hir().span_if_local(*def_id) {
-                err.span_label(sp, format!("{found} defined here"));
-            }
+            err.span_label(sp, format!("{found} defined here"));
         } else if !self.check_for_cast(err, expr, found, expected, expected_ty_expr) {
             let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id);
             if !methods.is_empty() {
@@ -911,3 +1065,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 }
+
+enum DefIdOrName {
+    DefId(DefId),
+    Name(&'static str),
+}
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index e532f392157..e99782fdc65 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -31,7 +31,7 @@ use std::cmp::Ordering;
 use std::iter;
 
 use super::probe::{Mode, ProbeScope};
-use super::{super::suggest_call_constructor, CandidateSource, MethodError, NoMatchData};
+use super::{CandidateSource, MethodError, NoMatchData};
 
 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool {
@@ -363,44 +363,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     );
                 }
 
-                if self.is_fn_ty(rcvr_ty, span) {
-                    if let SelfSource::MethodCall(expr) = source {
-                        let suggest = if let ty::FnDef(def_id, _) = rcvr_ty.kind() {
-                            if let Some(local_id) = def_id.as_local() {
-                                let hir_id = tcx.hir().local_def_id_to_hir_id(local_id);
-                                let node = tcx.hir().get(hir_id);
-                                let fields = node.tuple_fields();
-                                if let Some(fields) = fields
-                                    && let Some(DefKind::Ctor(of, _)) = self.tcx.opt_def_kind(local_id) {
-                                        Some((fields.len(), of))
-                                } else {
-                                    None
-                                }
-                            } else {
-                                // The logic here isn't smart but `associated_item_def_ids`
-                                // doesn't work nicely on local.
-                                if let DefKind::Ctor(of, _) = tcx.def_kind(def_id) {
-                                    let parent_def_id = tcx.parent(*def_id);
-                                    Some((tcx.associated_item_def_ids(parent_def_id).len(), of))
-                                } else {
-                                    None
-                                }
-                            }
-                        } else {
-                            None
-                        };
-
-                        // If the function is a tuple constructor, we recommend that they call it
-                        if let Some((fields, kind)) = suggest {
-                            suggest_call_constructor(expr.span, kind, fields, &mut err);
-                        } else {
-                            // General case
-                            err.span_label(
-                                expr.span,
-                                "this is a function, perhaps you wish to call it",
-                            );
-                        }
-                    }
+                if let SelfSource::MethodCall(rcvr_expr) = source {
+                    self.suggest_fn_call(&mut err, rcvr_expr, rcvr_ty, |output_ty| {
+                        let call_expr = self
+                            .tcx
+                            .hir()
+                            .expect_expr(self.tcx.hir().get_parent_node(rcvr_expr.hir_id));
+                        let probe = self.lookup_probe(
+                            span,
+                            item_name,
+                            output_ty,
+                            call_expr,
+                            ProbeScope::AllTraits,
+                        );
+                        probe.is_ok()
+                    });
                 }
 
                 let mut custom_span_label = false;
diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs
index f8d839b6483..3281dd8298b 100644
--- a/compiler/rustc_typeck/src/check/mod.rs
+++ b/compiler/rustc_typeck/src/check/mod.rs
@@ -96,7 +96,6 @@ use check::{check_abi, check_fn, check_mod_item_types};
 pub use diverges::Diverges;
 pub use expectation::Expectation;
 pub use fn_ctxt::*;
-use hir::def::CtorOf;
 pub use inherited::{Inherited, InheritedBuilder};
 
 use crate::astconv::AstConv;
@@ -960,31 +959,3 @@ fn has_expected_num_generic_args<'tcx>(
         generics.count() == expected + if generics.has_self { 1 } else { 0 }
     })
 }
-
-/// Suggests calling the constructor of a tuple struct or enum variant
-///
-/// * `snippet` - The snippet of code that references the constructor
-/// * `span` - The span of the snippet
-/// * `params` - The number of parameters the constructor accepts
-/// * `err` - A mutable diagnostic builder to add the suggestion to
-fn suggest_call_constructor(span: Span, kind: CtorOf, params: usize, err: &mut Diagnostic) {
-    // Note: tuple-structs don't have named fields, so just use placeholders
-    let args = vec!["_"; params].join(", ");
-    let applicable = if params > 0 {
-        Applicability::HasPlaceholders
-    } else {
-        // When n = 0, it's an empty-tuple struct/enum variant
-        // so we trivially know how to construct it
-        Applicability::MachineApplicable
-    };
-    let kind = match kind {
-        CtorOf::Struct => "a struct",
-        CtorOf::Variant => "an enum variant",
-    };
-    err.span_label(span, &format!("this is the constructor of {kind}"));
-    err.multipart_suggestion(
-        "call the constructor",
-        vec![(span.shrink_to_lo(), "(".to_string()), (span.shrink_to_hi(), format!(")({args})"))],
-        applicable,
-    );
-}
diff --git a/compiler/rustc_typeck/src/check/op.rs b/compiler/rustc_typeck/src/check/op.rs
index eb0c51bb2f9..952086e898f 100644
--- a/compiler/rustc_typeck/src/check/op.rs
+++ b/compiler/rustc_typeck/src/check/op.rs
@@ -410,26 +410,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         };
                         let mut err = struct_span_err!(self.tcx.sess, op.span, E0369, "{message}");
                         if !lhs_expr.span.eq(&rhs_expr.span) {
-                            self.add_type_neq_err_label(
-                                &mut err,
-                                lhs_expr.span,
-                                lhs_ty,
-                                rhs_ty,
-                                rhs_expr,
-                                op,
-                                is_assign,
-                                expected,
-                            );
-                            self.add_type_neq_err_label(
-                                &mut err,
-                                rhs_expr.span,
-                                rhs_ty,
-                                lhs_ty,
-                                lhs_expr,
-                                op,
-                                is_assign,
-                                expected,
-                            );
+                            err.span_label(lhs_expr.span, lhs_ty.to_string());
+                            err.span_label(rhs_expr.span, rhs_ty.to_string());
                         }
                         self.note_unmet_impls_on_type(&mut err, errors);
                         (err, missing_trait, use_output)
@@ -468,17 +450,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     }
                 };
 
+                let is_compatible = |lhs_ty, rhs_ty| {
+                    self.lookup_op_method(
+                        lhs_ty,
+                        Some(rhs_ty),
+                        Some(rhs_expr),
+                        Op::Binary(op, is_assign),
+                        expected,
+                    )
+                    .is_ok()
+                };
+
                 // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
                 // `a += b` => `*a += b` if a is a mut ref.
-                if is_assign == IsAssign::Yes
-                    && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
-                        suggest_deref_binop(lhs_deref_ty);
+                if !op.span.can_be_used_for_suggestions() {
+                    // Suppress suggestions when lhs and rhs are not in the same span as the error
+                } else if is_assign == IsAssign::Yes
+                    && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty)
+                {
+                    suggest_deref_binop(lhs_deref_ty);
                 } else if is_assign == IsAssign::No
-                    && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() {
-                    if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty, lhs_expr.span) {
+                    && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind()
+                {
+                    if self.type_is_copy_modulo_regions(
+                        self.param_env,
+                        *lhs_deref_ty,
+                        lhs_expr.span,
+                    ) {
                         suggest_deref_binop(*lhs_deref_ty);
                     }
+                } else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| {
+                    is_compatible(lhs_ty, rhs_ty)
+                }) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| {
+                    is_compatible(lhs_ty, rhs_ty)
+                }) || self.suggest_two_fn_call(
+                    &mut err,
+                    rhs_expr,
+                    rhs_ty,
+                    lhs_expr,
+                    lhs_ty,
+                    |lhs_ty, rhs_ty| is_compatible(lhs_ty, rhs_ty),
+                ) {
+                    // Cool
                 }
+
                 if let Some(missing_trait) = missing_trait {
                     let mut visitor = TypeParamVisitor(vec![]);
                     visitor.visit_ty(lhs_ty);
@@ -548,69 +563,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         (lhs_ty, rhs_ty, return_ty)
     }
 
-    /// If one of the types is an uncalled function and calling it would yield the other type,
-    /// suggest calling the function. Returns `true` if suggestion would apply (even if not given).
-    fn add_type_neq_err_label(
-        &self,
-        err: &mut Diagnostic,
-        span: Span,
-        ty: Ty<'tcx>,
-        other_ty: Ty<'tcx>,
-        other_expr: &'tcx hir::Expr<'tcx>,
-        op: hir::BinOp,
-        is_assign: IsAssign,
-        expected: Expectation<'tcx>,
-    ) -> bool /* did we suggest to call a function because of missing parentheses? */ {
-        err.span_label(span, ty.to_string());
-        if let FnDef(def_id, _) = *ty.kind() {
-            if !self.tcx.has_typeck_results(def_id) {
-                return false;
-            }
-            // FIXME: Instead of exiting early when encountering bound vars in
-            // the function signature, consider keeping the binder here and
-            // propagating it downwards.
-            let Some(fn_sig) = self.tcx.fn_sig(def_id).no_bound_vars() else {
-                return false;
-            };
-
-            let other_ty = if let FnDef(def_id, _) = *other_ty.kind() {
-                if !self.tcx.has_typeck_results(def_id) {
-                    return false;
-                }
-                // We're emitting a suggestion, so we can just ignore regions
-                self.tcx.fn_sig(def_id).skip_binder().output()
-            } else {
-                other_ty
-            };
-
-            if self
-                .lookup_op_method(
-                    fn_sig.output(),
-                    Some(other_ty),
-                    Some(other_expr),
-                    Op::Binary(op, is_assign),
-                    expected,
-                )
-                .is_ok()
-            {
-                let (variable_snippet, applicability) = if !fn_sig.inputs().is_empty() {
-                    ("( /* arguments */ )", Applicability::HasPlaceholders)
-                } else {
-                    ("()", Applicability::MaybeIncorrect)
-                };
-
-                err.span_suggestion_verbose(
-                    span.shrink_to_hi(),
-                    "you might have forgotten to call this function",
-                    variable_snippet,
-                    applicability,
-                );
-                return true;
-            }
-        }
-        false
-    }
-
     /// Provide actionable suggestions when trying to add two strings with incorrect types,
     /// like `&str + &str`, `String + String` and `&str + &String`.
     ///
diff --git a/compiler/rustc_typeck/src/check/wfcheck.rs b/compiler/rustc_typeck/src/check/wfcheck.rs
index ce42647c837..ba42453bd60 100644
--- a/compiler/rustc_typeck/src/check/wfcheck.rs
+++ b/compiler/rustc_typeck/src/check/wfcheck.rs
@@ -1262,7 +1262,11 @@ fn check_impl<'tcx>(
             }
             None => {
                 let self_ty = tcx.type_of(item.def_id);
-                let self_ty = wfcx.normalize(item.span, None, self_ty);
+                let self_ty = wfcx.normalize(
+                    item.span,
+                    Some(WellFormedLoc::Ty(item.hir_id().expect_owner())),
+                    self_ty,
+                );
                 wfcx.register_wf_obligation(
                     ast_self_ty.span,
                     Some(WellFormedLoc::Ty(item.hir_id().expect_owner())),
@@ -1307,7 +1311,11 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id
                     // parameter includes another (e.g., `<T, U = T>`). In those cases, we can't
                     // be sure if it will error or not as user might always specify the other.
                     if !ty.needs_subst() {
-                        wfcx.register_wf_obligation(tcx.def_span(param.def_id), None, ty.into());
+                        wfcx.register_wf_obligation(
+                            tcx.def_span(param.def_id),
+                            Some(WellFormedLoc::Ty(param.def_id.expect_local())),
+                            ty.into(),
+                        );
                     }
                 }
             }
@@ -1512,7 +1520,14 @@ fn check_fn_or_method<'tcx>(
         );
     }
 
-    wfcx.register_wf_obligation(hir_decl.output.span(), None, sig.output().into());
+    wfcx.register_wf_obligation(
+        hir_decl.output.span(),
+        Some(WellFormedLoc::Param {
+            function: def_id,
+            param_idx: sig.inputs().len().try_into().unwrap(),
+        }),
+        sig.output().into(),
+    );
 
     check_where_clauses(wfcx, span, def_id);
 }
diff --git a/compiler/rustc_typeck/src/hir_wf_check.rs b/compiler/rustc_typeck/src/hir_wf_check.rs
index fd9715e6ca3..7b080dc2942 100644
--- a/compiler/rustc_typeck/src/hir_wf_check.rs
+++ b/compiler/rustc_typeck/src/hir_wf_check.rs
@@ -140,6 +140,10 @@ fn diagnostic_hir_wf_check<'tcx>(
             hir::Node::ForeignItem(ForeignItem {
                 kind: ForeignItemKind::Static(ty, _), ..
             }) => Some(*ty),
+            hir::Node::GenericParam(hir::GenericParam {
+                kind: hir::GenericParamKind::Type { default: Some(ty), .. },
+                ..
+            }) => Some(*ty),
             ref node => bug!("Unexpected node {:?}", node),
         },
         WellFormedLoc::Param { function: _, param_idx } => {