about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_borrowck/src/borrowck_errors.rs7
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_name.rs2
-rw-r--r--compiler/rustc_borrowck/src/lib.rs1
-rw-r--r--compiler/rustc_borrowck/src/region_infer/mod.rs4
-rw-r--r--compiler/rustc_borrowck/src/region_infer/opaque_types.rs6
-rw-r--r--compiler/rustc_borrowck/src/renumber.rs2
-rw-r--r--compiler/rustc_borrowck/src/type_check/constraint_conversion.rs8
-rw-r--r--compiler/rustc_borrowck/src/type_check/free_region_relations.rs4
-rw-r--r--compiler/rustc_borrowck/src/type_check/input_output.rs2
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs4
-rw-r--r--compiler/rustc_borrowck/src/universal_regions.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/assert/context.rs96
-rw-r--r--compiler/rustc_builtin_macros/src/cfg.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/clone.rs48
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs44
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/debug.rs205
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/decodable.rs5
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs109
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/mod.rs15
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/proc_macro_harness.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/test_harness.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/builder.rs16
-rw-r--r--compiler/rustc_codegen_ssa/src/lib.rs1
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/intrinsic.rs58
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs83
-rw-r--r--compiler/rustc_const_eval/src/interpret/mod.rs2
-rw-r--r--compiler/rustc_data_structures/src/profiling.rs18
-rw-r--r--compiler/rustc_data_structures/src/sync.rs7
-rw-r--r--compiler/rustc_driver/src/lib.rs2
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0093.md4
-rw-r--r--compiler/rustc_error_messages/locales/en-US/builtin_macros.ftl4
-rw-r--r--compiler/rustc_error_messages/locales/en-US/privacy.ftl12
-rw-r--r--compiler/rustc_error_messages/src/lib.rs39
-rw-r--r--compiler/rustc_errors/src/emitter.rs5
-rw-r--r--compiler/rustc_expand/src/base.rs3
-rw-r--r--compiler/rustc_expand/src/build.rs31
-rw-r--r--compiler/rustc_expand/src/lib.rs1
-rw-r--r--compiler/rustc_expand/src/proc_macro_server.rs479
-rw-r--r--compiler/rustc_hir/src/intravisit.rs24
-rw-r--r--compiler/rustc_hir/src/itemlikevisit.rs9
-rw-r--r--compiler/rustc_hir/src/lib.rs1
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs2
-rw-r--r--compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs2
-rw-r--r--compiler/rustc_infer/src/infer/opaque_types.rs14
-rw-r--r--compiler/rustc_infer/src/infer/outlives/obligations.rs13
-rw-r--r--compiler/rustc_infer/src/infer/outlives/verify.rs9
-rw-r--r--compiler/rustc_infer/src/infer/resolve.rs6
-rw-r--r--compiler/rustc_lexer/src/unescape.rs2
-rw-r--r--compiler/rustc_lint/src/internal.rs9
-rw-r--r--compiler/rustc_lint/src/types.rs148
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs1
-rw-r--r--compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp2
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs460
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs37
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs11
-rw-r--r--compiler/rustc_macros/src/diagnostics/mod.rs10
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs84
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs38
-rw-r--r--compiler/rustc_middle/src/hir/map/mod.rs21
-rw-r--r--compiler/rustc_middle/src/hir/mod.rs17
-rw-r--r--compiler/rustc_middle/src/query/mod.rs19
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs71
-rw-r--r--compiler/rustc_middle/src/ty/fold.rs93
-rw-r--r--compiler/rustc_middle/src/ty/normalize_erasing_regions.rs4
-rw-r--r--compiler/rustc_middle/src/ty/print/pretty.rs233
-rw-r--r--compiler/rustc_middle/src/ty/subst.rs2
-rw-r--r--compiler/rustc_mir_build/src/thir/cx/expr.rs186
-rw-r--r--compiler/rustc_parse/src/lib.rs1
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs26
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs30
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs6
-rw-r--r--compiler/rustc_privacy/Cargo.toml7
-rw-r--r--compiler/rustc_privacy/src/errors.rs75
-rw-r--r--compiler/rustc_privacy/src/lib.rs92
-rw-r--r--compiler/rustc_session/src/session.rs23
-rw-r--r--compiler/rustc_span/src/source_map.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs5
-rw-r--r--compiler/rustc_target/src/lib.rs1
-rw-r--r--compiler/rustc_target/src/spec/aarch64_apple_darwin.rs2
-rw-r--r--compiler/rustc_target/src/spec/aarch64_unknown_uefi.rs11
-rw-r--r--compiler/rustc_target/src/spec/armv6k_nintendo_3ds.rs12
-rw-r--r--compiler/rustc_target/src/spec/armv7_linux_androideabi.rs2
-rw-r--r--compiler/rustc_target/src/spec/asmjs_unknown_emscripten.rs6
-rw-r--r--compiler/rustc_target/src/spec/avr_gnu_base.rs9
-rw-r--r--compiler/rustc_target/src/spec/avr_unknown_gnu_atmega328.rs2
-rw-r--r--compiler/rustc_target/src/spec/fuchsia_base.rs33
-rw-r--r--compiler/rustc_target/src/spec/hermit_base.rs9
-rw-r--r--compiler/rustc_target/src/spec/i686_apple_darwin.rs2
-rw-r--r--compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs9
-rw-r--r--compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs28
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_freebsd.rs4
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_haiku.rs2
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs2
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_netbsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_openbsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs9
-rw-r--r--compiler/rustc_target/src/spec/i686_wrs_vxworks.rs2
-rw-r--r--compiler/rustc_target/src/spec/illumos_base.rs11
-rw-r--r--compiler/rustc_target/src/spec/mipsel_sony_psp.rs6
-rw-r--r--compiler/rustc_target/src/spec/mod.rs38
-rw-r--r--compiler/rustc_target/src/spec/msvc_base.rs13
-rw-r--r--compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs6
-rw-r--r--compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs3
-rw-r--r--compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs3
-rw-r--r--compiler/rustc_target/src/spec/sparc64_unknown_netbsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/sparc64_unknown_openbsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs2
-rw-r--r--compiler/rustc_target/src/spec/sparcv9_sun_solaris.rs2
-rw-r--r--compiler/rustc_target/src/spec/tests/tests_impl.rs97
-rw-r--r--compiler/rustc_target/src/spec/thumbv7a_pc_windows_msvc.rs9
-rw-r--r--compiler/rustc_target/src/spec/thumbv7neon_linux_androideabi.rs2
-rw-r--r--compiler/rustc_target/src/spec/uefi_msvc_base.rs40
-rw-r--r--compiler/rustc_target/src/spec/wasm32_unknown_emscripten.rs26
-rw-r--r--compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs45
-rw-r--r--compiler/rustc_target/src/spec/wasm32_wasi.rs6
-rw-r--r--compiler/rustc_target/src/spec/wasm64_unknown_unknown.rs30
-rw-r--r--compiler/rustc_target/src/spec/wasm_base.rs105
-rw-r--r--compiler/rustc_target/src/spec/windows_gnu_base.rs82
-rw-r--r--compiler/rustc_target/src/spec/windows_gnullvm_base.rs33
-rw-r--r--compiler/rustc_target/src/spec/windows_uwp_gnu_base.rs31
-rw-r--r--compiler/rustc_target/src/spec/windows_uwp_msvc_base.rs9
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_darwin.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs73
-rw-r--r--compiler/rustc_target/src/spec/x86_64_linux_android.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_pc_solaris.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs9
-rw-r--r--compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_sun_solaris.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_none_linuxkernel.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_redox.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs9
-rw-r--r--compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/auto_trait.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/coherence.rs6
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs7
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs5
-rw-r--r--compiler/rustc_trait_selection/src/traits/object_safety.rs78
-rw-r--r--compiler/rustc_trait_selection/src/traits/project.rs21
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/normalize.rs6
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/confirmation.rs6
-rw-r--r--compiler/rustc_trait_selection/src/traits/specialize/mod.rs2
-rw-r--r--compiler/rustc_type_ir/src/lib.rs1
-rw-r--r--compiler/rustc_type_ir/src/sty.rs162
-rw-r--r--compiler/rustc_typeck/src/check/check.rs13
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs129
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/checks.rs1217
-rw-r--r--compiler/rustc_typeck/src/check/generator_interior.rs2
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs41
-rw-r--r--compiler/rustc_typeck/src/check/mod.rs10
-rw-r--r--compiler/rustc_typeck/src/check/pat.rs51
-rw-r--r--compiler/rustc_typeck/src/check/regionck.rs1
-rw-r--r--compiler/rustc_typeck/src/check/wfcheck.rs174
-rw-r--r--compiler/rustc_typeck/src/coherence/mod.rs19
-rw-r--r--compiler/rustc_typeck/src/coherence/orphan.rs176
-rw-r--r--compiler/rustc_typeck/src/coherence/unsafety.rs35
-rw-r--r--compiler/rustc_typeck/src/collect.rs4
-rw-r--r--compiler/rustc_typeck/src/collect/type_of.rs2
-rw-r--r--compiler/rustc_typeck/src/errors.rs70
-rw-r--r--compiler/rustc_typeck/src/impl_wf_check.rs54
-rw-r--r--compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs41
-rw-r--r--compiler/rustc_typeck/src/lib.rs18
183 files changed, 3305 insertions, 3097 deletions
diff --git a/compiler/rustc_borrowck/src/borrowck_errors.rs b/compiler/rustc_borrowck/src/borrowck_errors.rs
index a1233d62cb0..708fe8719a1 100644
--- a/compiler/rustc_borrowck/src/borrowck_errors.rs
+++ b/compiler/rustc_borrowck/src/borrowck_errors.rs
@@ -1,4 +1,6 @@
-use rustc_errors::{struct_span_err, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed, MultiSpan};
+use rustc_errors::{
+    struct_span_err, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, ErrorGuaranteed, MultiSpan,
+};
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_span::Span;
 
@@ -476,10 +478,11 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
         struct_span_err!(self, span, E0716, "temporary value dropped while borrowed",)
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     fn struct_span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
         self.infcx.tcx.sess.struct_span_err_with_code(sp, msg, code)
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
index 8f3699553d9..e60e11f11df 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
@@ -879,7 +879,7 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
         }
 
         let mut found = false;
-        tcx.fold_regions(tcx.type_of(body_parent_did), &mut true, |r: ty::Region<'tcx>, _| {
+        tcx.fold_regions(tcx.type_of(body_parent_did), |r: ty::Region<'tcx>, _| {
             if *r == ty::ReEarlyBound(region) {
                 found = true;
             }
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 8ef2974c372..a2df072aa31 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -6,6 +6,7 @@
 #![feature(let_else)]
 #![feature(min_specialization)]
 #![feature(never_type)]
+#![feature(rustc_attrs)]
 #![feature(stmt_expr_attributes)]
 #![feature(trusted_step)]
 #![feature(try_blocks)]
diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs
index 0fe3b45bc7c..f5c9392948b 100644
--- a/compiler/rustc_borrowck/src/region_infer/mod.rs
+++ b/compiler/rustc_borrowck/src/region_infer/mod.rs
@@ -1009,7 +1009,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
 
         debug!("try_promote_type_test_subject(ty = {:?})", ty);
 
-        let ty = tcx.fold_regions(ty, &mut false, |r, _depth| {
+        let ty = tcx.fold_regions(ty, |r, _depth| {
             let region_vid = self.to_region_vid(r);
 
             // The challenge if this. We have some region variable `r`
@@ -1289,7 +1289,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
     where
         T: TypeFoldable<'tcx>,
     {
-        tcx.fold_regions(value, &mut false, |r, _db| {
+        tcx.fold_regions(value, |r, _db| {
             let vid = self.to_region_vid(r);
             let scc = self.constraint_sccs.scc(vid);
             let repr = self.scc_representatives[scc];
diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
index 81073758791..d182c0cf4e8 100644
--- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
+++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
@@ -59,7 +59,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             debug!(?concrete_type, ?substs);
 
             let mut subst_regions = vec![self.universal_regions.fr_static];
-            let universal_substs = infcx.tcx.fold_regions(substs, &mut false, |region, _| {
+            let universal_substs = infcx.tcx.fold_regions(substs, |region, _| {
                 if let ty::RePlaceholder(..) = region.kind() {
                     // Higher kinded regions don't need remapping, they don't refer to anything outside of this the substs.
                     return region;
@@ -91,7 +91,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             subst_regions.dedup();
 
             let universal_concrete_type =
-                infcx.tcx.fold_regions(concrete_type, &mut false, |region, _| match *region {
+                infcx.tcx.fold_regions(concrete_type, |region, _| match *region {
                     ty::ReVar(vid) => subst_regions
                         .iter()
                         .find(|ur_vid| self.eval_equal(vid, **ur_vid))
@@ -146,7 +146,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
     where
         T: TypeFoldable<'tcx>,
     {
-        tcx.fold_regions(ty, &mut false, |region, _| match *region {
+        tcx.fold_regions(ty, |region, _| match *region {
             ty::ReVar(vid) => {
                 // Find something that we can name
                 let upper_bound = self.approx_universal_upper_bound(vid);
diff --git a/compiler/rustc_borrowck/src/renumber.rs b/compiler/rustc_borrowck/src/renumber.rs
index 2876d60527f..7a8ce621c5d 100644
--- a/compiler/rustc_borrowck/src/renumber.rs
+++ b/compiler/rustc_borrowck/src/renumber.rs
@@ -31,7 +31,7 @@ pub fn renumber_regions<'tcx, T>(infcx: &InferCtxt<'_, 'tcx>, value: T) -> T
 where
     T: TypeFoldable<'tcx>,
 {
-    infcx.tcx.fold_regions(value, &mut false, |_region, _depth| {
+    infcx.tcx.fold_regions(value, |_region, _depth| {
         let origin = NllRegionVariableOrigin::Existential { from_forall: false };
         infcx.next_nll_region_var(origin)
     })
diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
index 3c9e3870aea..5e33d9d25c2 100644
--- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
+++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
@@ -23,7 +23,7 @@ pub(crate) struct ConstraintConversion<'a, 'tcx> {
     tcx: TyCtxt<'tcx>,
     universal_regions: &'a UniversalRegions<'tcx>,
     region_bound_pairs: &'a RegionBoundPairs<'tcx>,
-    implicit_region_bound: Option<ty::Region<'tcx>>,
+    implicit_region_bound: ty::Region<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     locations: Locations,
     span: Span,
@@ -36,7 +36,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
         infcx: &'a InferCtxt<'a, 'tcx>,
         universal_regions: &'a UniversalRegions<'tcx>,
         region_bound_pairs: &'a RegionBoundPairs<'tcx>,
-        implicit_region_bound: Option<ty::Region<'tcx>>,
+        implicit_region_bound: ty::Region<'tcx>,
         param_env: ty::ParamEnv<'tcx>,
         locations: Locations,
         span: Span,
@@ -108,7 +108,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
                 // create new region variables, which can't be done later when
                 // verifying these bounds.
                 if t1.has_placeholders() {
-                    t1 = tcx.fold_regions(t1, &mut false, |r, _| match *r {
+                    t1 = tcx.fold_regions(t1, |r, _| match *r {
                         ty::RePlaceholder(placeholder) => {
                             self.constraints.placeholder_region(self.infcx, placeholder)
                         }
@@ -120,7 +120,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
                     &mut *self,
                     tcx,
                     region_bound_pairs,
-                    implicit_region_bound,
+                    Some(implicit_region_bound),
                     param_env,
                 )
                 .type_must_outlive(origin, t1, r2);
diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
index 813307356c4..421ef5be812 100644
--- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
+++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
@@ -61,7 +61,7 @@ pub(crate) struct CreateResult<'tcx> {
 pub(crate) fn create<'tcx>(
     infcx: &InferCtxt<'_, 'tcx>,
     param_env: ty::ParamEnv<'tcx>,
-    implicit_region_bound: Option<ty::Region<'tcx>>,
+    implicit_region_bound: ty::Region<'tcx>,
     universal_regions: &Rc<UniversalRegions<'tcx>>,
     constraints: &mut MirTypeckRegionConstraints<'tcx>,
 ) -> CreateResult<'tcx> {
@@ -223,7 +223,7 @@ struct UniversalRegionRelationsBuilder<'this, 'tcx> {
     infcx: &'this InferCtxt<'this, 'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     universal_regions: Rc<UniversalRegions<'tcx>>,
-    implicit_region_bound: Option<ty::Region<'tcx>>,
+    implicit_region_bound: ty::Region<'tcx>,
     constraints: &'this mut MirTypeckRegionConstraints<'tcx>,
 
     // outputs:
diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs
index 2259a59e195..2a6ca5246da 100644
--- a/compiler/rustc_borrowck/src/type_check/input_output.rs
+++ b/compiler/rustc_borrowck/src/type_check/input_output.rs
@@ -230,7 +230,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
                 self.infcx,
                 &self.borrowck_context.universal_regions,
                 &self.region_bound_pairs,
-                Some(self.implicit_region_bound),
+                self.implicit_region_bound,
                 self.param_env,
                 Locations::All(DUMMY_SP),
                 DUMMY_SP,
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 355254fe942..542fc6b0f48 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -157,7 +157,7 @@ pub(crate) fn type_check<'mir, 'tcx>(
     } = free_region_relations::create(
         infcx,
         param_env,
-        Some(implicit_region_bound),
+        implicit_region_bound,
         universal_regions,
         &mut constraints,
     );
@@ -1142,7 +1142,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             self.infcx,
             self.borrowck_context.universal_regions,
             self.region_bound_pairs,
-            Some(self.implicit_region_bound),
+            self.implicit_region_bound,
             self.param_env,
             locations,
             locations.span(self.body),
diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs
index c2c093f9f2f..89d84fcf09c 100644
--- a/compiler/rustc_borrowck/src/universal_regions.rs
+++ b/compiler/rustc_borrowck/src/universal_regions.rs
@@ -725,7 +725,7 @@ impl<'cx, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'cx, 'tcx> {
     where
         T: TypeFoldable<'tcx>,
     {
-        self.tcx.fold_regions(value, &mut false, |_region, _depth| self.next_nll_region_var(origin))
+        self.tcx.fold_regions(value, |_region, _depth| self.next_nll_region_var(origin))
     }
 
     #[instrument(level = "debug", skip(self, indices))]
@@ -817,9 +817,7 @@ impl<'tcx> UniversalRegionIndices<'tcx> {
     where
         T: TypeFoldable<'tcx>,
     {
-        tcx.fold_regions(value, &mut false, |region, _| {
-            tcx.mk_region(ty::ReVar(self.to_region_vid(region)))
-        })
+        tcx.fold_regions(value, |region, _| tcx.mk_region(ty::ReVar(self.to_region_vid(region))))
     }
 }
 
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
index cad30181212..9e50d33486c 100644
--- a/compiler/rustc_builtin_macros/src/assert/context.rs
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -1,11 +1,10 @@
-use crate::assert::expr_if_not;
 use rustc_ast::{
     attr,
     ptr::P,
     token,
     tokenstream::{DelimSpan, TokenStream, TokenTree},
-    BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability, Path,
-    PathSegment, Stmt, StructRest, UseTree, UseTreeKind, DUMMY_NODE_ID,
+    BinOpKind, BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability,
+    Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, DUMMY_NODE_ID,
 };
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashSet;
@@ -16,11 +15,19 @@ use rustc_span::{
 };
 
 pub(super) struct Context<'cx, 'a> {
+    // An optimization.
+    //
+    // Elements that aren't consumed (PartialEq, PartialOrd, ...) can be copied **after** the
+    // `assert!` expression fails rather than copied on-the-fly.
+    best_case_captures: Vec<Stmt>,
     // Top-level `let captureN = Capture::new()` statements
     capture_decls: Vec<Capture>,
     cx: &'cx ExtCtxt<'a>,
     // Formatting string used for debugging
     fmt_string: String,
+    // If the current expression being visited consumes itself. Used to construct
+    // `best_case_captures`.
+    is_consumed: bool,
     // Top-level `let __local_bindN = &expr` statements
     local_bind_decls: Vec<Stmt>,
     // Used to avoid capturing duplicated paths
@@ -36,9 +43,11 @@ pub(super) struct Context<'cx, 'a> {
 impl<'cx, 'a> Context<'cx, 'a> {
     pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
         Self {
+            best_case_captures: <_>::default(),
             capture_decls: <_>::default(),
             cx,
             fmt_string: <_>::default(),
+            is_consumed: true,
             local_bind_decls: <_>::default(),
             paths: <_>::default(),
             span,
@@ -69,14 +78,22 @@ impl<'cx, 'a> Context<'cx, 'a> {
         self.manage_cond_expr(&mut cond_expr);
         let initial_imports = self.build_initial_imports();
         let panic = self.build_panic(&expr_str, panic_path);
+        let cond_expr_with_unlikely = self.build_unlikely(cond_expr);
+
+        let Self { best_case_captures, capture_decls, cx, local_bind_decls, span, .. } = self;
 
-        let Self { capture_decls, cx, local_bind_decls, span, .. } = self;
+        let mut assert_then_stmts = Vec::with_capacity(2);
+        assert_then_stmts.extend(best_case_captures);
+        assert_then_stmts.push(self.cx.stmt_expr(panic));
+        let assert_then = self.cx.block(span, assert_then_stmts);
 
         let mut stmts = Vec::with_capacity(4);
         stmts.push(initial_imports);
         stmts.extend(capture_decls.into_iter().map(|c| c.decl));
         stmts.extend(local_bind_decls);
-        stmts.push(cx.stmt_expr(expr_if_not(cx, span, cond_expr, panic, None)));
+        stmts.push(
+            cx.stmt_expr(cx.expr(span, ExprKind::If(cond_expr_with_unlikely, assert_then, None))),
+        );
         cx.expr_block(cx.block(span, stmts))
     }
 
@@ -115,6 +132,16 @@ impl<'cx, 'a> Context<'cx, 'a> {
         )
     }
 
+    /// Takes the conditional expression of `assert!` and then wraps it inside `unlikely`
+    fn build_unlikely(&self, cond_expr: P<Expr>) -> P<Expr> {
+        let unlikely_path = self.cx.std_path(&[sym::intrinsics, sym::unlikely]);
+        self.cx.expr_call(
+            self.span,
+            self.cx.expr_path(self.cx.path(self.span, unlikely_path)),
+            vec![self.cx.expr(self.span, ExprKind::Unary(UnOp::Not, cond_expr))],
+        )
+    }
+
     /// The necessary custom `panic!(...)` expression.
     ///
     /// panic!(
@@ -167,17 +194,39 @@ impl<'cx, 'a> Context<'cx, 'a> {
     /// See [Self::manage_initial_capture] and [Self::manage_try_capture]
     fn manage_cond_expr(&mut self, expr: &mut P<Expr>) {
         match (*expr).kind {
-            ExprKind::AddrOf(_, _, ref mut local_expr) => {
-                self.manage_cond_expr(local_expr);
+            ExprKind::AddrOf(_, mutability, ref mut local_expr) => {
+                self.with_is_consumed_management(
+                    matches!(mutability, Mutability::Mut),
+                    |this| this.manage_cond_expr(local_expr)
+                );
             }
             ExprKind::Array(ref mut local_exprs) => {
                 for local_expr in local_exprs {
                     self.manage_cond_expr(local_expr);
                 }
             }
-            ExprKind::Binary(_, ref mut lhs, ref mut rhs) => {
-                self.manage_cond_expr(lhs);
-                self.manage_cond_expr(rhs);
+            ExprKind::Binary(ref op, ref mut lhs, ref mut rhs) => {
+                self.with_is_consumed_management(
+                    matches!(
+                        op.node,
+                        BinOpKind::Add
+                            | BinOpKind::And
+                            | BinOpKind::BitAnd
+                            | BinOpKind::BitOr
+                            | BinOpKind::BitXor
+                            | BinOpKind::Div
+                            | BinOpKind::Mul
+                            | BinOpKind::Or
+                            | BinOpKind::Rem
+                            | BinOpKind::Shl
+                            | BinOpKind::Shr
+                            | BinOpKind::Sub
+                    ),
+                    |this| {
+                        this.manage_cond_expr(lhs);
+                        this.manage_cond_expr(rhs);
+                    }
+                );
             }
             ExprKind::Call(_, ref mut local_exprs) => {
                 for local_expr in local_exprs {
@@ -228,8 +277,11 @@ impl<'cx, 'a> Context<'cx, 'a> {
                     self.manage_cond_expr(local_expr);
                 }
             }
-            ExprKind::Unary(_, ref mut local_expr) => {
-                self.manage_cond_expr(local_expr);
+            ExprKind::Unary(un_op, ref mut local_expr) => {
+                self.with_is_consumed_management(
+                    matches!(un_op, UnOp::Neg | UnOp::Not),
+                    |this| this.manage_cond_expr(local_expr)
+                );
             }
             // Expressions that are not worth or can not be captured.
             //
@@ -337,9 +389,23 @@ impl<'cx, 'a> Context<'cx, 'a> {
             ))
             .add_trailing_semicolon();
         let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
-        let ret = self.cx.stmt_expr(local_bind_path);
-        let block = self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret]));
-        *expr = self.cx.expr_deref(self.span, block);
+        let rslt = if self.is_consumed {
+            let ret = self.cx.stmt_expr(local_bind_path);
+            self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret]))
+        } else {
+            self.best_case_captures.push(try_capture_call);
+            local_bind_path
+        };
+        *expr = self.cx.expr_deref(self.span, rslt);
+    }
+
+    // Calls `f` with the internal `is_consumed` set to `curr_is_consumed` and then
+    // sets the internal `is_consumed` back to its original value.
+    fn with_is_consumed_management(&mut self, curr_is_consumed: bool, f: impl FnOnce(&mut Self)) {
+        let prev_is_consumed = self.is_consumed;
+        self.is_consumed = curr_is_consumed;
+        f(self);
+        self.is_consumed = prev_is_consumed;
     }
 }
 
diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs
index c75d83bd0a0..aa355150b4f 100644
--- a/compiler/rustc_builtin_macros/src/cfg.rs
+++ b/compiler/rustc_builtin_macros/src/cfg.rs
@@ -36,7 +36,7 @@ pub fn expand_cfg(
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "builtin-macros-requires-cfg-pattern")]
+#[error(builtin_macros::requires_cfg_pattern)]
 struct RequiresCfgPattern {
     #[primary_span]
     #[label]
@@ -44,7 +44,7 @@ struct RequiresCfgPattern {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "builtin-macros-expected-one-cfg-pattern")]
+#[error(builtin_macros::expected_one_cfg_pattern)]
 struct OneCfgPattern {
     #[primary_span]
     span: Span,
diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs
index c952fc0a866..83317106990 100644
--- a/compiler/rustc_builtin_macros/src/deriving/clone.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs
@@ -3,9 +3,9 @@ use crate::deriving::generic::*;
 use crate::deriving::path_std;
 
 use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, Expr, GenericArg, Generics, ItemKind, MetaItem, VariantData};
+use rustc_ast::{self as ast, Expr, Generics, ItemKind, MetaItem, VariantData};
 use rustc_expand::base::{Annotatable, ExtCtxt};
-use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::Span;
 
 pub fn expand_deriving_clone(
@@ -107,44 +107,38 @@ fn cs_clone_shallow(
     substr: &Substructure<'_>,
     is_union: bool,
 ) -> P<Expr> {
-    fn assert_ty_bounds(
-        cx: &mut ExtCtxt<'_>,
-        stmts: &mut Vec<ast::Stmt>,
-        ty: P<ast::Ty>,
-        span: Span,
-        helper_name: &str,
-    ) {
-        // Generate statement `let _: helper_name<ty>;`,
-        // set the expn ID so we can use the unstable struct.
-        let span = cx.with_def_site_ctxt(span);
-        let assert_path = cx.path_all(
-            span,
-            true,
-            cx.std_path(&[sym::clone, Symbol::intern(helper_name)]),
-            vec![GenericArg::Type(ty)],
-        );
-        stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
-    }
-    fn process_variant(cx: &mut ExtCtxt<'_>, stmts: &mut Vec<ast::Stmt>, variant: &VariantData) {
+    let mut stmts = Vec::new();
+    let mut process_variant = |variant: &VariantData| {
         for field in variant.fields() {
             // let _: AssertParamIsClone<FieldTy>;
-            assert_ty_bounds(cx, stmts, field.ty.clone(), field.span, "AssertParamIsClone");
+            super::assert_ty_bounds(
+                cx,
+                &mut stmts,
+                field.ty.clone(),
+                field.span,
+                &[sym::clone, sym::AssertParamIsClone],
+            );
         }
-    }
+    };
 
-    let mut stmts = Vec::new();
     if is_union {
         // let _: AssertParamIsCopy<Self>;
         let self_ty = cx.ty_path(cx.path_ident(trait_span, Ident::with_dummy_span(kw::SelfUpper)));
-        assert_ty_bounds(cx, &mut stmts, self_ty, trait_span, "AssertParamIsCopy");
+        super::assert_ty_bounds(
+            cx,
+            &mut stmts,
+            self_ty,
+            trait_span,
+            &[sym::clone, sym::AssertParamIsCopy],
+        );
     } else {
         match *substr.fields {
             StaticStruct(vdata, ..) => {
-                process_variant(cx, &mut stmts, vdata);
+                process_variant(vdata);
             }
             StaticEnum(enum_def, ..) => {
                 for variant in &enum_def.variants {
-                    process_variant(cx, &mut stmts, &variant.data);
+                    process_variant(&variant.data);
                 }
             }
             _ => cx.span_bug(
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
index 54ab88dc3ff..f54ded3c87c 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -3,9 +3,9 @@ use crate::deriving::generic::*;
 use crate::deriving::path_std;
 
 use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, Expr, GenericArg, MetaItem};
+use rustc_ast::{self as ast, Expr, MetaItem};
 use rustc_expand::base::{Annotatable, ExtCtxt};
-use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
 
 pub fn expand_deriving_eq(
@@ -55,43 +55,27 @@ fn cs_total_eq_assert(
     trait_span: Span,
     substr: &Substructure<'_>,
 ) -> P<Expr> {
-    fn assert_ty_bounds(
-        cx: &mut ExtCtxt<'_>,
-        stmts: &mut Vec<ast::Stmt>,
-        ty: P<ast::Ty>,
-        span: Span,
-        helper_name: &str,
-    ) {
-        // Generate statement `let _: helper_name<ty>;`,
-        // set the expn ID so we can use the unstable struct.
-        let span = cx.with_def_site_ctxt(span);
-        let assert_path = cx.path_all(
-            span,
-            true,
-            cx.std_path(&[sym::cmp, Symbol::intern(helper_name)]),
-            vec![GenericArg::Type(ty)],
-        );
-        stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
-    }
-    fn process_variant(
-        cx: &mut ExtCtxt<'_>,
-        stmts: &mut Vec<ast::Stmt>,
-        variant: &ast::VariantData,
-    ) {
+    let mut stmts = Vec::new();
+    let mut process_variant = |variant: &ast::VariantData| {
         for field in variant.fields() {
             // let _: AssertParamIsEq<FieldTy>;
-            assert_ty_bounds(cx, stmts, field.ty.clone(), field.span, "AssertParamIsEq");
+            super::assert_ty_bounds(
+                cx,
+                &mut stmts,
+                field.ty.clone(),
+                field.span,
+                &[sym::cmp, sym::AssertParamIsEq],
+            );
         }
-    }
+    };
 
-    let mut stmts = Vec::new();
     match *substr.fields {
         StaticStruct(vdata, ..) => {
-            process_variant(cx, &mut stmts, vdata);
+            process_variant(vdata);
         }
         StaticEnum(enum_def, ..) => {
             for variant in &enum_def.variants {
-                process_variant(cx, &mut stmts, &variant.data);
+                process_variant(&variant.data);
             }
         }
         _ => cx.span_bug(trait_span, "unexpected substructure in `derive(Eq)`"),
diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs
index ecf70da6d96..1fffd6f9727 100644
--- a/compiler/rustc_builtin_macros/src/deriving/debug.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs
@@ -3,14 +3,10 @@ use crate::deriving::generic::*;
 use crate::deriving::path_std;
 
 use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, Expr, LocalKind, MetaItem};
+use rustc_ast::{self as ast, Expr, MetaItem};
 use rustc_expand::base::{Annotatable, ExtCtxt};
-use rustc_span::symbol::{sym, Ident};
-use rustc_span::{Span, DUMMY_SP};
-
-fn make_mut_borrow(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<Expr>) -> P<Expr> {
-    cx.expr(sp, ast::ExprKind::AddrOf(ast::BorrowKind::Ref, ast::Mutability::Mut, expr))
-}
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
 
 pub fn expand_deriving_debug(
     cx: &mut ExtCtxt<'_>,
@@ -49,11 +45,7 @@ pub fn expand_deriving_debug(
     trait_def.expand(cx, mitem, item, push)
 }
 
-/// We use the debug builders to do the heavy lifting here
 fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> {
-    // build fmt.debug_struct(<name>).field(<fieldname>, &<fieldval>)....build()
-    // or fmt.debug_tuple(<name>).field(&<fieldval>)....build()
-    // based on the "shape".
     let (ident, vdata, fields) = match substr.fields {
         Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
         EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields),
@@ -67,93 +59,130 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>
     let name = cx.expr_lit(span, ast::LitKind::Str(ident.name, ast::StrStyle::Cooked));
     let fmt = substr.nonself_args[0].clone();
 
-    // Special fast path for unit variants. In the common case of an enum that is entirely unit
-    // variants (i.e. a C-like enum), this fast path allows LLVM to eliminate the entire switch in
-    // favor of a lookup table.
-    if let ast::VariantData::Unit(..) = vdata {
-        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
-        let expr = cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
-        let stmts = vec![cx.stmt_expr(expr)];
-        let block = cx.block(span, stmts);
-        return cx.expr_block(block);
-    }
-
-    let builder = Ident::new(sym::debug_trait_builder, span);
-    let builder_expr = cx.expr_ident(span, builder);
-
-    let mut stmts = Vec::with_capacity(fields.len() + 2);
-    let fn_path_finish;
-    match vdata {
+    // Struct and tuples are similar enough that we use the same code for both,
+    // with some extra pieces for structs due to the field names.
+    let (is_struct, args_per_field) = match vdata {
         ast::VariantData::Unit(..) => {
-            cx.span_bug(span, "unit variants should have been handled above");
+            // Special fast path for unit variants.
+            //let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+            //return cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
+            assert!(fields.is_empty());
+            (false, 0)
         }
-        ast::VariantData::Tuple(..) => {
-            // tuple struct/"normal" variant
-            let fn_path_debug_tuple = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_tuple]);
-            let expr = cx.expr_call_global(span, fn_path_debug_tuple, vec![fmt, name]);
-            let expr = make_mut_borrow(cx, span, expr);
-            stmts.push(cx.stmt_let(span, false, builder, expr));
-
-            for field in fields {
-                // Use double indirection to make sure this works for unsized types
-                let field = cx.expr_addr_of(field.span, field.self_.clone());
-                let field = cx.expr_addr_of(field.span, field);
-
-                let fn_path_field = cx.std_path(&[sym::fmt, sym::DebugTuple, sym::field]);
-                let expr =
-                    cx.expr_call_global(span, fn_path_field, vec![builder_expr.clone(), field]);
-
-                // Use `let _ = expr;` to avoid triggering the
-                // unused_results lint.
-                stmts.push(stmt_let_underscore(cx, span, expr));
-            }
+        ast::VariantData::Tuple(..) => (false, 1),
+        ast::VariantData::Struct(..) => (true, 2),
+    };
 
-            fn_path_finish = cx.std_path(&[sym::fmt, sym::DebugTuple, sym::finish]);
-        }
-        ast::VariantData::Struct(..) => {
-            // normal struct/struct variant
-            let fn_path_debug_struct = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_struct]);
-            let expr = cx.expr_call_global(span, fn_path_debug_struct, vec![fmt, name]);
-            let expr = make_mut_borrow(cx, span, expr);
-            stmts.push(cx.stmt_let(DUMMY_SP, false, builder, expr));
-
-            for field in fields {
+    // The number of fields that can be handled without an array.
+    const CUTOFF: usize = 5;
+
+    if fields.is_empty() {
+        // Special case for no fields.
+        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+        cx.expr_call_global(span, fn_path_write_str, vec![fmt, name])
+    } else if fields.len() <= CUTOFF {
+        // Few enough fields that we can use a specific-length method.
+        let debug = if is_struct {
+            format!("debug_struct_field{}_finish", fields.len())
+        } else {
+            format!("debug_tuple_field{}_finish", fields.len())
+        };
+        let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
+
+        let mut args = Vec::with_capacity(2 + fields.len() * args_per_field);
+        args.extend([fmt, name]);
+        for i in 0..fields.len() {
+            let field = &fields[i];
+            if is_struct {
                 let name = cx.expr_lit(
                     field.span,
                     ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
                 );
-
-                // Use double indirection to make sure this works for unsized types
-                let fn_path_field = cx.std_path(&[sym::fmt, sym::DebugStruct, sym::field]);
-                let field = cx.expr_addr_of(field.span, field.self_.clone());
-                let field = cx.expr_addr_of(field.span, field);
-                let expr = cx.expr_call_global(
-                    span,
-                    fn_path_field,
-                    vec![builder_expr.clone(), name, field],
-                );
-                stmts.push(stmt_let_underscore(cx, span, expr));
+                args.push(name);
             }
-            fn_path_finish = cx.std_path(&[sym::fmt, sym::DebugStruct, sym::finish]);
+            // Use double indirection to make sure this works for unsized types
+            let field = cx.expr_addr_of(field.span, field.self_.clone());
+            let field = cx.expr_addr_of(field.span, field);
+            args.push(field);
         }
-    }
+        cx.expr_call_global(span, fn_path_debug, args)
+    } else {
+        // Enough fields that we must use the any-length method.
+        let mut name_exprs = Vec::with_capacity(fields.len());
+        let mut value_exprs = Vec::with_capacity(fields.len());
+
+        for field in fields {
+            if is_struct {
+                name_exprs.push(cx.expr_lit(
+                    field.span,
+                    ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
+                ));
+            }
 
-    let expr = cx.expr_call_global(span, fn_path_finish, vec![builder_expr]);
+            // Use double indirection to make sure this works for unsized types
+            let value_ref = cx.expr_addr_of(field.span, field.self_.clone());
+            value_exprs.push(cx.expr_addr_of(field.span, value_ref));
+        }
 
-    stmts.push(cx.stmt_expr(expr));
-    let block = cx.block(span, stmts);
-    cx.expr_block(block)
-}
+        // `let names: &'static _ = &["field1", "field2"];`
+        let names_let = if is_struct {
+            let lt_static = Some(cx.lifetime_static(span));
+            let ty_static_ref =
+                cx.ty_rptr(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
+            Some(cx.stmt_let_ty(
+                span,
+                false,
+                Ident::new(sym::names, span),
+                Some(ty_static_ref),
+                cx.expr_array_ref(span, name_exprs),
+            ))
+        } else {
+            None
+        };
+
+        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
+        let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
+        let ty_dyn_debug = cx.ty(
+            span,
+            ast::TyKind::TraitObject(vec![cx.trait_bound(path_debug)], ast::TraitObjectSyntax::Dyn),
+        );
+        let ty_slice = cx.ty(
+            span,
+            ast::TyKind::Slice(cx.ty_rptr(span, ty_dyn_debug, None, ast::Mutability::Not)),
+        );
+        let values_let = cx.stmt_let_ty(
+            span,
+            false,
+            Ident::new(sym::values, span),
+            Some(cx.ty_rptr(span, ty_slice, None, ast::Mutability::Not)),
+            cx.expr_array_ref(span, value_exprs),
+        );
+
+        // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
+        // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
+        let sym_debug = if is_struct {
+            sym::debug_struct_fields_finish
+        } else {
+            sym::debug_tuple_fields_finish
+        };
+        let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
+
+        let mut args = Vec::with_capacity(4);
+        args.push(fmt);
+        args.push(name);
+        if is_struct {
+            args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
+        }
+        args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
+        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
 
-fn stmt_let_underscore(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<ast::Expr>) -> ast::Stmt {
-    let local = P(ast::Local {
-        pat: cx.pat_wild(sp),
-        ty: None,
-        id: ast::DUMMY_NODE_ID,
-        kind: LocalKind::Init(expr),
-        span: sp,
-        attrs: ast::AttrVec::new(),
-        tokens: None,
-    });
-    ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Local(local), span: sp }
+        let mut stmts = Vec::with_capacity(3);
+        if is_struct {
+            stmts.push(names_let.unwrap());
+        }
+        stmts.push(values_let);
+        stmts.push(cx.stmt_expr(expr));
+
+        cx.expr_block(cx.block(span, stmts))
+    }
 }
diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
index 1d892b20729..b39f35a9d40 100644
--- a/compiler/rustc_builtin_macros/src/deriving/decodable.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
@@ -162,14 +162,13 @@ fn decodable_substructure(
                 cx.expr_match(trait_span, cx.expr_ident(trait_span, variant), arms),
             );
             let lambda = cx.lambda(trait_span, vec![blkarg, variant], result);
-            let variant_vec = cx.expr_vec(trait_span, variants);
-            let variant_vec = cx.expr_addr_of(trait_span, variant_vec);
+            let variant_array_ref = cx.expr_array_ref(trait_span, variants);
             let fn_read_enum_variant_path: Vec<_> =
                 cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum_variant]);
             let result = cx.expr_call_global(
                 trait_span,
                 fn_read_enum_variant_path,
-                vec![blkdecoder, variant_vec, lambda],
+                vec![blkdecoder, variant_array_ref, lambda],
             );
             let fn_read_enum_path: Vec<_> =
                 cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum]);
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
index 53369afae27..8347cded2fe 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -1126,75 +1126,43 @@ impl<'a> MethodDef<'a> {
     ///     A1,
     ///     A2(i32)
     /// }
-    ///
-    /// // is equivalent to
-    ///
-    /// impl PartialEq for A {
+    /// ```
+    /// is equivalent to:
+    /// ```
+    /// impl ::core::cmp::PartialEq for A {
+    ///     #[inline]
     ///     fn eq(&self, other: &A) -> bool {
-    ///         use A::*;
-    ///         match (&*self, &*other) {
-    ///             (&A1, &A1) => true,
-    ///             (&A2(ref self_0),
-    ///              &A2(ref __arg_1_0)) => (*self_0).eq(&(*__arg_1_0)),
-    ///             _ => {
-    ///                 let __self_vi = match *self { A1 => 0, A2(..) => 1 };
-    ///                 let __arg_1_vi = match *other { A1 => 0, A2(..) => 1 };
-    ///                 false
+    ///         {
+    ///             let __self_vi = ::core::intrinsics::discriminant_value(&*self);
+    ///             let __arg_1_vi = ::core::intrinsics::discriminant_value(&*other);
+    ///             if true && __self_vi == __arg_1_vi {
+    ///                 match (&*self, &*other) {
+    ///                     (&A::A2(ref __self_0), &A::A2(ref __arg_1_0)) =>
+    ///                         (*__self_0) == (*__arg_1_0),
+    ///                     _ => true,
+    ///                 }
+    ///             } else {
+    ///                 false // catch-all handler
     ///             }
     ///         }
     ///     }
     /// }
     /// ```
-    ///
-    /// (Of course `__self_vi` and `__arg_1_vi` are unused for
-    /// `PartialEq`, and those subcomputations will hopefully be removed
-    /// as their results are unused. The point of `__self_vi` and
-    /// `__arg_1_vi` is for `PartialOrd`; see #15503.)
-    fn expand_enum_method_body<'b>(
-        &self,
-        cx: &mut ExtCtxt<'_>,
-        trait_: &TraitDef<'b>,
-        enum_def: &'b EnumDef,
-        type_ident: Ident,
-        self_args: Vec<P<Expr>>,
-        nonself_args: &[P<Expr>],
-    ) -> P<Expr> {
-        self.build_enum_match_tuple(cx, trait_, enum_def, type_ident, self_args, nonself_args)
-    }
-
     /// Creates a match for a tuple of all `self_args`, where either all
     /// variants match, or it falls into a catch-all for when one variant
     /// does not match.
-
+    ///
     /// There are N + 1 cases because is a case for each of the N
     /// variants where all of the variants match, and one catch-all for
     /// when one does not match.
-
+    ///
     /// As an optimization we generate code which checks whether all variants
     /// match first which makes llvm see that C-like enums can be compiled into
     /// a simple equality check (for PartialEq).
-
+    ///
     /// The catch-all handler is provided access the variant index values
     /// for each of the self-args, carried in precomputed variables.
-
-    /// ```{.text}
-    /// let __self0_vi = std::intrinsics::discriminant_value(&self);
-    /// let __self1_vi = std::intrinsics::discriminant_value(&arg1);
-    /// let __self2_vi = std::intrinsics::discriminant_value(&arg2);
-    ///
-    /// if __self0_vi == __self1_vi && __self0_vi == __self2_vi && ... {
-    ///     match (...) {
-    ///         (Variant1, Variant1, ...) => Body1
-    ///         (Variant2, Variant2, ...) => Body2,
-    ///         ...
-    ///         _ => ::core::intrinsics::unreachable()
-    ///     }
-    /// }
-    /// else {
-    ///     ... // catch-all remainder can inspect above variant index values.
-    /// }
-    /// ```
-    fn build_enum_match_tuple<'b>(
+    fn expand_enum_method_body<'b>(
         &self,
         cx: &mut ExtCtxt<'_>,
         trait_: &TraitDef<'b>,
@@ -1392,37 +1360,32 @@ impl<'a> MethodDef<'a> {
             //
             // i.e., for `enum E<T> { A, B(1), C(T, T) }`, and a deriving
             // with three Self args, builds three statements:
-            //
             // ```
-            // let __self0_vi = std::intrinsics::discriminant_value(&self);
-            // let __self1_vi = std::intrinsics::discriminant_value(&arg1);
-            // let __self2_vi = std::intrinsics::discriminant_value(&arg2);
+            // let __self_vi = std::intrinsics::discriminant_value(&self);
+            // let __arg_1_vi = std::intrinsics::discriminant_value(&arg1);
+            // let __arg_2_vi = std::intrinsics::discriminant_value(&arg2);
             // ```
             let mut index_let_stmts: Vec<ast::Stmt> = Vec::with_capacity(vi_idents.len() + 1);
 
-            // We also build an expression which checks whether all discriminants are equal
-            // discriminant_test = __self0_vi == __self1_vi && __self0_vi == __self2_vi && ...
+            // We also build an expression which checks whether all discriminants are equal:
+            // `__self_vi == __arg_1_vi && __self_vi == __arg_2_vi && ...`
             let mut discriminant_test = cx.expr_bool(span, true);
-
-            let mut first_ident = None;
-            for (&ident, self_arg) in iter::zip(&vi_idents, &self_args) {
+            for (i, (&ident, self_arg)) in iter::zip(&vi_idents, &self_args).enumerate() {
                 let self_addr = cx.expr_addr_of(span, self_arg.clone());
                 let variant_value =
                     deriving::call_intrinsic(cx, span, sym::discriminant_value, vec![self_addr]);
                 let let_stmt = cx.stmt_let(span, false, ident, variant_value);
                 index_let_stmts.push(let_stmt);
 
-                match first_ident {
-                    Some(first) => {
-                        let first_expr = cx.expr_ident(span, first);
-                        let id = cx.expr_ident(span, ident);
-                        let test = cx.expr_binary(span, BinOpKind::Eq, first_expr, id);
-                        discriminant_test =
-                            cx.expr_binary(span, BinOpKind::And, discriminant_test, test)
-                    }
-                    None => {
-                        first_ident = Some(ident);
-                    }
+                if i > 0 {
+                    let id0 = cx.expr_ident(span, vi_idents[0]);
+                    let id = cx.expr_ident(span, ident);
+                    let test = cx.expr_binary(span, BinOpKind::Eq, id0, id);
+                    discriminant_test = if i == 1 {
+                        test
+                    } else {
+                        cx.expr_binary(span, BinOpKind::And, discriminant_test, test)
+                    };
                 }
             }
 
@@ -1453,7 +1416,7 @@ impl<'a> MethodDef<'a> {
             //      }
             //  }
             //  else {
-            //      <delegated expression referring to __self0_vi, et al.>
+            //      <delegated expression referring to __self_vi, et al.>
             //  }
             let all_match = cx.expr_match(span, match_arg, match_arms);
             let arm_expr = cx.expr_if(span, discriminant_test, all_match, Some(arm_expr));
diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs
index c678c8cbd15..c1ca089da22 100644
--- a/compiler/rustc_builtin_macros/src/deriving/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs
@@ -2,7 +2,7 @@
 
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
-use rustc_ast::{Impl, ItemKind, MetaItem};
+use rustc_ast::{GenericArg, Impl, ItemKind, MetaItem};
 use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier};
 use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
@@ -193,3 +193,16 @@ fn inject_impl_of_structural_trait(
 
     push(Annotatable::Item(newitem));
 }
+
+fn assert_ty_bounds(
+    cx: &mut ExtCtxt<'_>,
+    stmts: &mut Vec<ast::Stmt>,
+    ty: P<ast::Ty>,
+    span: Span,
+    assert_path: &[Symbol],
+) {
+    // Generate statement `let _: assert_path<ty>;`.
+    let span = cx.with_def_site_ctxt(span);
+    let assert_path = cx.path_all(span, true, cx.std_path(assert_path), vec![GenericArg::Type(ty)]);
+    stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
+}
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 10348c4967c..6c2ac343544 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -776,7 +776,7 @@ impl<'a, 'b> Context<'a, 'b> {
 
         // First, build up the static array which will become our precompiled
         // format "string"
-        let pieces = self.ecx.expr_vec_slice(self.fmtsp, self.str_pieces);
+        let pieces = self.ecx.expr_array_ref(self.fmtsp, self.str_pieces);
 
         // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments
         // constructor. In general the expressions in this slice might be
@@ -849,7 +849,7 @@ impl<'a, 'b> Context<'a, 'b> {
             fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg));
         }
 
-        let args_array = self.ecx.expr_vec(self.macsp, fmt_args);
+        let args_array = self.ecx.expr_array(self.macsp, fmt_args);
         let args_slice = self.ecx.expr_addr_of(
             self.macsp,
             if no_need_for_match {
@@ -879,7 +879,7 @@ impl<'a, 'b> Context<'a, 'b> {
         } else {
             // Build up the static array which will store our precompiled
             // nonstandard placeholders, if there are any.
-            let fmt = self.ecx.expr_vec_slice(self.macsp, self.pieces);
+            let fmt = self.ecx.expr_array_ref(self.macsp, self.pieces);
 
             let path = self.ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]);
             let unsafe_arg = self.ecx.expr_call_global(self.macsp, path, Vec::new());
diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
index 03159d43950..5cfda33491d 100644
--- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
+++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
@@ -317,7 +317,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                         proc_macro_ty_method_path(cx, custom_derive),
                         vec![
                             cx.expr_str(span, cd.trait_name),
-                            cx.expr_vec_slice(
+                            cx.expr_array_ref(
                                 span,
                                 cd.attrs.iter().map(|&s| cx.expr_str(span, s)).collect::<Vec<_>>(),
                             ),
@@ -362,7 +362,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                 ast::Mutability::Not,
             ),
             ast::Mutability::Not,
-            cx.expr_vec_slice(span, decls),
+            cx.expr_array_ref(span, decls),
         )
         .map(|mut i| {
             let attr = cx.meta_word(span, sym::rustc_proc_macro_decls);
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
index a08495f7671..e2a6e77ab44 100644
--- a/compiler/rustc_builtin_macros/src/test_harness.rs
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -352,7 +352,7 @@ fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> {
     debug!("building test vector from {} tests", cx.test_cases.len());
     let ecx = &cx.ext_cx;
 
-    ecx.expr_vec_slice(
+    ecx.expr_array_ref(
         sp,
         cx.test_cases
             .iter()
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index c41a41980eb..8c1e865762c 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -1064,11 +1064,25 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         dst: &'ll Value,
         cmp: &'ll Value,
         src: &'ll Value,
-        order: rustc_codegen_ssa::common::AtomicOrdering,
+        mut order: rustc_codegen_ssa::common::AtomicOrdering,
         failure_order: rustc_codegen_ssa::common::AtomicOrdering,
         weak: bool,
     ) -> &'ll Value {
         let weak = if weak { llvm::True } else { llvm::False };
+        if llvm_util::get_version() < (13, 0, 0) {
+            use rustc_codegen_ssa::common::AtomicOrdering::*;
+            // Older llvm has the pre-C++17 restriction on
+            // success and failure memory ordering,
+            // requiring the former to be at least as strong as the latter.
+            // So, for llvm 12, we upgrade the success ordering to a stronger
+            // one if necessary.
+            match (order, failure_order) {
+                (Relaxed, Acquire) => order = Acquire,
+                (Release, Acquire) => order = AcquireRelease,
+                (_, SequentiallyConsistent) => order = SequentiallyConsistent,
+                _ => {}
+            }
+        }
         unsafe {
             llvm::LLVMRustBuildAtomicCmpXchg(
                 self.llbuilder,
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index 6c30923bc3d..4be3ae11e4e 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -6,6 +6,7 @@
 #![feature(associated_type_bounds)]
 #![feature(strict_provenance)]
 #![feature(int_roundings)]
+#![feature(if_let_guard)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 
diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
index 0ed4c3f1d94..7f14b95317b 100644
--- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
@@ -376,32 +376,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             }
 
             // This requires that atomic intrinsics follow a specific naming pattern:
-            // "atomic_<operation>[_<ordering>]", and no ordering means SeqCst
-            name if name_str.starts_with("atomic_") => {
+            // "atomic_<operation>[_<ordering>]"
+            name if let Some(atomic) = name_str.strip_prefix("atomic_") => {
                 use crate::common::AtomicOrdering::*;
                 use crate::common::{AtomicRmwBinOp, SynchronizationScope};
 
-                let split: Vec<_> = name_str.split('_').collect();
-
-                let is_cxchg = split[1] == "cxchg" || split[1] == "cxchgweak";
-                let (order, failorder) = match split.len() {
-                    2 => (SequentiallyConsistent, SequentiallyConsistent),
-                    3 => match split[2] {
-                        "unordered" => (Unordered, Unordered),
-                        "relaxed" => (Relaxed, Relaxed),
-                        "acq" => (Acquire, Acquire),
-                        "rel" => (Release, Relaxed),
-                        "acqrel" => (AcquireRelease, Acquire),
-                        "failrelaxed" if is_cxchg => (SequentiallyConsistent, Relaxed),
-                        "failacq" if is_cxchg => (SequentiallyConsistent, Acquire),
-                        _ => bx.sess().fatal("unknown ordering in atomic intrinsic"),
-                    },
-                    4 => match (split[2], split[3]) {
-                        ("acq", "failrelaxed") if is_cxchg => (Acquire, Relaxed),
-                        ("acqrel", "failrelaxed") if is_cxchg => (AcquireRelease, Relaxed),
-                        _ => bx.sess().fatal("unknown ordering in atomic intrinsic"),
-                    },
-                    _ => bx.sess().fatal("Atomic intrinsic not in correct format"),
+                let Some((instruction, ordering)) = atomic.split_once('_') else {
+                    bx.sess().fatal("Atomic intrinsic missing memory ordering");
+                };
+
+                let parse_ordering = |bx: &Bx, s| match s {
+                    "unordered" => Unordered,
+                    "relaxed" => Relaxed,
+                    "acquire" => Acquire,
+                    "release" => Release,
+                    "acqrel" => AcquireRelease,
+                    "seqcst" => SequentiallyConsistent,
+                    _ => bx.sess().fatal("unknown ordering in atomic intrinsic"),
                 };
 
                 let invalid_monomorphization = |ty| {
@@ -416,11 +407,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     );
                 };
 
-                match split[1] {
+                match instruction {
                     "cxchg" | "cxchgweak" => {
+                        let Some((success, failure)) = ordering.split_once('_') else {
+                            bx.sess().fatal("Atomic compare-exchange intrinsic missing failure memory ordering");
+                        };
                         let ty = substs.type_at(0);
                         if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() {
-                            let weak = split[1] == "cxchgweak";
+                            let weak = instruction == "cxchgweak";
                             let mut dst = args[0].immediate();
                             let mut cmp = args[1].immediate();
                             let mut src = args[2].immediate();
@@ -432,7 +426,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 cmp = bx.ptrtoint(cmp, bx.type_isize());
                                 src = bx.ptrtoint(src, bx.type_isize());
                             }
-                            let pair = bx.atomic_cmpxchg(dst, cmp, src, order, failorder, weak);
+                            let pair = bx.atomic_cmpxchg(dst, cmp, src, parse_ordering(bx, success), parse_ordering(bx, failure), weak);
                             let val = bx.extract_value(pair, 0);
                             let success = bx.extract_value(pair, 1);
                             let val = bx.from_immediate(val);
@@ -460,11 +454,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 let llty = bx.type_isize();
                                 let ptr_llty = bx.type_ptr_to(llty);
                                 source = bx.pointercast(source, ptr_llty);
-                                let result = bx.atomic_load(llty, source, order, size);
+                                let result = bx.atomic_load(llty, source, parse_ordering(bx, ordering), size);
                                 // ... and then cast the result back to a pointer
                                 bx.inttoptr(result, bx.backend_type(layout))
                             } else {
-                                bx.atomic_load(bx.backend_type(layout), source, order, size)
+                                bx.atomic_load(bx.backend_type(layout), source, parse_ordering(bx, ordering), size)
                             }
                         } else {
                             return invalid_monomorphization(ty);
@@ -484,7 +478,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 ptr = bx.pointercast(ptr, ptr_llty);
                                 val = bx.ptrtoint(val, bx.type_isize());
                             }
-                            bx.atomic_store(val, ptr, order, size);
+                            bx.atomic_store(val, ptr, parse_ordering(bx, ordering), size);
                             return;
                         } else {
                             return invalid_monomorphization(ty);
@@ -492,12 +486,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     }
 
                     "fence" => {
-                        bx.atomic_fence(order, SynchronizationScope::CrossThread);
+                        bx.atomic_fence(parse_ordering(bx, ordering), SynchronizationScope::CrossThread);
                         return;
                     }
 
                     "singlethreadfence" => {
-                        bx.atomic_fence(order, SynchronizationScope::SingleThread);
+                        bx.atomic_fence(parse_ordering(bx, ordering), SynchronizationScope::SingleThread);
                         return;
                     }
 
@@ -531,7 +525,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 ptr = bx.pointercast(ptr, ptr_llty);
                                 val = bx.ptrtoint(val, bx.type_isize());
                             }
-                            bx.atomic_rmw(atom_op, ptr, val, order)
+                            bx.atomic_rmw(atom_op, ptr, val, parse_ordering(bx, ordering))
                         } else {
                             return invalid_monomorphization(ty);
                         }
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index d46f2f38d3a..d5e68dbd5b7 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -56,15 +56,14 @@ impl<T: fmt::Display> fmt::Display for MemoryKind<T> {
     }
 }
 
-/// Used by `get_size_and_align` to indicate whether the allocation needs to be live.
-#[derive(Debug, Copy, Clone)]
-pub enum AllocCheck {
-    /// Allocation must be live and not a function pointer.
-    Dereferenceable,
-    /// Allocations needs to be live, but may be a function pointer.
-    Live,
-    /// Allocation may be dead.
-    MaybeDead,
+/// The return value of `get_alloc_info` indicates the "kind" of the allocation.
+pub enum AllocKind {
+    /// A regular live data allocation.
+    LiveData,
+    /// A function allocation (that fn ptrs point to).
+    Function,
+    /// A dead allocation.
+    Dead,
 }
 
 /// The value of a function pointer.
@@ -360,8 +359,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             align,
             CheckInAllocMsg::MemoryAccessTest,
             |alloc_id, offset, tag| {
-                let (size, align) =
-                    self.get_alloc_size_and_align(alloc_id, AllocCheck::Dereferenceable)?;
+                let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
                 Ok((size, align, (alloc_id, offset, tag)))
             },
         )
@@ -379,15 +377,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         msg: CheckInAllocMsg,
     ) -> InterpResult<'tcx> {
         self.check_and_deref_ptr(ptr, size, Some(align), msg, |alloc_id, _, _| {
-            let check = match msg {
-                CheckInAllocMsg::DerefTest | CheckInAllocMsg::MemoryAccessTest => {
-                    AllocCheck::Dereferenceable
-                }
-                CheckInAllocMsg::PointerArithmeticTest
-                | CheckInAllocMsg::OffsetFromTest
-                | CheckInAllocMsg::InboundsTest => AllocCheck::Live,
-            };
-            let (size, align) = self.get_alloc_size_and_align(alloc_id, check)?;
+            let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
             Ok((size, align, ()))
         })?;
         Ok(())
@@ -655,30 +645,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
     /// Obtain the size and alignment of an allocation, even if that allocation has
     /// been deallocated.
-    ///
-    /// If `liveness` is `AllocCheck::MaybeDead`, this function always returns `Ok`.
-    pub fn get_alloc_size_and_align(
-        &self,
-        id: AllocId,
-        liveness: AllocCheck,
-    ) -> InterpResult<'tcx, (Size, Align)> {
+    pub fn get_alloc_info(&self, id: AllocId) -> (Size, Align, AllocKind) {
         // # Regular allocations
         // Don't use `self.get_raw` here as that will
         // a) cause cycles in case `id` refers to a static
         // b) duplicate a global's allocation in miri
         if let Some((_, alloc)) = self.memory.alloc_map.get(id) {
-            return Ok((alloc.size(), alloc.align));
+            return (alloc.size(), alloc.align, AllocKind::LiveData);
         }
 
         // # Function pointers
         // (both global from `alloc_map` and local from `extra_fn_ptr_map`)
         if self.get_fn_alloc(id).is_some() {
-            return if let AllocCheck::Dereferenceable = liveness {
-                // The caller requested no function pointers.
-                throw_ub!(DerefFunctionPointer(id))
-            } else {
-                Ok((Size::ZERO, Align::ONE))
-            };
+            return (Size::ZERO, Align::ONE, AllocKind::Function);
         }
 
         // # Statics
@@ -690,32 +669,38 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 // Use size and align of the type.
                 let ty = self.tcx.type_of(did);
                 let layout = self.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap();
-                Ok((layout.size, layout.align.abi))
+                (layout.size, layout.align.abi, AllocKind::LiveData)
             }
             Some(GlobalAlloc::Memory(alloc)) => {
                 // Need to duplicate the logic here, because the global allocations have
                 // different associated types than the interpreter-local ones.
                 let alloc = alloc.inner();
-                Ok((alloc.size(), alloc.align))
+                (alloc.size(), alloc.align, AllocKind::LiveData)
             }
             Some(GlobalAlloc::Function(_)) => bug!("We already checked function pointers above"),
             // The rest must be dead.
             None => {
-                if let AllocCheck::MaybeDead = liveness {
-                    // Deallocated pointers are allowed, we should be able to find
-                    // them in the map.
-                    Ok(*self
-                        .memory
-                        .dead_alloc_map
-                        .get(&id)
-                        .expect("deallocated pointers should all be recorded in `dead_alloc_map`"))
-                } else {
-                    throw_ub!(PointerUseAfterFree(id))
-                }
+                // Deallocated pointers are allowed, we should be able to find
+                // them in the map.
+                let (size, align) = *self
+                    .memory
+                    .dead_alloc_map
+                    .get(&id)
+                    .expect("deallocated pointers should all be recorded in `dead_alloc_map`");
+                (size, align, AllocKind::Dead)
             }
         }
     }
 
+    /// Obtain the size and alignment of a live allocation.
+    pub fn get_live_alloc_size_and_align(&self, id: AllocId) -> InterpResult<'tcx, (Size, Align)> {
+        let (size, align, kind) = self.get_alloc_info(id);
+        if matches!(kind, AllocKind::Dead) {
+            throw_ub!(PointerUseAfterFree(id))
+        }
+        Ok((size, align))
+    }
+
     fn get_fn_alloc(&self, id: AllocId) -> Option<FnVal<'tcx, M::ExtraFnVal>> {
         if let Some(extra) = self.memory.extra_fn_ptr_map.get(&id) {
             Some(FnVal::Other(*extra))
@@ -1187,9 +1172,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let ptr = self.scalar_to_ptr(scalar)?;
                 match self.ptr_try_get_alloc_id(ptr) {
                     Ok((alloc_id, offset, _)) => {
-                        let (size, _align) = self
-                            .get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead)
-                            .expect("alloc info with MaybeDead cannot fail");
+                        let (size, _align, _kind) = self.get_alloc_info(alloc_id);
                         // If the pointer is out-of-bounds, it may be null.
                         // Note that one-past-the-end (offset == size) is still inbounds, and never null.
                         offset > size
diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs
index 2b73ad568e0..92f0a7498e3 100644
--- a/compiler/rustc_const_eval/src/interpret/mod.rs
+++ b/compiler/rustc_const_eval/src/interpret/mod.rs
@@ -23,7 +23,7 @@ pub use self::eval_context::{
 };
 pub use self::intern::{intern_const_alloc_recursive, InternKind};
 pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
-pub use self::memory::{AllocCheck, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
+pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
 pub use self::operand::{ImmTy, Immediate, OpTy, Operand};
 pub use self::place::{MPlaceTy, MemPlace, MemPlaceMeta, Place, PlaceTy};
 pub use self::validity::{CtfeValidationMode, RefTracking};
diff --git a/compiler/rustc_data_structures/src/profiling.rs b/compiler/rustc_data_structures/src/profiling.rs
index 88ff33b4d09..d8b26f9840b 100644
--- a/compiler/rustc_data_structures/src/profiling.rs
+++ b/compiler/rustc_data_structures/src/profiling.rs
@@ -826,6 +826,24 @@ cfg_if! {
                 }
             }
         }
+    } else if #[cfg(target_os = "macos")] {
+        pub fn get_resident_set_size() -> Option<usize> {
+            use libc::{c_int, c_void, getpid, proc_pidinfo, proc_taskinfo, PROC_PIDTASKINFO};
+            use std::mem;
+            const PROC_TASKINFO_SIZE: c_int = mem::size_of::<proc_taskinfo>() as c_int;
+
+            unsafe {
+                let mut info: proc_taskinfo = mem::zeroed();
+                let info_ptr = &mut info as *mut proc_taskinfo as *mut c_void;
+                let pid = getpid() as c_int;
+                let ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, info_ptr, PROC_TASKINFO_SIZE);
+                if ret == PROC_TASKINFO_SIZE {
+                    Some(info.pti_resident_size as usize)
+                } else {
+                    None
+                }
+            }
+        }
     } else if #[cfg(unix)] {
         pub fn get_resident_set_size() -> Option<usize> {
             let field = 1;
diff --git a/compiler/rustc_data_structures/src/sync.rs b/compiler/rustc_data_structures/src/sync.rs
index feb82cb0938..4437c0b1b69 100644
--- a/compiler/rustc_data_structures/src/sync.rs
+++ b/compiler/rustc_data_structures/src/sync.rs
@@ -21,6 +21,7 @@ use crate::owning_ref::{Erased, OwningRef};
 use std::collections::HashMap;
 use std::hash::{BuildHasher, Hash};
 use std::ops::{Deref, DerefMut};
+use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
 
 pub use std::sync::atomic::Ordering;
 pub use std::sync::atomic::Ordering::SeqCst;
@@ -41,7 +42,6 @@ cfg_if! {
         }
 
         use std::ops::Add;
-        use std::panic::{resume_unwind, catch_unwind, AssertUnwindSafe};
 
         /// This is a single threaded variant of `AtomicU64`, `AtomicUsize`, etc.
         /// It has explicit ordering arguments and is only intended for use with
@@ -339,7 +339,10 @@ cfg_if! {
             t: T,
             for_each: impl Fn(T::Item) + Sync + Send,
         ) {
-            t.into_par_iter().for_each(for_each)
+            let ps: Vec<_> = t.into_par_iter().map(|i| catch_unwind(AssertUnwindSafe(|| for_each(i)))).collect();
+            ps.into_iter().for_each(|p| if let Err(panic) = p {
+                resume_unwind(panic)
+            });
         }
 
         pub type MetadataRef = OwningRef<Box<dyn Erased + Send + Sync>, [u8]>;
diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs
index caa92e74808..3096af90d47 100644
--- a/compiler/rustc_driver/src/lib.rs
+++ b/compiler/rustc_driver/src/lib.rs
@@ -932,7 +932,7 @@ fn describe_codegen_flags() {
     print_flag_list("-C", config::CG_OPTIONS);
 }
 
-fn print_flag_list<T>(
+pub fn print_flag_list<T>(
     cmdline_opt: &str,
     flag_list: &[(&'static str, T, &'static str, &'static str)],
 ) {
diff --git a/compiler/rustc_error_codes/src/error_codes/E0093.md b/compiler/rustc_error_codes/src/error_codes/E0093.md
index 6d58e50ec88..b1683cf4fc4 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0093.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0093.md
@@ -24,12 +24,12 @@ functions are defined in `compiler/rustc_codegen_llvm/src/intrinsic.rs` and in
 #![feature(intrinsics)]
 
 extern "rust-intrinsic" {
-    fn atomic_fence(); // ok!
+    fn atomic_fence_seqcst(); // ok!
 }
 
 fn main() {
     unsafe {
-        atomic_fence();
+        atomic_fence_seqcst();
     }
 }
 ```
diff --git a/compiler/rustc_error_messages/locales/en-US/builtin_macros.ftl b/compiler/rustc_error_messages/locales/en-US/builtin_macros.ftl
index 1d3e33c8185..4a42d52f710 100644
--- a/compiler/rustc_error_messages/locales/en-US/builtin_macros.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/builtin_macros.ftl
@@ -1,5 +1,5 @@
-builtin-macros-requires-cfg-pattern =
+builtin_macros-requires-cfg-pattern =
     macro requires a cfg-pattern as an argument
     .label = cfg-pattern required
 
-builtin-macros-expected-one-cfg-pattern = expected 1 cfg-pattern
+builtin_macros-expected-one-cfg-pattern = expected 1 cfg-pattern
diff --git a/compiler/rustc_error_messages/locales/en-US/privacy.ftl b/compiler/rustc_error_messages/locales/en-US/privacy.ftl
new file mode 100644
index 00000000000..2b0778f48ca
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/privacy.ftl
@@ -0,0 +1,12 @@
+privacy-field-is-private = field `{$field_name}` of {$variant_descr} `{$def_path_str}` is private
+privacy-field-is-private-is-update-syntax-label = field `{$field_name}` is private
+privacy-field-is-private-label = private field
+
+privacy-item-is-private = {$kind} `{$descr}` is private
+    .label = private {$kind}
+privacy-unnamed-item-is-private = {$kind} is private
+    .label = private {$kind}
+
+privacy-in-public-interface = {$vis_descr} {$kind} `{$descr}` in public interface
+    .label = can't leak {$vis_descr} {$kind}
+    .visibility-label = `{$descr}` declared as {$vis_descr}
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index 7211c054326..90eb5ef5446 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -32,6 +32,7 @@ pub use unic_langid::{langid, LanguageIdentifier};
 // Generates `DEFAULT_LOCALE_RESOURCES` static and `fluent_generated` module.
 fluent_messages! {
     parser => "../locales/en-US/parser.ftl",
+    privacy => "../locales/en-US/privacy.ftl",
     typeck => "../locales/en-US/typeck.ftl",
     builtin_macros => "../locales/en-US/builtin_macros.ftl",
 }
@@ -258,18 +259,6 @@ pub enum SubdiagnosticMessage {
     FluentAttr(FluentId),
 }
 
-impl SubdiagnosticMessage {
-    /// Create a `SubdiagnosticMessage` for the provided Fluent attribute.
-    pub fn attr(id: impl Into<FluentId>) -> Self {
-        SubdiagnosticMessage::FluentAttr(id.into())
-    }
-
-    /// Create a `SubdiagnosticMessage` for the provided Fluent identifier.
-    pub fn message(id: impl Into<FluentId>) -> Self {
-        SubdiagnosticMessage::FluentIdentifier(id.into())
-    }
-}
-
 /// `From` impl that enables existing diagnostic calls to functions which now take
 /// `impl Into<SubdiagnosticMessage>` to continue to work as before.
 impl<S: Into<String>> From<S> for SubdiagnosticMessage {
@@ -332,11 +321,6 @@ impl DiagnosticMessage {
             _ => panic!("expected non-translatable diagnostic message"),
         }
     }
-
-    /// Create a `DiagnosticMessage` for the provided Fluent identifier.
-    pub fn new(id: impl Into<FluentId>) -> Self {
-        DiagnosticMessage::FluentIdentifier(id.into(), None)
-    }
 }
 
 /// `From` impl that enables existing diagnostic calls to functions which now take
@@ -347,6 +331,27 @@ impl<S: Into<String>> From<S> for DiagnosticMessage {
     }
 }
 
+/// Translating *into* a subdiagnostic message from a diagnostic message is a little strange - but
+/// the subdiagnostic functions (e.g. `span_label`) take a `SubdiagnosticMessage` and the
+/// subdiagnostic derive refers to typed identifiers that are `DiagnosticMessage`s, so need to be
+/// able to convert between these, as much as they'll be converted back into `DiagnosticMessage`
+/// using `with_subdiagnostic_message` eventually. Don't use this other than for the derive.
+impl Into<SubdiagnosticMessage> for DiagnosticMessage {
+    fn into(self) -> SubdiagnosticMessage {
+        match self {
+            DiagnosticMessage::Str(s) => SubdiagnosticMessage::Str(s),
+            DiagnosticMessage::FluentIdentifier(id, None) => {
+                SubdiagnosticMessage::FluentIdentifier(id)
+            }
+            // There isn't really a sensible behaviour for this because it loses information but
+            // this is the most sensible of the behaviours.
+            DiagnosticMessage::FluentIdentifier(_, Some(attr)) => {
+                SubdiagnosticMessage::FluentAttr(attr)
+            }
+        }
+    }
+}
+
 /// A span together with some additional data.
 #[derive(Clone, Debug)]
 pub struct SpanLabel {
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index a4cbc73978d..8b2a995f1c5 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -656,11 +656,6 @@ impl Emitter for SilentEmitter {
     }
 }
 
-/// Maximum number of lines we will print for a multiline suggestion; arbitrary.
-///
-/// This should be replaced with a more involved mechanism to output multiline suggestions that
-/// more closely mimics the regular diagnostic output, where irrelevant code lines are elided.
-pub const MAX_SUGGESTION_HIGHLIGHT_LINES: usize = 6;
 /// Maximum number of suggestions to be shown
 ///
 /// Arbitrary, but taken from trait import suggestion limit
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 245719bff12..1e57d66dd9f 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1077,6 +1077,7 @@ impl<'a> ExtCtxt<'a> {
         self.current_expansion.id.expansion_cause()
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -1101,9 +1102,11 @@ impl<'a> ExtCtxt<'a> {
     ///
     /// Compilation will be stopped in the near future (at the end of
     /// the macro expansion phase).
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_err<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
         self.sess.parse_sess.span_diagnostic.span_err(sp, msg);
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
         self.sess.parse_sess.span_diagnostic.span_warn(sp, msg);
     }
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index e73c31c98fe..1694a8865dd 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -57,6 +57,10 @@ impl<'a> ExtCtxt<'a> {
         P(ast::Ty { id: ast::DUMMY_NODE_ID, span, kind, tokens: None })
     }
 
+    pub fn ty_infer(&self, span: Span) -> P<ast::Ty> {
+        self.ty(span, ast::TyKind::Infer)
+    }
+
     pub fn ty_path(&self, path: ast::Path) -> P<ast::Ty> {
         self.ty(path.span, ast::TyKind::Path(None, path))
     }
@@ -140,11 +144,26 @@ impl<'a> ExtCtxt<'a> {
         ast::Lifetime { id: ast::DUMMY_NODE_ID, ident: ident.with_span_pos(span) }
     }
 
+    pub fn lifetime_static(&self, span: Span) -> ast::Lifetime {
+        self.lifetime(span, Ident::new(kw::StaticLifetime, span))
+    }
+
     pub fn stmt_expr(&self, expr: P<ast::Expr>) -> ast::Stmt {
         ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Expr(expr) }
     }
 
     pub fn stmt_let(&self, sp: Span, mutbl: bool, ident: Ident, ex: P<ast::Expr>) -> ast::Stmt {
+        self.stmt_let_ty(sp, mutbl, ident, None, ex)
+    }
+
+    pub fn stmt_let_ty(
+        &self,
+        sp: Span,
+        mutbl: bool,
+        ident: Ident,
+        ty: Option<P<ast::Ty>>,
+        ex: P<ast::Expr>,
+    ) -> ast::Stmt {
         let pat = if mutbl {
             let binding_mode = ast::BindingMode::ByValue(ast::Mutability::Mut);
             self.pat_ident_binding_mode(sp, ident, binding_mode)
@@ -153,7 +172,7 @@ impl<'a> ExtCtxt<'a> {
         };
         let local = P(ast::Local {
             pat,
-            ty: None,
+            ty,
             id: ast::DUMMY_NODE_ID,
             kind: LocalKind::Init(ex),
             span: sp,
@@ -315,12 +334,16 @@ impl<'a> ExtCtxt<'a> {
         self.expr_lit(sp, ast::LitKind::Bool(value))
     }
 
-    pub fn expr_vec(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
+    /// `[expr1, expr2, ...]`
+    pub fn expr_array(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
         self.expr(sp, ast::ExprKind::Array(exprs))
     }
-    pub fn expr_vec_slice(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
-        self.expr_addr_of(sp, self.expr_vec(sp, exprs))
+
+    /// `&[expr1, expr2, ...]`
+    pub fn expr_array_ref(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
+        self.expr_addr_of(sp, self.expr_array(sp, exprs))
     }
+
     pub fn expr_str(&self, sp: Span, s: Symbol) -> P<ast::Expr> {
         self.expr_lit(sp, ast::LitKind::Str(s, ast::StrStyle::Cooked))
     }
diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs
index 86ff110eec1..c18147592dc 100644
--- a/compiler/rustc_expand/src/lib.rs
+++ b/compiler/rustc_expand/src/lib.rs
@@ -9,6 +9,7 @@
 #![feature(proc_macro_diagnostic)]
 #![feature(proc_macro_internals)]
 #![feature(proc_macro_span)]
+#![feature(rustc_attrs)]
 #![feature(try_blocks)]
 #![recursion_limit = "256"]
 
diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs
index 1e4193a5a16..0e909e2f982 100644
--- a/compiler/rustc_expand/src/proc_macro_server.rs
+++ b/compiler/rustc_expand/src/proc_macro_server.rs
@@ -2,7 +2,7 @@ use crate::base::ExtCtxt;
 
 use rustc_ast as ast;
 use rustc_ast::token;
-use rustc_ast::tokenstream::{self, DelimSpan, Spacing::*, TokenStream, TreeAndSpacing};
+use rustc_ast::tokenstream::{self, Spacing::*, TokenStream};
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::Lrc;
@@ -14,8 +14,8 @@ use rustc_span::def_id::CrateNum;
 use rustc_span::symbol::{self, kw, sym, Symbol};
 use rustc_span::{BytePos, FileName, Pos, SourceFile, Span};
 
-use pm::bridge::{server, TokenTree};
-use pm::{Delimiter, Level, LineColumn, Spacing};
+use pm::bridge::{server, DelimSpan, ExpnGlobals, Group, Punct, TokenTree};
+use pm::{Delimiter, Level, LineColumn};
 use std::ops::Bound;
 use std::{ascii, panic};
 
@@ -49,158 +49,170 @@ impl ToInternal<token::Delimiter> for Delimiter {
     }
 }
 
-impl FromInternal<(TreeAndSpacing, &'_ mut Vec<Self>, &mut Rustc<'_, '_>)>
-    for TokenTree<Group, Punct, Ident, Literal>
+impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)>
+    for Vec<TokenTree<TokenStream, Span, Ident, Literal>>
 {
-    fn from_internal(
-        ((tree, spacing), stack, rustc): (TreeAndSpacing, &mut Vec<Self>, &mut Rustc<'_, '_>),
-    ) -> Self {
+    fn from_internal((stream, rustc): (TokenStream, &mut Rustc<'_, '_>)) -> Self {
         use rustc_ast::token::*;
 
-        let joint = spacing == Joint;
-        let Token { kind, span } = match tree {
-            tokenstream::TokenTree::Delimited(span, delim, tts) => {
-                let delimiter = pm::Delimiter::from_internal(delim);
-                return TokenTree::Group(Group { delimiter, stream: tts, span, flatten: false });
-            }
-            tokenstream::TokenTree::Token(token) => token,
-        };
+        // Estimate the capacity as `stream.len()` rounded up to the next power
+        // of two to limit the number of required reallocations.
+        let mut trees = Vec::with_capacity(stream.len().next_power_of_two());
+        let mut cursor = stream.into_trees();
 
-        macro_rules! tt {
-            ($ty:ident { $($field:ident $(: $value:expr)*),+ $(,)? }) => (
-                TokenTree::$ty(self::$ty {
-                    $($field $(: $value)*,)+
-                    span,
-                })
-            );
-            ($ty:ident::$method:ident($($value:expr),*)) => (
-                TokenTree::$ty(self::$ty::$method($($value,)* span))
-            );
-        }
-        macro_rules! op {
-            ($a:expr) => {
-                tt!(Punct::new($a, joint))
+        while let Some((tree, spacing)) = cursor.next_with_spacing() {
+            let joint = spacing == Joint;
+            let Token { kind, span } = match tree {
+                tokenstream::TokenTree::Delimited(span, delim, tts) => {
+                    let delimiter = pm::Delimiter::from_internal(delim);
+                    trees.push(TokenTree::Group(Group {
+                        delimiter,
+                        stream: Some(tts),
+                        span: DelimSpan {
+                            open: span.open,
+                            close: span.close,
+                            entire: span.entire(),
+                        },
+                    }));
+                    continue;
+                }
+                tokenstream::TokenTree::Token(token) => token,
             };
-            ($a:expr, $b:expr) => {{
-                stack.push(tt!(Punct::new($b, joint)));
-                tt!(Punct::new($a, true))
-            }};
-            ($a:expr, $b:expr, $c:expr) => {{
-                stack.push(tt!(Punct::new($c, joint)));
-                stack.push(tt!(Punct::new($b, true)));
-                tt!(Punct::new($a, true))
-            }};
-        }
 
-        match kind {
-            Eq => op!('='),
-            Lt => op!('<'),
-            Le => op!('<', '='),
-            EqEq => op!('=', '='),
-            Ne => op!('!', '='),
-            Ge => op!('>', '='),
-            Gt => op!('>'),
-            AndAnd => op!('&', '&'),
-            OrOr => op!('|', '|'),
-            Not => op!('!'),
-            Tilde => op!('~'),
-            BinOp(Plus) => op!('+'),
-            BinOp(Minus) => op!('-'),
-            BinOp(Star) => op!('*'),
-            BinOp(Slash) => op!('/'),
-            BinOp(Percent) => op!('%'),
-            BinOp(Caret) => op!('^'),
-            BinOp(And) => op!('&'),
-            BinOp(Or) => op!('|'),
-            BinOp(Shl) => op!('<', '<'),
-            BinOp(Shr) => op!('>', '>'),
-            BinOpEq(Plus) => op!('+', '='),
-            BinOpEq(Minus) => op!('-', '='),
-            BinOpEq(Star) => op!('*', '='),
-            BinOpEq(Slash) => op!('/', '='),
-            BinOpEq(Percent) => op!('%', '='),
-            BinOpEq(Caret) => op!('^', '='),
-            BinOpEq(And) => op!('&', '='),
-            BinOpEq(Or) => op!('|', '='),
-            BinOpEq(Shl) => op!('<', '<', '='),
-            BinOpEq(Shr) => op!('>', '>', '='),
-            At => op!('@'),
-            Dot => op!('.'),
-            DotDot => op!('.', '.'),
-            DotDotDot => op!('.', '.', '.'),
-            DotDotEq => op!('.', '.', '='),
-            Comma => op!(','),
-            Semi => op!(';'),
-            Colon => op!(':'),
-            ModSep => op!(':', ':'),
-            RArrow => op!('-', '>'),
-            LArrow => op!('<', '-'),
-            FatArrow => op!('=', '>'),
-            Pound => op!('#'),
-            Dollar => op!('$'),
-            Question => op!('?'),
-            SingleQuote => op!('\''),
-
-            Ident(name, false) if name == kw::DollarCrate => tt!(Ident::dollar_crate()),
-            Ident(name, is_raw) => tt!(Ident::new(rustc.sess(), name, is_raw)),
-            Lifetime(name) => {
-                let ident = symbol::Ident::new(name, span).without_first_quote();
-                stack.push(tt!(Ident::new(rustc.sess(), ident.name, false)));
-                tt!(Punct::new('\'', true))
-            }
-            Literal(lit) => tt!(Literal { lit }),
-            DocComment(_, attr_style, data) => {
-                let mut escaped = String::new();
-                for ch in data.as_str().chars() {
-                    escaped.extend(ch.escape_debug());
-                }
-                let stream = [
-                    Ident(sym::doc, false),
-                    Eq,
-                    TokenKind::lit(token::Str, Symbol::intern(&escaped), None),
-                ]
-                .into_iter()
-                .map(|kind| tokenstream::TokenTree::token(kind, span))
-                .collect();
-                stack.push(TokenTree::Group(Group {
-                    delimiter: pm::Delimiter::Bracket,
-                    stream,
-                    span: DelimSpan::from_single(span),
-                    flatten: false,
+            let mut op = |s: &str| {
+                assert!(s.is_ascii());
+                trees.extend(s.as_bytes().iter().enumerate().map(|(idx, &ch)| {
+                    TokenTree::Punct(Punct { ch, joint: joint || idx != s.len() - 1, span })
                 }));
-                if attr_style == ast::AttrStyle::Inner {
-                    stack.push(tt!(Punct::new('!', false)));
+            };
+
+            match kind {
+                Eq => op("="),
+                Lt => op("<"),
+                Le => op("<="),
+                EqEq => op("=="),
+                Ne => op("!="),
+                Ge => op(">="),
+                Gt => op(">"),
+                AndAnd => op("&&"),
+                OrOr => op("||"),
+                Not => op("!"),
+                Tilde => op("~"),
+                BinOp(Plus) => op("+"),
+                BinOp(Minus) => op("-"),
+                BinOp(Star) => op("*"),
+                BinOp(Slash) => op("/"),
+                BinOp(Percent) => op("%"),
+                BinOp(Caret) => op("^"),
+                BinOp(And) => op("&"),
+                BinOp(Or) => op("|"),
+                BinOp(Shl) => op("<<"),
+                BinOp(Shr) => op(">>"),
+                BinOpEq(Plus) => op("+="),
+                BinOpEq(Minus) => op("-="),
+                BinOpEq(Star) => op("*="),
+                BinOpEq(Slash) => op("/="),
+                BinOpEq(Percent) => op("%="),
+                BinOpEq(Caret) => op("^="),
+                BinOpEq(And) => op("&="),
+                BinOpEq(Or) => op("|="),
+                BinOpEq(Shl) => op("<<="),
+                BinOpEq(Shr) => op(">>="),
+                At => op("@"),
+                Dot => op("."),
+                DotDot => op(".."),
+                DotDotDot => op("..."),
+                DotDotEq => op("..="),
+                Comma => op(","),
+                Semi => op(";"),
+                Colon => op(":"),
+                ModSep => op("::"),
+                RArrow => op("->"),
+                LArrow => op("<-"),
+                FatArrow => op("=>"),
+                Pound => op("#"),
+                Dollar => op("$"),
+                Question => op("?"),
+                SingleQuote => op("'"),
+
+                Ident(name, false) if name == kw::DollarCrate => trees.push(TokenTree::Ident(Ident::dollar_crate(span))),
+                Ident(name, is_raw) => trees.push(TokenTree::Ident(Ident::new(rustc.sess(), name, is_raw, span))),
+                Lifetime(name) => {
+                    let ident = symbol::Ident::new(name, span).without_first_quote();
+                    trees.extend([
+                        TokenTree::Punct(Punct { ch: b'\'', joint: true, span }),
+                        TokenTree::Ident(Ident::new(rustc.sess(), ident.name, false, span)),
+                    ]);
+                }
+                Literal(lit) => trees.push(TokenTree::Literal(self::Literal { lit, span })),
+                DocComment(_, attr_style, data) => {
+                    let mut escaped = String::new();
+                    for ch in data.as_str().chars() {
+                        escaped.extend(ch.escape_debug());
+                    }
+                    let stream = [
+                        Ident(sym::doc, false),
+                        Eq,
+                        TokenKind::lit(token::Str, Symbol::intern(&escaped), None),
+                    ]
+                    .into_iter()
+                    .map(|kind| tokenstream::TokenTree::token(kind, span))
+                    .collect();
+                    trees.push(TokenTree::Punct(Punct { ch: b'#', joint: false, span }));
+                    if attr_style == ast::AttrStyle::Inner {
+                        trees.push(TokenTree::Punct(Punct { ch: b'!', joint: false, span }));
+                    }
+                    trees.push(TokenTree::Group(Group {
+                        delimiter: pm::Delimiter::Bracket,
+                        stream: Some(stream),
+                        span: DelimSpan::from_single(span),
+                    }));
                 }
-                tt!(Punct::new('#', false))
-            }
 
-            Interpolated(nt) if let NtIdent(ident, is_raw) = *nt => {
-                TokenTree::Ident(Ident::new(rustc.sess(), ident.name, is_raw, ident.span))
-            }
-            Interpolated(nt) => {
-                TokenTree::Group(Group {
-                    delimiter: pm::Delimiter::None,
-                    stream: TokenStream::from_nonterminal_ast(&nt),
-                    span: DelimSpan::from_single(span),
-                    flatten: crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.sess()),
-                })
-            }
+                Interpolated(nt) if let NtIdent(ident, is_raw) = *nt => {
+                    trees.push(TokenTree::Ident(Ident::new(rustc.sess(), ident.name, is_raw, ident.span)))
+                }
 
-            OpenDelim(..) | CloseDelim(..) => unreachable!(),
-            Eof => unreachable!(),
+                Interpolated(nt) => {
+                    let stream = TokenStream::from_nonterminal_ast(&nt);
+                    // A hack used to pass AST fragments to attribute and derive
+                    // macros as a single nonterminal token instead of a token
+                    // stream.  Such token needs to be "unwrapped" and not
+                    // represented as a delimited group.
+                    // FIXME: It needs to be removed, but there are some
+                    // compatibility issues (see #73345).
+                    if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.sess()) {
+                        trees.extend(Self::from_internal((stream, rustc)));
+                    } else {
+                        trees.push(TokenTree::Group(Group {
+                            delimiter: pm::Delimiter::None,
+                            stream: Some(stream),
+                            span: DelimSpan::from_single(span),
+                        }))
+                    }
+                }
+
+                OpenDelim(..) | CloseDelim(..) => unreachable!(),
+                Eof => unreachable!(),
+            }
         }
+        trees
     }
 }
 
-impl ToInternal<TokenStream> for TokenTree<Group, Punct, Ident, Literal> {
+impl ToInternal<TokenStream> for TokenTree<TokenStream, Span, Ident, Literal> {
     fn to_internal(self) -> TokenStream {
         use rustc_ast::token::*;
 
         let (ch, joint, span) = match self {
             TokenTree::Punct(Punct { ch, joint, span }) => (ch, joint, span),
-            TokenTree::Group(Group { delimiter, stream, span, .. }) => {
-                return tokenstream::TokenTree::Delimited(span, delimiter.to_internal(), stream)
-                    .into();
+            TokenTree::Group(Group { delimiter, stream, span: DelimSpan { open, close, .. } }) => {
+                return tokenstream::TokenTree::Delimited(
+                    tokenstream::DelimSpan { open, close },
+                    delimiter.to_internal(),
+                    stream.unwrap_or_default(),
+                )
+                .into();
             }
             TokenTree::Ident(self::Ident { sym, is_raw, span }) => {
                 return tokenstream::TokenTree::token(Ident(sym, is_raw), span).into();
@@ -233,28 +245,28 @@ impl ToInternal<TokenStream> for TokenTree<Group, Punct, Ident, Literal> {
         };
 
         let kind = match ch {
-            '=' => Eq,
-            '<' => Lt,
-            '>' => Gt,
-            '!' => Not,
-            '~' => Tilde,
-            '+' => BinOp(Plus),
-            '-' => BinOp(Minus),
-            '*' => BinOp(Star),
-            '/' => BinOp(Slash),
-            '%' => BinOp(Percent),
-            '^' => BinOp(Caret),
-            '&' => BinOp(And),
-            '|' => BinOp(Or),
-            '@' => At,
-            '.' => Dot,
-            ',' => Comma,
-            ';' => Semi,
-            ':' => Colon,
-            '#' => Pound,
-            '$' => Dollar,
-            '?' => Question,
-            '\'' => SingleQuote,
+            b'=' => Eq,
+            b'<' => Lt,
+            b'>' => Gt,
+            b'!' => Not,
+            b'~' => Tilde,
+            b'+' => BinOp(Plus),
+            b'-' => BinOp(Minus),
+            b'*' => BinOp(Star),
+            b'/' => BinOp(Slash),
+            b'%' => BinOp(Percent),
+            b'^' => BinOp(Caret),
+            b'&' => BinOp(And),
+            b'|' => BinOp(Or),
+            b'@' => At,
+            b'.' => Dot,
+            b',' => Comma,
+            b';' => Semi,
+            b':' => Colon,
+            b'#' => Pound,
+            b'$' => Dollar,
+            b'?' => Question,
+            b'\'' => SingleQuote,
             _ => unreachable!(),
         };
 
@@ -277,38 +289,6 @@ impl ToInternal<rustc_errors::Level> for Level {
 
 pub struct FreeFunctions;
 
-#[derive(Clone)]
-pub struct Group {
-    delimiter: Delimiter,
-    stream: TokenStream,
-    span: DelimSpan,
-    /// A hack used to pass AST fragments to attribute and derive macros
-    /// as a single nonterminal token instead of a token stream.
-    /// FIXME: It needs to be removed, but there are some compatibility issues (see #73345).
-    flatten: bool,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Hash)]
-pub struct Punct {
-    ch: char,
-    // NB. not using `Spacing` here because it doesn't implement `Hash`.
-    joint: bool,
-    span: Span,
-}
-
-impl Punct {
-    fn new(ch: char, joint: bool, span: Span) -> Punct {
-        const LEGAL_CHARS: &[char] = &[
-            '=', '<', '>', '!', '~', '+', '-', '*', '/', '%', '^', '&', '|', '@', '.', ',', ';',
-            ':', '#', '$', '?', '\'',
-        ];
-        if !LEGAL_CHARS.contains(&ch) {
-            panic!("unsupported character `{:?}`", ch)
-        }
-        Punct { ch, joint, span }
-    }
-}
-
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Ident {
     sym: Symbol,
@@ -370,15 +350,13 @@ impl<'a, 'b> Rustc<'a, 'b> {
     }
 
     fn lit(&mut self, kind: token::LitKind, symbol: Symbol, suffix: Option<Symbol>) -> Literal {
-        Literal { lit: token::Lit::new(kind, symbol, suffix), span: server::Span::call_site(self) }
+        Literal { lit: token::Lit::new(kind, symbol, suffix), span: self.call_site }
     }
 }
 
 impl server::Types for Rustc<'_, '_> {
     type FreeFunctions = FreeFunctions;
     type TokenStream = TokenStream;
-    type Group = Group;
-    type Punct = Punct;
     type Ident = Ident;
     type Literal = Literal;
     type SourceFile = Lrc<SourceFile>;
@@ -471,7 +449,7 @@ impl server::TokenStream for Rustc<'_, '_> {
 
     fn from_token_tree(
         &mut self,
-        tree: TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>,
+        tree: TokenTree<Self::TokenStream, Self::Span, Self::Ident, Self::Literal>,
     ) -> Self::TokenStream {
         tree.to_internal()
     }
@@ -479,7 +457,7 @@ impl server::TokenStream for Rustc<'_, '_> {
     fn concat_trees(
         &mut self,
         base: Option<Self::TokenStream>,
-        trees: Vec<TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>>,
+        trees: Vec<TokenTree<Self::TokenStream, Self::Span, Self::Ident, Self::Literal>>,
     ) -> Self::TokenStream {
         let mut builder = tokenstream::TokenStreamBuilder::new();
         if let Some(base) = base {
@@ -509,93 +487,8 @@ impl server::TokenStream for Rustc<'_, '_> {
     fn into_trees(
         &mut self,
         stream: Self::TokenStream,
-    ) -> Vec<TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>> {
-        // FIXME: This is a raw port of the previous approach (which had a
-        // `TokenStreamIter` server-side object with a single `next` method),
-        // and can probably be optimized (for bulk conversion).
-        let mut cursor = stream.into_trees();
-        let mut stack = Vec::new();
-        let mut tts = Vec::new();
-        loop {
-            let next = stack.pop().or_else(|| {
-                let next = cursor.next_with_spacing()?;
-                Some(TokenTree::from_internal((next, &mut stack, self)))
-            });
-            match next {
-                Some(TokenTree::Group(group)) => {
-                    // A hack used to pass AST fragments to attribute and derive
-                    // macros as a single nonterminal token instead of a token
-                    // stream.  Such token needs to be "unwrapped" and not
-                    // represented as a delimited group.
-                    // FIXME: It needs to be removed, but there are some
-                    // compatibility issues (see #73345).
-                    if group.flatten {
-                        tts.append(&mut self.into_trees(group.stream));
-                    } else {
-                        tts.push(TokenTree::Group(group));
-                    }
-                }
-                Some(tt) => tts.push(tt),
-                None => return tts,
-            }
-        }
-    }
-}
-
-impl server::Group for Rustc<'_, '_> {
-    fn new(&mut self, delimiter: Delimiter, stream: Option<Self::TokenStream>) -> Self::Group {
-        Group {
-            delimiter,
-            stream: stream.unwrap_or_default(),
-            span: DelimSpan::from_single(server::Span::call_site(self)),
-            flatten: false,
-        }
-    }
-
-    fn delimiter(&mut self, group: &Self::Group) -> Delimiter {
-        group.delimiter
-    }
-
-    fn stream(&mut self, group: &Self::Group) -> Self::TokenStream {
-        group.stream.clone()
-    }
-
-    fn span(&mut self, group: &Self::Group) -> Self::Span {
-        group.span.entire()
-    }
-
-    fn span_open(&mut self, group: &Self::Group) -> Self::Span {
-        group.span.open
-    }
-
-    fn span_close(&mut self, group: &Self::Group) -> Self::Span {
-        group.span.close
-    }
-
-    fn set_span(&mut self, group: &mut Self::Group, span: Self::Span) {
-        group.span = DelimSpan::from_single(span);
-    }
-}
-
-impl server::Punct for Rustc<'_, '_> {
-    fn new(&mut self, ch: char, spacing: Spacing) -> Self::Punct {
-        Punct::new(ch, spacing == Spacing::Joint, server::Span::call_site(self))
-    }
-
-    fn as_char(&mut self, punct: Self::Punct) -> char {
-        punct.ch
-    }
-
-    fn spacing(&mut self, punct: Self::Punct) -> Spacing {
-        if punct.joint { Spacing::Joint } else { Spacing::Alone }
-    }
-
-    fn span(&mut self, punct: Self::Punct) -> Self::Span {
-        punct.span
-    }
-
-    fn with_span(&mut self, punct: Self::Punct, span: Self::Span) -> Self::Punct {
-        Punct { span, ..punct }
+    ) -> Vec<TokenTree<Self::TokenStream, Self::Span, Self::Ident, Self::Literal>> {
+        FromInternal::from_internal((stream, self))
     }
 }
 
@@ -829,18 +722,6 @@ impl server::Span for Rustc<'_, '_> {
         }
     }
 
-    fn def_site(&mut self) -> Self::Span {
-        self.def_site
-    }
-
-    fn call_site(&mut self) -> Self::Span {
-        self.call_site
-    }
-
-    fn mixed_site(&mut self) -> Self::Span {
-        self.mixed_site
-    }
-
     fn source_file(&mut self, span: Self::Span) -> Self::SourceFile {
         self.sess().source_map().lookup_char_pos(span.lo()).file
     }
@@ -926,3 +807,13 @@ impl server::Span for Rustc<'_, '_> {
         })
     }
 }
+
+impl server::Server for Rustc<'_, '_> {
+    fn globals(&mut self) -> ExpnGlobals<Self::Span> {
+        ExpnGlobals {
+            def_site: self.def_site,
+            call_site: self.call_site,
+            mixed_site: self.mixed_site,
+        }
+    }
+}
diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs
index e68274e2ad9..384a0f9c225 100644
--- a/compiler/rustc_hir/src/intravisit.rs
+++ b/compiler/rustc_hir/src/intravisit.rs
@@ -65,7 +65,6 @@
 //! example generator inference, and possibly also HIR borrowck.
 
 use crate::hir::*;
-use crate::itemlikevisit::ParItemLikeVisitor;
 use rustc_ast::walk_list;
 use rustc_ast::{Attribute, Label};
 use rustc_span::symbol::{Ident, Symbol};
@@ -76,29 +75,6 @@ pub trait IntoVisitor<'hir> {
     fn into_visitor(&self) -> Self::Visitor;
 }
 
-pub struct ParDeepVisitor<V>(pub V);
-
-impl<'hir, V> ParItemLikeVisitor<'hir> for ParDeepVisitor<V>
-where
-    V: IntoVisitor<'hir>,
-{
-    fn visit_item(&self, item: &'hir Item<'hir>) {
-        self.0.into_visitor().visit_item(item);
-    }
-
-    fn visit_trait_item(&self, trait_item: &'hir TraitItem<'hir>) {
-        self.0.into_visitor().visit_trait_item(trait_item);
-    }
-
-    fn visit_impl_item(&self, impl_item: &'hir ImplItem<'hir>) {
-        self.0.into_visitor().visit_impl_item(impl_item);
-    }
-
-    fn visit_foreign_item(&self, foreign_item: &'hir ForeignItem<'hir>) {
-        self.0.into_visitor().visit_foreign_item(foreign_item);
-    }
-}
-
 #[derive(Copy, Clone, Debug)]
 pub enum FnKind<'a> {
     /// `#[xxx] pub async/const/extern "Abi" fn foo()`
diff --git a/compiler/rustc_hir/src/itemlikevisit.rs b/compiler/rustc_hir/src/itemlikevisit.rs
deleted file mode 100644
index a490268dc9f..00000000000
--- a/compiler/rustc_hir/src/itemlikevisit.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use super::{ForeignItem, ImplItem, Item, TraitItem};
-
-/// A parallel variant of `ItemLikeVisitor`.
-pub trait ParItemLikeVisitor<'hir> {
-    fn visit_item(&self, item: &'hir Item<'hir>);
-    fn visit_trait_item(&self, trait_item: &'hir TraitItem<'hir>);
-    fn visit_impl_item(&self, impl_item: &'hir ImplItem<'hir>);
-    fn visit_foreign_item(&self, foreign_item: &'hir ForeignItem<'hir>);
-}
diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs
index d845c433d8c..9f32a7da159 100644
--- a/compiler/rustc_hir/src/lib.rs
+++ b/compiler/rustc_hir/src/lib.rs
@@ -27,7 +27,6 @@ pub use rustc_span::def_id;
 mod hir;
 pub mod hir_id;
 pub mod intravisit;
-pub mod itemlikevisit;
 pub mod lang_items;
 pub mod pat_util;
 mod stable_hash_impls;
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs
index b9596cd10ed..42d52446ab6 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs
@@ -79,7 +79,7 @@ pub fn find_param_with_region<'tcx>(
             // May return None; sometimes the tables are not yet populated.
             let ty = fn_sig.inputs()[index];
             let mut found_anon_region = false;
-            let new_param_ty = tcx.fold_regions(ty, &mut false, |r, _| {
+            let new_param_ty = tcx.fold_regions(ty, |r, _| {
                 if r == anon_region {
                     found_anon_region = true;
                     replace_region
diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
index 455de47acef..68c709a2e24 100644
--- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
+++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
@@ -868,7 +868,7 @@ impl<'tcx> LexicalRegionResolutions<'tcx> {
     where
         T: TypeFoldable<'tcx>,
     {
-        tcx.fold_regions(value, &mut false, |r, _db| match *r {
+        tcx.fold_regions(value, |r, _db| match *r {
             ty::ReVar(rid) => self.resolve_var(rid),
             _ => r,
         })
diff --git a/compiler/rustc_infer/src/infer/opaque_types.rs b/compiler/rustc_infer/src/infer/opaque_types.rs
index 7684d861f3c..ebb8d443421 100644
--- a/compiler/rustc_infer/src/infer/opaque_types.rs
+++ b/compiler/rustc_infer/src/infer/opaque_types.rs
@@ -39,21 +39,19 @@ pub struct OpaqueTypeDecl<'tcx> {
 }
 
 impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
-    /// This is a backwards compatibility hack to prevent breaking changes from
-    /// lazy TAIT around RPIT handling.
-    pub fn replace_opaque_types_with_inference_vars<T: TypeFoldable<'tcx>>(
+    pub fn replace_opaque_types_with_inference_vars(
         &self,
-        value: T,
+        ty: Ty<'tcx>,
         body_id: HirId,
         span: Span,
         code: ObligationCauseCode<'tcx>,
         param_env: ty::ParamEnv<'tcx>,
-    ) -> InferOk<'tcx, T> {
-        if !value.has_opaque_types() {
-            return InferOk { value, obligations: vec![] };
+    ) -> InferOk<'tcx, Ty<'tcx>> {
+        if !ty.has_opaque_types() {
+            return InferOk { value: ty, obligations: vec![] };
         }
         let mut obligations = vec![];
-        let value = value.fold_with(&mut ty::fold::BottomUpFolder {
+        let value = ty.fold_with(&mut ty::fold::BottomUpFolder {
             tcx: self.tcx,
             lt_op: |lt| lt,
             ct_op: |ct| ct,
diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs
index a268493b28f..1c1906f3375 100644
--- a/compiler/rustc_infer/src/infer/outlives/obligations.rs
+++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs
@@ -141,9 +141,6 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
     ///   `('a, K)` in this list tells us that the bounds in scope
     ///   indicate that `K: 'a`, where `K` is either a generic
     ///   parameter like `T` or a projection like `T::Item`.
-    /// - `implicit_region_bound`: if some, this is a region bound
-    ///   that is considered to hold for all type parameters (the
-    ///   function body).
     /// - `param_env` is the parameter environment for the enclosing function.
     /// - `body_id` is the body-id whose region obligations are being
     ///   processed.
@@ -151,7 +148,6 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
     pub fn process_registered_region_obligations(
         &self,
         region_bound_pairs_map: &FxHashMap<hir::HirId, RegionBoundPairs<'tcx>>,
-        implicit_region_bound: Option<ty::Region<'tcx>>,
         param_env: ty::ParamEnv<'tcx>,
     ) {
         assert!(
@@ -170,13 +166,8 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
             let sup_type = self.resolve_vars_if_possible(sup_type);
 
             if let Some(region_bound_pairs) = region_bound_pairs_map.get(&body_id) {
-                let outlives = &mut TypeOutlives::new(
-                    self,
-                    self.tcx,
-                    &region_bound_pairs,
-                    implicit_region_bound,
-                    param_env,
-                );
+                let outlives =
+                    &mut TypeOutlives::new(self, self.tcx, &region_bound_pairs, None, param_env);
                 outlives.type_must_outlive(origin, sup_type, sub_region);
             } else {
                 self.tcx.sess.delay_span_bug(
diff --git a/compiler/rustc_infer/src/infer/outlives/verify.rs b/compiler/rustc_infer/src/infer/outlives/verify.rs
index 191f5f18ec2..86b025dce5e 100644
--- a/compiler/rustc_infer/src/infer/outlives/verify.rs
+++ b/compiler/rustc_infer/src/infer/outlives/verify.rs
@@ -16,6 +16,11 @@ use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt};
 pub struct VerifyBoundCx<'cx, 'tcx> {
     tcx: TyCtxt<'tcx>,
     region_bound_pairs: &'cx RegionBoundPairs<'tcx>,
+    /// During borrowck, if there are no outlives bounds on a generic
+    /// parameter `T`, we assume that `T: 'in_fn_body` holds.
+    ///
+    /// Outside of borrowck the only way to prove `T: '?0` is by
+    /// setting  `'?0` to `'empty`.
     implicit_region_bound: Option<ty::Region<'tcx>>,
     param_env: ty::ParamEnv<'tcx>,
 }
@@ -263,8 +268,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
         //     fn foo<'a, A>(x: &'a A) { x.bar() }
         //
         // The problem is that the type of `x` is `&'a A`. To be
-        // well-formed, then, A must be lower-generic by `'a`, but we
-        // don't know that this holds from first principles.
+        // well-formed, then, A must outlive `'a`, but we don't know that
+        // this holds from first principles.
         let from_region_bound_pairs = self.region_bound_pairs.iter().filter_map(|&(r, p)| {
             debug!(
                 "declared_generic_bounds_from_env_for_erased_ty: region_bound_pair = {:?}",
diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs
index d830000b65f..1f3cb401314 100644
--- a/compiler/rustc_infer/src/infer/resolve.rs
+++ b/compiler/rustc_infer/src/infer/resolve.rs
@@ -92,7 +92,7 @@ impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticRegionResolver<'a, 'tcx> {
                     .borrow_mut()
                     .unwrap_region_constraints()
                     .opportunistic_resolve_var(rid);
-                self.tcx().reuse_or_mk_region(r, ty::ReVar(resolved))
+                TypeFolder::tcx(self).reuse_or_mk_region(r, ty::ReVar(resolved))
             }
             _ => r,
         }
@@ -179,15 +179,13 @@ struct FullTypeResolver<'a, 'tcx> {
     infcx: &'a InferCtxt<'a, 'tcx>,
 }
 
-impl<'a, 'tcx> TypeFolder<'tcx> for FullTypeResolver<'a, 'tcx> {
+impl<'a, 'tcx> FallibleTypeFolder<'tcx> for FullTypeResolver<'a, 'tcx> {
     type Error = FixupError<'tcx>;
 
     fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
         self.infcx.tcx
     }
-}
 
-impl<'a, 'tcx> FallibleTypeFolder<'tcx> for FullTypeResolver<'a, 'tcx> {
     fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
         if !t.needs_infer() {
             Ok(t) // micro-optimize -- if there is nothing in this type that this fold affects...
diff --git a/compiler/rustc_lexer/src/unescape.rs b/compiler/rustc_lexer/src/unescape.rs
index 97f9588ae1e..3da6bc14622 100644
--- a/compiler/rustc_lexer/src/unescape.rs
+++ b/compiler/rustc_lexer/src/unescape.rs
@@ -238,7 +238,7 @@ fn scan_escape(chars: &mut Chars<'_>, mode: Mode) -> Result<char, EscapeError> {
                             c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?;
                         n_digits += 1;
                         if n_digits > 6 {
-                            // Stop updating value since we're sure that it's is incorrect already.
+                            // Stop updating value since we're sure that it's incorrect already.
                             continue;
                         }
                         let digit = digit as u32;
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
index fadb1c87933..56c8635a189 100644
--- a/compiler/rustc_lint/src/internal.rs
+++ b/compiler/rustc_lint/src/internal.rs
@@ -406,9 +406,12 @@ impl LateLintPass<'_> for Diagnostics {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
         debug!(?span, ?def_id, ?substs);
-        if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) &&
-            !cx.tcx.has_attr(instance.def_id(), sym::rustc_lint_diagnostics)
-        {
+        let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs)
+            .ok()
+            .and_then(|inst| inst)
+            .map(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics))
+            .unwrap_or(false);
+        if !has_attr {
             return;
         }
 
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index 2a2dc6822ce..5579e4d19cf 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -4,7 +4,6 @@ use rustc_attr as attr;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
-use rustc_hir::def_id::DefId;
 use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
 use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
 use rustc_middle::ty::subst::SubstsRef;
@@ -1483,39 +1482,32 @@ impl InvalidAtomicOrdering {
         None
     }
 
-    fn matches_ordering(cx: &LateContext<'_>, did: DefId, orderings: &[Symbol]) -> bool {
+    fn match_ordering(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<Symbol> {
+        let ExprKind::Path(ref ord_qpath) = ord_arg.kind else { return None };
+        let did = cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()?;
         let tcx = cx.tcx;
         let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering);
-        orderings.iter().any(|ordering| {
-            tcx.item_name(did) == *ordering && {
-                let parent = tcx.parent(did);
-                Some(parent) == atomic_ordering
-                    // needed in case this is a ctor, not a variant
-                    || tcx.opt_parent(parent) == atomic_ordering
-            }
-        })
-    }
-
-    fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<DefId> {
-        if let ExprKind::Path(ref ord_qpath) = ord_arg.kind {
-            cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()
-        } else {
-            None
-        }
+        let name = tcx.item_name(did);
+        let parent = tcx.parent(did);
+        [sym::Relaxed, sym::Release, sym::Acquire, sym::AcqRel, sym::SeqCst].into_iter().find(
+            |&ordering| {
+                name == ordering
+                    && (Some(parent) == atomic_ordering
+                            // needed in case this is a ctor, not a variant
+                            || tcx.opt_parent(parent) == atomic_ordering)
+            },
+        )
     }
 
     fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
-        use rustc_hir::def::{DefKind, Res};
-        use rustc_hir::QPath;
         if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store])
             && let Some((ordering_arg, invalid_ordering)) = match method {
                 sym::load => Some((&args[1], sym::Release)),
                 sym::store => Some((&args[2], sym::Acquire)),
                 _ => None,
             }
-            && let ExprKind::Path(QPath::Resolved(_, path)) = ordering_arg.kind
-            && let Res::Def(DefKind::Ctor(..), ctor_id) = path.res
-            && Self::matches_ordering(cx, ctor_id, &[invalid_ordering, sym::AcqRel])
+            && let Some(ordering) = Self::match_ordering(cx, ordering_arg)
+            && (ordering == invalid_ordering || ordering == sym::AcqRel)
         {
             cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| {
                 if method == sym::load {
@@ -1537,9 +1529,7 @@ impl InvalidAtomicOrdering {
             && let ExprKind::Path(ref func_qpath) = func.kind
             && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
             && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::fence | sym::compiler_fence))
-            && let ExprKind::Path(ref ordering_qpath) = &args[0].kind
-            && let Some(ordering_def_id) = cx.qpath_res(ordering_qpath, args[0].hir_id).opt_def_id()
-            && Self::matches_ordering(cx, ordering_def_id, &[sym::Relaxed])
+            && Self::match_ordering(cx, &args[0]) == Some(sym::Relaxed)
         {
             cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| {
                 diag.build("memory fences cannot have `Relaxed` ordering")
@@ -1550,62 +1540,56 @@ impl InvalidAtomicOrdering {
     }
 
     fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
-        if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak])
-            && let Some((success_order_arg, failure_order_arg)) = match method {
-                sym::fetch_update => Some((&args[1], &args[2])),
-                sym::compare_exchange | sym::compare_exchange_weak => Some((&args[3], &args[4])),
-                _ => None,
-            }
-            && let Some(fail_ordering_def_id) = Self::opt_ordering_defid(cx, failure_order_arg)
-        {
-            // Helper type holding on to some checking and error reporting data. Has
-            // - (success ordering,
-            // - list of failure orderings forbidden by the success order,
-            // - suggestion message)
-            type OrdLintInfo = (Symbol, &'static [Symbol], &'static str);
-            const RELAXED: OrdLintInfo = (sym::Relaxed, &[sym::SeqCst, sym::Acquire], "ordering mode `Relaxed`");
-            const ACQUIRE: OrdLintInfo = (sym::Acquire, &[sym::SeqCst], "ordering modes `Acquire` or `Relaxed`");
-            const SEQ_CST: OrdLintInfo = (sym::SeqCst, &[], "ordering modes `Acquire`, `SeqCst` or `Relaxed`");
-            const RELEASE: OrdLintInfo = (sym::Release, RELAXED.1, RELAXED.2);
-            const ACQREL: OrdLintInfo = (sym::AcqRel, ACQUIRE.1, ACQUIRE.2);
-            const SEARCH: [OrdLintInfo; 5] = [RELAXED, ACQUIRE, SEQ_CST, RELEASE, ACQREL];
-
-            let success_lint_info = Self::opt_ordering_defid(cx, success_order_arg)
-                .and_then(|success_ord_def_id| -> Option<OrdLintInfo> {
-                    SEARCH
-                        .iter()
-                        .copied()
-                        .find(|(ordering, ..)| {
-                            Self::matches_ordering(cx, success_ord_def_id, &[*ordering])
-                        })
-                });
-            if Self::matches_ordering(cx, fail_ordering_def_id, &[sym::Release, sym::AcqRel]) {
-                // If we don't know the success order is, use what we'd suggest
-                // if it were maximally permissive.
-                let suggested = success_lint_info.unwrap_or(SEQ_CST).2;
-                cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| {
-                    let msg = format!(
-                        "{}'s failure ordering may not be `Release` or `AcqRel`",
-                        method,
-                    );
-                    diag.build(&msg)
-                        .help(&format!("consider using {} instead", suggested))
-                        .emit();
-                });
-            } else if let Some((success_ord, bad_ords_given_success, suggested)) = success_lint_info {
-                if Self::matches_ordering(cx, fail_ordering_def_id, bad_ords_given_success) {
-                    cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| {
-                        let msg = format!(
-                            "{}'s failure ordering may not be stronger than the success ordering of `{}`",
-                            method,
-                            success_ord,
-                        );
-                        diag.build(&msg)
-                            .help(&format!("consider using {} instead", suggested))
-                            .emit();
-                    });
-                }
-            }
+        let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak])
+            else {return };
+
+        let (success_order_arg, fail_order_arg) = match method {
+            sym::fetch_update => (&args[1], &args[2]),
+            sym::compare_exchange | sym::compare_exchange_weak => (&args[3], &args[4]),
+            _ => return,
+        };
+
+        let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return };
+
+        if matches!(fail_ordering, sym::Release | sym::AcqRel) {
+            cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg.span, |diag| {
+                diag.build(&format!(
+                    "`{method}`'s failure ordering may not be `Release` or `AcqRel`, \
+                    since a failed `{method}` does not result in a write",
+                ))
+                .span_label(fail_order_arg.span, "invalid failure ordering")
+                .help("consider using `Acquire` or `Relaxed` failure ordering instead")
+                .emit();
+            });
+        }
+
+        let Some(success_ordering) = Self::match_ordering(cx, success_order_arg) else { return };
+
+        if matches!(
+            (success_ordering, fail_ordering),
+            (sym::Relaxed | sym::Release, sym::Acquire)
+                | (sym::Relaxed | sym::Release | sym::Acquire | sym::AcqRel, sym::SeqCst)
+        ) {
+            let success_suggestion =
+                if success_ordering == sym::Release && fail_ordering == sym::Acquire {
+                    sym::AcqRel
+                } else {
+                    fail_ordering
+                };
+            cx.struct_span_lint(INVALID_ATOMIC_ORDERING, success_order_arg.span, |diag| {
+                diag.build(&format!(
+                    "`{method}`'s success ordering must be at least as strong as its failure ordering"
+                ))
+                .span_label(fail_order_arg.span, format!("`{fail_ordering}` failure ordering"))
+                .span_label(success_order_arg.span, format!("`{success_ordering}` success ordering"))
+                .span_suggestion_short(
+                    success_order_arg.span,
+                    format!("consider using `{success_suggestion}` success ordering instead"),
+                    format!("std::sync::atomic::Ordering::{success_suggestion}"),
+                    Applicability::MaybeIncorrect,
+                )
+                .emit();
+            });
         }
     }
 }
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index b1bfd612b90..d52455e2576 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -289,6 +289,7 @@ declare_lint! {
     "constant evaluation encountered erroneous expression",
     @future_incompatible = FutureIncompatibleInfo {
         reference: "issue #71800 <https://github.com/rust-lang/rust/issues/71800>",
+        reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
     };
     report_in_external_macro
 }
diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp
index 38fddbdba54..0f4973ebf71 100644
--- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp
@@ -985,7 +985,9 @@ LLVMRustOptimizeWithNewPassManager(
     if (SanitizerOptions->SanitizeAddress) {
       OptimizerLastEPCallbacks.push_back(
         [SanitizerOptions](ModulePassManager &MPM, OptimizationLevel Level) {
+#if LLVM_VERSION_LT(15, 0)
           MPM.addPass(RequireAnalysisPass<ASanGlobalsMetadataAnalysis, Module>());
+#endif
 #if LLVM_VERSION_GE(14, 0)
           AddressSanitizerOptions opts = AddressSanitizerOptions{
             /*CompileKernel=*/false,
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 95ee0d4a060..d0c86527189 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -12,7 +12,9 @@ use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, quote};
 use std::collections::HashMap;
 use std::str::FromStr;
-use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type};
+use syn::{
+    parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
+};
 use synstructure::{BindingInfo, Structure};
 
 /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
@@ -118,23 +120,23 @@ impl<'a> SessionDiagnosticDerive<'a> {
                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
                     (Some((kind, _)), None) => {
-                        span_err(span, "`slug` not specified")
-                            .help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr()))
+                        span_err(span, "diagnostic slug not specified")
+                            .help(&format!(
+                                "specify the slug as the first argument to the attribute, such as \
+                                 `#[{}(typeck::example_error)]`",
+                                kind.descr()
+                            ))
                             .emit();
                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
                     (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
                         quote! {
-                            let mut #diag = #sess.struct_err(
-                                rustc_errors::DiagnosticMessage::new(#slug),
-                            );
+                            let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug);
                         }
                     }
                     (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
                         quote! {
-                            let mut #diag = #sess.struct_warn(
-                                rustc_errors::DiagnosticMessage::new(#slug),
-                            );
+                            let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug);
                         }
                     }
                 };
@@ -226,7 +228,7 @@ struct SessionDiagnosticDeriveBuilder {
     kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
     /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
     /// has the actual diagnostic message.
-    slug: Option<(String, proc_macro::Span)>,
+    slug: Option<(Path, proc_macro::Span)>,
     /// Error codes are a optional part of the struct attribute - this is only set to detect
     /// multiple specifications.
     code: Option<(String, proc_macro::Span)>,
@@ -240,50 +242,79 @@ impl HasFieldMap for SessionDiagnosticDeriveBuilder {
 
 impl SessionDiagnosticDeriveBuilder {
     /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
-    /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
+    /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
     /// diagnostic builder calls for setting error code and creating note/help messages.
     fn generate_structure_code(
         &mut self,
         attr: &Attribute,
     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+        let diag = &self.diag;
         let span = attr.span().unwrap();
 
         let name = attr.path.segments.last().unwrap().ident.to_string();
         let name = name.as_str();
         let meta = attr.parse_meta()?;
 
-        if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | Meta::NameValue(_)) {
-            let diag = &self.diag;
-            let id = match meta {
-                Meta::Path(..) => quote! { #name },
-                Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
-                    quote! { #s }
-                }
-                _ => unreachable!(),
-            };
-            let fn_name = proc_macro2::Ident::new(name, attr.span());
-
-            return Ok(quote! {
-                #diag.#fn_name(rustc_errors::SubdiagnosticMessage::attr(#id));
-            });
-        }
+        let is_help_or_note = matches!(name, "help" | "note");
 
         let nested = match meta {
+            // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or
+            // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug.
             Meta::List(MetaList { ref nested, .. }) => nested,
+            // Subdiagnostics without spans can be applied to the type too, and these are just
+            // paths: `#[help]` and `#[note]`
+            Meta::Path(_) if is_help_or_note => {
+                let fn_name = proc_macro2::Ident::new(name, attr.span());
+                return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
+            }
             _ => throw_invalid_attr!(attr, &meta),
         };
 
-        let kind = match name {
-            "error" => SessionDiagnosticKind::Error,
-            "warning" => SessionDiagnosticKind::Warn,
+        // Check the kind before doing any further processing so that there aren't misleading
+        // "no kind specified" errors if there are failures later.
+        match name {
+            "error" => self.kind.set_once((SessionDiagnosticKind::Error, span)),
+            "warning" => self.kind.set_once((SessionDiagnosticKind::Warn, span)),
+            "help" | "note" => (),
             _ => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help("only `error` and `warning` are valid attributes")
+                diag.help("only `error`, `warning`, `help` and `note` are valid attributes")
             }),
-        };
-        self.kind.set_once((kind, span));
+        }
+
+        // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or
+        // `#[help(typeck::another_help)]`.
+        let mut nested_iter = nested.into_iter();
+        if let Some(nested_attr) = nested_iter.next() {
+            // Report an error if there are any other list items after the path.
+            if is_help_or_note && nested_iter.next().is_some() {
+                throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                    diag.help("`help` and `note` struct attributes can only have one argument")
+                });
+            }
 
+            match nested_attr {
+                NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => {
+                    let fn_name = proc_macro2::Ident::new(name, attr.span());
+                    return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
+                }
+                NestedMeta::Meta(Meta::Path(path)) => {
+                    self.slug.set_once((path.clone(), span));
+                }
+                NestedMeta::Meta(meta @ Meta::NameValue(_))
+                    if !is_help_or_note
+                        && meta.path().segments.last().unwrap().ident.to_string() == "code" =>
+                {
+                    // 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")
+                }),
+            };
+        }
+
+        // Remaining attributes are optional, only `code = ".."` at the moment.
         let mut tokens = Vec::new();
-        for nested_attr in nested {
+        for nested_attr in nested_iter {
             let meta = match nested_attr {
                 syn::NestedMeta::Meta(meta) => meta,
                 _ => throw_invalid_nested_attr!(attr, &nested_attr),
@@ -291,28 +322,24 @@ impl SessionDiagnosticDeriveBuilder {
 
             let path = meta.path();
             let nested_name = path.segments.last().unwrap().ident.to_string();
-            match &meta {
-                // Struct attributes are only allowed to be applied once, and the diagnostic
-                // changes will be set in the initialisation code.
-                Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
-                    let span = s.span().unwrap();
-                    match nested_name.as_str() {
-                        "slug" => {
-                            self.slug.set_once((s.value(), span));
-                        }
-                        "code" => {
-                            self.code.set_once((s.value(), span));
-                            let (diag, code) = (&self.diag, &self.code.as_ref().map(|(v, _)| v));
-                            tokens.push(quote! {
-                                #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
-                            });
-                        }
-                        _ => invalid_nested_attr(attr, &nested_attr)
-                            .help("only `slug` and `code` are valid nested attributes")
-                            .emit(),
+            // Struct attributes are only allowed to be applied once, and the diagnostic
+            // changes will be set in the initialisation code.
+            if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta {
+                let span = s.span().unwrap();
+                match nested_name.as_str() {
+                    "code" => {
+                        self.code.set_once((s.value(), span));
+                        let code = &self.code.as_ref().map(|(v, _)| v);
+                        tokens.push(quote! {
+                            #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
+                        });
                     }
+                    _ => invalid_nested_attr(attr, &nested_attr)
+                        .help("only `code` is a valid nested attributes following the slug")
+                        .emit(),
                 }
-                _ => invalid_nested_attr(attr, &nested_attr).emit(),
+            } else {
+                invalid_nested_attr(attr, &nested_attr).emit()
             }
         }
 
@@ -382,142 +409,215 @@ impl SessionDiagnosticDeriveBuilder {
         info: FieldInfo<'_>,
         binding: TokenStream,
     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+        let meta = attr.parse_meta()?;
+        match meta {
+            Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding),
+            Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding),
+            _ => throw_invalid_attr!(attr, &meta),
+        }
+    }
+
+    fn generate_inner_field_code_path(
+        &mut self,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+        binding: TokenStream,
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+        assert!(matches!(attr.parse_meta()?, Meta::Path(_)));
         let diag = &self.diag;
 
+        let meta = attr.parse_meta()?;
+
         let ident = &attr.path.segments.last().unwrap().ident;
         let name = ident.to_string();
         let name = name.as_str();
+        match name {
+            "skip_arg" => {
+                // Don't need to do anything - by virtue of the attribute existing, the
+                // `set_arg` call will not be generated.
+                Ok(quote! {})
+            }
+            "primary_span" => {
+                report_error_if_not_applied_to_span(attr, &info)?;
+                Ok(quote! {
+                    #diag.set_span(#binding);
+                })
+            }
+            "label" => {
+                report_error_if_not_applied_to_span(attr, &info)?;
+                Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
+            }
+            "note" | "help" => {
+                let path = match name {
+                    "note" => parse_quote! { _subdiag::note },
+                    "help" => parse_quote! { _subdiag::help },
+                    _ => unreachable!(),
+                };
+                if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
+                    Ok(self.add_spanned_subdiagnostic(binding, ident, path))
+                } else if type_is_unit(&info.ty) {
+                    Ok(self.add_subdiagnostic(ident, path))
+                } else {
+                    report_type_error(attr, "`Span` or `()`")?;
+                }
+            }
+            "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
+            _ => throw_invalid_attr!(attr, &meta, |diag| {
+                diag.help(
+                    "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \
+                     are valid field attributes",
+                )
+            }),
+        }
+    }
 
+    fn generate_inner_field_code_list(
+        &mut self,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+        binding: TokenStream,
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
         let meta = attr.parse_meta()?;
-        match meta {
-            Meta::Path(_) => match name {
-                "skip_arg" => {
-                    // Don't need to do anything - by virtue of the attribute existing, the
-                    // `set_arg` call will not be generated.
-                    Ok(quote! {})
-                }
-                "primary_span" => {
-                    report_error_if_not_applied_to_span(attr, &info)?;
-                    Ok(quote! {
-                        #diag.set_span(#binding);
-                    })
-                }
-                "label" => {
-                    report_error_if_not_applied_to_span(attr, &info)?;
-                    Ok(self.add_spanned_subdiagnostic(binding, ident, name))
-                }
-                "note" | "help" => {
-                    if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
-                        Ok(self.add_spanned_subdiagnostic(binding, ident, name))
-                    } else if type_is_unit(&info.ty) {
-                        Ok(self.add_subdiagnostic(ident, name))
-                    } else {
-                        report_type_error(attr, "`Span` or `()`")?;
-                    }
-                }
-                "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
-                _ => throw_invalid_attr!(attr, &meta, |diag| {
-                    diag
-                        .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes")
-                }),
-            },
-            Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
-                "label" => {
-                    report_error_if_not_applied_to_span(attr, &info)?;
-                    Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value()))
-                }
-                "note" | "help" => {
-                    if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
-                        Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value()))
-                    } else if type_is_unit(&info.ty) {
-                        Ok(self.add_subdiagnostic(ident, &s.value()))
-                    } else {
-                        report_type_error(attr, "`Span` or `()`")?;
-                    }
-                }
-                _ => throw_invalid_attr!(attr, &meta, |diag| {
-                    diag.help("only `label`, `note` and `help` are valid field attributes")
-                }),
-            },
-            Meta::List(MetaList { ref path, ref nested, .. }) => {
-                let name = path.segments.last().unwrap().ident.to_string();
-                let name = name.as_ref();
-
-                match name {
-                    "suggestion" | "suggestion_short" | "suggestion_hidden"
-                    | "suggestion_verbose" => (),
-                    _ => throw_invalid_attr!(attr, &meta, |diag| {
-                        diag
-                            .help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes")
-                    }),
-                };
+        let Meta::List(MetaList { ref path, ref nested, .. }) = meta  else { unreachable!() };
 
-                let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
+        let ident = &attr.path.segments.last().unwrap().ident;
+        let name = path.segments.last().unwrap().ident.to_string();
+        let name = name.as_ref();
+        match name {
+            "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
+                return self.generate_inner_field_code_suggestion(attr, info);
+            }
+            "label" | "help" | "note" => (),
+            _ => throw_invalid_attr!(attr, &meta, |diag| {
+                diag.help(
+                    "only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \
+                     valid field attributes",
+                )
+            }),
+        }
 
-                let mut msg = None;
-                let mut code = None;
+        // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a
+        // path, e.g. `#[label(typeck::label)]`.
+        let mut nested_iter = nested.into_iter();
+        let msg = match nested_iter.next() {
+            Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(),
+            Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr),
+            None => throw_invalid_attr!(attr, &meta),
+        };
 
-                for nested_attr in nested {
-                    let meta = match nested_attr {
-                        syn::NestedMeta::Meta(ref meta) => meta,
-                        syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
-                    };
+        // None of these attributes should have anything following the slug.
+        if nested_iter.next().is_some() {
+            throw_invalid_attr!(attr, &meta);
+        }
 
-                    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), .. }) => {
-                            let span = meta.span().unwrap();
-                            match nested_name {
-                                "message" => {
-                                    msg = Some(s.value());
-                                }
-                                "code" => {
-                                    let formatted_str = self.build_format(&s.value(), s.span());
-                                    code = Some(formatted_str);
+        match name {
+            "label" => {
+                report_error_if_not_applied_to_span(attr, &info)?;
+                Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
+            }
+            "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => {
+                Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
+            }
+            "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
+            "note" | "help" => {
+                report_type_error(attr, "`Span` or `()`")?;
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    fn generate_inner_field_code_suggestion(
+        &mut self,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+        let diag = &self.diag;
+
+        let mut meta = attr.parse_meta()?;
+        let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta  else { unreachable!() };
+
+        let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
+
+        let mut msg = None;
+        let mut code = None;
+
+        let mut nested_iter = nested.into_iter().peekable();
+        if let Some(nested_attr) = nested_iter.peek() {
+            if let NestedMeta::Meta(Meta::Path(path)) = nested_attr {
+                msg = Some(path.clone());
+            }
+        };
+        // Move the iterator forward if a path was found (don't otherwise so that
+        // code/applicability can be found or an error emitted).
+        if msg.is_some() {
+            let _ = nested_iter.next();
+        }
+
+        for nested_attr in nested_iter {
+            let meta = match nested_attr {
+                syn::NestedMeta::Meta(ref meta) => meta,
+                syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
+            };
+
+            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), .. }) => {
+                    let span = meta.span().unwrap();
+                    match nested_name {
+                        "code" => {
+                            let formatted_str = self.build_format(&s.value(), s.span());
+                            code = Some(formatted_str);
+                        }
+                        "applicability" => {
+                            applicability = match applicability {
+                                Some(v) => {
+                                    span_err(
+                                        span,
+                                        "applicability cannot be set in both the field and \
+                                         attribute",
+                                    )
+                                    .emit();
+                                    Some(v)
                                 }
-                                "applicability" => {
-                                    applicability = match applicability {
-                                        Some(v) => {
-                                            span_err(
-                                                span,
-                                                "applicability cannot be set in both the field and attribute"
-                                            ).emit();
-                                            Some(v)
-                                        }
-                                        None => match Applicability::from_str(&s.value()) {
-                                            Ok(v) => Some(quote! { #v }),
-                                            Err(()) => {
-                                                span_err(span, "invalid applicability").emit();
-                                                None
-                                            }
-                                        },
+                                None => match Applicability::from_str(&s.value()) {
+                                    Ok(v) => Some(quote! { #v }),
+                                    Err(()) => {
+                                        span_err(span, "invalid applicability").emit();
+                                        None
                                     }
-                                }
-                                _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                                    diag.help(
-                                        "only `message`, `code` and `applicability` are valid field attributes",
-                                    )
-                                }),
+                                },
                             }
                         }
-                        _ => throw_invalid_nested_attr!(attr, &nested_attr),
+                        _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                            diag.help(
+                                "only `message`, `code` and `applicability` are valid field \
+                                 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 applicability = applicability
-                    .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
+        let applicability =
+            applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
 
-                let method = format_ident!("span_{}", name);
+        let name = path.segments.last().unwrap().ident.to_string();
+        let method = format_ident!("span_{}", name);
 
-                let msg = msg.as_deref().unwrap_or("suggestion");
-                let msg = quote! { rustc_errors::SubdiagnosticMessage::attr(#msg) };
-                let code = code.unwrap_or_else(|| quote! { String::new() });
+        let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion });
+        let msg = quote! { rustc_errors::fluent::#msg };
+        let code = code.unwrap_or_else(|| quote! { String::new() });
 
-                Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
-            }
-            _ => throw_invalid_attr!(attr, &meta),
-        }
+        Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
     }
 
     /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
@@ -526,24 +626,24 @@ impl SessionDiagnosticDeriveBuilder {
         &self,
         field_binding: TokenStream,
         kind: &Ident,
-        fluent_attr_identifier: &str,
+        fluent_attr_identifier: Path,
     ) -> TokenStream {
         let diag = &self.diag;
         let fn_name = format_ident!("span_{}", kind);
         quote! {
             #diag.#fn_name(
                 #field_binding,
-                rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier)
+                rustc_errors::fluent::#fluent_attr_identifier
             );
         }
     }
 
     /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
     /// and `fluent_attr_identifier`.
-    fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: &str) -> TokenStream {
+    fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
         let diag = &self.diag;
         quote! {
-            #diag.#kind(rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier));
+            #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
         }
     }
 
@@ -569,7 +669,8 @@ impl SessionDiagnosticDeriveBuilder {
                         } else {
                             throw_span_err!(
                                 info.span.unwrap(),
-                                "type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
+                                "type of field annotated with `#[suggestion(...)]` contains more \
+                                 than one `Span`"
                             );
                         }
                     } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
@@ -578,7 +679,8 @@ impl SessionDiagnosticDeriveBuilder {
                         } else {
                             throw_span_err!(
                                 info.span.unwrap(),
-                                "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
+                                "type of field annotated with `#[suggestion(...)]` contains more \
+                                 than one Applicability"
                             );
                         }
                     }
@@ -595,12 +697,18 @@ impl SessionDiagnosticDeriveBuilder {
                 }
 
                 throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
-                    diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
+                    diag.help(
+                        "`#[suggestion(...)]` on a tuple field must be applied to fields of type \
+                         `(Span, Applicability)`",
+                    )
                 });
             }
             // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
             _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
-                diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")
+                diag.help(
+                    "`#[suggestion(...)]` should be applied to fields of type `Span` or \
+                     `(Span, Applicability)`",
+                )
             }),
         }
     }
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
index fd1dc2f3073..d088402abc6 100644
--- a/compiler/rustc_macros/src/diagnostics/error.rs
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -39,6 +39,19 @@ pub(crate) fn _throw_err(
     SessionDiagnosticDeriveError::ErrorHandled
 }
 
+/// Helper function for printing `syn::Path` - doesn't handle arguments in paths and these are
+/// unlikely to come up much in use of the macro.
+fn path_to_string(path: &syn::Path) -> String {
+    let mut out = String::new();
+    for (i, segment) in path.segments.iter().enumerate() {
+        if i > 0 || path.leading_colon.is_some() {
+            out.push_str("::");
+        }
+        out.push_str(&segment.ident.to_string());
+    }
+    out
+}
+
 /// Returns an error diagnostic on span `span` with msg `msg`.
 pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
     Diagnostic::spanned(span, Level::Error, msg)
@@ -61,15 +74,13 @@ pub(crate) use throw_span_err;
 /// Returns an error diagnostic for an invalid attribute.
 pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
     let span = attr.span().unwrap();
-    let name = attr.path.segments.last().unwrap().ident.to_string();
-    let name = name.as_str();
-
+    let path = path_to_string(&attr.path);
     match meta {
-        Meta::Path(_) => span_err(span, &format!("`#[{}]` is not a valid attribute", name)),
+        Meta::Path(_) => span_err(span, &format!("`#[{}]` is not a valid attribute", path)),
         Meta::NameValue(_) => {
-            span_err(span, &format!("`#[{} = ...]` is not a valid attribute", name))
+            span_err(span, &format!("`#[{} = ...]` is not a valid attribute", path))
         }
-        Meta::List(_) => span_err(span, &format!("`#[{}(...)]` is not a valid attribute", name)),
+        Meta::List(_) => span_err(span, &format!("`#[{}(...)]` is not a valid attribute", path)),
     }
 }
 
@@ -101,18 +112,16 @@ pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diag
     };
 
     let span = meta.span().unwrap();
-    let nested_name = meta.path().segments.last().unwrap().ident.to_string();
-    let nested_name = nested_name.as_str();
+    let path = path_to_string(meta.path());
     match meta {
-        Meta::NameValue(..) => span_err(
-            span,
-            &format!("`#[{}({} = ...)]` is not a valid attribute", name, nested_name),
-        ),
+        Meta::NameValue(..) => {
+            span_err(span, &format!("`#[{}({} = ...)]` is not a valid attribute", name, path))
+        }
         Meta::Path(..) => {
-            span_err(span, &format!("`#[{}({})]` is not a valid attribute", name, nested_name))
+            span_err(span, &format!("`#[{}({})]` is not a valid attribute", name, path))
         }
         Meta::List(..) => {
-            span_err(span, &format!("`#[{}({}(...))]` is not a valid attribute", name, nested_name))
+            span_err(span, &format!("`#[{}({}(...))]` is not a valid attribute", name, path))
         }
     }
 }
diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs
index 42a9bf477a4..2317186e655 100644
--- a/compiler/rustc_macros/src/diagnostics/fluent.rs
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -254,6 +254,17 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
             ];
 
             #generated
+
+            pub mod _subdiag {
+                pub const note: crate::SubdiagnosticMessage =
+                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note"));
+                pub const help: crate::SubdiagnosticMessage =
+                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help"));
+                pub const label: crate::SubdiagnosticMessage =
+                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label"));
+                pub const suggestion: crate::SubdiagnosticMessage =
+                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("suggestion"));
+            }
         }
     }
     .into()
diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs
index 69573d904d4..2eee4bfb5dd 100644
--- a/compiler/rustc_macros/src/diagnostics/mod.rs
+++ b/compiler/rustc_macros/src/diagnostics/mod.rs
@@ -22,14 +22,14 @@ use synstructure::Structure;
 /// # extern crate rust_middle;
 /// # use rustc_middle::ty::Ty;
 /// #[derive(SessionDiagnostic)]
-/// #[error(code = "E0505", slug = "borrowck-move-out-of-borrow")]
+/// #[error(borrowck::move_out_of_borrow, code = "E0505")]
 /// pub struct MoveOutOfBorrowError<'tcx> {
 ///     pub name: Ident,
 ///     pub ty: Ty<'tcx>,
 ///     #[primary_span]
 ///     #[label]
 ///     pub span: Span,
-///     #[label = "first-borrow-label"]
+///     #[label(borrowck::first_borrow_label)]
 ///     pub first_borrow_span: Span,
 ///     #[suggestion(code = "{name}.clone()")]
 ///     pub clone_sugg: Option<(Span, Applicability)>
@@ -72,12 +72,12 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
 /// ```ignore (rust)
 /// #[derive(SessionSubdiagnostic)]
 /// pub enum ExpectedIdentifierLabel<'tcx> {
-///     #[label(slug = "parser-expected-identifier")]
+///     #[label(parser::expected_identifier)]
 ///     WithoutFound {
 ///         #[primary_span]
 ///         span: Span,
 ///     }
-///     #[label(slug = "parser-expected-identifier-found")]
+///     #[label(parser::expected_identifier_found)]
 ///     WithFound {
 ///         #[primary_span]
 ///         span: Span,
@@ -86,7 +86,7 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
 /// }
 ///
 /// #[derive(SessionSubdiagnostic)]
-/// #[suggestion_verbose(slug = "parser-raw-identifier")]
+/// #[suggestion_verbose(parser::raw_identifier)]
 /// pub struct RawIdentifierSuggestion<'tcx> {
 ///     #[primary_span]
 ///     span: Span,
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 9aeb484bfd5..eab954a9c1b 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -13,7 +13,7 @@ use quote::{format_ident, quote};
 use std::collections::HashMap;
 use std::fmt;
 use std::str::FromStr;
-use syn::{spanned::Spanned, Meta, MetaList, MetaNameValue};
+use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path};
 use synstructure::{BindingInfo, Structure, VariantInfo};
 
 /// Which kind of suggestion is being created?
@@ -194,8 +194,8 @@ struct SessionSubdiagnosticDeriveBuilder<'a> {
     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<(String, proc_macro::Span)>,
+    /// `#[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)>,
@@ -224,9 +224,34 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
             let meta = attr.parse_meta()?;
             let kind = match meta {
                 Meta::List(MetaList { ref nested, .. }) => {
-                    for nested_attr in 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",
+                                    )
+                                })
+                            }
+                        };
+                    }
+
+                    for nested_attr in nested_iter {
                         let meta = match nested_attr {
-                            syn::NestedMeta::Meta(ref meta) => meta,
+                            NestedMeta::Meta(ref meta) => meta,
                             _ => throw_invalid_nested_attr!(attr, &nested_attr),
                         };
 
@@ -241,7 +266,6 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                                         let formatted_str = self.build_format(&s.value(), s.span());
                                         self.code.set_once((formatted_str, span));
                                     }
-                                    "slug" => self.slug.set_once((s.value(), span)),
                                     "applicability" => {
                                         let value = match Applicability::from_str(&s.value()) {
                                             Ok(v) => v,
@@ -253,11 +277,23 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                                         self.applicability.set_once((quote! { #value }, span));
                                     }
                                     _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                                        diag.help("only `code`, `slug` and `applicability` are valid nested attributes")
+                                        diag.help(
+                                            "only `code` and `applicability` are valid nested \
+                                             attributes",
+                                        )
                                     }),
                                 }
                             }
-                            _ => throw_invalid_nested_attr!(attr, &nested_attr),
+                            _ => 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
+                                }
+                            }),
                         }
                     }
 
@@ -281,10 +317,27 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                 );
             }
 
+            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
+                    )
+                );
+            }
+
             if self.slug.is_none() {
                 throw_span_err!(
                     span,
-                    &format!("`slug` must be set in a `#[{}(...)]` attribute", name)
+                    &format!(
+                        "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
+                        name
+                    )
                 );
             }
 
@@ -335,7 +388,10 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                         return Ok(quote! {});
                     }
                     _ => throw_invalid_attr!(attr, &meta, |diag| {
-                        diag.help("only `primary_span`, `applicability` and `skip_arg` are valid field attributes")
+                        diag.help(
+                            "only `primary_span`, `applicability` and `skip_arg` are valid field \
+                             attributes",
+                        )
                     }),
                 },
                 _ => throw_invalid_attr!(attr, &meta),
@@ -375,7 +431,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
         }
 
         // Missing slug errors will already have been reported.
-        let slug = self.slug.as_ref().map(|(slug, _)| &**slug).unwrap_or("missing-slug");
+        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 => {
@@ -397,7 +457,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
 
         let diag = &self.diag;
         let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
-        let message = quote! { rustc_errors::SubdiagnosticMessage::message(#slug) };
+        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); }
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index c1ded99a25a..3280fd5c310 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -42,7 +42,7 @@ use std::io;
 use std::iter::TrustedLen;
 use std::mem;
 use std::num::NonZeroUsize;
-use std::path::Path;
+use std::path::PathBuf;
 use tracing::debug;
 
 pub(super) use cstore_impl::provide;
@@ -1473,20 +1473,26 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
         //
         // NOTE: if you update this, you might need to also update bootstrap's code for generating
         // the `rust-src` component in `Src::run` in `src/bootstrap/dist.rs`.
-        let virtual_rust_source_base_dir = option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR")
-            .map(Path::new)
-            .filter(|_| {
-                // Only spend time on further checks if we have what to translate *to*.
-                sess.opts.real_rust_source_base_dir.is_some()
-                    // Some tests need the translation to be always skipped.
-                    && sess.opts.debugging_opts.translate_remapped_path_to_local_path
-            })
-            .filter(|virtual_dir| {
-                // Don't translate away `/rustc/$hash` if we're still remapping to it,
-                // since that means we're still building `std`/`rustc` that need it,
-                // and we don't want the real path to leak into codegen/debuginfo.
-                !sess.opts.remap_path_prefix.iter().any(|(_from, to)| to == virtual_dir)
-            });
+        let virtual_rust_source_base_dir = [
+            option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR").map(PathBuf::from),
+            sess.opts.debugging_opts.simulate_remapped_rust_src_base.clone(),
+        ]
+        .into_iter()
+        .filter(|_| {
+            // Only spend time on further checks if we have what to translate *to*.
+            sess.opts.real_rust_source_base_dir.is_some()
+                // Some tests need the translation to be always skipped.
+                && sess.opts.debugging_opts.translate_remapped_path_to_local_path
+        })
+        .flatten()
+        .filter(|virtual_dir| {
+            // Don't translate away `/rustc/$hash` if we're still remapping to it,
+            // since that means we're still building `std`/`rustc` that need it,
+            // and we don't want the real path to leak into codegen/debuginfo.
+            !sess.opts.remap_path_prefix.iter().any(|(_from, to)| to == virtual_dir)
+        })
+        .collect::<Vec<_>>();
+
         let try_to_translate_virtual_to_real = |name: &mut rustc_span::FileName| {
             debug!(
                 "try_to_translate_virtual_to_real(name={:?}): \
@@ -1494,7 +1500,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
                 name, virtual_rust_source_base_dir, sess.opts.real_rust_source_base_dir,
             );
 
-            if let Some(virtual_dir) = virtual_rust_source_base_dir {
+            for virtual_dir in &virtual_rust_source_base_dir {
                 if let Some(real_dir) = &sess.opts.real_rust_source_base_dir {
                     if let rustc_span::FileName::Real(old_name) = name {
                         if let rustc_span::RealFileName::Remapped { local_path: _, virtual_name } =
diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs
index 738d47ba296..a27b8470e95 100644
--- a/compiler/rustc_middle/src/hir/map/mod.rs
+++ b/compiler/rustc_middle/src/hir/map/mod.rs
@@ -612,23 +612,6 @@ impl<'hir> Map<'hir> {
         }
     }
 
-    /// A parallel version of `visit_all_item_likes`.
-    pub fn par_visit_all_item_likes<V>(self, visitor: &V)
-    where
-        V: itemlikevisit::ParItemLikeVisitor<'hir> + Sync + Send,
-    {
-        let krate = self.krate();
-        par_for_each_in(&krate.owners.raw, |owner| match owner.map(OwnerInfo::node) {
-            MaybeOwner::Owner(OwnerNode::Item(item)) => visitor.visit_item(item),
-            MaybeOwner::Owner(OwnerNode::ForeignItem(item)) => visitor.visit_foreign_item(item),
-            MaybeOwner::Owner(OwnerNode::ImplItem(item)) => visitor.visit_impl_item(item),
-            MaybeOwner::Owner(OwnerNode::TraitItem(item)) => visitor.visit_trait_item(item),
-            MaybeOwner::Owner(OwnerNode::Crate(_))
-            | MaybeOwner::NonOwner(_)
-            | MaybeOwner::Phantom => {}
-        })
-    }
-
     /// If you don't care about nesting, you should use the
     /// `tcx.hir_module_items()` query or `module_items()` instead.
     /// Please see notes in `deep_visit_all_item_likes`.
@@ -867,6 +850,10 @@ impl<'hir> Map<'hir> {
         )
     }
 
+    pub fn expect_owner(self, id: LocalDefId) -> OwnerNode<'hir> {
+        self.tcx.hir_owner(id).unwrap_or_else(|| bug!("expected owner for {:?}", id)).node
+    }
+
     pub fn expect_item(self, id: LocalDefId) -> &'hir Item<'hir> {
         match self.tcx.hir_owner(id) {
             Some(Owner { node: OwnerNode::Item(item), .. }) => item,
diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs
index 09b142e0c41..8622a620721 100644
--- a/compiler/rustc_middle/src/hir/mod.rs
+++ b/compiler/rustc_middle/src/hir/mod.rs
@@ -10,6 +10,7 @@ use crate::ty::query::Providers;
 use crate::ty::{DefIdTree, ImplSubject, TyCtxt};
 use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+use rustc_data_structures::sync::{par_for_each_in, Send, Sync};
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::*;
 use rustc_query_system::ich::StableHashingContext;
@@ -61,6 +62,22 @@ impl ModuleItems {
     pub fn foreign_items(&self) -> impl Iterator<Item = ForeignItemId> + '_ {
         self.foreign_items.iter().copied()
     }
+
+    pub fn par_items(&self, f: impl Fn(ItemId) + Send + Sync) {
+        par_for_each_in(&self.items[..], |&id| f(id))
+    }
+
+    pub fn par_trait_items(&self, f: impl Fn(TraitItemId) + Send + Sync) {
+        par_for_each_in(&self.trait_items[..], |&id| f(id))
+    }
+
+    pub fn par_impl_items(&self, f: impl Fn(ImplItemId) + Send + Sync) {
+        par_for_each_in(&self.impl_items[..], |&id| f(id))
+    }
+
+    pub fn par_foreign_items(&self, f: impl Fn(ForeignItemId) + Send + Sync) {
+        par_for_each_in(&self.foreign_items[..], |&id| f(id))
+    }
 }
 
 impl<'tcx> TyCtxt<'tcx> {
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index ca2c03cb614..2e68fc8a7c0 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -826,6 +826,10 @@ rustc_queries! {
         desc { |tcx| "checking that impls are well-formed in {}", describe_as_module(key, tcx) }
     }
 
+    query check_mod_type_wf(key: LocalDefId) -> () {
+        desc { |tcx| "checking that types are well-formed in {}", describe_as_module(key, tcx) }
+    }
+
     query collect_mod_item_types(key: LocalDefId) -> () {
         desc { |tcx| "collecting item types in {}", describe_as_module(key, tcx) }
     }
@@ -906,9 +910,10 @@ rustc_queries! {
 
     /// Checks whether all impls in the crate pass the overlap check, returning
     /// which impls fail it. If all impls are correct, the returned slice is empty.
-    query orphan_check_crate(_: ()) -> &'tcx [LocalDefId] {
-        desc {
-            "checking whether the immpl in the this crate follow the orphan rules",
+    query orphan_check_impl(key: LocalDefId) -> Result<(), ErrorGuaranteed> {
+        desc { |tcx|
+            "checking whether impl `{}` follows the orphan rules",
+            tcx.def_path_str(key.to_def_id()),
         }
     }
 
@@ -1398,13 +1403,7 @@ rustc_queries! {
         separate_provide_extern
     }
 
-    query check_item_well_formed(key: LocalDefId) -> () {
-        desc { |tcx| "checking that `{}` is well-formed", tcx.def_path_str(key.to_def_id()) }
-    }
-    query check_trait_item_well_formed(key: LocalDefId) -> () {
-        desc { |tcx| "checking that `{}` is well-formed", tcx.def_path_str(key.to_def_id()) }
-    }
-    query check_impl_item_well_formed(key: LocalDefId) -> () {
+    query check_well_formed(key: LocalDefId) -> () {
         desc { |tcx| "checking that `{}` is well-formed", tcx.def_path_str(key.to_def_id()) }
     }
 
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index 5258d37a14c..7f913faf860 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -253,7 +253,7 @@ pub enum ObligationCauseCode<'tcx> {
     ObjectTypeBound(Ty<'tcx>, ty::Region<'tcx>),
 
     /// Obligation incurred due to an object cast.
-    ObjectCastObligation(/* Object type */ Ty<'tcx>),
+    ObjectCastObligation(/* Concrete type */ Ty<'tcx>, /* Object type */ Ty<'tcx>),
 
     /// Obligation incurred due to a coercion.
     Coercion {
@@ -406,7 +406,7 @@ pub enum ObligationCauseCode<'tcx> {
     QuestionMark,
 
     /// Well-formed checking. If a `WellFormedLoc` is provided,
-    /// then it will be used to eprform HIR-based wf checking
+    /// then it will be used to perform HIR-based wf checking
     /// after an error occurs, in order to generate a more precise error span.
     /// This is purely for diagnostic purposes - it is always
     /// correct to use `MiscObligation` instead, or to specify
@@ -863,7 +863,7 @@ pub enum ObjectSafetyViolation {
 
 impl ObjectSafetyViolation {
     pub fn error_msg(&self) -> Cow<'static, str> {
-        match *self {
+        match self {
             ObjectSafetyViolation::SizedSelf(_) => "it requires `Self: Sized`".into(),
             ObjectSafetyViolation::SupertraitSelf(ref spans) => {
                 if spans.iter().any(|sp| *sp != DUMMY_SP) {
@@ -873,7 +873,7 @@ impl ObjectSafetyViolation {
                         .into()
                 }
             }
-            ObjectSafetyViolation::Method(name, MethodViolationCode::StaticMethod(_, _, _), _) => {
+            ObjectSafetyViolation::Method(name, MethodViolationCode::StaticMethod(_), _) => {
                 format!("associated function `{}` has no `self` parameter", name).into()
             }
             ObjectSafetyViolation::Method(
@@ -897,9 +897,11 @@ impl ObjectSafetyViolation {
             ObjectSafetyViolation::Method(name, MethodViolationCode::Generic, _) => {
                 format!("method `{}` has generic type parameters", name).into()
             }
-            ObjectSafetyViolation::Method(name, MethodViolationCode::UndispatchableReceiver, _) => {
-                format!("method `{}`'s `self` parameter cannot be dispatched on", name).into()
-            }
+            ObjectSafetyViolation::Method(
+                name,
+                MethodViolationCode::UndispatchableReceiver(_),
+                _,
+            ) => format!("method `{}`'s `self` parameter cannot be dispatched on", name).into(),
             ObjectSafetyViolation::AssocConst(name, DUMMY_SP) => {
                 format!("it contains associated `const` `{}`", name).into()
             }
@@ -911,51 +913,40 @@ impl ObjectSafetyViolation {
     }
 
     pub fn solution(&self, err: &mut Diagnostic) {
-        match *self {
+        match self {
             ObjectSafetyViolation::SizedSelf(_) | ObjectSafetyViolation::SupertraitSelf(_) => {}
             ObjectSafetyViolation::Method(
                 name,
-                MethodViolationCode::StaticMethod(sugg, self_span, has_args),
+                MethodViolationCode::StaticMethod(Some((add_self_sugg, make_sized_sugg))),
                 _,
             ) => {
                 err.span_suggestion(
-                    self_span,
-                    &format!(
+                    add_self_sugg.1,
+                    format!(
                         "consider turning `{}` into a method by giving it a `&self` argument",
                         name
                     ),
-                    format!("&self{}", if has_args { ", " } else { "" }),
+                    add_self_sugg.0.to_string(),
+                    Applicability::MaybeIncorrect,
+                );
+                err.span_suggestion(
+                    make_sized_sugg.1,
+                    format!(
+                        "alternatively, consider constraining `{}` so it does not apply to \
+                             trait objects",
+                        name
+                    ),
+                    make_sized_sugg.0.to_string(),
                     Applicability::MaybeIncorrect,
                 );
-                match sugg {
-                    Some((sugg, span)) => {
-                        err.span_suggestion(
-                            span,
-                            &format!(
-                                "alternatively, consider constraining `{}` so it does not apply to \
-                                 trait objects",
-                                name
-                            ),
-                            sugg,
-                            Applicability::MaybeIncorrect,
-                        );
-                    }
-                    None => {
-                        err.help(&format!(
-                            "consider turning `{}` into a method by giving it a `&self` \
-                             argument or constraining it so it does not apply to trait objects",
-                            name
-                        ));
-                    }
-                }
             }
             ObjectSafetyViolation::Method(
                 name,
-                MethodViolationCode::UndispatchableReceiver,
-                span,
+                MethodViolationCode::UndispatchableReceiver(Some(span)),
+                _,
             ) => {
                 err.span_suggestion(
-                    span,
+                    *span,
                     &format!(
                         "consider changing method `{}`'s `self` parameter to be `&self`",
                         name
@@ -991,13 +982,13 @@ impl ObjectSafetyViolation {
 }
 
 /// Reasons a method might not be object-safe.
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable, PartialOrd, Ord)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable, PartialOrd, Ord)]
 pub enum MethodViolationCode {
     /// e.g., `fn foo()`
-    StaticMethod(Option<(&'static str, Span)>, Span, bool /* has args */),
+    StaticMethod(Option<(/* add &self */ (String, Span), /* add Self: Sized */ (String, Span))>),
 
     /// e.g., `fn foo(&self, x: Self)`
-    ReferencesSelfInput(usize),
+    ReferencesSelfInput(Option<Span>),
 
     /// e.g., `fn foo(&self) -> Self`
     ReferencesSelfOutput,
@@ -1009,7 +1000,7 @@ pub enum MethodViolationCode {
     Generic,
 
     /// the method's receiver (`self` argument) can't be dispatched on
-    UndispatchableReceiver,
+    UndispatchableReceiver(Option<Span>),
 }
 
 /// These are the error cases for `codegen_fulfill_obligation`.
diff --git a/compiler/rustc_middle/src/ty/fold.rs b/compiler/rustc_middle/src/ty/fold.rs
index 31b8fa1ce95..a6310ae5e66 100644
--- a/compiler/rustc_middle/src/ty/fold.rs
+++ b/compiler/rustc_middle/src/ty/fold.rs
@@ -86,7 +86,7 @@ pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
     /// A convenient alternative to `try_fold_with` for use with infallible
     /// folders. Do not override this method, to ensure coherence with
     /// `try_fold_with`.
-    fn fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
+    fn fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self {
         self.try_fold_with(folder).into_ok()
     }
 
@@ -216,7 +216,7 @@ pub trait TypeSuperFoldable<'tcx>: TypeFoldable<'tcx> {
     /// A convenient alternative to `try_super_fold_with` for use with
     /// infallible folders. Do not override this method, to ensure coherence
     /// with `try_super_fold_with`.
-    fn super_fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
+    fn super_fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self {
         self.try_super_fold_with(folder).into_ok()
     }
 
@@ -229,70 +229,46 @@ pub trait TypeSuperFoldable<'tcx>: TypeFoldable<'tcx> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
 }
 
-/// This trait is implemented for every folding traversal. There is a fold
-/// method defined for every type of interest. Each such method has a default
-/// that does an "identity" fold. Implementations of these methods often fall
-/// back to a `super_fold_with` method if the primary argument doesn't
-/// satisfy a particular condition.
+/// This trait is implemented for every infallible folding traversal. There is
+/// a fold method defined for every type of interest. Each such method has a
+/// default that does an "identity" fold. Implementations of these methods
+/// often fall back to a `super_fold_with` method if the primary argument
+/// doesn't satisfy a particular condition.
 ///
-/// If this folder is fallible (and therefore its [`Error`][`TypeFolder::Error`]
-/// associated type is something other than the default `!`) then
-/// [`FallibleTypeFolder`] should be implemented manually. Otherwise,
-/// a blanket implementation of [`FallibleTypeFolder`] will defer to
+/// A blanket implementation of [`FallibleTypeFolder`] will defer to
 /// the infallible methods of this trait to ensure that the two APIs
 /// are coherent.
-pub trait TypeFolder<'tcx>: Sized {
-    type Error = !;
-
+pub trait TypeFolder<'tcx>: FallibleTypeFolder<'tcx, Error = !> {
     fn tcx<'a>(&'a self) -> TyCtxt<'tcx>;
 
     fn fold_binder<T>(&mut self, t: Binder<'tcx, T>) -> Binder<'tcx, T>
     where
         T: TypeFoldable<'tcx>,
-        Self: TypeFolder<'tcx, Error = !>,
     {
         t.super_fold_with(self)
     }
 
-    fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx>
-    where
-        Self: TypeFolder<'tcx, Error = !>,
-    {
+    fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
         t.super_fold_with(self)
     }
 
-    fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx>
-    where
-        Self: TypeFolder<'tcx, Error = !>,
-    {
+    fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
         r.super_fold_with(self)
     }
 
-    fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx>
-    where
-        Self: TypeFolder<'tcx, Error = !>,
-    {
+    fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
         c.super_fold_with(self)
     }
 
-    fn fold_unevaluated(&mut self, uv: ty::Unevaluated<'tcx>) -> ty::Unevaluated<'tcx>
-    where
-        Self: TypeFolder<'tcx, Error = !>,
-    {
+    fn fold_unevaluated(&mut self, uv: ty::Unevaluated<'tcx>) -> ty::Unevaluated<'tcx> {
         uv.super_fold_with(self)
     }
 
-    fn fold_predicate(&mut self, p: ty::Predicate<'tcx>) -> ty::Predicate<'tcx>
-    where
-        Self: TypeFolder<'tcx, Error = !>,
-    {
+    fn fold_predicate(&mut self, p: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> {
         p.super_fold_with(self)
     }
 
-    fn fold_mir_const(&mut self, c: mir::ConstantKind<'tcx>) -> mir::ConstantKind<'tcx>
-    where
-        Self: TypeFolder<'tcx, Error = !>,
-    {
+    fn fold_mir_const(&mut self, c: mir::ConstantKind<'tcx>) -> mir::ConstantKind<'tcx> {
         bug!("most type folders should not be folding MIR datastructures: {:?}", c)
     }
 }
@@ -304,7 +280,11 @@ pub trait TypeFolder<'tcx>: Sized {
 /// A blanket implementation of this trait (that defers to the relevant
 /// method of [`TypeFolder`]) is provided for all infallible folders in
 /// order to ensure the two APIs are coherent.
-pub trait FallibleTypeFolder<'tcx>: TypeFolder<'tcx> {
+pub trait FallibleTypeFolder<'tcx>: Sized {
+    type Error;
+
+    fn tcx<'a>(&'a self) -> TyCtxt<'tcx>;
+
     fn try_fold_binder<T>(&mut self, t: Binder<'tcx, T>) -> Result<Binder<'tcx, T>, Self::Error>
     where
         T: TypeFoldable<'tcx>,
@@ -350,45 +330,48 @@ pub trait FallibleTypeFolder<'tcx>: TypeFolder<'tcx> {
 // delegates to infallible methods to ensure coherence.
 impl<'tcx, F> FallibleTypeFolder<'tcx> for F
 where
-    F: TypeFolder<'tcx, Error = !>,
+    F: TypeFolder<'tcx>,
 {
-    fn try_fold_binder<T>(&mut self, t: Binder<'tcx, T>) -> Result<Binder<'tcx, T>, Self::Error>
+    type Error = !;
+
+    fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
+        TypeFolder::tcx(self)
+    }
+
+    fn try_fold_binder<T>(&mut self, t: Binder<'tcx, T>) -> Result<Binder<'tcx, T>, !>
     where
         T: TypeFoldable<'tcx>,
     {
         Ok(self.fold_binder(t))
     }
 
-    fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
+    fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, !> {
         Ok(self.fold_ty(t))
     }
 
-    fn try_fold_region(&mut self, r: ty::Region<'tcx>) -> Result<ty::Region<'tcx>, Self::Error> {
+    fn try_fold_region(&mut self, r: ty::Region<'tcx>) -> Result<ty::Region<'tcx>, !> {
         Ok(self.fold_region(r))
     }
 
-    fn try_fold_const(&mut self, c: ty::Const<'tcx>) -> Result<ty::Const<'tcx>, Self::Error> {
+    fn try_fold_const(&mut self, c: ty::Const<'tcx>) -> Result<ty::Const<'tcx>, !> {
         Ok(self.fold_const(c))
     }
 
     fn try_fold_unevaluated(
         &mut self,
         c: ty::Unevaluated<'tcx>,
-    ) -> Result<ty::Unevaluated<'tcx>, Self::Error> {
+    ) -> Result<ty::Unevaluated<'tcx>, !> {
         Ok(self.fold_unevaluated(c))
     }
 
-    fn try_fold_predicate(
-        &mut self,
-        p: ty::Predicate<'tcx>,
-    ) -> Result<ty::Predicate<'tcx>, Self::Error> {
+    fn try_fold_predicate(&mut self, p: ty::Predicate<'tcx>) -> Result<ty::Predicate<'tcx>, !> {
         Ok(self.fold_predicate(p))
     }
 
     fn try_fold_mir_const(
         &mut self,
         c: mir::ConstantKind<'tcx>,
-    ) -> Result<mir::ConstantKind<'tcx>, Self::Error> {
+    ) -> Result<mir::ConstantKind<'tcx>, !> {
         Ok(self.fold_mir_const(c))
     }
 }
@@ -482,13 +465,12 @@ impl<'tcx> TyCtxt<'tcx> {
     pub fn fold_regions<T>(
         self,
         value: T,
-        skipped_regions: &mut bool,
         mut f: impl FnMut(ty::Region<'tcx>, ty::DebruijnIndex) -> ty::Region<'tcx>,
     ) -> T
     where
         T: TypeFoldable<'tcx>,
     {
-        value.fold_with(&mut RegionFolder::new(self, skipped_regions, &mut f))
+        value.fold_with(&mut RegionFolder::new(self, &mut f))
     }
 
     /// Invoke `callback` on every region appearing free in `value`.
@@ -596,7 +578,6 @@ impl<'tcx> TyCtxt<'tcx> {
 
 pub struct RegionFolder<'a, 'tcx> {
     tcx: TyCtxt<'tcx>,
-    skipped_regions: &'a mut bool,
 
     /// Stores the index of a binder *just outside* the stuff we have
     /// visited.  So this begins as INNERMOST; when we pass through a
@@ -614,10 +595,9 @@ impl<'a, 'tcx> RegionFolder<'a, 'tcx> {
     #[inline]
     pub fn new(
         tcx: TyCtxt<'tcx>,
-        skipped_regions: &'a mut bool,
         fold_region_fn: &'a mut dyn FnMut(ty::Region<'tcx>, ty::DebruijnIndex) -> ty::Region<'tcx>,
     ) -> RegionFolder<'a, 'tcx> {
-        RegionFolder { tcx, skipped_regions, current_index: ty::INNERMOST, fold_region_fn }
+        RegionFolder { tcx, current_index: ty::INNERMOST, fold_region_fn }
     }
 }
 
@@ -641,7 +621,6 @@ impl<'a, 'tcx> TypeFolder<'tcx> for RegionFolder<'a, 'tcx> {
         match *r {
             ty::ReLateBound(debruijn, _) if debruijn < self.current_index => {
                 debug!(?self.current_index, "skipped bound region");
-                *self.skipped_regions = true;
                 r
             }
             _ => {
diff --git a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs
index 9bbbd7e2f7c..66a0a192a87 100644
--- a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs
+++ b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs
@@ -228,15 +228,13 @@ impl<'tcx> TryNormalizeAfterErasingRegionsFolder<'tcx> {
     }
 }
 
-impl<'tcx> TypeFolder<'tcx> for TryNormalizeAfterErasingRegionsFolder<'tcx> {
+impl<'tcx> FallibleTypeFolder<'tcx> for TryNormalizeAfterErasingRegionsFolder<'tcx> {
     type Error = NormalizationError<'tcx>;
 
     fn tcx(&self) -> TyCtxt<'tcx> {
         self.tcx
     }
-}
 
-impl<'tcx> FallibleTypeFolder<'tcx> for TryNormalizeAfterErasingRegionsFolder<'tcx> {
     fn try_fold_ty(&mut self, ty: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
         match self.try_normalize_generic_arg_after_erasing_regions(ty.into()) {
             Ok(t) => Ok(t.expect_ty()),
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 200253d5755..c56909ba18b 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -226,7 +226,7 @@ pub trait PrettyPrinter<'tcx>:
         value.as_ref().skip_binder().print(self)
     }
 
-    fn wrap_binder<T, F: Fn(&T, Self) -> Result<Self, fmt::Error>>(
+    fn wrap_binder<T, F: FnOnce(&T, Self) -> Result<Self, fmt::Error>>(
         self,
         value: &ty::Binder<'tcx, T>,
         f: F,
@@ -773,18 +773,18 @@ pub trait PrettyPrinter<'tcx>:
         def_id: DefId,
         substs: &'tcx ty::List<ty::GenericArg<'tcx>>,
     ) -> Result<Self::Type, Self::Error> {
-        define_scoped_cx!(self);
+        let tcx = self.tcx();
 
         // Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
         // by looking up the projections associated with the def_id.
-        let bounds = self.tcx().bound_explicit_item_bounds(def_id);
+        let bounds = tcx.bound_explicit_item_bounds(def_id);
 
         let mut traits = FxIndexMap::default();
         let mut fn_traits = FxIndexMap::default();
         let mut is_sized = false;
 
         for predicate in bounds.transpose_iter().map(|e| e.map_bound(|(p, _)| *p)) {
-            let predicate = predicate.subst(self.tcx(), substs);
+            let predicate = predicate.subst(tcx, substs);
             let bound_predicate = predicate.kind();
 
             match bound_predicate.skip_binder() {
@@ -792,7 +792,7 @@ pub trait PrettyPrinter<'tcx>:
                     let trait_ref = bound_predicate.rebind(pred.trait_ref);
 
                     // Don't print + Sized, but rather + ?Sized if absent.
-                    if Some(trait_ref.def_id()) == self.tcx().lang_items().sized_trait() {
+                    if Some(trait_ref.def_id()) == tcx.lang_items().sized_trait() {
                         is_sized = true;
                         continue;
                     }
@@ -801,7 +801,7 @@ pub trait PrettyPrinter<'tcx>:
                 }
                 ty::PredicateKind::Projection(pred) => {
                     let proj_ref = bound_predicate.rebind(pred);
-                    let trait_ref = proj_ref.required_poly_trait_ref(self.tcx());
+                    let trait_ref = proj_ref.required_poly_trait_ref(tcx);
 
                     // Projection type entry -- the def-id for naming, and the ty.
                     let proj_ty = (proj_ref.projection_def_id(), proj_ref.term());
@@ -817,148 +817,155 @@ pub trait PrettyPrinter<'tcx>:
             }
         }
 
+        write!(self, "impl ")?;
+
         let mut first = true;
         // Insert parenthesis around (Fn(A, B) -> C) if the opaque ty has more than one other trait
         let paren_needed = fn_traits.len() > 1 || traits.len() > 0 || !is_sized;
 
-        p!("impl");
-
         for (fn_once_trait_ref, entry) in fn_traits {
-            // Get the (single) generic ty (the args) of this FnOnce trait ref.
-            let generics = self.tcx().generics_of(fn_once_trait_ref.def_id());
-            let args =
-                generics.own_substs_no_defaults(self.tcx(), fn_once_trait_ref.skip_binder().substs);
-
-            match (entry.return_ty, args[0].expect_ty()) {
-                // We can only print `impl Fn() -> ()` if we have a tuple of args and we recorded
-                // a return type.
-                (Some(return_ty), arg_tys) if matches!(arg_tys.kind(), ty::Tuple(_)) => {
-                    let name = if entry.fn_trait_ref.is_some() {
-                        "Fn"
-                    } else if entry.fn_mut_trait_ref.is_some() {
-                        "FnMut"
-                    } else {
-                        "FnOnce"
-                    };
+            write!(self, "{}", if first { "" } else { " + " })?;
+            write!(self, "{}", if paren_needed { "(" } else { "" })?;
 
-                    p!(
-                        write("{}", if first { " " } else { " + " }),
-                        write("{}{}(", if paren_needed { "(" } else { "" }, name)
-                    );
+            self = self.wrap_binder(&fn_once_trait_ref, |trait_ref, mut cx| {
+                define_scoped_cx!(cx);
+                // Get the (single) generic ty (the args) of this FnOnce trait ref.
+                let generics = tcx.generics_of(trait_ref.def_id);
+                let args = generics.own_substs_no_defaults(tcx, trait_ref.substs);
+
+                match (entry.return_ty, args[0].expect_ty()) {
+                    // We can only print `impl Fn() -> ()` if we have a tuple of args and we recorded
+                    // a return type.
+                    (Some(return_ty), arg_tys) if matches!(arg_tys.kind(), ty::Tuple(_)) => {
+                        let name = if entry.fn_trait_ref.is_some() {
+                            "Fn"
+                        } else if entry.fn_mut_trait_ref.is_some() {
+                            "FnMut"
+                        } else {
+                            "FnOnce"
+                        };
 
-                    for (idx, ty) in arg_tys.tuple_fields().iter().enumerate() {
-                        if idx > 0 {
-                            p!(", ");
+                        p!(write("{}(", name));
+
+                        for (idx, ty) in arg_tys.tuple_fields().iter().enumerate() {
+                            if idx > 0 {
+                                p!(", ");
+                            }
+                            p!(print(ty));
                         }
-                        p!(print(ty));
-                    }
 
-                    p!(")");
-                    if let Term::Ty(ty) = return_ty.skip_binder() {
-                        if !ty.is_unit() {
-                            p!(" -> ", print(return_ty));
+                        p!(")");
+                        if let Term::Ty(ty) = return_ty.skip_binder() {
+                            if !ty.is_unit() {
+                                p!(" -> ", print(return_ty));
+                            }
                         }
-                    }
-                    p!(write("{}", if paren_needed { ")" } else { "" }));
+                        p!(write("{}", if paren_needed { ")" } else { "" }));
 
-                    first = false;
-                }
-                // If we got here, we can't print as a `impl Fn(A, B) -> C`. Just record the
-                // trait_refs we collected in the OpaqueFnEntry as normal trait refs.
-                _ => {
-                    if entry.has_fn_once {
-                        traits.entry(fn_once_trait_ref).or_default().extend(
-                            // Group the return ty with its def id, if we had one.
-                            entry
-                                .return_ty
-                                .map(|ty| (self.tcx().lang_items().fn_once_output().unwrap(), ty)),
-                        );
-                    }
-                    if let Some(trait_ref) = entry.fn_mut_trait_ref {
-                        traits.entry(trait_ref).or_default();
+                        first = false;
                     }
-                    if let Some(trait_ref) = entry.fn_trait_ref {
-                        traits.entry(trait_ref).or_default();
+                    // If we got here, we can't print as a `impl Fn(A, B) -> C`. Just record the
+                    // trait_refs we collected in the OpaqueFnEntry as normal trait refs.
+                    _ => {
+                        if entry.has_fn_once {
+                            traits.entry(fn_once_trait_ref).or_default().extend(
+                                // Group the return ty with its def id, if we had one.
+                                entry
+                                    .return_ty
+                                    .map(|ty| (tcx.lang_items().fn_once_output().unwrap(), ty)),
+                            );
+                        }
+                        if let Some(trait_ref) = entry.fn_mut_trait_ref {
+                            traits.entry(trait_ref).or_default();
+                        }
+                        if let Some(trait_ref) = entry.fn_trait_ref {
+                            traits.entry(trait_ref).or_default();
+                        }
                     }
                 }
-            }
+
+                Ok(cx)
+            })?;
         }
 
         // Print the rest of the trait types (that aren't Fn* family of traits)
         for (trait_ref, assoc_items) in traits {
-            p!(
-                write("{}", if first { " " } else { " + " }),
-                print(trait_ref.skip_binder().print_only_trait_name())
-            );
+            write!(self, "{}", if first { "" } else { " + " })?;
+
+            self = self.wrap_binder(&trait_ref, |trait_ref, mut cx| {
+                define_scoped_cx!(cx);
+                p!(print(trait_ref.print_only_trait_name()));
 
-            let generics = self.tcx().generics_of(trait_ref.def_id());
-            let args = generics.own_substs_no_defaults(self.tcx(), trait_ref.skip_binder().substs);
+                let generics = tcx.generics_of(trait_ref.def_id);
+                let args = generics.own_substs_no_defaults(tcx, trait_ref.substs);
 
-            if !args.is_empty() || !assoc_items.is_empty() {
-                let mut first = true;
+                if !args.is_empty() || !assoc_items.is_empty() {
+                    let mut first = true;
 
-                for ty in args {
-                    if first {
-                        p!("<");
-                        first = false;
-                    } else {
-                        p!(", ");
+                    for ty in args {
+                        if first {
+                            p!("<");
+                            first = false;
+                        } else {
+                            p!(", ");
+                        }
+                        p!(print(ty));
                     }
-                    p!(print(trait_ref.rebind(*ty)));
-                }
 
-                for (assoc_item_def_id, term) in assoc_items {
-                    // Skip printing `<[generator@] as Generator<_>>::Return` from async blocks,
-                    // unless we can find out what generator return type it comes from.
-                    let term = if let Some(ty) = term.skip_binder().ty()
-                        && let ty::Projection(ty::ProjectionTy { item_def_id, substs }) = ty.kind()
-                        && Some(*item_def_id) == self.tcx().lang_items().generator_return()
-                    {
-                        if let ty::Generator(_, substs, _) = substs.type_at(0).kind() {
-                            let return_ty = substs.as_generator().return_ty();
-                            if !return_ty.is_ty_infer() {
-                                return_ty.into()
+                    for (assoc_item_def_id, term) in assoc_items {
+                        // Skip printing `<[generator@] as Generator<_>>::Return` from async blocks,
+                        // unless we can find out what generator return type it comes from.
+                        let term = if let Some(ty) = term.skip_binder().ty()
+                            && let ty::Projection(ty::ProjectionTy { item_def_id, substs }) = ty.kind()
+                            && Some(*item_def_id) == tcx.lang_items().generator_return()
+                        {
+                            if let ty::Generator(_, substs, _) = substs.type_at(0).kind() {
+                                let return_ty = substs.as_generator().return_ty();
+                                if !return_ty.is_ty_infer() {
+                                    return_ty.into()
+                                } else {
+                                    continue;
+                                }
                             } else {
                                 continue;
                             }
                         } else {
-                            continue;
-                        }
-                    } else {
-                        term.skip_binder()
-                    };
+                            term.skip_binder()
+                        };
 
-                    if first {
-                        p!("<");
-                        first = false;
-                    } else {
-                        p!(", ");
-                    }
+                        if first {
+                            p!("<");
+                            first = false;
+                        } else {
+                            p!(", ");
+                        }
 
-                    p!(write("{} = ", self.tcx().associated_item(assoc_item_def_id).name));
+                        p!(write("{} = ", tcx.associated_item(assoc_item_def_id).name));
 
-                    match term {
-                        Term::Ty(ty) => {
-                            p!(print(ty))
-                        }
-                        Term::Const(c) => {
-                            p!(print(c));
-                        }
-                    };
-                }
+                        match term {
+                            Term::Ty(ty) => {
+                                p!(print(ty))
+                            }
+                            Term::Const(c) => {
+                                p!(print(c));
+                            }
+                        };
+                    }
 
-                if !first {
-                    p!(">");
+                    if !first {
+                        p!(">");
+                    }
                 }
-            }
 
-            first = false;
+                first = false;
+                Ok(cx)
+            })?;
         }
 
         if !is_sized {
-            p!(write("{}?Sized", if first { " " } else { " + " }));
+            write!(self, "{}?Sized", if first { "" } else { " + " })?;
         } else if first {
-            p!(" Sized");
+            write!(self, "Sized")?;
         }
 
         Ok(self)
@@ -1869,7 +1876,7 @@ impl<'tcx> PrettyPrinter<'tcx> for FmtPrinter<'_, 'tcx> {
         self.pretty_in_binder(value)
     }
 
-    fn wrap_binder<T, C: Fn(&T, Self) -> Result<Self, Self::Error>>(
+    fn wrap_binder<T, C: FnOnce(&T, Self) -> Result<Self, Self::Error>>(
         self,
         value: &ty::Binder<'tcx, T>,
         f: C,
@@ -2256,7 +2263,7 @@ impl<'tcx> FmtPrinter<'_, 'tcx> {
         Ok(inner)
     }
 
-    pub fn pretty_wrap_binder<T, C: Fn(&T, Self) -> Result<Self, fmt::Error>>(
+    pub fn pretty_wrap_binder<T, C: FnOnce(&T, Self) -> Result<Self, fmt::Error>>(
         self,
         value: &ty::Binder<'tcx, T>,
         f: C,
diff --git a/compiler/rustc_middle/src/ty/subst.rs b/compiler/rustc_middle/src/ty/subst.rs
index ad2898ccd67..1417c8a511c 100644
--- a/compiler/rustc_middle/src/ty/subst.rs
+++ b/compiler/rustc_middle/src/ty/subst.rs
@@ -705,7 +705,7 @@ impl<'a, 'tcx> SubstFolder<'a, 'tcx> {
             return val;
         }
 
-        let result = ty::fold::shift_vars(self.tcx(), val, self.binders_passed);
+        let result = ty::fold::shift_vars(TypeFolder::tcx(self), val, self.binders_passed);
         debug!("shift_vars: shifted result = {:?}", result);
 
         result
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index b5f3cd828a0..08b66d0abc7 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -1,3 +1,4 @@
+use crate::thir::cx::region::Scope;
 use crate::thir::cx::Cx;
 use crate::thir::util::UserAnnotatedTyHelpers;
 use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -158,6 +159,98 @@ impl<'tcx> Cx<'tcx> {
         Expr { temp_lifetime, ty: adjustment.target, span, kind }
     }
 
+    /// Lowers a cast expression.
+    ///
+    /// Dealing with user type annotations is left to the caller.
+    fn mirror_expr_cast(
+        &mut self,
+        source: &'tcx hir::Expr<'tcx>,
+        temp_lifetime: Option<Scope>,
+        span: Span,
+    ) -> ExprKind<'tcx> {
+        let tcx = self.tcx;
+
+        // Check to see if this cast is a "coercion cast", where the cast is actually done
+        // using a coercion (or is a no-op).
+        if self.typeck_results().is_coercion_cast(source.hir_id) {
+            // Convert the lexpr to a vexpr.
+            ExprKind::Use { source: self.mirror_expr(source) }
+        } else if self.typeck_results().expr_ty(source).is_region_ptr() {
+            // Special cased so that we can type check that the element
+            // type of the source matches the pointed to type of the
+            // destination.
+            ExprKind::Pointer {
+                source: self.mirror_expr(source),
+                cast: PointerCast::ArrayToPointer,
+            }
+        } else {
+            // check whether this is casting an enum variant discriminant
+            // to prevent cycles, we refer to the discriminant initializer
+            // which is always an integer and thus doesn't need to know the
+            // enum's layout (or its tag type) to compute it during const eval
+            // Example:
+            // enum Foo {
+            //     A,
+            //     B = A as isize + 4,
+            // }
+            // The correct solution would be to add symbolic computations to miri,
+            // so we wouldn't have to compute and store the actual value
+
+            let hir::ExprKind::Path(ref qpath) = source.kind else {
+                return ExprKind::Cast { source: self.mirror_expr(source)};
+            };
+
+            let res = self.typeck_results().qpath_res(qpath, source.hir_id);
+            let ty = self.typeck_results().node_type(source.hir_id);
+            let ty::Adt(adt_def, substs) = ty.kind() else {
+                return ExprKind::Cast { source: self.mirror_expr(source)};
+            };
+
+            let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res else {
+                return ExprKind::Cast { source: self.mirror_expr(source)};
+            };
+
+            let idx = adt_def.variant_index_with_ctor_id(variant_ctor_id);
+            let (discr_did, discr_offset) = adt_def.discriminant_def_for_variant(idx);
+
+            use rustc_middle::ty::util::IntTypeExt;
+            let ty = adt_def.repr().discr_type();
+            let discr_ty = ty.to_ty(tcx);
+
+            let param_env_ty = self.param_env.and(discr_ty);
+            let size = tcx
+                .layout_of(param_env_ty)
+                .unwrap_or_else(|e| {
+                    panic!("could not compute layout for {:?}: {:?}", param_env_ty, e)
+                })
+                .size;
+
+            let lit = ScalarInt::try_from_uint(discr_offset as u128, size).unwrap();
+            let kind = ExprKind::NonHirLiteral { lit, user_ty: None };
+            let offset = self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind });
+
+            let source = match discr_did {
+                // in case we are offsetting from a computed discriminant
+                // and not the beginning of discriminants (which is always `0`)
+                Some(did) => {
+                    let kind = ExprKind::NamedConst { def_id: did, substs, user_ty: None };
+                    let lhs =
+                        self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind });
+                    let bin = ExprKind::Binary { op: BinOp::Add, lhs, rhs: offset };
+                    self.thir.exprs.push(Expr {
+                        temp_lifetime,
+                        ty: discr_ty,
+                        span: span,
+                        kind: bin,
+                    })
+                }
+                None => offset,
+            };
+
+            ExprKind::Cast { source }
+        }
+    }
+
     fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> {
         let tcx = self.tcx;
         let expr_ty = self.typeck_results().expr_ty(expr);
@@ -604,98 +697,7 @@ impl<'tcx> Cx<'tcx> {
                     expr, cast_ty.hir_id, user_ty,
                 );
 
-                // Check to see if this cast is a "coercion cast", where the cast is actually done
-                // using a coercion (or is a no-op).
-                let cast = if self.typeck_results().is_coercion_cast(source.hir_id) {
-                    // Convert the lexpr to a vexpr.
-                    ExprKind::Use { source: self.mirror_expr(source) }
-                } else if self.typeck_results().expr_ty(source).is_region_ptr() {
-                    // Special cased so that we can type check that the element
-                    // type of the source matches the pointed to type of the
-                    // destination.
-                    ExprKind::Pointer {
-                        source: self.mirror_expr(source),
-                        cast: PointerCast::ArrayToPointer,
-                    }
-                } else {
-                    // check whether this is casting an enum variant discriminant
-                    // to prevent cycles, we refer to the discriminant initializer
-                    // which is always an integer and thus doesn't need to know the
-                    // enum's layout (or its tag type) to compute it during const eval
-                    // Example:
-                    // enum Foo {
-                    //     A,
-                    //     B = A as isize + 4,
-                    // }
-                    // The correct solution would be to add symbolic computations to miri,
-                    // so we wouldn't have to compute and store the actual value
-                    let var = if let hir::ExprKind::Path(ref qpath) = source.kind {
-                        let res = self.typeck_results().qpath_res(qpath, source.hir_id);
-                        self.typeck_results().node_type(source.hir_id).ty_adt_def().and_then(
-                            |adt_def| match res {
-                                Res::Def(
-                                    DefKind::Ctor(CtorOf::Variant, CtorKind::Const),
-                                    variant_ctor_id,
-                                ) => {
-                                    let idx = adt_def.variant_index_with_ctor_id(variant_ctor_id);
-                                    let (d, o) = adt_def.discriminant_def_for_variant(idx);
-                                    use rustc_middle::ty::util::IntTypeExt;
-                                    let ty = adt_def.repr().discr_type();
-                                    let ty = ty.to_ty(tcx);
-                                    Some((d, o, ty))
-                                }
-                                _ => None,
-                            },
-                        )
-                    } else {
-                        None
-                    };
-
-                    let source = if let Some((did, offset, var_ty)) = var {
-                        let param_env_ty = self.param_env.and(var_ty);
-                        let size = tcx
-                            .layout_of(param_env_ty)
-                            .unwrap_or_else(|e| {
-                                panic!("could not compute layout for {:?}: {:?}", param_env_ty, e)
-                            })
-                            .size;
-                        let lit = ScalarInt::try_from_uint(offset as u128, size).unwrap();
-                        let kind = ExprKind::NonHirLiteral { lit, user_ty: None };
-                        let offset = self.thir.exprs.push(Expr {
-                            temp_lifetime,
-                            ty: var_ty,
-                            span: expr.span,
-                            kind,
-                        });
-                        match did {
-                            Some(did) => {
-                                // in case we are offsetting from a computed discriminant
-                                // and not the beginning of discriminants (which is always `0`)
-                                let substs = InternalSubsts::identity_for_item(tcx, did);
-                                let kind =
-                                    ExprKind::NamedConst { def_id: did, substs, user_ty: None };
-                                let lhs = self.thir.exprs.push(Expr {
-                                    temp_lifetime,
-                                    ty: var_ty,
-                                    span: expr.span,
-                                    kind,
-                                });
-                                let bin = ExprKind::Binary { op: BinOp::Add, lhs, rhs: offset };
-                                self.thir.exprs.push(Expr {
-                                    temp_lifetime,
-                                    ty: var_ty,
-                                    span: expr.span,
-                                    kind: bin,
-                                })
-                            }
-                            None => offset,
-                        }
-                    } else {
-                        self.mirror_expr(source)
-                    };
-
-                    ExprKind::Cast { source: source }
-                };
+                let cast = self.mirror_expr_cast(*source, temp_lifetime, expr.span);
 
                 if let Some(user_ty) = user_ty {
                     // NOTE: Creating a new Expr and wrapping a Cast inside of it may be
diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs
index f3bdd63ee6d..113af328a91 100644
--- a/compiler/rustc_parse/src/lib.rs
+++ b/compiler/rustc_parse/src/lib.rs
@@ -6,6 +6,7 @@
 #![feature(let_chains)]
 #![feature(let_else)]
 #![feature(never_type)]
+#![feature(rustc_attrs)]
 #![recursion_limit = "256"]
 
 #[macro_use]
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index ba325d70422..0869ed65ad2 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -244,7 +244,7 @@ impl MultiSugg {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "parser-maybe-report-ambiguous-plus")]
+#[error(parser::maybe_report_ambiguous_plus)]
 struct AmbiguousPlus {
     pub sum_ty: String,
     #[primary_span]
@@ -253,7 +253,7 @@ struct AmbiguousPlus {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0178", slug = "parser-maybe-recover-from-bad-type-plus")]
+#[error(parser::maybe_recover_from_bad_type_plus, code = "E0178")]
 struct BadTypePlus {
     pub ty: String,
     #[primary_span]
@@ -265,7 +265,7 @@ struct BadTypePlus {
 #[derive(SessionSubdiagnostic)]
 pub enum BadTypePlusSub {
     #[suggestion(
-        slug = "parser-add-paren",
+        parser::add_paren,
         code = "{sum_with_parens}",
         applicability = "machine-applicable"
     )]
@@ -274,12 +274,12 @@ pub enum BadTypePlusSub {
         #[primary_span]
         span: Span,
     },
-    #[label(slug = "parser-forgot-paren")]
+    #[label(parser::forgot_paren)]
     ForgotParen {
         #[primary_span]
         span: Span,
     },
-    #[label(slug = "parser-expect-path")]
+    #[label(parser::expect_path)]
     ExpectPath {
         #[primary_span]
         span: Span,
@@ -287,7 +287,7 @@ pub enum BadTypePlusSub {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "parser-maybe-recover-from-bad-qpath-stage-2")]
+#[error(parser::maybe_recover_from_bad_qpath_stage_2)]
 struct BadQPathStage2 {
     #[primary_span]
     #[suggestion(applicability = "maybe-incorrect")]
@@ -296,7 +296,7 @@ struct BadQPathStage2 {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "parser-incorrect-semicolon")]
+#[error(parser::incorrect_semicolon)]
 struct IncorrectSemicolon<'a> {
     #[primary_span]
     #[suggestion_short(applicability = "machine-applicable")]
@@ -307,26 +307,26 @@ struct IncorrectSemicolon<'a> {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "parser-incorrect-use-of-await")]
+#[error(parser::incorrect_use_of_await)]
 struct IncorrectUseOfAwait {
     #[primary_span]
-    #[suggestion(message = "parentheses-suggestion", applicability = "machine-applicable")]
+    #[suggestion(parser::parentheses_suggestion, applicability = "machine-applicable")]
     span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "parser-incorrect-use-of-await")]
+#[error(parser::incorrect_use_of_await)]
 struct IncorrectAwait {
     #[primary_span]
     span: Span,
-    #[suggestion(message = "postfix-suggestion", code = "{expr}.await{question_mark}")]
+    #[suggestion(parser::postfix_suggestion, code = "{expr}.await{question_mark}")]
     sugg_span: (Span, Applicability),
     expr: String,
     question_mark: &'static str,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "parser-in-in-typo")]
+#[error(parser::in_in_typo)]
 struct InInTypo {
     #[primary_span]
     span: Span,
@@ -357,6 +357,7 @@ impl<'a> DerefMut for SnapshotParser<'a> {
 }
 
 impl<'a> Parser<'a> {
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub(super) fn span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -365,6 +366,7 @@ impl<'a> Parser<'a> {
         err.span_err(sp, self.diagnostic())
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 81bab0e3513..2c43563b104 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -7,6 +7,7 @@ use super::{
 };
 use crate::maybe_recover_from_interpolated_ty_qpath;
 
+use core::mem;
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Delimiter, Token, TokenKind};
 use rustc_ast::tokenstream::Spacing;
@@ -26,7 +27,6 @@ use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_span::source_map::{self, Span, Spanned};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{BytePos, Pos};
-use std::mem;
 
 /// Possibly accepts an `token::Interpolated` expression (a pre-parsed expression
 /// dropped into the token stream, which happens while parsing the result of
@@ -2343,7 +2343,9 @@ impl<'a> Parser<'a> {
 
     /// Parses the condition of a `if` or `while` expression.
     fn parse_cond_expr(&mut self) -> PResult<'a, P<Expr>> {
-        let cond = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
+        let cond = self.with_let_management(true, |local_self| {
+            local_self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)
+        })?;
 
         if let ExprKind::Let(..) = cond.kind {
             // Remove the last feature gating of a `let` expression since it's stable.
@@ -2356,6 +2358,13 @@ impl<'a> Parser<'a> {
     /// Parses a `let $pat = $expr` pseudo-expression.
     /// The `let` token has already been eaten.
     fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        if !self.let_expr_allowed {
+            self.struct_span_err(
+                self.prev_token.span,
+                "expected expression, found `let` statement",
+            )
+            .emit();
+        }
         let lo = self.prev_token.span;
         let pat = self.parse_pat_allow_top_alt(
             None,
@@ -2672,6 +2681,8 @@ impl<'a> Parser<'a> {
     }
 
     pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
+        // Used to check the `let_chains` and `if_let_guard` features mostly by scaning
+        // `&&` tokens.
         fn check_let_expr(expr: &Expr) -> (bool, bool) {
             match expr.kind {
                 ExprKind::Binary(_, ref lhs, ref rhs) => {
@@ -2694,7 +2705,7 @@ impl<'a> Parser<'a> {
             )?;
             let guard = if this.eat_keyword(kw::If) {
                 let if_span = this.prev_token.span;
-                let cond = this.parse_expr()?;
+                let cond = this.with_let_management(true, |local_this| local_this.parse_expr())?;
                 let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
                 if has_let_expr {
                     if does_not_have_bin_op {
@@ -3256,4 +3267,17 @@ impl<'a> Parser<'a> {
             Ok((res, trailing))
         })
     }
+
+    // Calls `f` with the internal `let_expr_allowed` set to `let_expr_allowed` and then
+    // sets the internal `let_expr_allowed` back to its original value.
+    fn with_let_management<T>(
+        &mut self,
+        let_expr_allowed: bool,
+        f: impl FnOnce(&mut Self) -> T,
+    ) -> T {
+        let last_let_expr_allowed = mem::replace(&mut self.let_expr_allowed, let_expr_allowed);
+        let rslt = f(self);
+        self.let_expr_allowed = last_let_expr_allowed;
+        rslt
+    }
 }
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 6d6667717f0..acdf121522a 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -147,12 +147,15 @@ pub struct Parser<'a> {
     /// This allows us to recover when the user forget to add braces around
     /// multiple statements in the closure body.
     pub current_closure: Option<ClosureSpans>,
+    /// Used to track where `let`s are allowed. For example, `if true && let 1 = 1` is valid
+    /// but `[1, 2, 3][let _ = ()]` is not.
+    let_expr_allowed: bool,
 }
 
 // This type is used a lot, e.g. it's cloned when matching many declarative macro rules. Make sure
 // it doesn't unintentionally get bigger.
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(Parser<'_>, 328);
+rustc_data_structures::static_assert_size!(Parser<'_>, 336);
 
 /// Stores span information about a closure.
 #[derive(Clone)]
@@ -455,6 +458,7 @@ impl<'a> Parser<'a> {
                 inner_attr_ranges: Default::default(),
             },
             current_closure: None,
+            let_expr_allowed: false,
         };
 
         // Make parser point to the first token.
diff --git a/compiler/rustc_privacy/Cargo.toml b/compiler/rustc_privacy/Cargo.toml
index d952e288a64..5785921fb1e 100644
--- a/compiler/rustc_privacy/Cargo.toml
+++ b/compiler/rustc_privacy/Cargo.toml
@@ -4,14 +4,15 @@ version = "0.0.0"
 edition = "2021"
 
 [dependencies]
-rustc_middle = { path = "../rustc_middle" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_attr = { path = "../rustc_attr" }
+rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_errors = { path = "../rustc_errors" }
 rustc_hir = { path = "../rustc_hir" }
-rustc_typeck = { path = "../rustc_typeck" }
+rustc_macros = { path = "../rustc_macros" }
+rustc_middle = { path = "../rustc_middle" }
 rustc_session = { path = "../rustc_session" }
 rustc_span = { path = "../rustc_span" }
-rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_trait_selection = { path = "../rustc_trait_selection" }
+rustc_typeck = { path = "../rustc_typeck" }
 tracing = "0.1"
diff --git a/compiler/rustc_privacy/src/errors.rs b/compiler/rustc_privacy/src/errors.rs
new file mode 100644
index 00000000000..482721d373a
--- /dev/null
+++ b/compiler/rustc_privacy/src/errors.rs
@@ -0,0 +1,75 @@
+use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
+use rustc_span::{Span, Symbol};
+
+#[derive(SessionDiagnostic)]
+#[error(privacy::field_is_private, code = "E0451")]
+pub struct FieldIsPrivate {
+    #[primary_span]
+    pub span: Span,
+    pub field_name: Symbol,
+    pub variant_descr: &'static str,
+    pub def_path_str: String,
+    #[subdiagnostic]
+    pub label: FieldIsPrivateLabel,
+}
+
+#[derive(SessionSubdiagnostic)]
+pub enum FieldIsPrivateLabel {
+    #[label(privacy::field_is_private_is_update_syntax_label)]
+    IsUpdateSyntax {
+        #[primary_span]
+        span: Span,
+        field_name: Symbol,
+    },
+    #[label(privacy::field_is_private_label)]
+    Other {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(SessionDiagnostic)]
+#[error(privacy::item_is_private)]
+pub struct ItemIsPrivate<'a> {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub kind: &'a str,
+    pub descr: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(privacy::unnamed_item_is_private)]
+pub struct UnnamedItemIsPrivate {
+    #[primary_span]
+    pub span: Span,
+    pub kind: &'static str,
+}
+
+// Duplicate of `InPublicInterface` but with a different error code, shares the same slug.
+#[derive(SessionDiagnostic)]
+#[error(privacy::in_public_interface, code = "E0445")]
+pub struct InPublicInterfaceTraits<'a> {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub vis_descr: &'static str,
+    pub kind: &'a str,
+    pub descr: String,
+    #[label(privacy::visibility_label)]
+    pub vis_span: Span,
+}
+
+// Duplicate of `InPublicInterfaceTraits` but with a different error code, shares the same slug.
+#[derive(SessionDiagnostic)]
+#[error(privacy::in_public_interface, code = "E0446")]
+pub struct InPublicInterface<'a> {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub vis_descr: &'static str,
+    pub kind: &'a str,
+    pub descr: String,
+    #[label(privacy::visibility_label)]
+    pub vis_span: Span,
+}
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index b27c986d0f9..238c917bbc3 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -1,15 +1,19 @@
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(associated_type_defaults)]
 #![feature(control_flow_enum)]
+#![feature(rustc_private)]
 #![feature(try_blocks)]
-#![feature(associated_type_defaults)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
+#![cfg_attr(not(bootstrap), deny(rustc::untranslatable_diagnostic))]
+#![cfg_attr(not(bootstrap), deny(rustc::diagnostic_outside_of_impl))]
+
+mod errors;
 
 use rustc_ast::MacroDef;
 use rustc_attr as attr;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::intern::Interned;
-use rustc_errors::struct_span_err;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID};
@@ -34,6 +38,11 @@ use std::marker::PhantomData;
 use std::ops::ControlFlow;
 use std::{cmp, fmt, mem};
 
+use errors::{
+    FieldIsPrivate, FieldIsPrivateLabel, InPublicInterface, InPublicInterfaceTraits, ItemIsPrivate,
+    UnnamedItemIsPrivate,
+};
+
 ////////////////////////////////////////////////////////////////////////////////
 /// Generic infrastructure used to implement specific visitors below.
 ////////////////////////////////////////////////////////////////////////////////
@@ -935,23 +944,17 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
         let hir_id = self.tcx.hir().local_def_id_to_hir_id(self.current_item);
         let def_id = self.tcx.adjust_ident_and_get_scope(ident, def.did(), hir_id).1;
         if !field.vis.is_accessible_from(def_id, self.tcx) {
-            let label = if in_update_syntax {
-                format!("field `{}` is private", field.name)
-            } else {
-                "private field".to_string()
-            };
-
-            struct_span_err!(
-                self.tcx.sess,
+            self.tcx.sess.emit_err(FieldIsPrivate {
                 span,
-                E0451,
-                "field `{}` of {} `{}` is private",
-                field.name,
-                def.variant_descr(),
-                self.tcx.def_path_str(def.did())
-            )
-            .span_label(span, label)
-            .emit();
+                field_name: field.name,
+                variant_descr: def.variant_descr(),
+                def_path_str: self.tcx.def_path_str(def.did()),
+                label: if in_update_syntax {
+                    FieldIsPrivateLabel::IsUpdateSyntax { span, field_name: field.name }
+                } else {
+                    FieldIsPrivateLabel::Other { span }
+                },
+            });
         }
     }
 }
@@ -1075,11 +1078,11 @@ impl<'tcx> TypePrivacyVisitor<'tcx> {
     fn check_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
         let is_error = !self.item_is_accessible(def_id);
         if is_error {
-            self.tcx
-                .sess
-                .struct_span_err(self.span, &format!("{} `{}` is private", kind, descr))
-                .span_label(self.span, &format!("private {}", kind))
-                .emit();
+            self.tcx.sess.emit_err(ItemIsPrivate {
+                span: self.span,
+                kind,
+                descr: descr.to_string(),
+            });
         }
         is_error
     }
@@ -1250,13 +1253,10 @@ impl<'tcx> Visitor<'tcx> for TypePrivacyVisitor<'tcx> {
                     hir::QPath::TypeRelative(_, segment) => Some(segment.ident.to_string()),
                 };
                 let kind = kind.descr(def_id);
-                let msg = match name {
-                    Some(name) => format!("{} `{}` is private", kind, name),
-                    None => format!("{} is private", kind),
+                let _ = match name {
+                    Some(name) => sess.emit_err(ItemIsPrivate { span, kind, descr: name }),
+                    None => sess.emit_err(UnnamedItemIsPrivate { span, kind }),
                 };
-                sess.struct_span_err(span, &msg)
-                    .span_label(span, &format!("private {}", kind))
-                    .emit();
                 return;
             }
         }
@@ -1753,22 +1753,31 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> {
                     }
                 }
             };
-            let make_msg = || format!("{} {} `{}` in public interface", vis_descr, kind, descr);
             let span = self.tcx.def_span(self.item_def_id.to_def_id());
             if self.has_old_errors
                 || self.in_assoc_ty
                 || self.tcx.resolutions(()).has_pub_restricted
             {
-                let mut err = if kind == "trait" {
-                    struct_span_err!(self.tcx.sess, span, E0445, "{}", make_msg())
-                } else {
-                    struct_span_err!(self.tcx.sess, span, E0446, "{}", make_msg())
-                };
+                let descr = descr.to_string();
                 let vis_span =
                     self.tcx.sess.source_map().guess_head_span(self.tcx.def_span(def_id));
-                err.span_label(span, format!("can't leak {} {}", vis_descr, kind));
-                err.span_label(vis_span, format!("`{}` declared as {}", descr, vis_descr));
-                err.emit();
+                if kind == "trait" {
+                    self.tcx.sess.emit_err(InPublicInterfaceTraits {
+                        span,
+                        vis_descr,
+                        kind,
+                        descr,
+                        vis_span,
+                    });
+                } else {
+                    self.tcx.sess.emit_err(InPublicInterface {
+                        span,
+                        vis_descr,
+                        kind,
+                        descr,
+                        vis_span,
+                    });
+                }
             } else {
                 let err_code = if kind == "trait" { "E0445" } else { "E0446" };
                 self.tcx.struct_span_lint_hir(
@@ -1776,7 +1785,12 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> {
                     hir_id,
                     span,
                     |lint| {
-                        lint.build(&format!("{} (error {})", make_msg(), err_code)).emit();
+                        lint.build(&format!(
+                            "{} (error {})",
+                            format!("{} {} `{}` in public interface", vis_descr, kind, descr),
+                            err_code
+                        ))
+                        .emit();
                     },
                 );
             }
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index f1814eebfa6..b5058fd699a 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -280,6 +280,7 @@ impl Session {
         self.crate_types.set(crate_types).expect("`crate_types` was initialized twice")
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_warn<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -287,6 +288,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn(sp, msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_warn_with_expectation<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -295,6 +297,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn_with_expectation(sp, msg, id)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_warn_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -303,9 +306,11 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn_with_code(sp, msg, code)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_warn(msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_warn_with_expectation(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -313,6 +318,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_warn_with_expectation(msg, id)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_allow<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -320,9 +326,11 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_allow(sp, msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_allow(msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_expect(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -330,6 +338,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_expect(msg, id)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -337,6 +346,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_span_err(sp, msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -346,12 +356,14 @@ impl Session {
         self.diagnostic().struct_span_err_with_code(sp, msg, code)
     }
     // FIXME: This method should be removed (every error should have an associated error code).
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_err(
         &self,
         msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.parse_sess.struct_err(msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_err_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -359,6 +371,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_err_with_code(msg, code)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_warn_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -366,6 +379,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_warn_with_code(msg, code)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_fatal<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -373,6 +387,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_span_fatal(sp, msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_fatal_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -381,13 +396,16 @@ impl Session {
     ) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_span_fatal_with_code(sp, msg, code)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_fatal(msg)
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_fatal<S: Into<MultiSpan>>(&self, sp: S, msg: impl Into<DiagnosticMessage>) -> ! {
         self.diagnostic().span_fatal(sp, msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_fatal_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -396,9 +414,11 @@ impl Session {
     ) -> ! {
         self.diagnostic().span_fatal_with_code(sp, msg, code)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> ! {
         self.diagnostic().fatal(msg).raise()
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_err_or_warn<S: Into<MultiSpan>>(
         &self,
         is_warning: bool,
@@ -411,6 +431,7 @@ impl Session {
             self.span_err(sp, msg);
         }
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -418,6 +439,7 @@ impl Session {
     ) -> ErrorGuaranteed {
         self.diagnostic().span_err(sp, msg)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -426,6 +448,7 @@ impl Session {
     ) {
         self.diagnostic().span_err_with_code(sp, msg, code)
     }
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.diagnostic().err(msg)
     }
diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs
index 95ea7029617..227127aed50 100644
--- a/compiler/rustc_span/src/source_map.rs
+++ b/compiler/rustc_span/src/source_map.rs
@@ -956,7 +956,7 @@ impl SourceMap {
     }
 
     pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
-        let prev_span = self.span_extend_to_prev_str(span, "fn", true, true).unwrap_or(span);
+        let prev_span = self.span_extend_to_prev_str(span, "fn", true, true)?;
         if let Ok(snippet) = self.span_to_snippet(prev_span) {
             debug!(
                 "generate_fn_name_span: span={:?}, prev_span={:?}, snippet={:?}",
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 8a6941a4516..4e28d2b6001 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -135,6 +135,9 @@ symbols! {
         Arguments,
         AsMut,
         AsRef,
+        AssertParamIsClone,
+        AssertParamIsCopy,
+        AssertParamIsEq,
         AtomicBool,
         AtomicI128,
         AtomicI16,
@@ -567,8 +570,10 @@ symbols! {
         debug_assert_ne_macro,
         debug_assertions,
         debug_struct,
+        debug_struct_fields_finish,
         debug_trait_builder,
         debug_tuple,
+        debug_tuple_fields_finish,
         debugger_visualizer,
         decl_macro,
         declare_lint_pass,
diff --git a/compiler/rustc_target/src/lib.rs b/compiler/rustc_target/src/lib.rs
index a8ddcc9bfac..59dbea70534 100644
--- a/compiler/rustc_target/src/lib.rs
+++ b/compiler/rustc_target/src/lib.rs
@@ -8,6 +8,7 @@
 //! LLVM.
 
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(assert_matches)]
 #![feature(associated_type_bounds)]
 #![feature(exhaustive_patterns)]
 #![feature(let_else)]
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs b/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs
index 86f76fdb6a7..9d36e37d7b8 100644
--- a/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs
@@ -8,7 +8,7 @@ pub fn target() -> Target {
     // FIXME: The leak sanitizer currently fails the tests, see #88132.
     base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::THREAD;
 
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-arch".into(), "arm64".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-arch", "arm64"]);
     base.link_env_remove.to_mut().extend(super::apple_base::macos_link_env_remove());
 
     // Clang automatically chooses a more specific target based on
diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_uefi.rs b/compiler/rustc_target/src/spec/aarch64_unknown_uefi.rs
index 965b254c289..162b091b269 100644
--- a/compiler/rustc_target/src/spec/aarch64_unknown_uefi.rs
+++ b/compiler/rustc_target/src/spec/aarch64_unknown_uefi.rs
@@ -2,20 +2,13 @@
 // uefi-base module for generic UEFI options.
 
 use super::uefi_msvc_base;
-use crate::spec::{LinkerFlavor, LldFlavor, Target};
+use crate::spec::{LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut base = uefi_msvc_base::opts();
 
     base.max_atomic_width = Some(64);
-
-    let pre_link_args_msvc = vec!["/machine:arm64".into()];
-
-    base.pre_link_args.get_mut(&LinkerFlavor::Msvc).unwrap().extend(pre_link_args_msvc.clone());
-    base.pre_link_args
-        .get_mut(&LinkerFlavor::Lld(LldFlavor::Link))
-        .unwrap()
-        .extend(pre_link_args_msvc);
+    base.add_pre_link_args(LinkerFlavor::Msvc, &["/machine:arm64"]);
 
     Target {
         llvm_target: "aarch64-unknown-windows".into(),
diff --git a/compiler/rustc_target/src/spec/armv6k_nintendo_3ds.rs b/compiler/rustc_target/src/spec/armv6k_nintendo_3ds.rs
index 67df73fa935..8c2a9bcfde6 100644
--- a/compiler/rustc_target/src/spec/armv6k_nintendo_3ds.rs
+++ b/compiler/rustc_target/src/spec/armv6k_nintendo_3ds.rs
@@ -1,19 +1,13 @@
-use crate::spec::{cvs, LinkArgs, LinkerFlavor, RelocModel, Target, TargetOptions};
+use crate::spec::{cvs, LinkerFlavor, RelocModel, Target, TargetOptions};
 
 /// A base target for Nintendo 3DS devices using the devkitARM toolchain.
 ///
 /// Requires the devkitARM toolchain for 3DS targets on the host system.
 
 pub fn target() -> Target {
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args.insert(
+    let pre_link_args = TargetOptions::link_args(
         LinkerFlavor::Gcc,
-        vec![
-            "-specs=3dsx.specs".into(),
-            "-mtune=mpcore".into(),
-            "-mfloat-abi=hard".into(),
-            "-mtp=soft".into(),
-        ],
+        &["-specs=3dsx.specs", "-mtune=mpcore", "-mfloat-abi=hard", "-mtp=soft"],
     );
 
     Target {
diff --git a/compiler/rustc_target/src/spec/armv7_linux_androideabi.rs b/compiler/rustc_target/src/spec/armv7_linux_androideabi.rs
index 2afd93fcad8..38c117a495e 100644
--- a/compiler/rustc_target/src/spec/armv7_linux_androideabi.rs
+++ b/compiler/rustc_target/src/spec/armv7_linux_androideabi.rs
@@ -10,7 +10,7 @@ use crate::spec::{LinkerFlavor, SanitizerSet, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::android_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-march=armv7-a".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-march=armv7-a"]);
     Target {
         llvm_target: "armv7-none-linux-android".into(),
         pointer_width: 32,
diff --git a/compiler/rustc_target/src/spec/asmjs_unknown_emscripten.rs b/compiler/rustc_target/src/spec/asmjs_unknown_emscripten.rs
index 269bf8b8bcd..b4cf2c5ee22 100644
--- a/compiler/rustc_target/src/spec/asmjs_unknown_emscripten.rs
+++ b/compiler/rustc_target/src/spec/asmjs_unknown_emscripten.rs
@@ -2,10 +2,6 @@ use super::{wasm32_unknown_emscripten, LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut target = wasm32_unknown_emscripten::target();
-    target.post_link_args.entry(LinkerFlavor::Em).or_default().extend(vec![
-        "-sWASM=0".into(),
-        "--memory-init-file".into(),
-        "0".into(),
-    ]);
+    target.add_post_link_args(LinkerFlavor::Em, &["-sWASM=0", "--memory-init-file", "0"]);
     target
 }
diff --git a/compiler/rustc_target/src/spec/avr_gnu_base.rs b/compiler/rustc_target/src/spec/avr_gnu_base.rs
index c288e8b0e9e..4fd6c06394d 100644
--- a/compiler/rustc_target/src/spec/avr_gnu_base.rs
+++ b/compiler/rustc_target/src/spec/avr_gnu_base.rs
@@ -3,7 +3,8 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 /// A base target for AVR devices using the GNU toolchain.
 ///
 /// Requires GNU avr-gcc and avr-binutils on the host system.
-pub fn target(target_cpu: &'static str) -> Target {
+/// FIXME: Remove the second parameter when const string concatenation is possible.
+pub fn target(target_cpu: &'static str, mmcu: &'static str) -> Target {
     Target {
         arch: "avr".into(),
         data_layout: "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8".into(),
@@ -17,10 +18,8 @@ pub fn target(target_cpu: &'static str) -> Target {
             linker: Some("avr-gcc".into()),
             executables: true,
             eh_frame_header: false,
-            pre_link_args: [(LinkerFlavor::Gcc, vec![format!("-mmcu={}", target_cpu).into()])]
-                .into_iter()
-                .collect(),
-            late_link_args: [(LinkerFlavor::Gcc, vec!["-lgcc".into()])].into_iter().collect(),
+            pre_link_args: TargetOptions::link_args(LinkerFlavor::Gcc, &[mmcu]),
+            late_link_args: TargetOptions::link_args(LinkerFlavor::Gcc, &["-lgcc"]),
             max_atomic_width: Some(0),
             atomic_cas: false,
             ..TargetOptions::default()
diff --git a/compiler/rustc_target/src/spec/avr_unknown_gnu_atmega328.rs b/compiler/rustc_target/src/spec/avr_unknown_gnu_atmega328.rs
index 6871ca0f789..6c16b03cc28 100644
--- a/compiler/rustc_target/src/spec/avr_unknown_gnu_atmega328.rs
+++ b/compiler/rustc_target/src/spec/avr_unknown_gnu_atmega328.rs
@@ -1,5 +1,5 @@
 use crate::spec::Target;
 
 pub fn target() -> Target {
-    super::avr_gnu_base::target("atmega328")
+    super::avr_gnu_base::target("atmega328", "-mmcu=atmega328")
 }
diff --git a/compiler/rustc_target/src/spec/fuchsia_base.rs b/compiler/rustc_target/src/spec/fuchsia_base.rs
index b64875e32bd..b02b70f76ee 100644
--- a/compiler/rustc_target/src/spec/fuchsia_base.rs
+++ b/compiler/rustc_target/src/spec/fuchsia_base.rs
@@ -1,23 +1,20 @@
-use crate::spec::{
-    crt_objects, cvs, LinkArgs, LinkOutputKind, LinkerFlavor, LldFlavor, TargetOptions,
-};
+use crate::spec::{crt_objects, cvs, LinkOutputKind, LinkerFlavor, LldFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args.insert(
-        LinkerFlavor::Lld(LldFlavor::Ld),
-        vec![
-            "--build-id".into(),
-            "--hash-style=gnu".into(),
-            "-z".into(),
-            "max-page-size=4096".into(),
-            "-z".into(),
-            "now".into(),
-            "-z".into(),
-            "rodynamic".into(),
-            "-z".into(),
-            "separate-loadable-segments".into(),
-            "--pack-dyn-relocs=relr".into(),
+    let pre_link_args = TargetOptions::link_args(
+        LinkerFlavor::Ld,
+        &[
+            "--build-id",
+            "--hash-style=gnu",
+            "-z",
+            "max-page-size=4096",
+            "-z",
+            "now",
+            "-z",
+            "rodynamic",
+            "-z",
+            "separate-loadable-segments",
+            "--pack-dyn-relocs=relr",
         ],
     );
 
diff --git a/compiler/rustc_target/src/spec/hermit_base.rs b/compiler/rustc_target/src/spec/hermit_base.rs
index 7cbd42417e6..e43153177f0 100644
--- a/compiler/rustc_target/src/spec/hermit_base.rs
+++ b/compiler/rustc_target/src/spec/hermit_base.rs
@@ -1,10 +1,9 @@
-use crate::spec::{LinkArgs, LinkerFlavor, LldFlavor, PanicStrategy, TargetOptions, TlsModel};
+use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, TargetOptions, TlsModel};
 
 pub fn opts() -> TargetOptions {
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args.insert(
-        LinkerFlavor::Lld(LldFlavor::Ld),
-        vec!["--build-id".into(), "--hash-style=gnu".into(), "--Bstatic".into()],
+    let pre_link_args = TargetOptions::link_args(
+        LinkerFlavor::Ld,
+        &["--build-id", "--hash-style=gnu", "--Bstatic"],
     );
 
     TargetOptions {
diff --git a/compiler/rustc_target/src/spec/i686_apple_darwin.rs b/compiler/rustc_target/src/spec/i686_apple_darwin.rs
index ad716a6cd5a..1718bd77b86 100644
--- a/compiler/rustc_target/src/spec/i686_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/i686_apple_darwin.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::apple_base::opts("macos");
     base.cpu = "yonah".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m32".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     base.link_env_remove.to_mut().extend(super::apple_base::macos_link_env_remove());
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
diff --git a/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs b/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs
index 554b0f34499..6318654399c 100644
--- a/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs
+++ b/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs
@@ -1,19 +1,16 @@
-use crate::spec::{FramePointer, LinkerFlavor, LldFlavor, Target};
+use crate::spec::{FramePointer, LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut base = super::windows_gnu_base::opts();
     base.cpu = "pentium4".into();
-    base.pre_link_args.insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["-m".into(), "i386pe".into()]);
     base.max_atomic_width = Some(64);
     base.frame_pointer = FramePointer::Always; // Required for backtraces
     base.linker = Some("i686-w64-mingw32-gcc".into());
 
     // Mark all dynamic libraries and executables as compatible with the larger 4GiB address
     // space available to x86 Windows binaries on x86_64.
-    base.pre_link_args
-        .entry(LinkerFlavor::Gcc)
-        .or_default()
-        .push("-Wl,--large-address-aware".into());
+    base.add_pre_link_args(LinkerFlavor::Ld, &["-m", "i386pe", "--large-address-aware"]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-Wl,--large-address-aware"]);
 
     Target {
         llvm_target: "i686-pc-windows-gnu".into(),
diff --git a/compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs b/compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs
index fb0cb6a6943..f4ceaa1ca4b 100644
--- a/compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs
+++ b/compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs
@@ -1,24 +1,22 @@
-use crate::spec::{LinkerFlavor, LldFlavor, Target};
+use crate::spec::{LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut base = super::windows_msvc_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
 
-    let pre_link_args_msvc = vec![
-        // Mark all dynamic libraries and executables as compatible with the larger 4GiB address
-        // space available to x86 Windows binaries on x86_64.
-        "/LARGEADDRESSAWARE".into(),
-        // Ensure the linker will only produce an image if it can also produce a table of
-        // the image's safe exception handlers.
-        // https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers
-        "/SAFESEH".into(),
-    ];
-    base.pre_link_args.entry(LinkerFlavor::Msvc).or_default().extend(pre_link_args_msvc.clone());
-    base.pre_link_args
-        .entry(LinkerFlavor::Lld(LldFlavor::Link))
-        .or_default()
-        .extend(pre_link_args_msvc);
+    base.add_pre_link_args(
+        LinkerFlavor::Msvc,
+        &[
+            // Mark all dynamic libraries and executables as compatible with the larger 4GiB address
+            // space available to x86 Windows binaries on x86_64.
+            "/LARGEADDRESSAWARE",
+            // Ensure the linker will only produce an image if it can also produce a table of
+            // the image's safe exception handlers.
+            // https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers
+            "/SAFESEH",
+        ],
+    );
     // Workaround for #95429
     base.has_thread_local = false;
 
diff --git a/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs b/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
index 9f0cb04c65d..aff284bf2bc 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
@@ -4,9 +4,7 @@ pub fn target() -> Target {
     let mut base = super::freebsd_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    let pre_link_args = base.pre_link_args.entry(LinkerFlavor::Gcc).or_default();
-    pre_link_args.push("-m32".into());
-    pre_link_args.push("-Wl,-znotext".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "-Wl,-znotext"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/i686_unknown_haiku.rs b/compiler/rustc_target/src/spec/i686_unknown_haiku.rs
index d1af163f1cf..87aa74e406c 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_haiku.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_haiku.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::haiku_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m32".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
index 0998c618f31..765803d1692 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::linux_gnu_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
index a697f292da0..d9492804349 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
@@ -4,8 +4,7 @@ pub fn target() -> Target {
     let mut base = super::linux_musl_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-Wl,-melf_i386".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "-Wl,-melf_i386"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs b/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
index 2807d328205..8de698b51f0 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::netbsd_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs b/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
index 78462eb63b8..7f25a1a16c1 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
@@ -4,8 +4,7 @@ pub fn target() -> Target {
     let mut base = super::openbsd_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-fuse-ld=lld".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "-fuse-ld=lld"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs b/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs
index 75f7a2209c8..d52810d2fb0 100644
--- a/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs
+++ b/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs
@@ -1,18 +1,15 @@
-use crate::spec::{FramePointer, LinkerFlavor, LldFlavor, Target};
+use crate::spec::{FramePointer, LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut base = super::windows_uwp_gnu_base::opts();
     base.cpu = "pentium4".into();
-    base.pre_link_args.insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["-m".into(), "i386pe".into()]);
     base.max_atomic_width = Some(64);
     base.frame_pointer = FramePointer::Always; // Required for backtraces
 
     // Mark all dynamic libraries and executables as compatible with the larger 4GiB address
     // space available to x86 Windows binaries on x86_64.
-    base.pre_link_args
-        .entry(LinkerFlavor::Gcc)
-        .or_default()
-        .push("-Wl,--large-address-aware".into());
+    base.add_pre_link_args(LinkerFlavor::Ld, &["-m", "i386pe", "--large-address-aware"]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-Wl,--large-address-aware"]);
 
     Target {
         llvm_target: "i686-pc-windows-gnu".into(),
diff --git a/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs b/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
index d51ed7c1f7a..f62404e8279 100644
--- a/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
+++ b/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::vxworks_base::opts();
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/illumos_base.rs b/compiler/rustc_target/src/spec/illumos_base.rs
index ef8f90a4da8..b0e1b109be1 100644
--- a/compiler/rustc_target/src/spec/illumos_base.rs
+++ b/compiler/rustc_target/src/spec/illumos_base.rs
@@ -1,10 +1,9 @@
-use crate::spec::{cvs, FramePointer, LinkArgs, LinkerFlavor, TargetOptions};
+use crate::spec::{cvs, FramePointer, LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
-    let mut late_link_args = LinkArgs::new();
-    late_link_args.insert(
+    let late_link_args = TargetOptions::link_args(
         LinkerFlavor::Gcc,
-        vec![
+        &[
             // The illumos libc contains a stack unwinding implementation, as
             // does libgcc_s.  The latter implementation includes several
             // additional symbols that are not always in base libc.  To force
@@ -15,13 +14,13 @@ pub fn opts() -> TargetOptions {
             // FIXME: This should be replaced by a more complete and generic
             // mechanism for controlling the order of library arguments passed
             // to the linker.
-            "-lc".into(),
+            "-lc",
             // LLVM will insert calls to the stack protector functions
             // "__stack_chk_fail" and "__stack_chk_guard" into code in native
             // object files.  Some platforms include these symbols directly in
             // libc, but at least historically these have been provided in
             // libssp.so on illumos and Solaris systems.
-            "-lssp".into(),
+            "-lssp",
         ],
     );
 
diff --git a/compiler/rustc_target/src/spec/mipsel_sony_psp.rs b/compiler/rustc_target/src/spec/mipsel_sony_psp.rs
index 03e0934ea5e..e3522de6de0 100644
--- a/compiler/rustc_target/src/spec/mipsel_sony_psp.rs
+++ b/compiler/rustc_target/src/spec/mipsel_sony_psp.rs
@@ -1,13 +1,11 @@
 use crate::spec::{cvs, Target, TargetOptions};
-use crate::spec::{LinkArgs, LinkerFlavor, LldFlavor, RelocModel};
+use crate::spec::{LinkerFlavor, LldFlavor, RelocModel};
 
 // The PSP has custom linker requirements.
 const LINKER_SCRIPT: &str = include_str!("./mipsel_sony_psp_linker_script.ld");
 
 pub fn target() -> Target {
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args
-        .insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["--emit-relocs".into(), "--nmagic".into()]);
+    let pre_link_args = TargetOptions::link_args(LinkerFlavor::Ld, &["--emit-relocs", "--nmagic"]);
 
     Target {
         llvm_target: "mipsel-sony-psp".into(),
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index da0589cdd20..a08603da040 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -1459,6 +1459,44 @@ pub struct TargetOptions {
     pub supports_stack_protector: bool,
 }
 
+/// Add arguments for the given flavor and also for its "twin" flavors
+/// that have a compatible command line interface.
+fn add_link_args(link_args: &mut LinkArgs, flavor: LinkerFlavor, args: &[&'static str]) {
+    let mut insert = |flavor| {
+        link_args.entry(flavor).or_default().extend(args.iter().copied().map(Cow::Borrowed))
+    };
+    insert(flavor);
+    match flavor {
+        LinkerFlavor::Ld => insert(LinkerFlavor::Lld(LldFlavor::Ld)),
+        LinkerFlavor::Msvc => insert(LinkerFlavor::Lld(LldFlavor::Link)),
+        LinkerFlavor::Lld(LldFlavor::Wasm) => {}
+        LinkerFlavor::Lld(lld_flavor) => {
+            panic!("add_link_args: use non-LLD flavor for {:?}", lld_flavor)
+        }
+        LinkerFlavor::Gcc
+        | LinkerFlavor::Em
+        | LinkerFlavor::L4Bender
+        | LinkerFlavor::BpfLinker
+        | LinkerFlavor::PtxLinker => {}
+    }
+}
+
+impl TargetOptions {
+    fn link_args(flavor: LinkerFlavor, args: &[&'static str]) -> LinkArgs {
+        let mut link_args = LinkArgs::new();
+        add_link_args(&mut link_args, flavor, args);
+        link_args
+    }
+
+    fn add_pre_link_args(&mut self, flavor: LinkerFlavor, args: &[&'static str]) {
+        add_link_args(&mut self.pre_link_args, flavor, args);
+    }
+
+    fn add_post_link_args(&mut self, flavor: LinkerFlavor, args: &[&'static str]) {
+        add_link_args(&mut self.post_link_args, flavor, args);
+    }
+}
+
 impl Default for TargetOptions {
     /// Creates a set of "sane defaults" for any target. This is still
     /// incomplete, and if used for compilation, will certainly not work.
diff --git a/compiler/rustc_target/src/spec/msvc_base.rs b/compiler/rustc_target/src/spec/msvc_base.rs
index 00cc9620243..c4df4b546e3 100644
--- a/compiler/rustc_target/src/spec/msvc_base.rs
+++ b/compiler/rustc_target/src/spec/msvc_base.rs
@@ -1,14 +1,9 @@
-use crate::spec::{LinkArgs, LinkerFlavor, LldFlavor, SplitDebuginfo, TargetOptions};
+use crate::spec::{LinkerFlavor, LldFlavor, SplitDebuginfo, TargetOptions};
 
 pub fn opts() -> TargetOptions {
-    let pre_link_args_msvc = vec![
-        // Suppress the verbose logo and authorship debugging output, which would needlessly
-        // clog any log files.
-        "/NOLOGO".into(),
-    ];
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args.insert(LinkerFlavor::Msvc, pre_link_args_msvc.clone());
-    pre_link_args.insert(LinkerFlavor::Lld(LldFlavor::Link), pre_link_args_msvc);
+    // Suppress the verbose logo and authorship debugging output, which would needlessly
+    // clog any log files.
+    let pre_link_args = TargetOptions::link_args(LinkerFlavor::Msvc, &["/NOLOGO"]);
 
     TargetOptions {
         linker_flavor: LinkerFlavor::Msvc,
diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs
index 595769c4bfa..803453c4ac4 100644
--- a/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs
@@ -4,7 +4,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::freebsd_base::opts();
     base.cpu = "ppc64".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs
index 24d5d187e1a..5413c4f33ff 100644
--- a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs
@@ -4,7 +4,7 @@ use crate::spec::{LinkerFlavor, RelroLevel, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::linux_gnu_base::opts();
     base.cpu = "ppc64".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     // ld.so in at least RHEL6 on ppc64 has a bug related to BIND_NOW, so only enable partial RELRO
diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs
index 0f465ccfe77..159335eb607 100644
--- a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs
@@ -4,7 +4,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::linux_musl_base::opts();
     base.cpu = "ppc64".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs b/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs
index 491d344aedb..b7420d232ca 100644
--- a/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs
+++ b/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs
@@ -4,7 +4,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::vxworks_base::opts();
     base.cpu = "ppc64".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs b/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs
index b198e667ccc..a3d18004371 100644
--- a/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::freebsd_base::opts();
     base.cpu = "ppc64le".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs
index 09e3936db26..e18ff3be448 100644
--- a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::linux_gnu_base::opts();
     base.cpu = "ppc64le".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs
index 8a947b091cb..b84943d23a9 100644
--- a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::linux_musl_base::opts();
     base.cpu = "ppc64le".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs b/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs
index c27b84775df..516b2de37ea 100644
--- a/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs
@@ -3,12 +3,8 @@ use crate::spec::{LinkerFlavor, RelocModel, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::freebsd_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
     // Extra hint to linker that we are generating secure-PLT code.
-    base.pre_link_args
-        .entry(LinkerFlavor::Gcc)
-        .or_default()
-        .push("--target=powerpc-unknown-freebsd13.0".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "--target=powerpc-unknown-freebsd13.0"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs
index 88f61500e3c..6686a0bbf04 100644
--- a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::linux_gnu_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs
index 3ee548750b9..6a250f4b51c 100644
--- a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs
+++ b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::linux_gnu_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-mspe".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-mspe"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs
index ce33c787f33..34200c67906 100644
--- a/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::linux_musl_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs b/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs
index 998225f4dae..60661ef9b5d 100644
--- a/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs
@@ -3,7 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::netbsd_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs
index 76709cec591..3f24966e06e 100644
--- a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs
+++ b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs
@@ -3,8 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::vxworks_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m32".into());
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("--secure-plt".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "--secure-plt"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs
index 7b5d1242c52..0f04f41f9e5 100644
--- a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs
+++ b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs
@@ -3,8 +3,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::vxworks_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-mspe".into());
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("--secure-plt".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-mspe", "--secure-plt"]);
     base.max_atomic_width = Some(32);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/sparc64_unknown_netbsd.rs b/compiler/rustc_target/src/spec/sparc64_unknown_netbsd.rs
index 718303a4b4d..836ab0e3728 100644
--- a/compiler/rustc_target/src/spec/sparc64_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/sparc64_unknown_netbsd.rs
@@ -4,7 +4,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::netbsd_base::opts();
     base.cpu = "v9".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/sparc64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/sparc64_unknown_openbsd.rs
index 2aaa0ca6df8..4a192df392f 100644
--- a/compiler/rustc_target/src/spec/sparc64_unknown_openbsd.rs
+++ b/compiler/rustc_target/src/spec/sparc64_unknown_openbsd.rs
@@ -5,7 +5,7 @@ pub fn target() -> Target {
     let mut base = super::openbsd_base::opts();
     base.endian = Endian::Big;
     base.cpu = "v9".into();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs
index 71d3de0bfd1..ea4fafa4b06 100644
--- a/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs
@@ -6,7 +6,7 @@ pub fn target() -> Target {
     base.endian = Endian::Big;
     base.cpu = "v9".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-mv8plus".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-mv8plus"]);
 
     Target {
         llvm_target: "sparc-unknown-linux-gnu".into(),
diff --git a/compiler/rustc_target/src/spec/sparcv9_sun_solaris.rs b/compiler/rustc_target/src/spec/sparcv9_sun_solaris.rs
index 79ae54aa666..aac09181a74 100644
--- a/compiler/rustc_target/src/spec/sparcv9_sun_solaris.rs
+++ b/compiler/rustc_target/src/spec/sparcv9_sun_solaris.rs
@@ -4,7 +4,7 @@ use crate::spec::{LinkerFlavor, Target};
 pub fn target() -> Target {
     let mut base = super::solaris_base::opts();
     base.endian = Endian::Big;
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m64".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // llvm calls this "v9"
     base.cpu = "v9".into();
     base.vendor = "sun".into();
diff --git a/compiler/rustc_target/src/spec/tests/tests_impl.rs b/compiler/rustc_target/src/spec/tests/tests_impl.rs
index 0865ca7ea7d..c7c5a231901 100644
--- a/compiler/rustc_target/src/spec/tests/tests_impl.rs
+++ b/compiler/rustc_target/src/spec/tests/tests_impl.rs
@@ -1,4 +1,5 @@
 use super::super::*;
+use std::assert_matches::assert_matches;
 
 // Test target self-consistency and JSON encoding/decoding roundtrip.
 pub(super) fn test_target(target: Target) {
@@ -14,35 +15,105 @@ impl Target {
         assert_eq!(self.is_like_wasm, self.arch == "wasm32" || self.arch == "wasm64");
         assert!(self.is_like_windows || !self.is_like_msvc);
 
-        // Check that LLD with the given flavor is treated identically to the linker it emulates.
-        // If your target really needs to deviate from the rules below, except it and document the
-        // reasons.
-        assert_eq!(
-            self.linker_flavor == LinkerFlavor::Msvc
-                || self.linker_flavor == LinkerFlavor::Lld(LldFlavor::Link),
-            self.lld_flavor == LldFlavor::Link,
-        );
-        assert_eq!(self.is_like_msvc, self.lld_flavor == LldFlavor::Link);
-        for args in &[
+        // Check that default linker flavor and lld flavor are compatible
+        // with some other key properties.
+        assert_eq!(self.is_like_osx, matches!(self.lld_flavor, LldFlavor::Ld64));
+        assert_eq!(self.is_like_msvc, matches!(self.lld_flavor, LldFlavor::Link));
+        assert_eq!(self.is_like_wasm, matches!(self.lld_flavor, LldFlavor::Wasm));
+        assert_eq!(self.os == "l4re", matches!(self.linker_flavor, LinkerFlavor::L4Bender));
+        assert_eq!(self.os == "emscripten", matches!(self.linker_flavor, LinkerFlavor::Em));
+        assert_eq!(self.arch == "bpf", matches!(self.linker_flavor, LinkerFlavor::BpfLinker));
+        assert_eq!(self.arch == "nvptx64", matches!(self.linker_flavor, LinkerFlavor::PtxLinker));
+
+        for args in [
             &self.pre_link_args,
             &self.late_link_args,
             &self.late_link_args_dynamic,
             &self.late_link_args_static,
             &self.post_link_args,
         ] {
+            for (&flavor, flavor_args) in args {
+                assert!(!flavor_args.is_empty());
+                // Check that flavors mentioned in link args are compatible with the default flavor.
+                match (self.linker_flavor, self.lld_flavor) {
+                    (
+                        LinkerFlavor::Ld | LinkerFlavor::Lld(LldFlavor::Ld) | LinkerFlavor::Gcc,
+                        LldFlavor::Ld,
+                    ) => {
+                        assert_matches!(
+                            flavor,
+                            LinkerFlavor::Ld | LinkerFlavor::Lld(LldFlavor::Ld) | LinkerFlavor::Gcc
+                        )
+                    }
+                    (LinkerFlavor::Gcc, LldFlavor::Ld64) => {
+                        assert_matches!(flavor, LinkerFlavor::Gcc)
+                    }
+                    (LinkerFlavor::Msvc | LinkerFlavor::Lld(LldFlavor::Link), LldFlavor::Link) => {
+                        assert_matches!(
+                            flavor,
+                            LinkerFlavor::Msvc | LinkerFlavor::Lld(LldFlavor::Link)
+                        )
+                    }
+                    (LinkerFlavor::Lld(LldFlavor::Wasm) | LinkerFlavor::Gcc, LldFlavor::Wasm) => {
+                        assert_matches!(
+                            flavor,
+                            LinkerFlavor::Lld(LldFlavor::Wasm) | LinkerFlavor::Gcc
+                        )
+                    }
+                    (LinkerFlavor::L4Bender, LldFlavor::Ld) => {
+                        assert_matches!(flavor, LinkerFlavor::L4Bender)
+                    }
+                    (LinkerFlavor::Em, LldFlavor::Wasm) => {
+                        assert_matches!(flavor, LinkerFlavor::Em)
+                    }
+                    (LinkerFlavor::BpfLinker, LldFlavor::Ld) => {
+                        assert_matches!(flavor, LinkerFlavor::BpfLinker)
+                    }
+                    (LinkerFlavor::PtxLinker, LldFlavor::Ld) => {
+                        assert_matches!(flavor, LinkerFlavor::PtxLinker)
+                    }
+                    flavors => unreachable!("unexpected flavor combination: {:?}", flavors),
+                }
+
+                // Check that link args for cc and non-cc versions of flavors are consistent.
+                let check_noncc = |noncc_flavor| {
+                    if let Some(noncc_args) = args.get(&noncc_flavor) {
+                        for arg in flavor_args {
+                            if let Some(suffix) = arg.strip_prefix("-Wl,") {
+                                assert!(noncc_args.iter().any(|a| a == suffix));
+                            }
+                        }
+                    }
+                };
+                match self.linker_flavor {
+                    LinkerFlavor::Gcc => match self.lld_flavor {
+                        LldFlavor::Ld => {
+                            check_noncc(LinkerFlavor::Ld);
+                            check_noncc(LinkerFlavor::Lld(LldFlavor::Ld));
+                        }
+                        LldFlavor::Wasm => check_noncc(LinkerFlavor::Lld(LldFlavor::Wasm)),
+                        LldFlavor::Ld64 | LldFlavor::Link => {}
+                    },
+                    _ => {}
+                }
+            }
+
+            // Check that link args for lld and non-lld versions of flavors are consistent.
+            assert_eq!(args.get(&LinkerFlavor::Ld), args.get(&LinkerFlavor::Lld(LldFlavor::Ld)));
             assert_eq!(
                 args.get(&LinkerFlavor::Msvc),
                 args.get(&LinkerFlavor::Lld(LldFlavor::Link)),
             );
-            if args.contains_key(&LinkerFlavor::Msvc) {
-                assert_eq!(self.lld_flavor, LldFlavor::Link);
-            }
         }
+
         assert!(
             (self.pre_link_objects_fallback.is_empty()
                 && self.post_link_objects_fallback.is_empty())
                 || self.crt_objects_fallback.is_some()
         );
+
+        // If your target really needs to deviate from the rules below,
+        // except it and document the reasons.
         // Keep the default "unknown" vendor instead.
         assert_ne!(self.vendor, "");
         if !self.can_use_os_unknown() {
diff --git a/compiler/rustc_target/src/spec/thumbv7a_pc_windows_msvc.rs b/compiler/rustc_target/src/spec/thumbv7a_pc_windows_msvc.rs
index f6cbbd38cf4..4d09d3a4d10 100644
--- a/compiler/rustc_target/src/spec/thumbv7a_pc_windows_msvc.rs
+++ b/compiler/rustc_target/src/spec/thumbv7a_pc_windows_msvc.rs
@@ -1,4 +1,4 @@
-use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, Target, TargetOptions};
+use crate::spec::{LinkerFlavor, PanicStrategy, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::windows_msvc_base::opts();
@@ -9,12 +9,7 @@ pub fn target() -> Target {
     // should be smart enough to insert branch islands only
     // where necessary, but this is not the observed behavior.
     // Disabling the LBR optimization works around the issue.
-    let pre_link_args_msvc = "/OPT:NOLBR";
-    base.pre_link_args.entry(LinkerFlavor::Msvc).or_default().push(pre_link_args_msvc.into());
-    base.pre_link_args
-        .entry(LinkerFlavor::Lld(LldFlavor::Link))
-        .or_default()
-        .push(pre_link_args_msvc.into());
+    base.add_pre_link_args(LinkerFlavor::Msvc, &["/OPT:NOLBR"]);
 
     Target {
         llvm_target: "thumbv7a-pc-windows-msvc".into(),
diff --git a/compiler/rustc_target/src/spec/thumbv7neon_linux_androideabi.rs b/compiler/rustc_target/src/spec/thumbv7neon_linux_androideabi.rs
index 9a3e8b5c5f8..4cad9e18370 100644
--- a/compiler/rustc_target/src/spec/thumbv7neon_linux_androideabi.rs
+++ b/compiler/rustc_target/src/spec/thumbv7neon_linux_androideabi.rs
@@ -10,7 +10,7 @@ use crate::spec::{LinkerFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
     let mut base = super::android_base::opts();
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-march=armv7-a".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-march=armv7-a"]);
     Target {
         llvm_target: "armv7-none-linux-android".into(),
         pointer_width: 32,
diff --git a/compiler/rustc_target/src/spec/uefi_msvc_base.rs b/compiler/rustc_target/src/spec/uefi_msvc_base.rs
index bc7244b3a45..aee8eb2e31c 100644
--- a/compiler/rustc_target/src/spec/uefi_msvc_base.rs
+++ b/compiler/rustc_target/src/spec/uefi_msvc_base.rs
@@ -14,27 +14,25 @@ use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, StackProbeType, Target
 pub fn opts() -> TargetOptions {
     let mut base = super::msvc_base::opts();
 
-    let pre_link_args_msvc = vec![
-        // Non-standard subsystems have no default entry-point in PE+ files. We have to define
-        // one. "efi_main" seems to be a common choice amongst other implementations and the
-        // spec.
-        "/entry:efi_main".into(),
-        // COFF images have a "Subsystem" field in their header, which defines what kind of
-        // program it is. UEFI has 3 fields reserved, which are EFI_APPLICATION,
-        // EFI_BOOT_SERVICE_DRIVER, and EFI_RUNTIME_DRIVER. We default to EFI_APPLICATION,
-        // which is very likely the most common option. Individual projects can override this
-        // with custom linker flags.
-        // The subsystem-type only has minor effects on the application. It defines the memory
-        // regions the application is loaded into (runtime-drivers need to be put into
-        // reserved areas), as well as whether a return from the entry-point is treated as
-        // exit (default for applications).
-        "/subsystem:efi_application".into(),
-    ];
-    base.pre_link_args.entry(LinkerFlavor::Msvc).or_default().extend(pre_link_args_msvc.clone());
-    base.pre_link_args
-        .entry(LinkerFlavor::Lld(LldFlavor::Link))
-        .or_default()
-        .extend(pre_link_args_msvc);
+    base.add_pre_link_args(
+        LinkerFlavor::Msvc,
+        &[
+            // Non-standard subsystems have no default entry-point in PE+ files. We have to define
+            // one. "efi_main" seems to be a common choice amongst other implementations and the
+            // spec.
+            "/entry:efi_main",
+            // COFF images have a "Subsystem" field in their header, which defines what kind of
+            // program it is. UEFI has 3 fields reserved, which are EFI_APPLICATION,
+            // EFI_BOOT_SERVICE_DRIVER, and EFI_RUNTIME_DRIVER. We default to EFI_APPLICATION,
+            // which is very likely the most common option. Individual projects can override this
+            // with custom linker flags.
+            // The subsystem-type only has minor effects on the application. It defines the memory
+            // regions the application is loaded into (runtime-drivers need to be put into
+            // reserved areas), as well as whether a return from the entry-point is treated as
+            // exit (default for applications).
+            "/subsystem:efi_application",
+        ],
+    );
 
     TargetOptions {
         os: "uefi".into(),
diff --git a/compiler/rustc_target/src/spec/wasm32_unknown_emscripten.rs b/compiler/rustc_target/src/spec/wasm32_unknown_emscripten.rs
index f1087db09d1..c7e7d221086 100644
--- a/compiler/rustc_target/src/spec/wasm32_unknown_emscripten.rs
+++ b/compiler/rustc_target/src/spec/wasm32_unknown_emscripten.rs
@@ -1,22 +1,12 @@
 use super::{cvs, wasm_base};
-use super::{LinkArgs, LinkerFlavor, PanicStrategy, Target, TargetOptions};
+use super::{LinkArgs, LinkerFlavor, PanicStrategy, RelocModel, Target, TargetOptions};
 
 pub fn target() -> Target {
-    let mut options = wasm_base::options();
-
-    let clang_args = options.pre_link_args.entry(LinkerFlavor::Gcc).or_default();
-
-    // Rust really needs a way for users to specify exports and imports in
-    // the source code. --export-dynamic isn't the right tool for this job,
-    // however it does have the side effect of automatically exporting a lot
-    // of symbols, which approximates what people want when compiling for
-    // wasm32-unknown-unknown expect, so use it for now.
-    clang_args.push("--export-dynamic".into());
-
-    let mut post_link_args = LinkArgs::new();
-    post_link_args.insert(
+    // Reset flags for non-Em flavors back to empty to satisfy sanity checking tests.
+    let pre_link_args = LinkArgs::new();
+    let post_link_args = TargetOptions::link_args(
         LinkerFlavor::Em,
-        vec!["-sABORTING_MALLOC=0".into(), "-Wl,--fatal-warnings".into()],
+        &["-sABORTING_MALLOC=0", "-Wl,--fatal-warnings"],
     );
 
     let opts = TargetOptions {
@@ -26,11 +16,13 @@ pub fn target() -> Target {
         // functionality, and a .wasm file.
         exe_suffix: ".js".into(),
         linker: None,
+        pre_link_args,
+        post_link_args,
+        relocation_model: RelocModel::Pic,
         panic_strategy: PanicStrategy::Unwind,
         no_default_libraries: false,
-        post_link_args,
         families: cvs!["unix", "wasm"],
-        ..options
+        ..wasm_base::options()
     };
     Target {
         llvm_target: "wasm32-unknown-emscripten".into(),
diff --git a/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs b/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs
index 214b5fce5a6..4e2927dd913 100644
--- a/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs
+++ b/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs
@@ -29,27 +29,30 @@ pub fn target() -> Target {
     // code on this target due to this ABI mismatch.
     options.default_adjusted_cabi = Some(Abi::Wasm);
 
-    let clang_args = options.pre_link_args.entry(LinkerFlavor::Gcc).or_default();
-
-    // Make sure clang uses LLD as its linker and is configured appropriately
-    // otherwise
-    clang_args.push("--target=wasm32-unknown-unknown".into());
-
-    // For now this target just never has an entry symbol no matter the output
-    // type, so unconditionally pass this.
-    clang_args.push("-Wl,--no-entry".into());
-
-    // Rust really needs a way for users to specify exports and imports in
-    // the source code. --export-dynamic isn't the right tool for this job,
-    // however it does have the side effect of automatically exporting a lot
-    // of symbols, which approximates what people want when compiling for
-    // wasm32-unknown-unknown expect, so use it for now.
-    clang_args.push("-Wl,--export-dynamic".into());
-
-    // Add the flags to wasm-ld's args too.
-    let lld_args = options.pre_link_args.entry(LinkerFlavor::Lld(LldFlavor::Wasm)).or_default();
-    lld_args.push("--no-entry".into());
-    lld_args.push("--export-dynamic".into());
+    options.add_pre_link_args(
+        LinkerFlavor::Lld(LldFlavor::Wasm),
+        &[
+            // For now this target just never has an entry symbol no matter the output
+            // type, so unconditionally pass this.
+            "--no-entry",
+            // Rust really needs a way for users to specify exports and imports in
+            // the source code. --export-dynamic isn't the right tool for this job,
+            // however it does have the side effect of automatically exporting a lot
+            // of symbols, which approximates what people want when compiling for
+            // wasm32-unknown-unknown expect, so use it for now.
+            "--export-dynamic",
+        ],
+    );
+    options.add_pre_link_args(
+        LinkerFlavor::Gcc,
+        &[
+            // Make sure clang uses LLD as its linker and is configured appropriately
+            // otherwise
+            "--target=wasm32-unknown-unknown",
+            "-Wl,--no-entry",
+            "-Wl,--export-dynamic",
+        ],
+    );
 
     Target {
         llvm_target: "wasm32-unknown-unknown".into(),
diff --git a/compiler/rustc_target/src/spec/wasm32_wasi.rs b/compiler/rustc_target/src/spec/wasm32_wasi.rs
index 10eb78e4e25..280457d68b9 100644
--- a/compiler/rustc_target/src/spec/wasm32_wasi.rs
+++ b/compiler/rustc_target/src/spec/wasm32_wasi.rs
@@ -80,11 +80,7 @@ pub fn target() -> Target {
 
     options.os = "wasi".into();
     options.linker_flavor = LinkerFlavor::Lld(LldFlavor::Wasm);
-    options
-        .pre_link_args
-        .entry(LinkerFlavor::Gcc)
-        .or_insert(Vec::new())
-        .push("--target=wasm32-wasi".into());
+    options.add_pre_link_args(LinkerFlavor::Gcc, &["--target=wasm32-wasi"]);
 
     options.pre_link_objects_fallback = crt_objects::pre_wasi_fallback();
     options.post_link_objects_fallback = crt_objects::post_wasi_fallback();
diff --git a/compiler/rustc_target/src/spec/wasm64_unknown_unknown.rs b/compiler/rustc_target/src/spec/wasm64_unknown_unknown.rs
index 6826c0ac62b..5211f7707fb 100644
--- a/compiler/rustc_target/src/spec/wasm64_unknown_unknown.rs
+++ b/compiler/rustc_target/src/spec/wasm64_unknown_unknown.rs
@@ -14,19 +14,25 @@ pub fn target() -> Target {
     let mut options = wasm_base::options();
     options.os = "unknown".into();
     options.linker_flavor = LinkerFlavor::Lld(LldFlavor::Wasm);
-    let clang_args = options.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap();
 
-    // Make sure clang uses LLD as its linker and is configured appropriately
-    // otherwise
-    clang_args.push("--target=wasm64-unknown-unknown".into());
-
-    // For now this target just never has an entry symbol no matter the output
-    // type, so unconditionally pass this.
-    clang_args.push("-Wl,--no-entry".into());
-
-    let lld_args = options.pre_link_args.get_mut(&LinkerFlavor::Lld(LldFlavor::Wasm)).unwrap();
-    lld_args.push("--no-entry".into());
-    lld_args.push("-mwasm64".into());
+    options.add_pre_link_args(
+        LinkerFlavor::Lld(LldFlavor::Wasm),
+        &[
+            // For now this target just never has an entry symbol no matter the output
+            // type, so unconditionally pass this.
+            "--no-entry",
+            "-mwasm64",
+        ],
+    );
+    options.add_pre_link_args(
+        LinkerFlavor::Gcc,
+        &[
+            // Make sure clang uses LLD as its linker and is configured appropriately
+            // otherwise
+            "--target=wasm64-unknown-unknown",
+            "-Wl,--no-entry",
+        ],
+    );
 
     // Any engine that implements wasm64 will surely implement the rest of these
     // features since they were all merged into the official spec by the time
diff --git a/compiler/rustc_target/src/spec/wasm_base.rs b/compiler/rustc_target/src/spec/wasm_base.rs
index de7b7374af3..5736402ae14 100644
--- a/compiler/rustc_target/src/spec/wasm_base.rs
+++ b/compiler/rustc_target/src/spec/wasm_base.rs
@@ -1,63 +1,56 @@
 use super::crt_objects::CrtObjectsFallback;
 use super::{cvs, LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, TargetOptions, TlsModel};
-use std::collections::BTreeMap;
 
 pub fn options() -> TargetOptions {
-    let mut lld_args = Vec::new();
-    let mut clang_args = Vec::new();
-    let mut arg = |arg: &'static str| {
-        lld_args.push(arg.into());
-        clang_args.push(format!("-Wl,{}", arg).into());
-    };
-
-    // By default LLD only gives us one page of stack (64k) which is a
-    // little small. Default to a larger stack closer to other PC platforms
-    // (1MB) and users can always inject their own link-args to override this.
-    arg("-z");
-    arg("stack-size=1048576");
-
-    // By default LLD's memory layout is:
-    //
-    // 1. First, a blank page
-    // 2. Next, all static data
-    // 3. Finally, the main stack (which grows down)
-    //
-    // This has the unfortunate consequence that on stack overflows you
-    // corrupt static data and can cause some exceedingly weird bugs. To
-    // help detect this a little sooner we instead request that the stack is
-    // placed before static data.
-    //
-    // This means that we'll generate slightly larger binaries as references
-    // to static data will take more bytes in the ULEB128 encoding, but
-    // stack overflow will be guaranteed to trap as it underflows instead of
-    // corrupting static data.
-    arg("--stack-first");
-
-    // FIXME we probably shouldn't pass this but instead pass an explicit list
-    // of symbols we'll allow to be undefined. We don't currently have a
-    // mechanism of knowing, however, which symbols are intended to be imported
-    // from the environment and which are intended to be imported from other
-    // objects linked elsewhere. This is a coarse approximation but is sure to
-    // hide some bugs and frustrate someone at some point, so we should ideally
-    // work towards a world where we can explicitly list symbols that are
-    // supposed to be imported and have all other symbols generate errors if
-    // they remain undefined.
-    arg("--allow-undefined");
-
-    // Rust code should never have warnings, and warnings are often
-    // indicative of bugs, let's prevent them.
-    arg("--fatal-warnings");
-
-    // LLD only implements C++-like demangling, which doesn't match our own
-    // mangling scheme. Tell LLD to not demangle anything and leave it up to
-    // us to demangle these symbols later. Currently rustc does not perform
-    // further demangling, but tools like twiggy and wasm-bindgen are intended
-    // to do so.
-    arg("--no-demangle");
-
-    let mut pre_link_args = BTreeMap::new();
-    pre_link_args.insert(LinkerFlavor::Lld(LldFlavor::Wasm), lld_args);
-    pre_link_args.insert(LinkerFlavor::Gcc, clang_args);
+    macro_rules! args {
+        ($prefix:literal) => {
+            &[
+                // By default LLD only gives us one page of stack (64k) which is a
+                // little small. Default to a larger stack closer to other PC platforms
+                // (1MB) and users can always inject their own link-args to override this.
+                concat!($prefix, "-z"),
+                concat!($prefix, "stack-size=1048576"),
+                // By default LLD's memory layout is:
+                //
+                // 1. First, a blank page
+                // 2. Next, all static data
+                // 3. Finally, the main stack (which grows down)
+                //
+                // This has the unfortunate consequence that on stack overflows you
+                // corrupt static data and can cause some exceedingly weird bugs. To
+                // help detect this a little sooner we instead request that the stack is
+                // placed before static data.
+                //
+                // This means that we'll generate slightly larger binaries as references
+                // to static data will take more bytes in the ULEB128 encoding, but
+                // stack overflow will be guaranteed to trap as it underflows instead of
+                // corrupting static data.
+                concat!($prefix, "--stack-first"),
+                // FIXME we probably shouldn't pass this but instead pass an explicit list
+                // of symbols we'll allow to be undefined. We don't currently have a
+                // mechanism of knowing, however, which symbols are intended to be imported
+                // from the environment and which are intended to be imported from other
+                // objects linked elsewhere. This is a coarse approximation but is sure to
+                // hide some bugs and frustrate someone at some point, so we should ideally
+                // work towards a world where we can explicitly list symbols that are
+                // supposed to be imported and have all other symbols generate errors if
+                // they remain undefined.
+                concat!($prefix, "--allow-undefined"),
+                // Rust code should never have warnings, and warnings are often
+                // indicative of bugs, let's prevent them.
+                concat!($prefix, "--fatal-warnings"),
+                // LLD only implements C++-like demangling, which doesn't match our own
+                // mangling scheme. Tell LLD to not demangle anything and leave it up to
+                // us to demangle these symbols later. Currently rustc does not perform
+                // further demangling, but tools like twiggy and wasm-bindgen are intended
+                // to do so.
+                concat!($prefix, "--no-demangle"),
+            ]
+        };
+    }
+
+    let mut pre_link_args = TargetOptions::link_args(LinkerFlavor::Lld(LldFlavor::Wasm), args!(""));
+    super::add_link_args(&mut pre_link_args, LinkerFlavor::Gcc, args!("-Wl,"));
 
     TargetOptions {
         is_like_wasm: true,
diff --git a/compiler/rustc_target/src/spec/windows_gnu_base.rs b/compiler/rustc_target/src/spec/windows_gnu_base.rs
index d11f1f7d3f8..a0480f386f7 100644
--- a/compiler/rustc_target/src/spec/windows_gnu_base.rs
+++ b/compiler/rustc_target/src/spec/windows_gnu_base.rs
@@ -1,31 +1,35 @@
 use crate::spec::crt_objects::{self, CrtObjectsFallback};
-use crate::spec::{cvs, LinkArgs, LinkerFlavor, LldFlavor, TargetOptions};
+use crate::spec::{cvs, LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args.insert(
+    let mut pre_link_args = TargetOptions::link_args(
+        LinkerFlavor::Ld,
+        &[
+            // Enable ASLR
+            "--dynamicbase",
+            // ASLR will rebase it anyway so leaving that option enabled only leads to confusion
+            "--disable-auto-image-base",
+        ],
+    );
+    super::add_link_args(
+        &mut pre_link_args,
         LinkerFlavor::Gcc,
-        vec![
+        &[
             // Tell GCC to avoid linker plugins, because we are not bundling
             // them with Windows installer, and Rust does its own LTO anyways.
-            "-fno-use-linker-plugin".into(),
-            // Enable ASLR
-            "-Wl,--dynamicbase".into(),
-            // ASLR will rebase it anyway so leaving that option enabled only leads to confusion
-            "-Wl,--disable-auto-image-base".into(),
+            "-fno-use-linker-plugin",
+            "-Wl,--dynamicbase",
+            "-Wl,--disable-auto-image-base",
         ],
     );
 
-    let mut late_link_args = LinkArgs::new();
-    let mut late_link_args_dynamic = LinkArgs::new();
-    let mut late_link_args_static = LinkArgs::new();
     // Order of `late_link_args*` was found through trial and error to work with various
     // mingw-w64 versions (not tested on the CI). It's expected to change from time to time.
-    let mingw_libs = vec![
-        "-lmsvcrt".into(),
-        "-lmingwex".into(),
-        "-lmingw32".into(),
-        "-lgcc".into(), // alas, mingw* libraries above depend on libgcc
+    let mingw_libs = &[
+        "-lmsvcrt",
+        "-lmingwex",
+        "-lmingw32",
+        "-lgcc", // alas, mingw* libraries above depend on libgcc
         // mingw's msvcrt is a weird hybrid import library and static library.
         // And it seems that the linker fails to use import symbols from msvcrt
         // that are required from functions in msvcrt in certain cases. For example
@@ -33,31 +37,27 @@ pub fn opts() -> TargetOptions {
         // The library is purposely listed twice to fix that.
         //
         // See https://github.com/rust-lang/rust/pull/47483 for some more details.
-        "-lmsvcrt".into(),
-        "-luser32".into(),
-        "-lkernel32".into(),
-    ];
-    late_link_args.insert(LinkerFlavor::Gcc, mingw_libs.clone());
-    late_link_args.insert(LinkerFlavor::Lld(LldFlavor::Ld), mingw_libs);
-    let dynamic_unwind_libs = vec![
-        // If any of our crates are dynamically linked then we need to use
-        // the shared libgcc_s-dw2-1.dll. This is required to support
-        // unwinding across DLL boundaries.
-        "-lgcc_s".into(),
-    ];
-    late_link_args_dynamic.insert(LinkerFlavor::Gcc, dynamic_unwind_libs.clone());
-    late_link_args_dynamic.insert(LinkerFlavor::Lld(LldFlavor::Ld), dynamic_unwind_libs);
-    let static_unwind_libs = vec![
-        // If all of our crates are statically linked then we can get away
-        // with statically linking the libgcc unwinding code. This allows
-        // binaries to be redistributed without the libgcc_s-dw2-1.dll
-        // dependency, but unfortunately break unwinding across DLL
-        // boundaries when unwinding across FFI boundaries.
-        "-lgcc_eh".into(),
-        "-l:libpthread.a".into(),
+        "-lmsvcrt",
+        "-luser32",
+        "-lkernel32",
     ];
-    late_link_args_static.insert(LinkerFlavor::Gcc, static_unwind_libs.clone());
-    late_link_args_static.insert(LinkerFlavor::Lld(LldFlavor::Ld), static_unwind_libs);
+    let mut late_link_args = TargetOptions::link_args(LinkerFlavor::Ld, mingw_libs);
+    super::add_link_args(&mut late_link_args, LinkerFlavor::Gcc, mingw_libs);
+    // If any of our crates are dynamically linked then we need to use
+    // the shared libgcc_s-dw2-1.dll. This is required to support
+    // unwinding across DLL boundaries.
+    let dynamic_unwind_libs = &["-lgcc_s"];
+    let mut late_link_args_dynamic =
+        TargetOptions::link_args(LinkerFlavor::Ld, dynamic_unwind_libs);
+    super::add_link_args(&mut late_link_args_dynamic, LinkerFlavor::Gcc, dynamic_unwind_libs);
+    // If all of our crates are statically linked then we can get away
+    // with statically linking the libgcc unwinding code. This allows
+    // binaries to be redistributed without the libgcc_s-dw2-1.dll
+    // dependency, but unfortunately break unwinding across DLL
+    // boundaries when unwinding across FFI boundaries.
+    let static_unwind_libs = &["-lgcc_eh", "-l:libpthread.a"];
+    let mut late_link_args_static = TargetOptions::link_args(LinkerFlavor::Ld, static_unwind_libs);
+    super::add_link_args(&mut late_link_args_static, LinkerFlavor::Gcc, static_unwind_libs);
 
     TargetOptions {
         os: "windows".into(),
diff --git a/compiler/rustc_target/src/spec/windows_gnullvm_base.rs b/compiler/rustc_target/src/spec/windows_gnullvm_base.rs
index 9f9f8be8718..30f995007a9 100644
--- a/compiler/rustc_target/src/spec/windows_gnullvm_base.rs
+++ b/compiler/rustc_target/src/spec/windows_gnullvm_base.rs
@@ -1,28 +1,17 @@
-use crate::spec::{cvs, LinkArgs, LinkerFlavor, TargetOptions};
+use crate::spec::{cvs, LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
-    let pre_link_args = LinkArgs::from([(
+    // We cannot use `-nodefaultlibs` because compiler-rt has to be passed
+    // as a path since it's not added to linker search path by the default.
+    // There were attemts to make it behave like libgcc (so one can just use -l<name>)
+    // but LLVM maintainers rejected it: https://reviews.llvm.org/D51440
+    let pre_link_args =
+        TargetOptions::link_args(LinkerFlavor::Gcc, &["-nolibc", "--unwindlib=none"]);
+    // Order of `late_link_args*` does not matter with LLD.
+    let late_link_args = TargetOptions::link_args(
         LinkerFlavor::Gcc,
-        vec![
-            // We cannot use `-nodefaultlibs` because compiler-rt has to be passed
-            // as a path since it's not added to linker search path by the default.
-            // There were attemts to make it behave like libgcc (so one can just use -l<name>)
-            // but LLVM maintainers rejected it: https://reviews.llvm.org/D51440
-            "-nolibc".into(),
-            "--unwindlib=none".into(),
-        ],
-    )]);
-    let late_link_args = LinkArgs::from([(
-        LinkerFlavor::Gcc,
-        // Order of `late_link_args*` does not matter with LLD.
-        vec![
-            "-lmingw32".into(),
-            "-lmingwex".into(),
-            "-lmsvcrt".into(),
-            "-lkernel32".into(),
-            "-luser32".into(),
-        ],
-    )]);
+        &["-lmingw32", "-lmingwex", "-lmsvcrt", "-lkernel32", "-luser32"],
+    );
 
     TargetOptions {
         os: "windows".into(),
diff --git a/compiler/rustc_target/src/spec/windows_uwp_gnu_base.rs b/compiler/rustc_target/src/spec/windows_uwp_gnu_base.rs
index 11968391776..334dec43ef7 100644
--- a/compiler/rustc_target/src/spec/windows_uwp_gnu_base.rs
+++ b/compiler/rustc_target/src/spec/windows_uwp_gnu_base.rs
@@ -1,28 +1,25 @@
-use crate::spec::{LinkArgs, LinkerFlavor, LldFlavor, TargetOptions};
+use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
     let base = super::windows_gnu_base::opts();
 
     // FIXME: This should be updated for the exception machinery changes from #67502
     // and inherit from `windows_gnu_base`, at least partially.
-    let mut late_link_args = LinkArgs::new();
+    let mingw_libs = &[
+        "-lwinstorecompat",
+        "-lruntimeobject",
+        "-lsynchronization",
+        "-lvcruntime140_app",
+        "-lucrt",
+        "-lwindowsapp",
+        "-lmingwex",
+        "-lmingw32",
+    ];
+    let mut late_link_args = TargetOptions::link_args(LinkerFlavor::Ld, mingw_libs);
+    super::add_link_args(&mut late_link_args, LinkerFlavor::Gcc, mingw_libs);
+    // Reset the flags back to empty until the FIXME above is addressed.
     let late_link_args_dynamic = LinkArgs::new();
     let late_link_args_static = LinkArgs::new();
-    let mingw_libs = vec![
-        //"-lwinstorecompat".into(),
-        //"-lmingwex".into(),
-        //"-lwinstorecompat".into(),
-        "-lwinstorecompat".into(),
-        "-lruntimeobject".into(),
-        "-lsynchronization".into(),
-        "-lvcruntime140_app".into(),
-        "-lucrt".into(),
-        "-lwindowsapp".into(),
-        "-lmingwex".into(),
-        "-lmingw32".into(),
-    ];
-    late_link_args.insert(LinkerFlavor::Gcc, mingw_libs.clone());
-    late_link_args.insert(LinkerFlavor::Lld(LldFlavor::Ld), mingw_libs);
 
     TargetOptions {
         abi: "uwp".into(),
diff --git a/compiler/rustc_target/src/spec/windows_uwp_msvc_base.rs b/compiler/rustc_target/src/spec/windows_uwp_msvc_base.rs
index d6b065b529a..f2573fc2d21 100644
--- a/compiler/rustc_target/src/spec/windows_uwp_msvc_base.rs
+++ b/compiler/rustc_target/src/spec/windows_uwp_msvc_base.rs
@@ -1,16 +1,11 @@
-use crate::spec::{LinkerFlavor, LldFlavor, TargetOptions};
+use crate::spec::{LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
     let mut opts = super::windows_msvc_base::opts();
 
     opts.abi = "uwp".into();
     opts.vendor = "uwp".into();
-    let pre_link_args_msvc = vec!["/APPCONTAINER".into(), "mincore.lib".into()];
-    opts.pre_link_args.entry(LinkerFlavor::Msvc).or_default().extend(pre_link_args_msvc.clone());
-    opts.pre_link_args
-        .entry(LinkerFlavor::Lld(LldFlavor::Link))
-        .or_default()
-        .extend(pre_link_args_msvc);
+    opts.add_pre_link_args(LinkerFlavor::Msvc, &["/APPCONTAINER", "mincore.lib"]);
 
     opts
 }
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
index 51d14f0403a..dbd26899c18 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
@@ -6,8 +6,7 @@ pub fn target() -> Target {
     base.cpu = "core2".into();
     base.max_atomic_width = Some(128); // core2 support cmpxchg16b
     base.frame_pointer = FramePointer::Always;
-    base.pre_link_args
-        .insert(LinkerFlavor::Gcc, vec!["-m64".into(), "-arch".into(), "x86_64".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64", "-arch", "x86_64"]);
     base.link_env_remove.to_mut().extend(super::apple_base::macos_link_env_remove());
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
diff --git a/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs b/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs
index 47c70513faf..4348d924579 100644
--- a/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs
+++ b/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs
@@ -1,41 +1,44 @@
-use std::{borrow::Cow, iter};
+use std::borrow::Cow;
 
 use crate::spec::cvs;
 
 use super::{LinkerFlavor, LldFlavor, Target, TargetOptions};
 
 pub fn target() -> Target {
-    const PRE_LINK_ARGS: &[&str] = &[
-        "-e",
-        "elf_entry",
-        "-Bstatic",
-        "--gc-sections",
-        "-z",
-        "text",
-        "-z",
-        "norelro",
-        "--no-undefined",
-        "--error-unresolved-symbols",
-        "--no-undefined-version",
-        "-Bsymbolic",
-        "--export-dynamic",
-        // The following symbols are needed by libunwind, which is linked after
-        // libstd. Make sure they're included in the link.
-        "-u",
-        "__rust_abort",
-        "-u",
-        "__rust_c_alloc",
-        "-u",
-        "__rust_c_dealloc",
-        "-u",
-        "__rust_print_err",
-        "-u",
-        "__rust_rwlock_rdlock",
-        "-u",
-        "__rust_rwlock_unlock",
-        "-u",
-        "__rust_rwlock_wrlock",
-    ];
+    let pre_link_args = TargetOptions::link_args(
+        LinkerFlavor::Ld,
+        &[
+            "-e",
+            "elf_entry",
+            "-Bstatic",
+            "--gc-sections",
+            "-z",
+            "text",
+            "-z",
+            "norelro",
+            "--no-undefined",
+            "--error-unresolved-symbols",
+            "--no-undefined-version",
+            "-Bsymbolic",
+            "--export-dynamic",
+            // The following symbols are needed by libunwind, which is linked after
+            // libstd. Make sure they're included in the link.
+            "-u",
+            "__rust_abort",
+            "-u",
+            "__rust_c_alloc",
+            "-u",
+            "__rust_c_dealloc",
+            "-u",
+            "__rust_print_err",
+            "-u",
+            "__rust_rwlock_rdlock",
+            "-u",
+            "__rust_rwlock_unlock",
+            "-u",
+            "__rust_rwlock_wrlock",
+        ],
+    );
 
     const EXPORT_SYMBOLS: &[&str] = &[
         "sgx_entry",
@@ -66,11 +69,7 @@ pub fn target() -> Target {
         features: "+rdrnd,+rdseed,+lvi-cfi,+lvi-load-hardening".into(),
         llvm_args: cvs!["--x86-experimental-lvi-inline-asm-hardening"],
         position_independent_executables: true,
-        pre_link_args: iter::once((
-            LinkerFlavor::Lld(LldFlavor::Ld),
-            PRE_LINK_ARGS.iter().cloned().map(Cow::from).collect(),
-        ))
-        .collect(),
+        pre_link_args,
         override_export_symbols: Some(EXPORT_SYMBOLS.iter().cloned().map(Cow::from).collect()),
         relax_elf_relocations: true,
         ..Default::default()
diff --git a/compiler/rustc_target/src/spec/x86_64_linux_android.rs b/compiler/rustc_target/src/spec/x86_64_linux_android.rs
index 049cab0d554..6d19cf26574 100644
--- a/compiler/rustc_target/src/spec/x86_64_linux_android.rs
+++ b/compiler/rustc_target/src/spec/x86_64_linux_android.rs
@@ -6,7 +6,7 @@ pub fn target() -> Target {
     // https://developer.android.com/ndk/guides/abis.html#86-64
     base.features = "+mmx,+sse,+sse2,+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs
index 2a697daeb28..0550b221fd9 100644
--- a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs
+++ b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs
@@ -2,7 +2,7 @@ use crate::spec::{LinkerFlavor, SanitizerSet, StackProbeType, Target};
 
 pub fn target() -> Target {
     let mut base = super::solaris_base::opts();
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m64".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.cpu = "x86-64".into();
     base.vendor = "pc".into();
     base.max_atomic_width = Some(64);
diff --git a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs
index 0fa43481c9b..59a8cffca48 100644
--- a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs
+++ b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs
@@ -1,14 +1,11 @@
-use crate::spec::{LinkerFlavor, LldFlavor, Target};
+use crate::spec::{LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut base = super::windows_gnu_base::opts();
     base.cpu = "x86-64".into();
-    let gcc_pre_link_args = base.pre_link_args.entry(LinkerFlavor::Gcc).or_default();
-    gcc_pre_link_args.push("-m64".into());
     // Use high-entropy 64 bit address space for ASLR
-    gcc_pre_link_args.push("-Wl,--high-entropy-va".into());
-    base.pre_link_args
-        .insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["-m".into(), "i386pep".into()]);
+    base.add_pre_link_args(LinkerFlavor::Ld, &["-m", "i386pep", "--high-entropy-va"]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64", "-Wl,--high-entropy-va"]);
     base.max_atomic_width = Some(64);
     base.linker = Some("x86_64-w64-mingw32-gcc".into());
 
diff --git a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs
index b5ff63e0532..d3909b3895e 100644
--- a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs
+++ b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs
@@ -3,8 +3,7 @@ use crate::spec::{LinkerFlavor, Target};
 pub fn target() -> Target {
     let mut base = super::windows_gnullvm_base::opts();
     base.cpu = "x86-64".into();
-    let gcc_pre_link_args = base.pre_link_args.entry(LinkerFlavor::Gcc).or_default();
-    gcc_pre_link_args.push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.max_atomic_width = Some(64);
     base.linker = Some("x86_64-w64-mingw32-clang".into());
 
diff --git a/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs b/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs
index a02018266fb..cbe87589a70 100644
--- a/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs
+++ b/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs
@@ -2,7 +2,7 @@ use crate::spec::{LinkerFlavor, StackProbeType, Target};
 
 pub fn target() -> Target {
     let mut base = super::solaris_base::opts();
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m64".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.cpu = "x86-64".into();
     base.vendor = "sun".into();
     base.max_atomic_width = Some(64);
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs b/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs
index 1f2b998a7ba..746f6478178 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::dragonfly_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
index c9aedd6ea82..b30784ed692 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::freebsd_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     base.supported_sanitizers =
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs b/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs
index aebbd18c66a..d6d03362982 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::haiku_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m64".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     // This option is required to build executables on Haiku x86_64
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs b/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs
index 9529fa9640d..9f19c3a2b2a 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs
@@ -2,7 +2,7 @@ use crate::spec::{LinkerFlavor, SanitizerSet, Target};
 
 pub fn target() -> Target {
     let mut base = super::illumos_base::opts();
-    base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m64".into(), "-std=c99".into()]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64", "-std=c99"]);
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI;
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
index e525cfdde14..956be0353fa 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::linux_gnu_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     base.static_position_independent_executables = true;
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs
index 863b41633e2..140882747c2 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs
@@ -5,7 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.abi = "x32".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-mx32".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-mx32"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     base.has_thread_local = false;
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
index 8678f06e2cb..87e7784d1f9 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::linux_musl_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     base.static_position_independent_executables = true;
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
index a7115dace1c..d3a67619aa8 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::netbsd_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     base.supported_sanitizers = SanitizerSet::ADDRESS
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_none_linuxkernel.rs b/compiler/rustc_target/src/spec/x86_64_unknown_none_linuxkernel.rs
index 0db88d64ac0..593345a5f1d 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_none_linuxkernel.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_none_linuxkernel.rs
@@ -10,7 +10,7 @@ pub fn target() -> Target {
     base.features =
         "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float".into();
     base.code_model = Some(CodeModel::Kernel);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
 
     Target {
         // FIXME: Some dispute, the linux-on-clang folks think this should use
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
index 11e9cc4abc0..f50c6bceec9 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::openbsd_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs b/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs
index af8b9673c30..668ae905417 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::redox_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs b/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs
index a94bbbf6ede..76d2013cf7f 100644
--- a/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs
+++ b/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs
@@ -1,14 +1,11 @@
-use crate::spec::{LinkerFlavor, LldFlavor, Target};
+use crate::spec::{LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut base = super::windows_uwp_gnu_base::opts();
     base.cpu = "x86-64".into();
-    let gcc_pre_link_args = base.pre_link_args.entry(LinkerFlavor::Gcc).or_default();
-    gcc_pre_link_args.push("-m64".into());
     // Use high-entropy 64 bit address space for ASLR
-    gcc_pre_link_args.push("-Wl,--high-entropy-va".into());
-    base.pre_link_args
-        .insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["-m".into(), "i386pep".into()]);
+    base.add_pre_link_args(LinkerFlavor::Ld, &["-m", "i386pep", "--high-entropy-va"]);
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64", "-Wl,--high-entropy-va"]);
     base.max_atomic_width = Some(64);
 
     Target {
diff --git a/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs b/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs
index 16d29753e7d..1298974952f 100644
--- a/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs
+++ b/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs
@@ -4,7 +4,7 @@ pub fn target() -> Target {
     let mut base = super::vxworks_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".into());
+    base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
     base.stack_probes = StackProbeType::Call;
     base.disable_redzone = true;
diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
index a63790b594d..90ff07cba02 100644
--- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs
+++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
@@ -220,7 +220,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
                 .map(|&(id, _)| (id, vec![]))
                 .collect();
 
-            infcx.process_registered_region_obligations(&body_id_map, None, full_env);
+            infcx.process_registered_region_obligations(&body_id_map, full_env);
 
             let region_data = infcx
                 .inner
diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs
index b37db4b9e18..2b26b916d32 100644
--- a/compiler/rustc_trait_selection/src/traits/coherence.rs
+++ b/compiler/rustc_trait_selection/src/traits/coherence.rs
@@ -407,11 +407,7 @@ fn resolve_negative_obligation<'cx, 'tcx>(
     // function bodies with closures).
     outlives_env.save_implied_bounds(CRATE_HIR_ID);
 
-    infcx.process_registered_region_obligations(
-        outlives_env.region_bound_pairs_map(),
-        Some(tcx.lifetimes.re_root_empty),
-        param_env,
-    );
+    infcx.process_registered_region_obligations(outlives_env.region_bound_pairs_map(), param_env);
 
     let errors = infcx.resolve_regions(region_context, &outlives_env);
 
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index af0803fbd54..debb9e82951 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -484,10 +484,9 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                             err.span_label(span, explanation);
                         }
 
-                        if let ObligationCauseCode::ObjectCastObligation(obj_ty) = obligation.cause.code().peel_derives() &&
-                           let Some(self_ty) = trait_predicate.self_ty().no_bound_vars() &&
+                        if let ObligationCauseCode::ObjectCastObligation(concrete_ty, obj_ty) = obligation.cause.code().peel_derives() &&
                            Some(trait_ref.def_id()) == self.tcx.lang_items().sized_trait() {
-                            self.suggest_borrowing_for_object_cast(&mut err, &obligation, self_ty, *obj_ty);
+                            self.suggest_borrowing_for_object_cast(&mut err, &root_obligation, *concrete_ty, *obj_ty);
                         }
 
                         if trait_predicate.is_const_if_const() && obligation.param_env.is_const() {
@@ -1560,7 +1559,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
                     obligation.cause.code().peel_derives(),
                     ObligationCauseCode::ItemObligation(_)
                         | ObligationCauseCode::BindingObligation(_, _)
-                        | ObligationCauseCode::ObjectCastObligation(_)
+                        | ObligationCauseCode::ObjectCastObligation(..)
                         | ObligationCauseCode::OpaqueType
                 );
                 if let Err(error) = self.at(&obligation.cause, obligation.param_env).eq_exp(
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index fbe66d7dcdd..12858172ee5 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -2217,9 +2217,10 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     err.span_note(tcx.def_span(item_def_id), &descr);
                 }
             }
-            ObligationCauseCode::ObjectCastObligation(object_ty) => {
+            ObligationCauseCode::ObjectCastObligation(concrete_ty, object_ty) => {
                 err.note(&format!(
-                    "required for the cast to the object type `{}`",
+                    "required for the cast from `{}` to the object type `{}`",
+                    self.ty_to_string(concrete_ty),
                     self.ty_to_string(object_ty)
                 ));
             }
diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs
index 132c335a7e6..8d344591915 100644
--- a/compiler/rustc_trait_selection/src/traits/object_safety.rs
+++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs
@@ -366,15 +366,9 @@ fn object_safety_violation_for_method(
     // Get an accurate span depending on the violation.
     violation.map(|v| {
         let node = tcx.hir().get_if_local(method.def_id);
-        let span = match (v, node) {
-            (MethodViolationCode::ReferencesSelfInput(arg), Some(node)) => node
-                .fn_decl()
-                .and_then(|decl| decl.inputs.get(arg + 1))
-                .map_or(method.ident(tcx).span, |arg| arg.span),
-            (MethodViolationCode::UndispatchableReceiver, Some(node)) => node
-                .fn_decl()
-                .and_then(|decl| decl.inputs.get(0))
-                .map_or(method.ident(tcx).span, |arg| arg.span),
+        let span = match (&v, node) {
+            (MethodViolationCode::ReferencesSelfInput(Some(span)), _) => *span,
+            (MethodViolationCode::UndispatchableReceiver(Some(span)), _) => *span,
             (MethodViolationCode::ReferencesSelfOutput, Some(node)) => {
                 node.fn_decl().map_or(method.ident(tcx).span, |decl| decl.output.span())
             }
@@ -397,32 +391,41 @@ fn virtual_call_violation_for_method<'tcx>(
 
     // The method's first parameter must be named `self`
     if !method.fn_has_self_parameter {
-        // We'll attempt to provide a structured suggestion for `Self: Sized`.
-        let sugg =
-            tcx.hir().get_if_local(method.def_id).as_ref().and_then(|node| node.generics()).map(
-                |generics| match generics.predicates {
-                    [] => (" where Self: Sized", generics.where_clause_span),
-                    [.., pred] => (", Self: Sized", pred.span().shrink_to_hi()),
-                },
-            );
-        // Get the span pointing at where the `self` receiver should be.
-        let sm = tcx.sess.source_map();
-        let self_span = method.ident(tcx).span.to(tcx
-            .hir()
-            .span_if_local(method.def_id)
-            .unwrap_or_else(|| sm.next_point(method.ident(tcx).span))
-            .shrink_to_hi());
-        let self_span = sm.span_through_char(self_span, '(').shrink_to_hi();
-        return Some(MethodViolationCode::StaticMethod(
-            sugg,
-            self_span,
-            !sig.inputs().skip_binder().is_empty(),
-        ));
+        let sugg = if let Some(hir::Node::TraitItem(hir::TraitItem {
+            generics,
+            kind: hir::TraitItemKind::Fn(sig, _),
+            ..
+        })) = tcx.hir().get_if_local(method.def_id).as_ref()
+        {
+            let sm = tcx.sess.source_map();
+            Some((
+                (
+                    format!("&self{}", if sig.decl.inputs.is_empty() { "" } else { ", " }),
+                    sm.span_through_char(sig.span, '(').shrink_to_hi(),
+                ),
+                (
+                    format!("{} Self: Sized", generics.add_where_or_trailing_comma()),
+                    generics.tail_span_for_predicate_suggestion(),
+                ),
+            ))
+        } else {
+            None
+        };
+        return Some(MethodViolationCode::StaticMethod(sugg));
     }
 
-    for (i, &input_ty) in sig.skip_binder().inputs()[1..].iter().enumerate() {
+    for (i, &input_ty) in sig.skip_binder().inputs().iter().enumerate().skip(1) {
         if contains_illegal_self_type_reference(tcx, trait_def_id, sig.rebind(input_ty)) {
-            return Some(MethodViolationCode::ReferencesSelfInput(i));
+            let span = if let Some(hir::Node::TraitItem(hir::TraitItem {
+                kind: hir::TraitItemKind::Fn(sig, _),
+                ..
+            })) = tcx.hir().get_if_local(method.def_id).as_ref()
+            {
+                Some(sig.decl.inputs[i].span)
+            } else {
+                None
+            };
+            return Some(MethodViolationCode::ReferencesSelfInput(span));
         }
     }
     if contains_illegal_self_type_reference(tcx, trait_def_id, sig.output()) {
@@ -456,7 +459,16 @@ fn virtual_call_violation_for_method<'tcx>(
     // `Receiver: Unsize<Receiver[Self => dyn Trait]>`.
     if receiver_ty != tcx.types.self_param {
         if !receiver_is_dispatchable(tcx, method, receiver_ty) {
-            return Some(MethodViolationCode::UndispatchableReceiver);
+            let span = if let Some(hir::Node::TraitItem(hir::TraitItem {
+                kind: hir::TraitItemKind::Fn(sig, _),
+                ..
+            })) = tcx.hir().get_if_local(method.def_id).as_ref()
+            {
+                Some(sig.decl.inputs[0].span)
+            } else {
+                None
+            };
+            return Some(MethodViolationCode::UndispatchableReceiver(span));
         } else {
             // Do sanity check to make sure the receiver actually has the layout of a pointer.
 
diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs
index 82c54291a5d..aba4f144d4b 100644
--- a/compiler/rustc_trait_selection/src/traits/project.rs
+++ b/compiler/rustc_trait_selection/src/traits/project.rs
@@ -28,7 +28,6 @@ use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
 use rustc_infer::infer::resolve::OpportunisticRegionResolver;
-use rustc_infer::traits::ObligationCauseCode;
 use rustc_middle::traits::select::OverflowError;
 use rustc_middle::ty::fold::{MaxUniverse, TypeFoldable, TypeFolder, TypeSuperFoldable};
 use rustc_middle::ty::subst::Subst;
@@ -252,22 +251,10 @@ fn project_and_unify_type<'cx, 'tcx>(
         Err(InProgress) => return ProjectAndUnifyResult::Recursive,
     };
     debug!(?normalized, ?obligations, "project_and_unify_type result");
-    let actual = obligation.predicate.term;
-    // HACK: lazy TAIT would regress src/test/ui/impl-trait/nested-return-type2.rs, so we add
-    // a back-compat hack hat converts the RPITs into inference vars, just like they were before
-    // lazy TAIT.
-    // This does not affect TAITs in general, as tested in the nested-return-type-tait* tests.
-    let InferOk { value: actual, obligations: new } =
-        selcx.infcx().replace_opaque_types_with_inference_vars(
-            actual,
-            obligation.cause.body_id,
-            obligation.cause.span,
-            ObligationCauseCode::MiscObligation,
-            obligation.param_env,
-        );
-    obligations.extend(new);
-
-    match infcx.at(&obligation.cause, obligation.param_env).eq(normalized, actual) {
+    match infcx
+        .at(&obligation.cause, obligation.param_env)
+        .eq(normalized, obligation.predicate.term)
+    {
         Ok(InferOk { obligations: inferred_obligations, value: () }) => {
             obligations.extend(inferred_obligations);
             ProjectAndUnifyResult::Holds(obligations)
diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
index b80a27eb07d..7f15b683fda 100644
--- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
@@ -12,7 +12,7 @@ use rustc_data_structures::sso::SsoHashMap;
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_infer::traits::Normalized;
 use rustc_middle::mir;
-use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable};
 use rustc_middle::ty::subst::Subst;
 use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitor};
 
@@ -162,15 +162,13 @@ struct QueryNormalizer<'cx, 'tcx> {
     universes: Vec<Option<ty::UniverseIndex>>,
 }
 
-impl<'cx, 'tcx> TypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
+impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
     type Error = NoSolution;
 
     fn tcx<'c>(&'c self) -> TyCtxt<'tcx> {
         self.infcx.tcx
     }
-}
 
-impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
     fn try_fold_binder<T: TypeFoldable<'tcx>>(
         &mut self,
         t: ty::Binder<'tcx, T>,
diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
index 5942bb79d69..e1131140c39 100644
--- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
@@ -813,7 +813,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                 let cause = ObligationCause::new(
                     obligation.cause.span,
                     obligation.cause.body_id,
-                    ObjectCastObligation(target),
+                    ObjectCastObligation(source, target),
                 );
                 let outlives = ty::OutlivesPredicate(r_a, r_b);
                 nested.push(Obligation::with_depth(
@@ -910,7 +910,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                 let cause = ObligationCause::new(
                     obligation.cause.span,
                     obligation.cause.body_id,
-                    ObjectCastObligation(target),
+                    ObjectCastObligation(source, target),
                 );
                 let outlives = ty::OutlivesPredicate(r_a, r_b);
                 nested.push(Obligation::with_depth(
@@ -931,7 +931,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                 let cause = ObligationCause::new(
                     obligation.cause.span,
                     obligation.cause.body_id,
-                    ObjectCastObligation(target),
+                    ObjectCastObligation(source, target),
                 );
 
                 let predicate_to_obligation = |predicate| {
diff --git a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
index 95f1e224a4c..a1861529b59 100644
--- a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
@@ -452,7 +452,7 @@ fn report_conflicting_impls(
     match used_to_be_allowed {
         None => {
             let reported = if overlap.with_impl.is_local()
-                || !tcx.orphan_check_crate(()).contains(&impl_def_id)
+                || tcx.orphan_check_impl(impl_def_id).is_ok()
             {
                 let err = struct_span_err!(tcx.sess, impl_span, E0119, "");
                 Some(decorate(
diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs
index cb1602816ae..f9708d6d919 100644
--- a/compiler/rustc_type_ir/src/lib.rs
+++ b/compiler/rustc_type_ir/src/lib.rs
@@ -1,3 +1,4 @@
+#![feature(fmt_helpers_for_derive)]
 #![feature(min_specialization)]
 #![feature(rustc_attrs)]
 
diff --git a/compiler/rustc_type_ir/src/sty.rs b/compiler/rustc_type_ir/src/sty.rs
index 5cd2901324a..74737e30bb4 100644
--- a/compiler/rustc_type_ir/src/sty.rs
+++ b/compiler/rustc_type_ir/src/sty.rs
@@ -554,137 +554,37 @@ impl<I: Interner> hash::Hash for TyKind<I> {
 // This is manually implemented because a derive would require `I: Debug`
 impl<I: Interner> fmt::Debug for TyKind<I> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match (&*self,) {
-            (&Bool,) => fmt::Formatter::write_str(f, "Bool"),
-            (&Char,) => fmt::Formatter::write_str(f, "Char"),
-            (&Int(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Int");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Uint(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Uint");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Float(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Float");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Adt(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Adt");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Foreign(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Foreign");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Str,) => fmt::Formatter::write_str(f, "Str"),
-            (&Array(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Array");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Slice(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Slice");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&RawPtr(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "RawPtr");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Ref(ref __self_0, ref __self_1, ref __self_2),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Ref");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_2);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&FnDef(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "FnDef");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&FnPtr(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "FnPtr");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Dynamic(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Dynamic");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Closure(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Closure");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Generator(ref __self_0, ref __self_1, ref __self_2),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Generator");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_2);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&GeneratorWitness(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "GeneratorWitness");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Never,) => fmt::Formatter::write_str(f, "Never"),
-            (&Tuple(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Tuple");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Projection(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Projection");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Opaque(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Opaque");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Param(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Param");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Bound(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Bound");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Placeholder(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Placeholder");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Infer(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Infer");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Error(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Error");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
+        use std::fmt::*;
+        match self {
+            Bool => Formatter::write_str(f, "Bool"),
+            Char => Formatter::write_str(f, "Char"),
+            Int(f0) => Formatter::debug_tuple_field1_finish(f, "Int", f0),
+            Uint(f0) => Formatter::debug_tuple_field1_finish(f, "Uint", f0),
+            Float(f0) => Formatter::debug_tuple_field1_finish(f, "Float", f0),
+            Adt(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Adt", f0, f1),
+            Foreign(f0) => Formatter::debug_tuple_field1_finish(f, "Foreign", f0),
+            Str => Formatter::write_str(f, "Str"),
+            Array(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Array", f0, f1),
+            Slice(f0) => Formatter::debug_tuple_field1_finish(f, "Slice", f0),
+            RawPtr(f0) => Formatter::debug_tuple_field1_finish(f, "RawPtr", f0),
+            Ref(f0, f1, f2) => Formatter::debug_tuple_field3_finish(f, "Ref", f0, f1, f2),
+            FnDef(f0, f1) => Formatter::debug_tuple_field2_finish(f, "FnDef", f0, f1),
+            FnPtr(f0) => Formatter::debug_tuple_field1_finish(f, "FnPtr", f0),
+            Dynamic(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Dynamic", f0, f1),
+            Closure(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Closure", f0, f1),
+            Generator(f0, f1, f2) => {
+                Formatter::debug_tuple_field3_finish(f, "Generator", f0, f1, f2)
+            }
+            GeneratorWitness(f0) => Formatter::debug_tuple_field1_finish(f, "GeneratorWitness", f0),
+            Never => Formatter::write_str(f, "Never"),
+            Tuple(f0) => Formatter::debug_tuple_field1_finish(f, "Tuple", f0),
+            Projection(f0) => Formatter::debug_tuple_field1_finish(f, "Projection", f0),
+            Opaque(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Opaque", f0, f1),
+            Param(f0) => Formatter::debug_tuple_field1_finish(f, "Param", f0),
+            Bound(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Bound", f0, f1),
+            Placeholder(f0) => Formatter::debug_tuple_field1_finish(f, "Placeholder", f0),
+            Infer(f0) => Formatter::debug_tuple_field1_finish(f, "Infer", f0),
+            TyKind::Error(f0) => Formatter::debug_tuple_field1_finish(f, "Error", f0),
         }
     }
 }
diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs
index 45c011b78e3..8ffffbed93c 100644
--- a/compiler/rustc_typeck/src/check/check.rs
+++ b/compiler/rustc_typeck/src/check/check.rs
@@ -32,11 +32,6 @@ use rustc_ty_utils::representability::{self, Representability};
 use std::iter;
 use std::ops::ControlFlow;
 
-pub fn check_wf_new(tcx: TyCtxt<'_>) {
-    let visit = wfcheck::CheckTypeWellFormedVisitor::new(tcx);
-    tcx.hir().par_visit_all_item_likes(&visit);
-}
-
 pub(super) fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: Abi) {
     match tcx.sess.target.is_abi_supported(abi) {
         Some(true) => (),
@@ -754,7 +749,7 @@ fn check_opaque_meets_bounds<'tcx>(
     });
 }
 
-pub fn check_item_type<'tcx>(tcx: TyCtxt<'tcx>, id: hir::ItemId) {
+fn check_item_type<'tcx>(tcx: TyCtxt<'tcx>, id: hir::ItemId) {
     debug!(
         "check_item_type(it.def_id={:?}, it.name={})",
         id.def_id,
@@ -1543,12 +1538,6 @@ pub(super) fn check_mod_item_types(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
     }
 }
 
-pub(super) use wfcheck::check_item_well_formed;
-
-pub(super) use wfcheck::check_trait_item as check_trait_item_well_formed;
-
-pub(super) use wfcheck::check_impl_item as check_impl_item_well_formed;
-
 fn async_opaque_type_cycle_error(tcx: TyCtxt<'_>, span: Span) -> ErrorGuaranteed {
     struct_span_err!(tcx.sess, span, E0733, "recursion in an `async fn` requires boxing")
         .span_label(span, "recursive `async fn`")
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs b/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs
index b7ba9d97878..7602f2550e8 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs
@@ -1,7 +1,26 @@
 use std::cmp;
 
+use rustc_index::vec::IndexVec;
 use rustc_middle::ty::error::TypeError;
 
+rustc_index::newtype_index! {
+    pub(crate) struct ExpectedIdx {
+        DEBUG_FORMAT = "ExpectedIdx({})",
+    }
+}
+
+rustc_index::newtype_index! {
+    pub(crate) struct ProvidedIdx {
+        DEBUG_FORMAT = "ProvidedIdx({})",
+    }
+}
+
+impl ExpectedIdx {
+    pub fn to_provided_idx(self) -> ProvidedIdx {
+        ProvidedIdx::from_usize(self.as_usize())
+    }
+}
+
 // An issue that might be found in the compatibility matrix
 #[derive(Debug)]
 enum Issue {
@@ -27,24 +46,24 @@ pub(crate) enum Compatibility<'tcx> {
 #[derive(Debug)]
 pub(crate) enum Error<'tcx> {
     /// The provided argument is the invalid type for the expected input
-    Invalid(usize, usize, Compatibility<'tcx>), // provided, expected
+    Invalid(ProvidedIdx, ExpectedIdx, Compatibility<'tcx>),
     /// There is a missing input
-    Missing(usize),
+    Missing(ExpectedIdx),
     /// There's a superfluous argument
-    Extra(usize),
+    Extra(ProvidedIdx),
     /// Two arguments should be swapped
-    Swap(usize, usize, usize, usize),
+    Swap(ProvidedIdx, ProvidedIdx, ExpectedIdx, ExpectedIdx),
     /// Several arguments should be reordered
-    Permutation(Vec<(usize, usize)>), // dest_arg, dest_input
+    Permutation(Vec<(ExpectedIdx, ProvidedIdx)>),
 }
 
 pub(crate) struct ArgMatrix<'tcx> {
     /// Maps the indices in the `compatibility_matrix` rows to the indices of
     /// the *user provided* inputs
-    input_indexes: Vec<usize>,
+    provided_indices: Vec<ProvidedIdx>,
     /// Maps the indices in the `compatibility_matrix` columns to the indices
     /// of the *expected* args
-    arg_indexes: Vec<usize>,
+    expected_indices: Vec<ExpectedIdx>,
     /// The first dimension (rows) are the remaining user provided inputs to
     /// match and the second dimension (cols) are the remaining expected args
     /// to match
@@ -52,62 +71,64 @@ pub(crate) struct ArgMatrix<'tcx> {
 }
 
 impl<'tcx> ArgMatrix<'tcx> {
-    pub(crate) fn new<F: FnMut(usize, usize) -> Compatibility<'tcx>>(
-        minimum_input_count: usize,
-        provided_arg_count: usize,
+    pub(crate) fn new<F: FnMut(ProvidedIdx, ExpectedIdx) -> Compatibility<'tcx>>(
+        provided_count: usize,
+        expected_input_count: usize,
         mut is_compatible: F,
     ) -> Self {
-        let compatibility_matrix = (0..provided_arg_count)
-            .map(|i| (0..minimum_input_count).map(|j| is_compatible(i, j)).collect())
+        let compatibility_matrix = (0..provided_count)
+            .map(|i| {
+                (0..expected_input_count)
+                    .map(|j| is_compatible(ProvidedIdx::from_usize(i), ExpectedIdx::from_usize(j)))
+                    .collect()
+            })
             .collect();
         ArgMatrix {
-            input_indexes: (0..provided_arg_count).collect(),
-            arg_indexes: (0..minimum_input_count).collect(),
+            provided_indices: (0..provided_count).map(ProvidedIdx::from_usize).collect(),
+            expected_indices: (0..expected_input_count).map(ExpectedIdx::from_usize).collect(),
             compatibility_matrix,
         }
     }
 
     /// Remove a given input from consideration
-    fn eliminate_input(&mut self, idx: usize) {
-        self.input_indexes.remove(idx);
+    fn eliminate_provided(&mut self, idx: usize) {
+        self.provided_indices.remove(idx);
         self.compatibility_matrix.remove(idx);
     }
 
     /// Remove a given argument from consideration
-    fn eliminate_arg(&mut self, idx: usize) {
-        self.arg_indexes.remove(idx);
+    fn eliminate_expected(&mut self, idx: usize) {
+        self.expected_indices.remove(idx);
         for row in &mut self.compatibility_matrix {
             row.remove(idx);
         }
     }
 
     /// "satisfy" an input with a given arg, removing both from consideration
-    fn satisfy_input(&mut self, input_idx: usize, arg_idx: usize) {
-        self.eliminate_input(input_idx);
-        self.eliminate_arg(arg_idx);
+    fn satisfy_input(&mut self, provided_idx: usize, expected_idx: usize) {
+        self.eliminate_provided(provided_idx);
+        self.eliminate_expected(expected_idx);
     }
 
     // Returns a `Vec` of (user input, expected arg) of matched arguments. These
     // are inputs on the remaining diagonal that match.
-    fn eliminate_satisfied(&mut self) -> Vec<(usize, usize)> {
-        let mut i = cmp::min(self.input_indexes.len(), self.arg_indexes.len());
+    fn eliminate_satisfied(&mut self) -> Vec<(ProvidedIdx, ExpectedIdx)> {
+        let num_args = cmp::min(self.provided_indices.len(), self.expected_indices.len());
         let mut eliminated = vec![];
-        while i > 0 {
-            let idx = i - 1;
-            if matches!(self.compatibility_matrix[idx][idx], Compatibility::Compatible) {
-                eliminated.push((self.input_indexes[idx], self.arg_indexes[idx]));
-                self.satisfy_input(idx, idx);
+        for i in (0..num_args).rev() {
+            if matches!(self.compatibility_matrix[i][i], Compatibility::Compatible) {
+                eliminated.push((self.provided_indices[i], self.expected_indices[i]));
+                self.satisfy_input(i, i);
             }
-            i -= 1;
         }
-        return eliminated;
+        eliminated
     }
 
     // Find some issue in the compatibility matrix
     fn find_issue(&self) -> Option<Issue> {
         let mat = &self.compatibility_matrix;
-        let ai = &self.arg_indexes;
-        let ii = &self.input_indexes;
+        let ai = &self.expected_indices;
+        let ii = &self.provided_indices;
 
         for i in 0..cmp::max(ai.len(), ii.len()) {
             // If we eliminate the last row, any left-over inputs are considered missing
@@ -264,12 +285,15 @@ impl<'tcx> ArgMatrix<'tcx> {
     //
     // We'll want to know which arguments and inputs these rows and columns correspond to
     // even after we delete them.
-    pub(crate) fn find_errors(mut self) -> (Vec<Error<'tcx>>, Vec<Option<usize>>) {
-        let provided_arg_count = self.input_indexes.len();
+    pub(crate) fn find_errors(
+        mut self,
+    ) -> (Vec<Error<'tcx>>, IndexVec<ExpectedIdx, Option<ProvidedIdx>>) {
+        let provided_arg_count = self.provided_indices.len();
 
         let mut errors: Vec<Error<'tcx>> = vec![];
         // For each expected argument, the matched *actual* input
-        let mut matched_inputs: Vec<Option<usize>> = vec![None; self.arg_indexes.len()];
+        let mut matched_inputs: IndexVec<ExpectedIdx, Option<ProvidedIdx>> =
+            IndexVec::from_elem_n(None, self.expected_indices.len());
 
         // Before we start looking for issues, eliminate any arguments that are already satisfied,
         // so that an argument which is already spoken for by the input it's in doesn't
@@ -280,34 +304,34 @@ impl<'tcx> ArgMatrix<'tcx> {
         // Without this elimination, the first argument causes the second argument
         // to show up as both a missing input and extra argument, rather than
         // just an invalid type.
-        for (inp, arg) in self.eliminate_satisfied() {
-            matched_inputs[arg] = Some(inp);
+        for (provided, expected) in self.eliminate_satisfied() {
+            matched_inputs[expected] = Some(provided);
         }
 
-        while self.input_indexes.len() > 0 || self.arg_indexes.len() > 0 {
+        while !self.provided_indices.is_empty() || !self.expected_indices.is_empty() {
             match self.find_issue() {
                 Some(Issue::Invalid(idx)) => {
                     let compatibility = self.compatibility_matrix[idx][idx].clone();
-                    let input_idx = self.input_indexes[idx];
-                    let arg_idx = self.arg_indexes[idx];
+                    let input_idx = self.provided_indices[idx];
+                    let arg_idx = self.expected_indices[idx];
                     self.satisfy_input(idx, idx);
                     errors.push(Error::Invalid(input_idx, arg_idx, compatibility));
                 }
                 Some(Issue::Extra(idx)) => {
-                    let input_idx = self.input_indexes[idx];
-                    self.eliminate_input(idx);
+                    let input_idx = self.provided_indices[idx];
+                    self.eliminate_provided(idx);
                     errors.push(Error::Extra(input_idx));
                 }
                 Some(Issue::Missing(idx)) => {
-                    let arg_idx = self.arg_indexes[idx];
-                    self.eliminate_arg(idx);
+                    let arg_idx = self.expected_indices[idx];
+                    self.eliminate_expected(idx);
                     errors.push(Error::Missing(arg_idx));
                 }
                 Some(Issue::Swap(idx, other)) => {
-                    let input_idx = self.input_indexes[idx];
-                    let other_input_idx = self.input_indexes[other];
-                    let arg_idx = self.arg_indexes[idx];
-                    let other_arg_idx = self.arg_indexes[other];
+                    let input_idx = self.provided_indices[idx];
+                    let other_input_idx = self.provided_indices[other];
+                    let arg_idx = self.expected_indices[idx];
+                    let other_arg_idx = self.expected_indices[other];
                     let (min, max) = (cmp::min(idx, other), cmp::max(idx, other));
                     self.satisfy_input(min, max);
                     // Subtract 1 because we already removed the "min" row
@@ -319,13 +343,14 @@ impl<'tcx> ArgMatrix<'tcx> {
                 Some(Issue::Permutation(args)) => {
                     let mut idxs: Vec<usize> = args.iter().filter_map(|&a| a).collect();
 
-                    let mut real_idxs = vec![None; provided_arg_count];
+                    let mut real_idxs: IndexVec<ProvidedIdx, Option<(ExpectedIdx, ProvidedIdx)>> =
+                        IndexVec::from_elem_n(None, provided_arg_count);
                     for (src, dst) in
                         args.iter().enumerate().filter_map(|(src, dst)| dst.map(|dst| (src, dst)))
                     {
-                        let src_input_idx = self.input_indexes[src];
-                        let dst_input_idx = self.input_indexes[dst];
-                        let dest_arg_idx = self.arg_indexes[dst];
+                        let src_input_idx = self.provided_indices[src];
+                        let dst_input_idx = self.provided_indices[dst];
+                        let dest_arg_idx = self.expected_indices[dst];
                         real_idxs[src_input_idx] = Some((dest_arg_idx, dst_input_idx));
                         matched_inputs[dest_arg_idx] = Some(src_input_idx);
                     }
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
index 2326c4069e4..08df01c0c1a 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
@@ -1,6 +1,8 @@
 use crate::astconv::AstConv;
 use crate::check::coercion::CoerceMany;
-use crate::check::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error};
+use crate::check::fn_ctxt::arg_matrix::{
+    ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx,
+};
 use crate::check::gather_locals::Declaration;
 use crate::check::method::MethodCallee;
 use crate::check::Expectation::*;
@@ -17,13 +19,14 @@ use rustc_hir as hir;
 use rustc_hir::def::{CtorOf, DefKind, Res};
 use rustc_hir::def_id::DefId;
 use rustc_hir::{ExprKind, Node, QPath};
+use rustc_index::vec::IndexVec;
 use rustc_infer::infer::error_reporting::{FailureCode, ObligationCauseExt};
 use rustc_infer::infer::InferOk;
 use rustc_infer::infer::TypeTrace;
 use rustc_middle::ty::adjustment::AllowTwoPhase;
 use rustc_middle::ty::error::TypeError;
 use rustc_middle::ty::fold::TypeFoldable;
-use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt};
 use rustc_session::Session;
 use rustc_span::symbol::Ident;
 use rustc_span::{self, Span};
@@ -214,6 +217,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let provided_arg_count = provided_args.len();
 
         // We'll also want to keep track of the fully coerced argument types, for an awkward hack near the end
+        // FIXME(compiler-errors): Get rid of this, actually.
         let mut final_arg_types: Vec<Option<(Ty<'_>, Ty<'_>)>> = vec![None; provided_arg_count];
 
         // We introduce a helper function to demand that a given argument satisfy a given input
@@ -287,54 +291,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             }
         };
 
-        // A "softer" version of the helper above, which checks types without persisting them,
-        // and treats error types differently
-        // This will allow us to "probe" for other argument orders that would likely have been correct
-        let check_compatible = |input_idx, arg_idx| {
-            let formal_input_ty: Ty<'tcx> = formal_input_tys[arg_idx];
-            let expected_input_ty: Ty<'tcx> = expected_input_tys[arg_idx];
-
-            // If either is an error type, we defy the usual convention and consider them to *not* be
-            // coercible.  This prevents our error message heuristic from trying to pass errors into
-            // every argument.
-            if formal_input_ty.references_error() || expected_input_ty.references_error() {
-                return Compatibility::Incompatible(None);
-            }
-
-            let provided_arg: &hir::Expr<'tcx> = &provided_args[input_idx];
-            let expectation = Expectation::rvalue_hint(self, expected_input_ty);
-            // FIXME: check that this is safe; I don't believe this commits any of the obligations, but I can't be sure.
-            //
-            //   I had another method of "soft" type checking before,
-            //   but it was failing to find the type of some expressions (like "")
-            //   so I prodded this method and made it pub(super) so I could call it, and it seems to work well.
-            let checked_ty = self.check_expr_kind(provided_arg, expectation);
-
-            let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty);
-            let can_coerce = self.can_coerce(checked_ty, coerced_ty);
-
-            if !can_coerce {
-                return Compatibility::Incompatible(None);
-            }
-
-            let subtyping_result = self
-                .at(&self.misc(provided_arg.span), self.param_env)
-                .sup(formal_input_ty, coerced_ty);
-
-            // Same as above: if either the coerce type or the checked type is an error type,
-            // consider them *not* compatible.
-            let coercible =
-                !coerced_ty.references_error() && !checked_ty.references_error() && can_coerce;
-
-            match (coercible, &subtyping_result) {
-                (true, Ok(_)) => Compatibility::Compatible,
-                _ => Compatibility::Incompatible(subtyping_result.err()),
-            }
-        };
-
         // To start, we only care "along the diagonal", where we expect every
         // provided arg to be in the right spot
-        let mut compatibility = vec![Compatibility::Incompatible(None); provided_args.len()];
+        let mut compatibility_diagonal =
+            vec![Compatibility::Incompatible(None); provided_args.len()];
 
         // Keep track of whether we *could possibly* be satisfied, i.e. whether we're on the happy path
         // if the wrong number of arguments were supplied, we CAN'T be satisfied,
@@ -394,7 +354,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
                 let compatible = demand_compatible(idx, &mut final_arg_types);
                 let is_compatible = matches!(compatible, Compatibility::Compatible);
-                compatibility[idx] = compatible;
+                compatibility_diagonal[idx] = compatible;
 
                 if !is_compatible {
                     call_appears_satisfied = false;
@@ -402,70 +362,54 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             }
         }
 
-        // Logic here is a bit hairy
-        'errors: {
-            // If something above didn't typecheck, we've fallen off the happy path
-            // and we should make some effort to provide better error messages
-            if call_appears_satisfied {
-                break 'errors;
-            }
-
-            self.set_tainted_by_errors();
+        if c_variadic && provided_arg_count < minimum_input_count {
+            err_code = "E0060";
+        }
 
-            // The algorithm here is inspired by levenshtein distance and longest common subsequence.
-            // We'll try to detect 4 different types of mistakes:
-            // - An extra parameter has been provided that doesn't satisfy *any* of the other inputs
-            // - An input is missing, which isn't satisfied by *any* of the other arguments
-            // - Some number of arguments have been provided in the wrong order
-            // - A type is straight up invalid
+        for arg in provided_args.iter().skip(minimum_input_count) {
+            // Make sure we've checked this expr at least once.
+            let arg_ty = self.check_expr(&arg);
 
-            // First, let's find the errors
-            let mut compatibility: Vec<_> = compatibility.into_iter().map(Some).collect();
-            let (mut errors, matched_inputs) =
-                ArgMatrix::new(minimum_input_count, provided_arg_count, |i, j| {
-                    if i == j { compatibility[i].take().unwrap() } else { check_compatible(i, j) }
-                })
-                .find_errors();
+            // If the function is c-style variadic, we skipped a bunch of arguments
+            // so we need to check those, and write out the types
+            // Ideally this would be folded into the above, for uniform style
+            // but c-variadic is already a corner case
+            if c_variadic {
+                fn variadic_error<'tcx>(
+                    sess: &'tcx Session,
+                    span: Span,
+                    ty: Ty<'tcx>,
+                    cast_ty: &str,
+                ) {
+                    use crate::structured_errors::MissingCastForVariadicArg;
 
-            // Okay, so here's where it gets complicated in regards to what errors
-            // we emit and how.
-            // There are 3 different "types" of errors we might encounter.
-            //   1) Missing/extra/swapped arguments
-            //   2) Valid but incorrect arguments
-            //   3) Invalid arguments
-            //      - Currently I think this only comes up with `CyclicTy`
-            //
-            // We first need to go through, remove those from (3) and emit those
-            // as their own error, particularly since they're error code and
-            // message is special. From what I can tell, we *must* emit these
-            // here (vs somewhere prior to this function) since the arguments
-            // become invalid *because* of how they get used in the function.
-            // It is what it is.
-
-            let found_errors = !errors.is_empty();
-
-            errors.drain_filter(|error| {
-                let Error::Invalid(input_idx, arg_idx, Compatibility::Incompatible(Some(e))) = error else { return false };
-                let expected_ty = expected_input_tys[*arg_idx];
-                let provided_ty = final_arg_types[*input_idx].map(|ty| ty.0).unwrap_or_else(|| tcx.ty_error());
-                let cause = &self.misc(provided_args[*input_idx].span);
-                let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
-                if !matches!(trace.cause.as_failure_code(e), FailureCode::Error0308(_)) {
-                    self.report_and_explain_type_error(trace, e).emit();
-                    return true;
+                    MissingCastForVariadicArg { sess, span, ty, cast_ty }.diagnostic().emit();
                 }
-                false
-            });
 
-            // We're done if we found errors, but we already emitted them.
-            // I don't think we *should* be able to enter this bit of code
-            // (`!call_appears_satisfied`) without *also* finding errors, but we
-            // don't want to accidentally not emit an error if there is some
-            // logic bug in the `ArgMatrix` code.
-            if found_errors && errors.is_empty() {
-                break 'errors;
+                // There are a few types which get autopromoted when passed via varargs
+                // in C but we just error out instead and require explicit casts.
+                let arg_ty = self.structurally_resolved_type(arg.span, arg_ty);
+                match arg_ty.kind() {
+                    ty::Float(ty::FloatTy::F32) => {
+                        variadic_error(tcx.sess, arg.span, arg_ty, "c_double");
+                    }
+                    ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Bool => {
+                        variadic_error(tcx.sess, arg.span, arg_ty, "c_int");
+                    }
+                    ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) => {
+                        variadic_error(tcx.sess, arg.span, arg_ty, "c_uint");
+                    }
+                    ty::FnDef(..) => {
+                        let ptr_ty = self.tcx.mk_fn_ptr(arg_ty.fn_sig(self.tcx));
+                        let ptr_ty = self.resolve_vars_if_possible(ptr_ty);
+                        variadic_error(tcx.sess, arg.span, arg_ty, &ptr_ty.to_string());
+                    }
+                    _ => {}
+                }
             }
+        }
 
+        if !call_appears_satisfied {
             // Next, let's construct the error
             let (error_span, full_call_span, ctor_of) = match &call_expr.kind {
                 hir::ExprKind::Call(
@@ -500,524 +444,633 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 Some(CtorOf::Variant) => "enum variant",
                 None => "function",
             };
-            if c_variadic && provided_arg_count < minimum_input_count {
-                err_code = "E0060";
-            }
 
-            // Next special case: The case where we expect a single tuple and
-            // wrapping all the args in parentheses (or adding a comma to
-            // already existing parentheses) will result in a tuple that
-            // satisfies the call.
-            // This isn't super ideal code, because we copy code from elsewhere
-            // and somewhat duplicate this. We also delegate to the general type
-            // mismatch suggestions for the single arg case.
-            let sugg_tuple_wrap_args =
-                self.suggested_tuple_wrap(&expected_input_tys, provided_args);
-            match sugg_tuple_wrap_args {
-                TupleMatchFound::None => {}
-                TupleMatchFound::Single => {
-                    let expected_ty = expected_input_tys[0];
-                    let provided_ty = final_arg_types[0].map(|ty| ty.0).unwrap();
-                    let expected_ty = self.resolve_vars_if_possible(expected_ty);
-                    let provided_ty = self.resolve_vars_if_possible(provided_ty);
-                    let cause = &self.misc(provided_args[0].span);
-                    let compatibility = demand_compatible(0, &mut final_arg_types);
-                    let type_error = match compatibility {
-                        Compatibility::Incompatible(Some(error)) => error,
-                        _ => TypeError::Mismatch,
-                    };
-                    let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
-                    let mut err = self.report_and_explain_type_error(trace, &type_error);
-                    self.emit_coerce_suggestions(
-                        &mut err,
-                        &provided_args[0],
-                        final_arg_types[0].map(|ty| ty.0).unwrap(),
-                        final_arg_types[0].map(|ty| ty.1).unwrap(),
-                        None,
-                        None,
-                    );
-                    err.span_label(
-                        full_call_span,
-                        format!("arguments to this {} are incorrect", call_name),
-                    );
-                    // Call out where the function is defined
-                    label_fn_like(tcx, &mut err, fn_def_id);
-                    err.emit();
-                    break 'errors;
-                }
-                TupleMatchFound::Multiple(start, end) => {
-                    let mut err = tcx.sess.struct_span_err_with_code(
-                        full_call_span,
-                        &format!(
-                            "this {} takes {}{} but {} {} supplied",
-                            call_name,
-                            if c_variadic { "at least " } else { "" },
-                            potentially_plural_count(minimum_input_count, "argument"),
-                            potentially_plural_count(provided_arg_count, "argument"),
-                            if provided_arg_count == 1 { "was" } else { "were" }
-                        ),
-                        DiagnosticId::Error(err_code.to_owned()),
-                    );
-                    // Call out where the function is defined
-                    label_fn_like(tcx, &mut err, fn_def_id);
-                    err.multipart_suggestion(
-                        "use parentheses to construct a tuple",
-                        vec![(start, '('.to_string()), (end, ')'.to_string())],
-                        Applicability::MachineApplicable,
-                    );
-                    err.emit();
-                    break 'errors;
+            let try_tuple_wrap_args = || {
+                // The case where we expect a single tuple and wrapping all the args
+                // in parentheses (or adding a comma to already existing parentheses)
+                // will result in a tuple that satisfies the call.
+                // This isn't super ideal code, because we copy code from elsewhere
+                // and somewhat duplicate this. We also delegate to the general type
+                // mismatch suggestions for the single arg case.
+                match self.suggested_tuple_wrap(&expected_input_tys, provided_args) {
+                    TupleMatchFound::Single => {
+                        let expected_ty = expected_input_tys[0];
+                        let provided_ty = final_arg_types[0].map(|ty| ty.0).unwrap();
+                        let expected_ty = self.resolve_vars_if_possible(expected_ty);
+                        let provided_ty = self.resolve_vars_if_possible(provided_ty);
+                        let cause = &self.misc(provided_args[0].span);
+                        let compatibility = demand_compatible(0, &mut final_arg_types);
+                        let type_error = match compatibility {
+                            Compatibility::Incompatible(Some(error)) => error,
+                            _ => TypeError::Mismatch,
+                        };
+                        let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+                        let mut err = self.report_and_explain_type_error(trace, &type_error);
+                        self.emit_coerce_suggestions(
+                            &mut err,
+                            &provided_args[0],
+                            final_arg_types[0].map(|ty| ty.0).unwrap(),
+                            final_arg_types[0].map(|ty| ty.1).unwrap(),
+                            None,
+                            None,
+                        );
+                        err.span_label(
+                            full_call_span,
+                            format!("arguments to this {} are incorrect", call_name),
+                        );
+                        // Call out where the function is defined
+                        label_fn_like(tcx, &mut err, fn_def_id);
+                        err.emit();
+                        return true;
+                    }
+                    TupleMatchFound::Multiple(start, end) => {
+                        let mut err = tcx.sess.struct_span_err_with_code(
+                            full_call_span,
+                            &format!(
+                                "this {} takes {}{} but {} {} supplied",
+                                call_name,
+                                if c_variadic { "at least " } else { "" },
+                                potentially_plural_count(minimum_input_count, "argument"),
+                                potentially_plural_count(provided_arg_count, "argument"),
+                                if provided_arg_count == 1 { "was" } else { "were" }
+                            ),
+                            DiagnosticId::Error(err_code.to_owned()),
+                        );
+                        // Call out where the function is defined
+                        label_fn_like(tcx, &mut err, fn_def_id);
+                        err.multipart_suggestion(
+                            "use parentheses to construct a tuple",
+                            vec![(start, '('.to_string()), (end, ')'.to_string())],
+                            Applicability::MachineApplicable,
+                        );
+                        err.emit();
+                        return true;
+                    }
+                    TupleMatchFound::None => {}
                 }
+                false
+            };
+
+            let compatibility_diagonal = IndexVec::from_raw(compatibility_diagonal);
+            let provided_args = IndexVec::from_iter(provided_args.iter().take(if c_variadic {
+                minimum_input_count
+            } else {
+                provided_arg_count
+            }));
+            debug_assert_eq!(
+                formal_input_tys.len(),
+                expected_input_tys.len(),
+                "expected formal_input_tys to be the same size as expected_input_tys"
+            );
+            let formal_and_expected_inputs = IndexVec::from_iter(
+                formal_input_tys
+                    .iter()
+                    .copied()
+                    .zip(expected_input_tys.iter().copied())
+                    .map(|vars| self.resolve_vars_if_possible(vars)),
+            );
+
+            self.report_arg_errors(
+                compatibility_diagonal,
+                formal_and_expected_inputs,
+                provided_args,
+                full_call_span,
+                error_span,
+                args_span,
+                call_name,
+                c_variadic,
+                err_code,
+                fn_def_id,
+                try_tuple_wrap_args,
+            );
+        }
+    }
+
+    fn report_arg_errors(
+        &self,
+        compatibility_diagonal: IndexVec<ProvidedIdx, Compatibility<'tcx>>,
+        formal_and_expected_inputs: IndexVec<ExpectedIdx, (Ty<'tcx>, Ty<'tcx>)>,
+        provided_args: IndexVec<ProvidedIdx, &'tcx hir::Expr<'tcx>>,
+        full_call_span: Span,
+        error_span: Span,
+        args_span: Span,
+        call_name: &str,
+        c_variadic: bool,
+        err_code: &str,
+        fn_def_id: Option<DefId>,
+        try_tuple_wrap_args: impl FnOnce() -> bool,
+    ) {
+        // Don't print if it has error types or is just plain `_`
+        fn has_error_or_infer<'tcx>(tys: impl IntoIterator<Item = Ty<'tcx>>) -> bool {
+            tys.into_iter().any(|ty| ty.references_error() || ty.is_ty_var())
+        }
+
+        self.set_tainted_by_errors();
+        let tcx = self.tcx;
+
+        // A "softer" version of the `demand_compatible`, which checks types without persisting them,
+        // and treats error types differently
+        // This will allow us to "probe" for other argument orders that would likely have been correct
+        let check_compatible = |provided_idx: ProvidedIdx, expected_idx: ExpectedIdx| {
+            if provided_idx.as_usize() == expected_idx.as_usize() {
+                return compatibility_diagonal[provided_idx].clone();
+            }
+
+            let (formal_input_ty, expected_input_ty) = formal_and_expected_inputs[expected_idx];
+            // If either is an error type, we defy the usual convention and consider them to *not* be
+            // coercible.  This prevents our error message heuristic from trying to pass errors into
+            // every argument.
+            if (formal_input_ty, expected_input_ty).references_error() {
+                return Compatibility::Incompatible(None);
+            }
+
+            let provided_arg: &hir::Expr<'tcx> = &provided_args[provided_idx];
+            let expectation = Expectation::rvalue_hint(self, expected_input_ty);
+            // FIXME: check that this is safe; I don't believe this commits any of the obligations, but I can't be sure.
+            //
+            //   I had another method of "soft" type checking before,
+            //   but it was failing to find the type of some expressions (like "")
+            //   so I prodded this method and made it pub(super) so I could call it, and it seems to work well.
+            let checked_ty = self.check_expr_kind(provided_arg, expectation);
+
+            let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty);
+            let can_coerce = self.can_coerce(checked_ty, coerced_ty);
+            if !can_coerce {
+                return Compatibility::Incompatible(None);
+            }
+
+            let subtyping_result = self
+                .at(&self.misc(provided_arg.span), self.param_env)
+                .sup(formal_input_ty, coerced_ty);
+
+            // Same as above: if either the coerce type or the checked type is an error type,
+            // consider them *not* compatible.
+            let references_error = (coerced_ty, checked_ty).references_error();
+            match (references_error, &subtyping_result) {
+                (false, Ok(_)) => Compatibility::Compatible,
+                _ => Compatibility::Incompatible(subtyping_result.err()),
             }
+        };
+
+        // The algorithm here is inspired by levenshtein distance and longest common subsequence.
+        // We'll try to detect 4 different types of mistakes:
+        // - An extra parameter has been provided that doesn't satisfy *any* of the other inputs
+        // - An input is missing, which isn't satisfied by *any* of the other arguments
+        // - Some number of arguments have been provided in the wrong order
+        // - A type is straight up invalid
+
+        // First, let's find the errors
+        let (mut errors, matched_inputs) =
+            ArgMatrix::new(provided_args.len(), formal_and_expected_inputs.len(), check_compatible)
+                .find_errors();
+
+        // Precompute the provided types and spans, since that's all we typically need for below
+        let provided_arg_tys: IndexVec<ProvidedIdx, (Ty<'tcx>, Span)> = provided_args
+            .iter()
+            .map(|expr| {
+                let ty = self
+                    .in_progress_typeck_results
+                    .as_ref()
+                    .unwrap()
+                    .borrow()
+                    .expr_ty_adjusted_opt(*expr)
+                    .unwrap_or_else(|| tcx.ty_error());
+                (self.resolve_vars_if_possible(ty), expr.span)
+            })
+            .collect();
+
+        // Okay, so here's where it gets complicated in regards to what errors
+        // we emit and how.
+        // There are 3 different "types" of errors we might encounter.
+        //   1) Missing/extra/swapped arguments
+        //   2) Valid but incorrect arguments
+        //   3) Invalid arguments
+        //      - Currently I think this only comes up with `CyclicTy`
+        //
+        // We first need to go through, remove those from (3) and emit those
+        // as their own error, particularly since they're error code and
+        // message is special. From what I can tell, we *must* emit these
+        // here (vs somewhere prior to this function) since the arguments
+        // become invalid *because* of how they get used in the function.
+        // It is what it is.
+
+        if errors.is_empty() {
+            if cfg!(debug_assertions) {
+                span_bug!(error_span, "expected errors from argument matrix");
+            } else {
+                tcx.sess
+                    .struct_span_err(
+                        error_span,
+                        "argument type mismatch was detected, \
+                        but rustc had trouble determining where",
+                    )
+                    .note(
+                        "we would appreciate a bug report: \
+                        https://github.com/rust-lang/rust-clippy/issues/new",
+                    )
+                    .emit();
+            }
+            return;
+        }
+
+        errors.drain_filter(|error| {
+                let Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(error)) = error else { return false };
+                let (provided_ty, provided_span) = provided_arg_tys[*provided_idx];
+                let (expected_ty, _) = formal_and_expected_inputs[*expected_idx];
+                let cause = &self.misc(provided_span);
+                let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+                if let Some(e) = error {
+                    if !matches!(trace.cause.as_failure_code(e), FailureCode::Error0308(_)) {
+                        self.report_and_explain_type_error(trace, e).emit();
+                        return true;
+                    }
+                }
+                false
+            });
+
+        // We're done if we found errors, but we already emitted them.
+        if errors.is_empty() {
+            return;
+        }
+
+        // Okay, now that we've emitted the special errors separately, we
+        // are only left missing/extra/swapped and mismatched arguments, both
+        // can be collated pretty easily if needed.
+
+        // Next special case: if there is only one "Incompatible" error, just emit that
+        if let [
+            Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(err))),
+        ] = &errors[..]
+        {
+            let (formal_ty, expected_ty) = formal_and_expected_inputs[*expected_idx];
+            let (provided_ty, provided_arg_span) = provided_arg_tys[*provided_idx];
+            let cause = &self.misc(provided_arg_span);
+            let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+            let mut err = self.report_and_explain_type_error(trace, err);
+            self.emit_coerce_suggestions(
+                &mut err,
+                &provided_args[*provided_idx],
+                provided_ty,
+                Expectation::rvalue_hint(self, expected_ty)
+                    .only_has_type(self)
+                    .unwrap_or(formal_ty),
+                None,
+                None,
+            );
+            err.span_label(
+                full_call_span,
+                format!("arguments to this {} are incorrect", call_name),
+            );
+            // Call out where the function is defined
+            label_fn_like(tcx, &mut err, fn_def_id);
+            err.emit();
+            return;
+        }
+
+        // Second, let's try tuple wrapping the args.
+        // FIXME(compiler-errors): This is currently in its own closure because
+        // I didn't want to factor it out.
+        if try_tuple_wrap_args() {
+            return;
+        }
+
+        let mut err = if formal_and_expected_inputs.len() == provided_args.len() {
+            struct_span_err!(
+                tcx.sess,
+                full_call_span,
+                E0308,
+                "arguments to this {} are incorrect",
+                call_name,
+            )
+        } else {
+            tcx.sess.struct_span_err_with_code(
+                full_call_span,
+                &format!(
+                    "this {} takes {}{} but {} {} supplied",
+                    call_name,
+                    if c_variadic { "at least " } else { "" },
+                    potentially_plural_count(formal_and_expected_inputs.len(), "argument"),
+                    potentially_plural_count(provided_args.len(), "argument"),
+                    if provided_args.len() == 1 { "was" } else { "were" }
+                ),
+                DiagnosticId::Error(err_code.to_owned()),
+            )
+        };
+
+        // As we encounter issues, keep track of what we want to provide for the suggestion
+        let mut labels = vec![];
+        // If there is a single error, we give a specific suggestion; otherwise, we change to
+        // "did you mean" with the suggested function call
+        enum SuggestionText {
+            None,
+            Provide(bool),
+            Remove(bool),
+            Swap,
+            Reorder,
+            DidYouMean,
+        }
+        let mut suggestion_text = SuggestionText::None;
+
+        let mut errors = errors.into_iter().peekable();
+        while let Some(error) = errors.next() {
+            match error {
+                Error::Invalid(provided_idx, expected_idx, compatibility) => {
+                    let (formal_ty, expected_ty) = formal_and_expected_inputs[expected_idx];
+                    let (provided_ty, provided_span) = provided_arg_tys[provided_idx];
+                    if let Compatibility::Incompatible(error) = &compatibility {
+                        let cause = &self.misc(provided_span);
+                        let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+                        if let Some(e) = error {
+                            self.note_type_err(
+                                &mut err,
+                                &trace.cause,
+                                None,
+                                Some(trace.values),
+                                e,
+                                false,
+                                true,
+                            );
+                        }
+                    }
 
-            // Okay, now that we've emitted the special errors separately, we
-            // are only left missing/extra/swapped and mismatched arguments, both
-            // can be collated pretty easily if needed.
-
-            // Next special case: if there is only one "Incompatible" error, just emit that
-            if errors.len() == 1 {
-                if let Some(Error::Invalid(
-                    input_idx,
-                    arg_idx,
-                    Compatibility::Incompatible(Some(error)),
-                )) = errors.iter().next()
-                {
-                    let expected_ty = expected_input_tys[*arg_idx];
-                    let provided_ty = final_arg_types[*input_idx]
-                        .map(|ty| ty.0)
-                        .unwrap_or_else(|| tcx.ty_error());
-                    let expected_ty = self.resolve_vars_if_possible(expected_ty);
-                    let provided_ty = self.resolve_vars_if_possible(provided_ty);
-                    let cause = &self.misc(provided_args[*input_idx].span);
-                    let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
-                    let mut err = self.report_and_explain_type_error(trace, error);
                     self.emit_coerce_suggestions(
                         &mut err,
-                        &provided_args[*input_idx],
+                        &provided_args[provided_idx],
                         provided_ty,
-                        final_arg_types[*input_idx]
-                            .map(|ty| ty.1)
-                            .unwrap_or_else(|| tcx.ty_error()),
+                        Expectation::rvalue_hint(self, expected_ty)
+                            .only_has_type(self)
+                            .unwrap_or(formal_ty),
                         None,
                         None,
                     );
-                    err.span_label(
-                        full_call_span,
-                        format!("arguments to this {} are incorrect", call_name),
-                    );
-                    // Call out where the function is defined
-                    label_fn_like(tcx, &mut err, fn_def_id);
-                    err.emit();
-                    break 'errors;
                 }
-            }
-
-            let mut err = if minimum_input_count == provided_arg_count {
-                struct_span_err!(
-                    tcx.sess,
-                    full_call_span,
-                    E0308,
-                    "arguments to this {} are incorrect",
-                    call_name,
-                )
-            } else {
-                tcx.sess.struct_span_err_with_code(
-                    full_call_span,
-                    &format!(
-                        "this {} takes {}{} but {} {} supplied",
-                        call_name,
-                        if c_variadic { "at least " } else { "" },
-                        potentially_plural_count(minimum_input_count, "argument"),
-                        potentially_plural_count(provided_arg_count, "argument"),
-                        if provided_arg_count == 1 { "was" } else { "were" }
-                    ),
-                    DiagnosticId::Error(err_code.to_owned()),
-                )
-            };
-
-            // As we encounter issues, keep track of what we want to provide for the suggestion
-            let mut labels = vec![];
-            // If there is a single error, we give a specific suggestion; otherwise, we change to
-            // "did you mean" with the suggested function call
-            enum SuggestionText {
-                None,
-                Provide(bool),
-                Remove(bool),
-                Swap,
-                Reorder,
-                DidYouMean,
-            }
-            let mut suggestion_text = SuggestionText::None;
-
-            let mut errors = errors.into_iter().peekable();
-            while let Some(error) = errors.next() {
-                match error {
-                    Error::Invalid(input_idx, arg_idx, compatibility) => {
-                        let expected_ty = expected_input_tys[arg_idx];
-                        let provided_ty = final_arg_types[input_idx]
-                            .map(|ty| ty.0)
-                            .unwrap_or_else(|| tcx.ty_error());
-                        let expected_ty = self.resolve_vars_if_possible(expected_ty);
-                        let provided_ty = self.resolve_vars_if_possible(provided_ty);
-                        if let Compatibility::Incompatible(error) = &compatibility {
-                            let cause = &self.misc(provided_args[input_idx].span);
-                            let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
-                            if let Some(e) = error {
-                                self.note_type_err(
-                                    &mut err,
-                                    &trace.cause,
-                                    None,
-                                    Some(trace.values),
-                                    e,
-                                    false,
-                                    true,
-                                );
-                            }
+                Error::Extra(arg_idx) => {
+                    let (provided_ty, provided_span) = provided_arg_tys[arg_idx];
+                    let provided_ty_name = if !has_error_or_infer([provided_ty]) {
+                        // FIXME: not suggestable, use something else
+                        format!(" of type `{}`", provided_ty)
+                    } else {
+                        "".to_string()
+                    };
+                    labels
+                        .push((provided_span, format!("argument{} unexpected", provided_ty_name)));
+                    suggestion_text = match suggestion_text {
+                        SuggestionText::None => SuggestionText::Remove(false),
+                        SuggestionText::Remove(_) => SuggestionText::Remove(true),
+                        _ => SuggestionText::DidYouMean,
+                    };
+                }
+                Error::Missing(expected_idx) => {
+                    // If there are multiple missing arguments adjacent to each other,
+                    // then we can provide a single error.
+
+                    let mut missing_idxs = vec![expected_idx];
+                    while let Some(e) = errors.next_if(|e| {
+                        matches!(e, Error::Missing(next_expected_idx)
+                            if *next_expected_idx == *missing_idxs.last().unwrap() + 1)
+                    }) {
+                        match e {
+                            Error::Missing(expected_idx) => missing_idxs.push(expected_idx),
+                            _ => unreachable!(),
                         }
-
-                        self.emit_coerce_suggestions(
-                            &mut err,
-                            &provided_args[input_idx],
-                            provided_ty,
-                            // FIXME(compiler-errors): expected_ty?
-                            final_arg_types[input_idx]
-                                .map(|ty| ty.1)
-                                .unwrap_or_else(|| tcx.ty_error()),
-                            None,
-                            None,
-                        );
                     }
-                    Error::Extra(arg_idx) => {
-                        let arg_type = if let Some((_, ty)) = final_arg_types[arg_idx] {
-                            if ty.references_error() || ty.has_infer_types() {
-                                "".into()
+
+                    // NOTE: Because we might be re-arranging arguments, might have extra
+                    // arguments, etc. it's hard to *really* know where we should provide
+                    // this error label, so as a heuristic, we point to the provided arg, or
+                    // to the call if the missing inputs pass the provided args.
+                    match &missing_idxs[..] {
+                        &[expected_idx] => {
+                            let (_, input_ty) = formal_and_expected_inputs[expected_idx];
+                            let span = if let Some((_, arg_span)) =
+                                provided_arg_tys.get(expected_idx.to_provided_idx())
+                            {
+                                *arg_span
                             } else {
-                                format!(" of type `{}`", ty)
-                            }
-                        } else {
-                            "".into()
-                        };
-                        labels.push((
-                            provided_args[arg_idx].span,
-                            format!("argument{} unexpected", arg_type),
-                        ));
-                        suggestion_text = match suggestion_text {
-                            SuggestionText::None => SuggestionText::Remove(false),
-                            SuggestionText::Remove(_) => SuggestionText::Remove(true),
-                            _ => SuggestionText::DidYouMean,
-                        };
-                    }
-                    Error::Missing(input_idx) => {
-                        // If there are multiple missing arguments adjacent to each other,
-                        // then we can provide a single error.
-
-                        let mut missing_idxs = vec![input_idx];
-                        while let Some(e) = errors.next_if(|e| matches!(e, Error::Missing(input_idx) if *input_idx == (missing_idxs.last().unwrap() + 1))) {
-                            match e {
-                                Error::Missing(input_idx) => missing_idxs.push(input_idx),
-                                _ => unreachable!(),
-                            }
+                                args_span
+                            };
+                            let rendered = if !has_error_or_infer([input_ty]) {
+                                format!(" of type `{}`", input_ty)
+                            } else {
+                                "".to_string()
+                            };
+                            labels.push((span, format!("an argument{} is missing", rendered)));
+                            suggestion_text = match suggestion_text {
+                                SuggestionText::None => SuggestionText::Provide(false),
+                                SuggestionText::Provide(_) => SuggestionText::Provide(true),
+                                _ => SuggestionText::DidYouMean,
+                            };
                         }
-
-                        // NOTE: Because we might be re-arranging arguments, might have extra
-                        // arguments, etc. it's hard to *really* know where we should provide
-                        // this error label, so as a heuristic, we point to the provided arg, or
-                        // to the call if the missing inputs pass the provided args.
-                        match &missing_idxs[..] {
-                            &[input_idx] => {
-                                let expected_ty = expected_input_tys[input_idx];
-                                let input_ty = self.resolve_vars_if_possible(expected_ty);
-                                let span = if input_idx < provided_arg_count {
-                                    let arg_span = provided_args[input_idx].span;
-                                    Span::new(arg_span.lo(), arg_span.hi(), arg_span.ctxt(), None)
-                                } else {
-                                    args_span
-                                };
-                                let arg_type =
-                                    if input_ty.references_error() || input_ty.has_infer_types() {
-                                        "".into()
-                                    } else {
-                                        format!(" of type `{}`", input_ty)
-                                    };
-                                labels.push((span, format!("an argument{} is missing", arg_type)));
-                                suggestion_text = match suggestion_text {
-                                    SuggestionText::None => SuggestionText::Provide(false),
-                                    SuggestionText::Provide(_) => SuggestionText::Provide(true),
-                                    _ => SuggestionText::DidYouMean,
-                                };
-                            }
-                            &[first_idx, second_idx] => {
-                                let first_input_ty =
-                                    self.resolve_vars_if_possible(expected_input_tys[first_idx]);
-                                let second_input_ty =
-                                    self.resolve_vars_if_possible(expected_input_tys[second_idx]);
-
-                                let span = if second_idx < provided_arg_count {
-                                    let first_arg_span = provided_args[first_idx].span;
-                                    let second_arg_span = provided_args[second_idx].span;
-                                    Span::new(
-                                        first_arg_span.lo(),
-                                        second_arg_span.hi(),
-                                        first_arg_span.ctxt(),
-                                        None,
-                                    )
-                                } else {
-                                    args_span
-                                };
-                                let any_unnameable = false
-                                    || first_input_ty.references_error()
-                                    || first_input_ty.has_infer_types()
-                                    || second_input_ty.references_error()
-                                    || second_input_ty.has_infer_types();
-                                let arg_type = if any_unnameable {
-                                    "".into()
-                                } else {
+                        &[first_idx, second_idx] => {
+                            let (_, first_expected_ty) = formal_and_expected_inputs[first_idx];
+                            let (_, second_expected_ty) = formal_and_expected_inputs[second_idx];
+                            let span = if let (Some((_, first_span)), Some((_, second_span))) = (
+                                provided_arg_tys.get(first_idx.to_provided_idx()),
+                                provided_arg_tys.get(second_idx.to_provided_idx()),
+                            ) {
+                                first_span.to(*second_span)
+                            } else {
+                                args_span
+                            };
+                            let rendered =
+                                if !has_error_or_infer([first_expected_ty, second_expected_ty]) {
                                     format!(
                                         " of type `{}` and `{}`",
-                                        first_input_ty, second_input_ty
-                                    )
-                                };
-                                labels
-                                    .push((span, format!("two arguments{} are missing", arg_type)));
-                                suggestion_text = match suggestion_text {
-                                    SuggestionText::None | SuggestionText::Provide(_) => {
-                                        SuggestionText::Provide(true)
-                                    }
-                                    _ => SuggestionText::DidYouMean,
-                                };
-                            }
-                            &[first_idx, second_idx, third_idx] => {
-                                let first_input_ty =
-                                    self.resolve_vars_if_possible(expected_input_tys[first_idx]);
-                                let second_input_ty =
-                                    self.resolve_vars_if_possible(expected_input_tys[second_idx]);
-                                let third_input_ty =
-                                    self.resolve_vars_if_possible(expected_input_tys[third_idx]);
-                                let span = if third_idx < provided_arg_count {
-                                    let first_arg_span = provided_args[first_idx].span;
-                                    let third_arg_span = provided_args[third_idx].span;
-                                    Span::new(
-                                        first_arg_span.lo(),
-                                        third_arg_span.hi(),
-                                        first_arg_span.ctxt(),
-                                        None,
-                                    )
-                                } else {
-                                    args_span
-                                };
-                                let any_unnameable = false
-                                    || first_input_ty.references_error()
-                                    || first_input_ty.has_infer_types()
-                                    || second_input_ty.references_error()
-                                    || second_input_ty.has_infer_types()
-                                    || third_input_ty.references_error()
-                                    || third_input_ty.has_infer_types();
-                                let arg_type = if any_unnameable {
-                                    "".into()
-                                } else {
-                                    format!(
-                                        " of type `{}`, `{}`, and `{}`",
-                                        first_input_ty, second_input_ty, third_input_ty
-                                    )
-                                };
-                                labels.push((
-                                    span,
-                                    format!("three arguments{} are missing", arg_type),
-                                ));
-                                suggestion_text = match suggestion_text {
-                                    SuggestionText::None | SuggestionText::Provide(_) => {
-                                        SuggestionText::Provide(true)
-                                    }
-                                    _ => SuggestionText::DidYouMean,
-                                };
-                            }
-                            missing_idxs => {
-                                let first_idx = *missing_idxs.first().unwrap();
-                                let last_idx = *missing_idxs.last().unwrap();
-                                // NOTE: Because we might be re-arranging arguments, might have extra arguments, etc.
-                                // It's hard to *really* know where we should provide this error label, so this is a
-                                // decent heuristic
-                                let span = if last_idx < provided_arg_count {
-                                    let first_arg_span = provided_args[first_idx].span;
-                                    let last_arg_span = provided_args[last_idx].span;
-                                    Span::new(
-                                        first_arg_span.lo(),
-                                        last_arg_span.hi(),
-                                        first_arg_span.ctxt(),
-                                        None,
+                                        first_expected_ty, second_expected_ty
                                     )
                                 } else {
-                                    // Otherwise just label the whole function
-                                    args_span
-                                };
-                                labels.push((span, format!("multiple arguments are missing")));
-                                suggestion_text = match suggestion_text {
-                                    SuggestionText::None | SuggestionText::Provide(_) => {
-                                        SuggestionText::Provide(true)
-                                    }
-                                    _ => SuggestionText::DidYouMean,
+                                    "".to_string()
                                 };
-                            }
+                            labels.push((span, format!("two arguments{} are missing", rendered)));
+                            suggestion_text = match suggestion_text {
+                                SuggestionText::None | SuggestionText::Provide(_) => {
+                                    SuggestionText::Provide(true)
+                                }
+                                _ => SuggestionText::DidYouMean,
+                            };
                         }
-                    }
-                    Error::Swap(input_idx, other_input_idx, arg_idx, other_arg_idx) => {
-                        let first_span = provided_args[input_idx].span;
-                        let second_span = provided_args[other_input_idx].span;
-
-                        let first_expected_ty =
-                            self.resolve_vars_if_possible(expected_input_tys[arg_idx]);
-                        let first_provided_ty = if let Some((ty, _)) = final_arg_types[input_idx] {
-                            format!(", found `{}`", ty)
-                        } else {
-                            String::new()
-                        };
-                        labels.push((
-                            first_span,
-                            format!("expected `{}`{}", first_expected_ty, first_provided_ty),
-                        ));
-                        let other_expected_ty =
-                            self.resolve_vars_if_possible(expected_input_tys[other_arg_idx]);
-                        let other_provided_ty =
-                            if let Some((ty, _)) = final_arg_types[other_input_idx] {
-                                format!(", found `{}`", ty)
+                        &[first_idx, second_idx, third_idx] => {
+                            let (_, first_expected_ty) = formal_and_expected_inputs[first_idx];
+                            let (_, second_expected_ty) = formal_and_expected_inputs[second_idx];
+                            let (_, third_expected_ty) = formal_and_expected_inputs[third_idx];
+                            let span = if let (Some((_, first_span)), Some((_, third_span))) = (
+                                provided_arg_tys.get(first_idx.to_provided_idx()),
+                                provided_arg_tys.get(third_idx.to_provided_idx()),
+                            ) {
+                                first_span.to(*third_span)
                             } else {
-                                String::new()
+                                args_span
                             };
-                        labels.push((
-                            second_span,
-                            format!("expected `{}`{}", other_expected_ty, other_provided_ty),
-                        ));
-                        suggestion_text = match suggestion_text {
-                            SuggestionText::None => SuggestionText::Swap,
-                            _ => SuggestionText::DidYouMean,
-                        };
-                    }
-                    Error::Permutation(args) => {
-                        for (dst_arg, dest_input) in args {
-                            let expected_ty =
-                                self.resolve_vars_if_possible(expected_input_tys[dst_arg]);
-                            let provided_ty = if let Some((ty, _)) = final_arg_types[dest_input] {
-                                format!(", found `{}`", ty)
+                            let rendered = if !has_error_or_infer([
+                                first_expected_ty,
+                                second_expected_ty,
+                                third_expected_ty,
+                            ]) {
+                                format!(
+                                    " of type `{}`, `{}`, and `{}`",
+                                    first_expected_ty, second_expected_ty, third_expected_ty
+                                )
                             } else {
-                                String::new()
+                                "".to_string()
+                            };
+                            labels.push((span, format!("three arguments{} are missing", rendered)));
+                            suggestion_text = match suggestion_text {
+                                SuggestionText::None | SuggestionText::Provide(_) => {
+                                    SuggestionText::Provide(true)
+                                }
+                                _ => SuggestionText::DidYouMean,
+                            };
+                        }
+                        missing_idxs => {
+                            let first_idx = *missing_idxs.first().unwrap();
+                            let last_idx = *missing_idxs.last().unwrap();
+                            // NOTE: Because we might be re-arranging arguments, might have extra arguments, etc.
+                            // It's hard to *really* know where we should provide this error label, so this is a
+                            // decent heuristic
+                            let span = if let (Some((_, first_span)), Some((_, last_span))) = (
+                                provided_arg_tys.get(first_idx.to_provided_idx()),
+                                provided_arg_tys.get(last_idx.to_provided_idx()),
+                            ) {
+                                first_span.to(*last_span)
+                            } else {
+                                args_span
+                            };
+                            labels.push((span, format!("multiple arguments are missing")));
+                            suggestion_text = match suggestion_text {
+                                SuggestionText::None | SuggestionText::Provide(_) => {
+                                    SuggestionText::Provide(true)
+                                }
+                                _ => SuggestionText::DidYouMean,
                             };
-                            labels.push((
-                                provided_args[dest_input].span,
-                                format!("expected `{}`{}", expected_ty, provided_ty),
-                            ));
                         }
-
-                        suggestion_text = match suggestion_text {
-                            SuggestionText::None => SuggestionText::Reorder,
-                            _ => SuggestionText::DidYouMean,
-                        };
                     }
                 }
-            }
-
-            // If we have less than 5 things to say, it would be useful to call out exactly what's wrong
-            if labels.len() <= 5 {
-                for (span, label) in labels {
-                    err.span_label(span, label);
-                }
-            }
-
-            // Call out where the function is defined
-            label_fn_like(tcx, &mut err, fn_def_id);
-
-            // And add a suggestion block for all of the parameters
-            let suggestion_text = match suggestion_text {
-                SuggestionText::None => None,
-                SuggestionText::Provide(plural) => {
-                    Some(format!("provide the argument{}", if plural { "s" } else { "" }))
-                }
-                SuggestionText::Remove(plural) => {
-                    Some(format!("remove the extra argument{}", if plural { "s" } else { "" }))
-                }
-                SuggestionText::Swap => Some("swap these arguments".to_string()),
-                SuggestionText::Reorder => Some("reorder these arguments".to_string()),
-                SuggestionText::DidYouMean => Some("did you mean".to_string()),
-            };
-            if let Some(suggestion_text) = suggestion_text {
-                let source_map = self.sess().source_map();
-                let mut suggestion = format!(
-                    "{}(",
-                    source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| String::new())
-                );
-                for (arg_index, input_idx) in matched_inputs.iter().enumerate() {
-                    let suggestion_text = if let Some(input_idx) = input_idx {
-                        let arg_span = provided_args[*input_idx].span.source_callsite();
-                        let arg_text = source_map.span_to_snippet(arg_span).unwrap();
-                        arg_text
+                Error::Swap(
+                    first_provided_idx,
+                    second_provided_idx,
+                    first_expected_idx,
+                    second_expected_idx,
+                ) => {
+                    let (first_provided_ty, first_span) = provided_arg_tys[first_provided_idx];
+                    let (_, first_expected_ty) = formal_and_expected_inputs[first_expected_idx];
+                    let first_provided_ty_name = if !has_error_or_infer([first_provided_ty]) {
+                        format!(", found `{}`", first_provided_ty)
                     } else {
-                        // Propose a placeholder of the correct type
-                        let expected_ty = expected_input_tys[arg_index];
-                        let input_ty = self.resolve_vars_if_possible(expected_ty);
-                        if input_ty.is_unit() {
-                            "()".to_string()
-                        } else if !input_ty.is_ty_var() {
-                            format!("/* {} */", input_ty)
+                        String::new()
+                    };
+                    labels.push((
+                        first_span,
+                        format!("expected `{}`{}", first_expected_ty, first_provided_ty_name),
+                    ));
+
+                    let (second_provided_ty, second_span) = provided_arg_tys[second_provided_idx];
+                    let (_, second_expected_ty) = formal_and_expected_inputs[second_expected_idx];
+                    let second_provided_ty_name = if !has_error_or_infer([second_provided_ty]) {
+                        format!(", found `{}`", second_provided_ty)
+                    } else {
+                        String::new()
+                    };
+                    labels.push((
+                        second_span,
+                        format!("expected `{}`{}", second_expected_ty, second_provided_ty_name),
+                    ));
+
+                    suggestion_text = match suggestion_text {
+                        SuggestionText::None => SuggestionText::Swap,
+                        _ => SuggestionText::DidYouMean,
+                    };
+                }
+                Error::Permutation(args) => {
+                    for (dst_arg, dest_input) in args {
+                        let (_, expected_ty) = formal_and_expected_inputs[dst_arg];
+                        let (provided_ty, provided_span) = provided_arg_tys[dest_input];
+                        let provided_ty_name = if !has_error_or_infer([provided_ty]) {
+                            format!(", found `{}`", provided_ty)
                         } else {
-                            "/* value */".to_string()
+                            String::new()
+                        };
+                        // FIXME(compiler-errors): Why do we get permutations with the same type?
+                        if expected_ty != provided_ty {
+                            labels.push((
+                                provided_span,
+                                format!("expected `{}`{}", expected_ty, provided_ty_name),
+                            ));
                         }
-                    };
-                    suggestion += &suggestion_text;
-                    if arg_index < minimum_input_count - 1 {
-                        suggestion += ", ";
                     }
+
+                    suggestion_text = match suggestion_text {
+                        SuggestionText::None => SuggestionText::Reorder,
+                        _ => SuggestionText::DidYouMean,
+                    };
                 }
-                suggestion += ")";
-                err.span_suggestion_verbose(
-                    error_span,
-                    &suggestion_text,
-                    suggestion,
-                    Applicability::HasPlaceholders,
-                );
             }
-            err.emit();
         }
 
-        for arg in provided_args.iter().skip(minimum_input_count) {
-            let arg_ty = self.check_expr(&arg);
+        // If we have less than 5 things to say, it would be useful to call out exactly what's wrong
+        if labels.len() <= 5 {
+            for (span, label) in labels {
+                err.span_label(span, label);
+            }
+        }
 
-            // If the function is c-style variadic, we skipped a bunch of arguments
-            // so we need to check those, and write out the types
-            // Ideally this would be folded into the above, for uniform style
-            // but c-variadic is already a corner case
-            if c_variadic {
-                fn variadic_error<'tcx>(
-                    sess: &'tcx Session,
-                    span: Span,
-                    ty: Ty<'tcx>,
-                    cast_ty: &str,
-                ) {
-                    use crate::structured_errors::MissingCastForVariadicArg;
+        // Call out where the function is defined
+        label_fn_like(tcx, &mut err, fn_def_id);
 
-                    MissingCastForVariadicArg { sess, span, ty, cast_ty }.diagnostic().emit();
+        // And add a suggestion block for all of the parameters
+        let suggestion_text = match suggestion_text {
+            SuggestionText::None => None,
+            SuggestionText::Provide(plural) => {
+                Some(format!("provide the argument{}", if plural { "s" } else { "" }))
+            }
+            SuggestionText::Remove(plural) => {
+                Some(format!("remove the extra argument{}", if plural { "s" } else { "" }))
+            }
+            SuggestionText::Swap => Some("swap these arguments".to_string()),
+            SuggestionText::Reorder => Some("reorder these arguments".to_string()),
+            SuggestionText::DidYouMean => Some("did you mean".to_string()),
+        };
+        if let Some(suggestion_text) = suggestion_text {
+            let source_map = self.sess().source_map();
+            let mut suggestion = format!(
+                "{}(",
+                source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| fn_def_id
+                    .map_or("".to_string(), |fn_def_id| tcx.item_name(fn_def_id).to_string()))
+            );
+            let mut needs_comma = false;
+            for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() {
+                if needs_comma {
+                    suggestion += ", ";
+                } else {
+                    needs_comma = true;
                 }
-
-                // There are a few types which get autopromoted when passed via varargs
-                // in C but we just error out instead and require explicit casts.
-                let arg_ty = self.structurally_resolved_type(arg.span, arg_ty);
-                match arg_ty.kind() {
-                    ty::Float(ty::FloatTy::F32) => {
-                        variadic_error(tcx.sess, arg.span, arg_ty, "c_double");
-                    }
-                    ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Bool => {
-                        variadic_error(tcx.sess, arg.span, arg_ty, "c_int");
-                    }
-                    ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) => {
-                        variadic_error(tcx.sess, arg.span, arg_ty, "c_uint");
-                    }
-                    ty::FnDef(..) => {
-                        let ptr_ty = self.tcx.mk_fn_ptr(arg_ty.fn_sig(self.tcx));
-                        let ptr_ty = self.resolve_vars_if_possible(ptr_ty);
-                        variadic_error(tcx.sess, arg.span, arg_ty, &ptr_ty.to_string());
+                let suggestion_text =
+                    if let Some(provided_idx) = provided_idx
+                    && let (_, provided_span) = provided_arg_tys[*provided_idx]
+                    && let Ok(arg_text) = source_map.span_to_snippet(provided_span.source_callsite()) {
+                    arg_text
+                } else {
+                    // Propose a placeholder of the correct type
+                    let (_, expected_ty) = formal_and_expected_inputs[expected_idx];
+                    if expected_ty.is_unit() {
+                        "()".to_string()
+                    } else if expected_ty.is_suggestable(tcx) {
+                        format!("/* {} */", expected_ty)
+                    } else {
+                        "/* value */".to_string()
                     }
-                    _ => {}
-                }
+                };
+                suggestion += &suggestion_text;
             }
+            suggestion += ")";
+            err.span_suggestion_verbose(
+                error_span,
+                &suggestion_text,
+                suggestion,
+                Applicability::HasPlaceholders,
+            );
         }
+
+        err.emit();
     }
 
     fn suggested_tuple_wrap(
diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs
index f09c7f51f47..6ee989070b4 100644
--- a/compiler/rustc_typeck/src/check/generator_interior.rs
+++ b/compiler/rustc_typeck/src/check/generator_interior.rs
@@ -225,7 +225,7 @@ pub fn resolve_interior<'a, 'tcx>(
                 // Note that each region slot in the types gets a new fresh late bound region,
                 // which means that none of the regions inside relate to any other, even if
                 // typeck had previously found constraints that would cause them to be related.
-                let folded = fcx.tcx.fold_regions(erased, &mut false, |_, current_depth| {
+                let folded = fcx.tcx.fold_regions(erased, |_, current_depth| {
                     let br = ty::BoundRegion {
                         var: ty::BoundVar::from_u32(counter),
                         kind: ty::BrAnon(counter),
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index 8880049371e..79a13ce2fca 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -346,19 +346,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     );
                 }
 
-                if let Some(def) = actual.ty_adt_def() {
-                    if let Some(full_sp) = tcx.hir().span_if_local(def.did()) {
-                        let def_sp = tcx.sess.source_map().guess_head_span(full_sp);
-                        err.span_label(
-                            def_sp,
-                            format!(
-                                "{} `{}` not found {}",
-                                item_kind,
-                                item_name,
-                                if def.is_enum() && !is_method { "here" } else { "for this" }
-                            ),
-                        );
+                let ty_span = match actual.kind() {
+                    ty::Param(param_type) => {
+                        let generics = self.tcx.generics_of(self.body_id.owner.to_def_id());
+                        let type_param = generics.type_param(param_type, self.tcx);
+                        Some(self.tcx.def_span(type_param.def_id))
+                    }
+                    ty::Adt(def, _) if def.did().is_local() => {
+                        tcx.def_ident_span(def.did()).map(|span| span)
                     }
+                    _ => None,
+                };
+
+                if let Some(span) = ty_span {
+                    err.span_label(
+                        span,
+                        format!(
+                            "{item_kind} `{item_name}` not found for this {}",
+                            actual.prefix_string(self.tcx)
+                        ),
+                    );
                 }
 
                 if self.is_fn_ty(rcvr_ty, span) {
@@ -987,6 +994,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         span,
                         rcvr_ty,
                         item_name,
+                        args.map(|args| args.len()),
                         source,
                         out_of_scope_traits,
                         &unsatisfied_predicates,
@@ -1725,6 +1733,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         span: Span,
         rcvr_ty: Ty<'tcx>,
         item_name: Ident,
+        inputs_len: Option<usize>,
         source: SelfSource<'tcx>,
         valid_out_of_scope_traits: Vec<DefId>,
         unsatisfied_predicates: &[(
@@ -1801,7 +1810,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         // Explicitly ignore the `Pin::as_ref()` method as `Pin` does not
                         // implement the `AsRef` trait.
                         let skip = skippable.contains(&did)
-                            || (("Pin::new" == *pre) && (sym::as_ref == item_name.name));
+                            || (("Pin::new" == *pre) && (sym::as_ref == item_name.name))
+                            || inputs_len.map_or(false, |inputs_len| pick.item.kind == ty::AssocKind::Fn && self.tcx.fn_sig(pick.item.def_id).skip_binder().inputs().len() != inputs_len);
                         // Make sure the method is defined for the *actual* receiver: we don't
                         // want to treat `Box<Self>` as a receiver if it only works because of
                         // an autoderef to `&self`
@@ -1951,9 +1961,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 )
             };
             // Obtain the span for `param` and use it for a structured suggestion.
-            if let (Some(param), Some(table)) = (param_type, self.in_progress_typeck_results) {
-                let table_owner = table.borrow().hir_owner;
-                let generics = self.tcx.generics_of(table_owner.to_def_id());
+            if let Some(param) = param_type {
+                let generics = self.tcx.generics_of(self.body_id.owner.to_def_id());
                 let type_param = generics.type_param(param, self.tcx);
                 let hir = self.tcx.hir();
                 if let Some(def_id) = type_param.def_id.as_local() {
diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs
index e26f211c1c1..0ede9ef7756 100644
--- a/compiler/rustc_typeck/src/check/mod.rs
+++ b/compiler/rustc_typeck/src/check/mod.rs
@@ -93,11 +93,7 @@ mod upvar;
 mod wfcheck;
 pub mod writeback;
 
-use check::{
-    check_abi, check_fn, check_impl_item_well_formed, check_item_well_formed, check_mod_item_types,
-    check_trait_item_well_formed,
-};
-pub use check::{check_item_type, check_wf_new};
+use check::{check_abi, check_fn, check_mod_item_types};
 pub use diverges::Diverges;
 pub use expectation::Expectation;
 pub use fn_ctxt::*;
@@ -245,6 +241,7 @@ impl<'tcx> EnclosingBreakables<'tcx> {
 
 pub fn provide(providers: &mut Providers) {
     method::provide(providers);
+    wfcheck::provide(providers);
     *providers = Providers {
         typeck_item_bodies,
         typeck_const_arg,
@@ -253,9 +250,6 @@ pub fn provide(providers: &mut Providers) {
         has_typeck_results,
         adt_destructor,
         used_trait_imports,
-        check_item_well_formed,
-        check_trait_item_well_formed,
-        check_impl_item_well_formed,
         check_mod_item_types,
         region_scope_tree,
         ..*providers
diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_typeck/src/check/pat.rs
index f45b94bcdff..e1ec9f13cd1 100644
--- a/compiler/rustc_typeck/src/check/pat.rs
+++ b/compiler/rustc_typeck/src/check/pat.rs
@@ -663,6 +663,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 ast::Mutability::Not => "",
             };
 
+            let mut_var_suggestion = 'block: {
+                if !matches!(mutbl, ast::Mutability::Mut) {
+                    break 'block None;
+                }
+
+                let ident_kind = match binding_parent {
+                    hir::Node::Param(_) => "parameter",
+                    hir::Node::Local(_) => "variable",
+                    hir::Node::Arm(_) => "binding",
+
+                    // Provide diagnostics only if the parent pattern is struct-like,
+                    // i.e. where `mut binding` makes sense
+                    hir::Node::Pat(Pat { kind, .. }) => match kind {
+                        PatKind::Struct(..)
+                        | PatKind::TupleStruct(..)
+                        | PatKind::Or(..)
+                        | PatKind::Tuple(..)
+                        | PatKind::Slice(..) => "binding",
+
+                        PatKind::Wild
+                        | PatKind::Binding(..)
+                        | PatKind::Path(..)
+                        | PatKind::Box(..)
+                        | PatKind::Ref(..)
+                        | PatKind::Lit(..)
+                        | PatKind::Range(..) => break 'block None,
+                    },
+
+                    // Don't provide suggestions in other cases
+                    _ => break 'block None,
+                };
+
+                Some((
+                    pat.span,
+                    format!("to declare a mutable {ident_kind} use"),
+                    format!("mut {binding}"),
+                ))
+
+            };
+
             match binding_parent {
                 // Check that there is explicit type (ie this is not a closure param with inferred type)
                 // so we don't suggest moving something to the type that does not exist
@@ -675,6 +715,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         ],
                         Applicability::MachineApplicable
                     );
+
+                    if let Some((sp, msg, sugg)) = mut_var_suggestion {
+                        err.span_note(sp, format!("{msg}: `{sugg}`"));
+                    }
                 }
                 hir::Node::Param(_) | hir::Node::Arm(_) | hir::Node::Pat(_) => {
                     // rely on match ergonomics or it might be nested `&&pat`
@@ -684,6 +728,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         "",
                         Applicability::MaybeIncorrect,
                     );
+
+                    if let Some((sp, msg, sugg)) = mut_var_suggestion {
+                        err.span_note(sp, format!("{msg}: `{sugg}`"));
+                    }
+                }
+                _ if let Some((sp, msg, sugg)) = mut_var_suggestion => {
+                    err.span_suggestion(sp, msg, sugg, Applicability::MachineApplicable);
                 }
                 _ => {} // don't provide suggestions in other cases #55175
             }
diff --git a/compiler/rustc_typeck/src/check/regionck.rs b/compiler/rustc_typeck/src/check/regionck.rs
index 161ec31793d..0ce63922098 100644
--- a/compiler/rustc_typeck/src/check/regionck.rs
+++ b/compiler/rustc_typeck/src/check/regionck.rs
@@ -366,7 +366,6 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
     fn resolve_regions_and_report_errors(&self) {
         self.infcx.process_registered_region_obligations(
             self.outlives_environment.region_bound_pairs_map(),
-            Some(self.tcx.lifetimes.re_root_empty),
             self.param_env,
         );
 
diff --git a/compiler/rustc_typeck/src/check/wfcheck.rs b/compiler/rustc_typeck/src/check/wfcheck.rs
index 79312153895..67eb88b73fd 100644
--- a/compiler/rustc_typeck/src/check/wfcheck.rs
+++ b/compiler/rustc_typeck/src/check/wfcheck.rs
@@ -7,21 +7,18 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed};
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::intravisit as hir_visit;
-use rustc_hir::intravisit::Visitor;
-use rustc_hir::itemlikevisit::ParItemLikeVisitor;
 use rustc_hir::lang_items::LangItem;
 use rustc_hir::ItemKind;
 use rustc_infer::infer::outlives::env::OutlivesEnvironment;
 use rustc_infer::infer::outlives::obligations::TypeOutlives;
 use rustc_infer::infer::region_constraints::GenericKind;
 use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
-use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts, Subst};
 use rustc_middle::ty::trait_def::TraitSpecializationKind;
 use rustc_middle::ty::{
-    self, AdtKind, EarlyBinder, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable,
-    TypeSuperFoldable, TypeVisitor,
+    self, AdtKind, DefIdTree, EarlyBinder, GenericParamDefKind, ToPredicate, Ty, TyCtxt,
+    TypeFoldable, TypeSuperFoldable, TypeVisitor,
 };
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::{sym, Ident, Symbol};
@@ -70,6 +67,23 @@ impl<'tcx> CheckWfFcxBuilder<'tcx> {
     }
 }
 
+fn check_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) {
+    let node = tcx.hir().expect_owner(def_id);
+    match node {
+        hir::OwnerNode::Crate(_) => {}
+        hir::OwnerNode::Item(item) => check_item(tcx, item),
+        hir::OwnerNode::TraitItem(item) => check_trait_item(tcx, item),
+        hir::OwnerNode::ImplItem(item) => check_impl_item(tcx, item),
+        hir::OwnerNode::ForeignItem(item) => check_foreign_item(tcx, item),
+    }
+
+    if let Some(generics) = node.generics() {
+        for param in generics.params {
+            check_param_wf(tcx, param)
+        }
+    }
+}
+
 /// Checks that the field types (in a struct def'n) or argument types (in an enum def'n) are
 /// well-formed, meaning that they do not require any constraints not declared in the struct
 /// definition itself. For example, this definition would be illegal:
@@ -84,8 +98,8 @@ impl<'tcx> CheckWfFcxBuilder<'tcx> {
 /// not included it frequently leads to confusing errors in fn bodies. So it's better to check
 /// the types first.
 #[instrument(skip(tcx), level = "debug")]
-pub fn check_item_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) {
-    let item = tcx.hir().expect_item(def_id);
+fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) {
+    let def_id = item.def_id;
 
     debug!(
         ?item.def_id,
@@ -156,20 +170,6 @@ pub fn check_item_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) {
         hir::ItemKind::Const(ty, ..) => {
             check_item_type(tcx, item.def_id, ty.span, false);
         }
-        hir::ItemKind::ForeignMod { items, .. } => {
-            for it in items.iter() {
-                let it = tcx.hir().foreign_item(it.id);
-                match it.kind {
-                    hir::ForeignItemKind::Fn(decl, ..) => {
-                        check_item_fn(tcx, it.def_id, it.ident, it.span, decl)
-                    }
-                    hir::ForeignItemKind::Static(ty, ..) => {
-                        check_item_type(tcx, it.def_id, ty.span, true)
-                    }
-                    hir::ForeignItemKind::Type => (),
-                }
-            }
-        }
         hir::ItemKind::Struct(ref struct_def, ref ast_generics) => {
             check_type_defn(tcx, item, false, |fcx| vec![fcx.non_enum_variant(struct_def)]);
 
@@ -191,13 +191,31 @@ pub fn check_item_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) {
         hir::ItemKind::TraitAlias(..) => {
             check_trait(tcx, item);
         }
+        // `ForeignItem`s are handled separately.
+        hir::ItemKind::ForeignMod { .. } => {}
         _ => {}
     }
 }
 
-pub fn check_trait_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
-    let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
-    let trait_item = tcx.hir().expect_trait_item(def_id);
+fn check_foreign_item(tcx: TyCtxt<'_>, item: &hir::ForeignItem<'_>) {
+    let def_id = item.def_id;
+
+    debug!(
+        ?item.def_id,
+        item.name = ? tcx.def_path_str(def_id.to_def_id())
+    );
+
+    match item.kind {
+        hir::ForeignItemKind::Fn(decl, ..) => {
+            check_item_fn(tcx, item.def_id, item.ident, item.span, decl)
+        }
+        hir::ForeignItemKind::Static(ty, ..) => check_item_type(tcx, item.def_id, ty.span, true),
+        hir::ForeignItemKind::Type => (),
+    }
+}
+
+fn check_trait_item(tcx: TyCtxt<'_>, trait_item: &hir::TraitItem<'_>) {
+    let def_id = trait_item.def_id;
 
     let (method_sig, span) = match trait_item.kind {
         hir::TraitItemKind::Fn(ref sig, _) => (Some(sig), trait_item.span),
@@ -207,7 +225,7 @@ pub fn check_trait_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
     check_object_unsafe_self_trait_by_name(tcx, trait_item);
     check_associated_item(tcx, trait_item.def_id, span, method_sig);
 
-    let encl_trait_def_id = tcx.hir().get_parent_item(hir_id);
+    let encl_trait_def_id = tcx.local_parent(def_id);
     let encl_trait = tcx.hir().expect_item(encl_trait_def_id);
     let encl_trait_def_id = encl_trait.def_id.to_def_id();
     let fn_lang_item_name = if Some(encl_trait_def_id) == tcx.lang_items().fn_trait() {
@@ -596,13 +614,7 @@ fn ty_known_to_outlive<'tcx>(
 ) -> bool {
     resolve_regions_with_wf_tys(tcx, id, param_env, &wf_tys, |infcx, region_bound_pairs| {
         let origin = infer::RelateParamBound(DUMMY_SP, ty, None);
-        let outlives = &mut TypeOutlives::new(
-            infcx,
-            tcx,
-            region_bound_pairs,
-            Some(infcx.tcx.lifetimes.re_root_empty),
-            param_env,
-        );
+        let outlives = &mut TypeOutlives::new(infcx, tcx, region_bound_pairs, None, param_env);
         outlives.type_must_outlive(origin, ty, region);
     })
 }
@@ -770,8 +782,8 @@ fn check_object_unsafe_self_trait_by_name(tcx: TyCtxt<'_>, item: &hir::TraitItem
     }
 }
 
-pub fn check_impl_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
-    let impl_item = tcx.hir().expect_impl_item(def_id);
+fn check_impl_item(tcx: TyCtxt<'_>, impl_item: &hir::ImplItem<'_>) {
+    let def_id = impl_item.def_id;
 
     let (method_sig, span) = match impl_item.kind {
         hir::ImplItemKind::Fn(ref sig, _) => (Some(sig), impl_item.span),
@@ -780,7 +792,7 @@ pub fn check_impl_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
         _ => (None, impl_item.span),
     };
 
-    check_associated_item(tcx, impl_item.def_id, span, method_sig);
+    check_associated_item(tcx, def_id, span, method_sig);
 }
 
 fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) {
@@ -990,6 +1002,15 @@ fn check_type_defn<'tcx, F>(
         let packed = tcx.adt_def(item.def_id).repr().packed();
 
         for variant in &variants {
+            // All field types must be well-formed.
+            for field in &variant.fields {
+                fcx.register_wf_obligation(
+                    field.ty.into(),
+                    field.span,
+                    ObligationCauseCode::WellFormed(Some(WellFormedLoc::Ty(field.def_id))),
+                )
+            }
+
             // For DST, or when drop needs to copy things around, all
             // intermediate types must be sized.
             let needs_drop_copy = || {
@@ -1006,6 +1027,7 @@ fn check_type_defn<'tcx, F>(
                     }
                 }
             };
+            // All fields (except for possibly the last) should be sized.
             let all_sized = all_sized || variant.fields.is_empty() || needs_drop_copy();
             let unsized_len = if all_sized { 0 } else { 1 };
             for (idx, field) in
@@ -1030,15 +1052,6 @@ fn check_type_defn<'tcx, F>(
                 );
             }
 
-            // All field types must be well-formed.
-            for field in &variant.fields {
-                fcx.register_wf_obligation(
-                    field.ty.into(),
-                    field.span,
-                    ObligationCauseCode::WellFormed(Some(WellFormedLoc::Ty(field.def_id))),
-                )
-            }
-
             // Explicit `enum` discriminant values must const-evaluate successfully.
             if let Some(discr_def_id) = variant.explicit_discr {
                 let discr_substs = InternalSubsts::identity_for_item(tcx, discr_def_id.to_def_id());
@@ -1827,67 +1840,12 @@ fn check_false_global_bounds(fcx: &FnCtxt<'_, '_>, mut span: Span, id: hir::HirI
     fcx.select_all_obligations_or_error();
 }
 
-#[derive(Clone, Copy)]
-pub struct CheckTypeWellFormedVisitor<'tcx> {
-    tcx: TyCtxt<'tcx>,
-}
-
-impl<'tcx> CheckTypeWellFormedVisitor<'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>) -> CheckTypeWellFormedVisitor<'tcx> {
-        CheckTypeWellFormedVisitor { tcx }
-    }
-}
-
-impl<'tcx> ParItemLikeVisitor<'tcx> for CheckTypeWellFormedVisitor<'tcx> {
-    fn visit_item(&self, i: &'tcx hir::Item<'tcx>) {
-        Visitor::visit_item(&mut self.clone(), i);
-    }
-
-    fn visit_trait_item(&self, trait_item: &'tcx hir::TraitItem<'tcx>) {
-        Visitor::visit_trait_item(&mut self.clone(), trait_item);
-    }
-
-    fn visit_impl_item(&self, impl_item: &'tcx hir::ImplItem<'tcx>) {
-        Visitor::visit_impl_item(&mut self.clone(), impl_item);
-    }
-
-    fn visit_foreign_item(&self, foreign_item: &'tcx hir::ForeignItem<'tcx>) {
-        Visitor::visit_foreign_item(&mut self.clone(), foreign_item)
-    }
-}
-
-impl<'tcx> Visitor<'tcx> for CheckTypeWellFormedVisitor<'tcx> {
-    type NestedFilter = nested_filter::OnlyBodies;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.tcx.hir()
-    }
-
-    #[instrument(skip(self, i), level = "debug")]
-    fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
-        trace!(?i);
-        self.tcx.ensure().check_item_well_formed(i.def_id);
-        hir_visit::walk_item(self, i);
-    }
-
-    #[instrument(skip(self, trait_item), level = "debug")]
-    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
-        trace!(?trait_item);
-        self.tcx.ensure().check_trait_item_well_formed(trait_item.def_id);
-        hir_visit::walk_trait_item(self, trait_item);
-    }
-
-    #[instrument(skip(self, impl_item), level = "debug")]
-    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
-        trace!(?impl_item);
-        self.tcx.ensure().check_impl_item_well_formed(impl_item.def_id);
-        hir_visit::walk_impl_item(self, impl_item);
-    }
-
-    fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
-        check_param_wf(self.tcx, p);
-        hir_visit::walk_generic_param(self, p);
-    }
+fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalDefId) {
+    let items = tcx.hir_module_items(module);
+    items.par_items(|item| tcx.ensure().check_well_formed(item.def_id));
+    items.par_impl_items(|item| tcx.ensure().check_well_formed(item.def_id));
+    items.par_trait_items(|item| tcx.ensure().check_well_formed(item.def_id));
+    items.par_foreign_items(|item| tcx.ensure().check_well_formed(item.def_id));
 }
 
 ///////////////////////////////////////////////////////////////////////////
@@ -1972,3 +1930,7 @@ fn error_392(
     err.span_label(span, "unused parameter");
     err
 }
+
+pub fn provide(providers: &mut Providers) {
+    *providers = Providers { check_mod_type_wf, check_well_formed, ..*providers };
+}
diff --git a/compiler/rustc_typeck/src/coherence/mod.rs b/compiler/rustc_typeck/src/coherence/mod.rs
index 3903448a007..447ec87f302 100644
--- a/compiler/rustc_typeck/src/coherence/mod.rs
+++ b/compiler/rustc_typeck/src/coherence/mod.rs
@@ -146,7 +146,7 @@ pub fn provide(providers: &mut Providers) {
     use self::builtin::coerce_unsized_info;
     use self::inherent_impls::{crate_incoherent_impls, crate_inherent_impls, inherent_impls};
     use self::inherent_impls_overlap::crate_inherent_impls_overlap_check;
-    use self::orphan::orphan_check_crate;
+    use self::orphan::orphan_check_impl;
 
     *providers = Providers {
         coherent_trait,
@@ -155,7 +155,7 @@ pub fn provide(providers: &mut Providers) {
         inherent_impls,
         crate_inherent_impls_overlap_check,
         coerce_unsized_info,
-        orphan_check_crate,
+        orphan_check_impl,
         ..*providers
     };
 }
@@ -171,21 +171,12 @@ fn coherent_trait(tcx: TyCtxt<'_>, def_id: DefId) {
 
         check_impl(tcx, impl_def_id, trait_ref);
         check_object_overlap(tcx, impl_def_id, trait_ref);
-    }
-    builtin::check_trait(tcx, def_id);
-}
 
-pub fn check_coherence(tcx: TyCtxt<'_>) {
-    tcx.sess.time("unsafety_checking", || unsafety::check(tcx));
-    tcx.ensure().orphan_check_crate(());
-
-    for &trait_def_id in tcx.all_local_trait_impls(()).keys() {
-        tcx.ensure().coherent_trait(trait_def_id);
+        tcx.sess.time("unsafety_checking", || unsafety::check_item(tcx, impl_def_id));
+        tcx.sess.time("orphan_checking", || tcx.ensure().orphan_check_impl(impl_def_id));
     }
 
-    // these queries are executed for side-effects (error reporting):
-    tcx.ensure().crate_inherent_impls(());
-    tcx.ensure().crate_inherent_impls_overlap_check(());
+    builtin::check_trait(tcx, def_id);
 }
 
 /// Checks whether an impl overlaps with the automatic `impl Trait for dyn Trait`.
diff --git a/compiler/rustc_typeck/src/coherence/orphan.rs b/compiler/rustc_typeck/src/coherence/orphan.rs
index 9ddfc8d5cc8..f3a043a08a3 100644
--- a/compiler/rustc_typeck/src/coherence/orphan.rs
+++ b/compiler/rustc_typeck/src/coherence/orphan.rs
@@ -18,26 +18,29 @@ use rustc_span::Span;
 use rustc_trait_selection::traits;
 use std::ops::ControlFlow;
 
-pub(super) fn orphan_check_crate(tcx: TyCtxt<'_>, (): ()) -> &[LocalDefId] {
-    let mut errors = Vec::new();
-    for (&trait_def_id, impls_of_trait) in tcx.all_local_trait_impls(()) {
-        for &impl_of_trait in impls_of_trait {
-            match orphan_check_impl(tcx, impl_of_trait) {
-                Ok(()) => {}
-                Err(_) => errors.push(impl_of_trait),
-            }
-        }
+#[instrument(skip(tcx), level = "debug")]
+pub(crate) fn orphan_check_impl(
+    tcx: TyCtxt<'_>,
+    impl_def_id: LocalDefId,
+) -> Result<(), ErrorGuaranteed> {
+    let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
+    if let Some(err) = trait_ref.error_reported() {
+        return Err(err);
+    }
 
-        if tcx.trait_is_auto(trait_def_id) {
-            lint_auto_trait_impls(tcx, trait_def_id, impls_of_trait);
-        }
+    let ret = do_orphan_check_impl(tcx, trait_ref, impl_def_id);
+    if tcx.trait_is_auto(trait_ref.def_id) {
+        lint_auto_trait_impl(tcx, trait_ref, impl_def_id);
     }
-    tcx.arena.alloc_slice(&errors)
+
+    ret
 }
 
-#[instrument(skip(tcx), level = "debug")]
-fn orphan_check_impl(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
-    let trait_ref = tcx.impl_trait_ref(def_id).unwrap();
+fn do_orphan_check_impl<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    trait_ref: ty::TraitRef<'tcx>,
+    def_id: LocalDefId,
+) -> Result<(), ErrorGuaranteed> {
     let trait_def_id = trait_ref.def_id;
 
     let item = tcx.hir().item(hir::ItemId { def_id });
@@ -329,89 +332,82 @@ fn emit_orphan_check_error<'tcx>(
 
 /// Lint impls of auto traits if they are likely to have
 /// unsound or surprising effects on auto impls.
-fn lint_auto_trait_impls(tcx: TyCtxt<'_>, trait_def_id: DefId, impls: &[LocalDefId]) {
-    let mut non_covering_impls = Vec::new();
-    for &impl_def_id in impls {
-        let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
-        if trait_ref.references_error() {
-            return;
-        }
+fn lint_auto_trait_impl<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    trait_ref: ty::TraitRef<'tcx>,
+    impl_def_id: LocalDefId,
+) {
+    if tcx.impl_polarity(impl_def_id) != ImplPolarity::Positive {
+        return;
+    }
 
-        if tcx.impl_polarity(impl_def_id) != ImplPolarity::Positive {
+    assert_eq!(trait_ref.substs.len(), 1);
+    let self_ty = trait_ref.self_ty();
+    let (self_type_did, substs) = match self_ty.kind() {
+        ty::Adt(def, substs) => (def.did(), substs),
+        _ => {
+            // FIXME: should also lint for stuff like `&i32` but
+            // considering that auto traits are unstable, that
+            // isn't too important for now as this only affects
+            // crates using `nightly`, and std.
             return;
         }
+    };
 
-        assert_eq!(trait_ref.substs.len(), 1);
-        let self_ty = trait_ref.self_ty();
-        let (self_type_did, substs) = match self_ty.kind() {
-            ty::Adt(def, substs) => (def.did(), substs),
-            _ => {
-                // FIXME: should also lint for stuff like `&i32` but
-                // considering that auto traits are unstable, that
-                // isn't too important for now as this only affects
-                // crates using `nightly`, and std.
-                continue;
-            }
-        };
+    // Impls which completely cover a given root type are fine as they
+    // disable auto impls entirely. So only lint if the substs
+    // are not a permutation of the identity substs.
+    let Err(arg) = tcx.uses_unique_generic_params(substs, IgnoreRegions::Yes) else {
+        // ok
+        return;
+    };
 
-        // Impls which completely cover a given root type are fine as they
-        // disable auto impls entirely. So only lint if the substs
-        // are not a permutation of the identity substs.
-        match tcx.uses_unique_generic_params(substs, IgnoreRegions::Yes) {
-            Ok(()) => {} // ok
-            Err(arg) => {
-                // Ideally:
-                //
-                // - compute the requirements for the auto impl candidate
-                // - check whether these are implied by the non covering impls
-                // - if not, emit the lint
-                //
-                // What we do here is a bit simpler:
-                //
-                // - badly check if an auto impl candidate definitely does not apply
-                //   for the given simplified type
-                // - if so, do not lint
-                if fast_reject_auto_impl(tcx, trait_def_id, self_ty) {
-                    // ok
-                } else {
-                    non_covering_impls.push((impl_def_id, self_type_did, arg));
-                }
-            }
-        }
+    // Ideally:
+    //
+    // - compute the requirements for the auto impl candidate
+    // - check whether these are implied by the non covering impls
+    // - if not, emit the lint
+    //
+    // What we do here is a bit simpler:
+    //
+    // - badly check if an auto impl candidate definitely does not apply
+    //   for the given simplified type
+    // - if so, do not lint
+    if fast_reject_auto_impl(tcx, trait_ref.def_id, self_ty) {
+        // ok
+        return;
     }
 
-    for &(impl_def_id, self_type_did, arg) in &non_covering_impls {
-        tcx.struct_span_lint_hir(
-            lint::builtin::SUSPICIOUS_AUTO_TRAIT_IMPLS,
-            tcx.hir().local_def_id_to_hir_id(impl_def_id),
-            tcx.def_span(impl_def_id),
-            |err| {
-                let item_span = tcx.def_span(self_type_did);
-                let self_descr = tcx.def_kind(self_type_did).descr(self_type_did);
-                let mut err = err.build(&format!(
-                    "cross-crate traits with a default impl, like `{}`, \
+    tcx.struct_span_lint_hir(
+        lint::builtin::SUSPICIOUS_AUTO_TRAIT_IMPLS,
+        tcx.hir().local_def_id_to_hir_id(impl_def_id),
+        tcx.def_span(impl_def_id),
+        |err| {
+            let item_span = tcx.def_span(self_type_did);
+            let self_descr = tcx.def_kind(self_type_did).descr(self_type_did);
+            let mut err = err.build(&format!(
+                "cross-crate traits with a default impl, like `{}`, \
                          should not be specialized",
-                    tcx.def_path_str(trait_def_id),
-                ));
-                match arg {
-                    ty::util::NotUniqueParam::DuplicateParam(arg) => {
-                        err.note(&format!("`{}` is mentioned multiple times", arg));
-                    }
-                    ty::util::NotUniqueParam::NotParam(arg) => {
-                        err.note(&format!("`{}` is not a generic parameter", arg));
-                    }
+                tcx.def_path_str(trait_ref.def_id),
+            ));
+            match arg {
+                ty::util::NotUniqueParam::DuplicateParam(arg) => {
+                    err.note(&format!("`{}` is mentioned multiple times", arg));
                 }
-                err.span_note(
-                    item_span,
-                    &format!(
-                        "try using the same sequence of generic parameters as the {} definition",
-                        self_descr,
-                    ),
-                );
-                err.emit();
-            },
-        );
-    }
+                ty::util::NotUniqueParam::NotParam(arg) => {
+                    err.note(&format!("`{}` is not a generic parameter", arg));
+                }
+            }
+            err.span_note(
+                item_span,
+                &format!(
+                    "try using the same sequence of generic parameters as the {} definition",
+                    self_descr,
+                ),
+            );
+            err.emit();
+        },
+    );
 }
 
 fn fast_reject_auto_impl<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId, self_ty: Ty<'tcx>) -> bool {
diff --git a/compiler/rustc_typeck/src/coherence/unsafety.rs b/compiler/rustc_typeck/src/coherence/unsafety.rs
index 3cfc96ccbfd..e45fb5fe41c 100644
--- a/compiler/rustc_typeck/src/coherence/unsafety.rs
+++ b/compiler/rustc_typeck/src/coherence/unsafety.rs
@@ -6,37 +6,18 @@ use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::Unsafety;
 use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::LocalDefId;
 
-pub fn check(tcx: TyCtxt<'_>) {
-    for id in tcx.hir().items() {
-        if matches!(tcx.def_kind(id.def_id), DefKind::Impl) {
-            let item = tcx.hir().item(id);
-            if let hir::ItemKind::Impl(ref impl_) = item.kind {
-                check_unsafety_coherence(
-                    tcx,
-                    item,
-                    Some(&impl_.generics),
-                    impl_.unsafety,
-                    impl_.polarity,
-                );
-            }
-        }
-    }
-}
+pub(super) fn check_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
+    debug_assert!(matches!(tcx.def_kind(def_id), DefKind::Impl));
+    let item = tcx.hir().expect_item(def_id);
+    let hir::ItemKind::Impl(ref impl_) = item.kind else { bug!() };
 
-fn check_unsafety_coherence<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    item: &hir::Item<'_>,
-    impl_generics: Option<&hir::Generics<'_>>,
-    unsafety: hir::Unsafety,
-    polarity: hir::ImplPolarity,
-) {
     if let Some(trait_ref) = tcx.impl_trait_ref(item.def_id) {
         let trait_def = tcx.trait_def(trait_ref.def_id);
-        let unsafe_attr = impl_generics.and_then(|generics| {
-            generics.params.iter().find(|p| p.pure_wrt_drop).map(|_| "may_dangle")
-        });
-        match (trait_def.unsafety, unsafe_attr, unsafety, polarity) {
+        let unsafe_attr =
+            impl_.generics.params.iter().find(|p| p.pure_wrt_drop).map(|_| "may_dangle");
+        match (trait_def.unsafety, unsafe_attr, impl_.unsafety, impl_.polarity) {
             (Unsafety::Normal, None, Unsafety::Unsafe, hir::ImplPolarity::Positive) => {
                 struct_span_err!(
                     tcx.sess,
diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs
index 1f2e6ad86bd..2a52167c597 100644
--- a/compiler/rustc_typeck/src/collect.rs
+++ b/compiler/rustc_typeck/src/collect.rs
@@ -393,7 +393,7 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> {
     }
 
     fn ct_infer(&self, ty: Ty<'tcx>, _: Option<&ty::GenericParamDef>, span: Span) -> Const<'tcx> {
-        let ty = self.tcx.fold_regions(ty, &mut false, |r, _| match *r {
+        let ty = self.tcx.fold_regions(ty, |r, _| match *r {
             ty::ReErased => self.tcx.lifetimes.re_static,
             _ => r,
         });
@@ -1917,7 +1917,7 @@ fn infer_return_ty_for_fn_sig<'tcx>(
         Some(ty) => {
             let fn_sig = tcx.typeck(def_id).liberated_fn_sigs()[hir_id];
             // Typeck doesn't expect erased regions to be returned from `type_of`.
-            let fn_sig = tcx.fold_regions(fn_sig, &mut false, |r, _| match *r {
+            let fn_sig = tcx.fold_regions(fn_sig, |r, _| match *r {
                 ty::ReErased => tcx.lifetimes.re_static,
                 _ => r,
             });
diff --git a/compiler/rustc_typeck/src/collect/type_of.rs b/compiler/rustc_typeck/src/collect/type_of.rs
index 7011dd6e15c..6ee2b544916 100644
--- a/compiler/rustc_typeck/src/collect/type_of.rs
+++ b/compiler/rustc_typeck/src/collect/type_of.rs
@@ -772,7 +772,7 @@ fn infer_placeholder_type<'a>(
     }
 
     // Typeck doesn't expect erased regions to be returned from `type_of`.
-    tcx.fold_regions(ty, &mut false, |r, _| match *r {
+    tcx.fold_regions(ty, |r, _| match *r {
         ty::ReErased => tcx.lifetimes.re_static,
         _ => r,
     })
diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs
index 67a3d4a4d02..4cdec615d82 100644
--- a/compiler/rustc_typeck/src/errors.rs
+++ b/compiler/rustc_typeck/src/errors.rs
@@ -6,18 +6,18 @@ use rustc_session::{parse::ParseSess, SessionDiagnostic};
 use rustc_span::{symbol::Ident, Span, Symbol};
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0062", slug = "typeck-field-multiply-specified-in-initializer")]
+#[error(typeck::field_multiply_specified_in_initializer, code = "E0062")]
 pub struct FieldMultiplySpecifiedInInitializer {
     #[primary_span]
     #[label]
     pub span: Span,
-    #[label = "previous-use-label"]
+    #[label(typeck::previous_use_label)]
     pub prev_span: Span,
     pub ident: Ident,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0092", slug = "typeck-unrecognized-atomic-operation")]
+#[error(typeck::unrecognized_atomic_operation, code = "E0092")]
 pub struct UnrecognizedAtomicOperation<'a> {
     #[primary_span]
     #[label]
@@ -26,7 +26,7 @@ pub struct UnrecognizedAtomicOperation<'a> {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0094", slug = "typeck-wrong-number-of-generic-arguments-to-intrinsic")]
+#[error(typeck::wrong_number_of_generic_arguments_to_intrinsic, code = "E0094")]
 pub struct WrongNumberOfGenericArgumentsToIntrinsic<'a> {
     #[primary_span]
     #[label]
@@ -37,7 +37,7 @@ pub struct WrongNumberOfGenericArgumentsToIntrinsic<'a> {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0093", slug = "typeck-unrecognized-intrinsic-function")]
+#[error(typeck::unrecognized_intrinsic_function, code = "E0093")]
 pub struct UnrecognizedIntrinsicFunction {
     #[primary_span]
     #[label]
@@ -46,19 +46,19 @@ pub struct UnrecognizedIntrinsicFunction {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0195", slug = "typeck-lifetimes-or-bounds-mismatch-on-trait")]
+#[error(typeck::lifetimes_or_bounds_mismatch_on_trait, code = "E0195")]
 pub struct LifetimesOrBoundsMismatchOnTrait {
     #[primary_span]
     #[label]
     pub span: Span,
-    #[label = "generics-label"]
+    #[label(typeck::generics_label)]
     pub generics_span: Option<Span>,
     pub item_kind: &'static str,
     pub ident: Ident,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0120", slug = "typeck-drop-impl-on-wrong-item")]
+#[error(typeck::drop_impl_on_wrong_item, code = "E0120")]
 pub struct DropImplOnWrongItem {
     #[primary_span]
     #[label]
@@ -66,18 +66,18 @@ pub struct DropImplOnWrongItem {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0124", slug = "typeck-field-already-declared")]
+#[error(typeck::field_already_declared, code = "E0124")]
 pub struct FieldAlreadyDeclared {
     pub field_name: Ident,
     #[primary_span]
     #[label]
     pub span: Span,
-    #[label = "previous-decl-label"]
+    #[label(typeck::previous_decl_label)]
     pub prev_span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0184", slug = "typeck-copy-impl-on-type-with-dtor")]
+#[error(typeck::copy_impl_on_type_with_dtor, code = "E0184")]
 pub struct CopyImplOnTypeWithDtor {
     #[primary_span]
     #[label]
@@ -85,14 +85,14 @@ pub struct CopyImplOnTypeWithDtor {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0203", slug = "typeck-multiple-relaxed-default-bounds")]
+#[error(typeck::multiple_relaxed_default_bounds, code = "E0203")]
 pub struct MultipleRelaxedDefaultBounds {
     #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0206", slug = "typeck-copy-impl-on-non-adt")]
+#[error(typeck::copy_impl_on_non_adt, code = "E0206")]
 pub struct CopyImplOnNonAdt {
     #[primary_span]
     #[label]
@@ -100,23 +100,23 @@ pub struct CopyImplOnNonAdt {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0224", slug = "typeck-trait-object-declared-with-no-traits")]
+#[error(typeck::trait_object_declared_with_no_traits, code = "E0224")]
 pub struct TraitObjectDeclaredWithNoTraits {
     #[primary_span]
     pub span: Span,
-    #[label = "alias-span"]
+    #[label(typeck::alias_span)]
     pub trait_alias_span: Option<Span>,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0227", slug = "typeck-ambiguous-lifetime-bound")]
+#[error(typeck::ambiguous_lifetime_bound, code = "E0227")]
 pub struct AmbiguousLifetimeBound {
     #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0229", slug = "typeck-assoc-type-binding-not-allowed")]
+#[error(typeck::assoc_type_binding_not_allowed, code = "E0229")]
 pub struct AssocTypeBindingNotAllowed {
     #[primary_span]
     #[label]
@@ -124,14 +124,14 @@ pub struct AssocTypeBindingNotAllowed {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0436", slug = "typeck-functional-record-update-on-non-struct")]
+#[error(typeck::functional_record_update_on_non_struct, code = "E0436")]
 pub struct FunctionalRecordUpdateOnNonStruct {
     #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0516", slug = "typeck-typeof-reserved-keyword-used")]
+#[error(typeck::typeof_reserved_keyword_used, code = "E0516")]
 pub struct TypeofReservedKeywordUsed<'tcx> {
     pub ty: Ty<'tcx>,
     #[primary_span]
@@ -142,25 +142,25 @@ pub struct TypeofReservedKeywordUsed<'tcx> {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0572", slug = "typeck-return-stmt-outside-of-fn-body")]
+#[error(typeck::return_stmt_outside_of_fn_body, code = "E0572")]
 pub struct ReturnStmtOutsideOfFnBody {
     #[primary_span]
     pub span: Span,
-    #[label = "encl-body-label"]
+    #[label(typeck::encl_body_label)]
     pub encl_body_span: Option<Span>,
-    #[label = "encl-fn-label"]
+    #[label(typeck::encl_fn_label)]
     pub encl_fn_span: Option<Span>,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0627", slug = "typeck-yield-expr-outside-of-generator")]
+#[error(typeck::yield_expr_outside_of_generator, code = "E0627")]
 pub struct YieldExprOutsideOfGenerator {
     #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0639", slug = "typeck-struct-expr-non-exhaustive")]
+#[error(typeck::struct_expr_non_exhaustive, code = "E0639")]
 pub struct StructExprNonExhaustive {
     #[primary_span]
     pub span: Span,
@@ -168,26 +168,26 @@ pub struct StructExprNonExhaustive {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0699", slug = "typeck-method-call-on-unknown-type")]
+#[error(typeck::method_call_on_unknown_type, code = "E0699")]
 pub struct MethodCallOnUnknownType {
     #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0719", slug = "typeck-value-of-associated-struct-already-specified")]
+#[error(typeck::value_of_associated_struct_already_specified, code = "E0719")]
 pub struct ValueOfAssociatedStructAlreadySpecified {
     #[primary_span]
     #[label]
     pub span: Span,
-    #[label = "previous-bound-label"]
+    #[label(typeck::previous_bound_label)]
     pub prev_span: Span,
     pub item_name: Ident,
     pub def_path: String,
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0745", slug = "typeck-address-of-temporary-taken")]
+#[error(typeck::address_of_temporary_taken, code = "E0745")]
 pub struct AddressOfTemporaryTaken {
     #[primary_span]
     #[label]
@@ -197,7 +197,7 @@ pub struct AddressOfTemporaryTaken {
 #[derive(SessionSubdiagnostic)]
 pub enum AddReturnTypeSuggestion<'tcx> {
     #[suggestion(
-        slug = "typeck-add-return-type-add",
+        typeck::add_return_type_add,
         code = "-> {found} ",
         applicability = "machine-applicable"
     )]
@@ -207,7 +207,7 @@ pub enum AddReturnTypeSuggestion<'tcx> {
         found: Ty<'tcx>,
     },
     #[suggestion(
-        slug = "typeck-add-return-type-missing-here",
+        typeck::add_return_type_missing_here,
         code = "-> _ ",
         applicability = "has-placeholders"
     )]
@@ -219,12 +219,12 @@ pub enum AddReturnTypeSuggestion<'tcx> {
 
 #[derive(SessionSubdiagnostic)]
 pub enum ExpectedReturnTypeLabel<'tcx> {
-    #[label(slug = "typeck-expected-default-return-type")]
+    #[label(typeck::expected_default_return_type)]
     Unit {
         #[primary_span]
         span: Span,
     },
-    #[label(slug = "typeck-expected-return-type")]
+    #[label(typeck::expected_return_type)]
     Other {
         #[primary_span]
         span: Span,
@@ -233,7 +233,7 @@ pub enum ExpectedReturnTypeLabel<'tcx> {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "typeck-unconstrained-opaque-type")]
+#[error(typeck::unconstrained_opaque_type)]
 #[note]
 pub struct UnconstrainedOpaqueType {
     #[primary_span]
@@ -301,7 +301,7 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(code = "E0183", slug = "typeck-manual-implementation")]
+#[error(typeck::manual_implementation, code = "E0183")]
 #[help]
 pub struct ManualImplementation {
     #[primary_span]
@@ -311,7 +311,7 @@ pub struct ManualImplementation {
 }
 
 #[derive(SessionDiagnostic)]
-#[error(slug = "typeck-substs-on-overridden-impl")]
+#[error(typeck::substs_on_overridden_impl)]
 pub struct SubstsOnOverriddenImpl {
     #[primary_span]
     pub span: Span,
diff --git a/compiler/rustc_typeck/src/impl_wf_check.rs b/compiler/rustc_typeck/src/impl_wf_check.rs
index c089d25d222..981c35e184b 100644
--- a/compiler/rustc_typeck/src/impl_wf_check.rs
+++ b/compiler/rustc_typeck/src/impl_wf_check.rs
@@ -13,7 +13,6 @@ use min_specialization::check_min_specialization;
 
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::struct_span_err;
-use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalDefId;
 use rustc_middle::ty::query::Providers;
@@ -54,25 +53,15 @@ mod min_specialization;
 /// impl<'a> Trait<Foo> for Bar { type X = &'a i32; }
 /// //   ^ 'a is unused and appears in assoc type, error
 /// ```
-pub fn impl_wf_check(tcx: TyCtxt<'_>) {
-    // We will tag this as part of the WF check -- logically, it is,
-    // but it's one that we must perform earlier than the rest of
-    // WfCheck.
-    tcx.hir().for_each_module(|module| tcx.ensure().check_mod_impl_wf(module))
-}
-
 fn check_mod_impl_wf(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
     let min_specialization = tcx.features().min_specialization;
     let module = tcx.hir_module_items(module_def_id);
     for id in module.items() {
         if matches!(tcx.def_kind(id.def_id), DefKind::Impl) {
-            let item = tcx.hir().item(id);
-            if let hir::ItemKind::Impl(ref impl_) = item.kind {
-                enforce_impl_params_are_constrained(tcx, item.def_id, impl_.items);
-                enforce_impl_items_are_distinct(tcx, impl_.items);
-                if min_specialization {
-                    check_min_specialization(tcx, item.def_id.to_def_id(), item.span);
-                }
+            enforce_impl_params_are_constrained(tcx, id.def_id);
+            enforce_impl_items_are_distinct(tcx, id.def_id);
+            if min_specialization {
+                check_min_specialization(tcx, id.def_id);
             }
         }
     }
@@ -82,11 +71,7 @@ pub fn provide(providers: &mut Providers) {
     *providers = Providers { check_mod_impl_wf, ..*providers };
 }
 
-fn enforce_impl_params_are_constrained(
-    tcx: TyCtxt<'_>,
-    impl_def_id: LocalDefId,
-    impl_item_refs: &[hir::ImplItemRef],
-) {
+fn enforce_impl_params_are_constrained(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) {
     // Every lifetime used in an associated type must be constrained.
     let impl_self_ty = tcx.type_of(impl_def_id);
     if impl_self_ty.references_error() {
@@ -114,9 +99,9 @@ fn enforce_impl_params_are_constrained(
     );
 
     // Disallow unconstrained lifetimes, but only if they appear in assoc types.
-    let lifetimes_in_associated_types: FxHashSet<_> = impl_item_refs
+    let lifetimes_in_associated_types: FxHashSet<_> = tcx
+        .associated_item_def_ids(impl_def_id)
         .iter()
-        .map(|item_ref| item_ref.id.def_id)
         .flat_map(|def_id| {
             let item = tcx.associated_item(def_id);
             match item.kind {
@@ -216,33 +201,32 @@ fn report_unused_parameter(tcx: TyCtxt<'_>, span: Span, kind: &str, name: &str)
 }
 
 /// Enforce that we do not have two items in an impl with the same name.
-fn enforce_impl_items_are_distinct(tcx: TyCtxt<'_>, impl_item_refs: &[hir::ImplItemRef]) {
+fn enforce_impl_items_are_distinct(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) {
     let mut seen_type_items = FxHashMap::default();
     let mut seen_value_items = FxHashMap::default();
-    for impl_item_ref in impl_item_refs {
-        let impl_item = tcx.hir().impl_item(impl_item_ref.id);
+    for &impl_item_ref in tcx.associated_item_def_ids(impl_def_id) {
+        let impl_item = tcx.associated_item(impl_item_ref);
         let seen_items = match impl_item.kind {
-            hir::ImplItemKind::TyAlias(_) => &mut seen_type_items,
+            ty::AssocKind::Type => &mut seen_type_items,
             _ => &mut seen_value_items,
         };
-        match seen_items.entry(impl_item.ident.normalize_to_macros_2_0()) {
+        let span = tcx.def_span(impl_item_ref);
+        let ident = impl_item.ident(tcx);
+        match seen_items.entry(ident.normalize_to_macros_2_0()) {
             Occupied(entry) => {
                 let mut err = struct_span_err!(
                     tcx.sess,
-                    impl_item.span,
+                    span,
                     E0201,
                     "duplicate definitions with name `{}`:",
-                    impl_item.ident
-                );
-                err.span_label(
-                    *entry.get(),
-                    format!("previous definition of `{}` here", impl_item.ident),
+                    ident
                 );
-                err.span_label(impl_item.span, "duplicate definition");
+                err.span_label(*entry.get(), format!("previous definition of `{}` here", ident));
+                err.span_label(span, "duplicate definition");
                 err.emit();
             }
             Vacant(entry) => {
-                entry.insert(impl_item.span);
+                entry.insert(span);
             }
         }
     }
diff --git a/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs b/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs
index 0ecc28e6054..f07396ce74f 100644
--- a/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs
+++ b/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs
@@ -79,19 +79,19 @@ use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
 use rustc_span::Span;
 use rustc_trait_selection::traits::{self, translate_substs, wf};
 
-pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: DefId, span: Span) {
+pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) {
     if let Some(node) = parent_specialization_node(tcx, impl_def_id) {
         tcx.infer_ctxt().enter(|infcx| {
-            check_always_applicable(&infcx, impl_def_id, node, span);
+            check_always_applicable(&infcx, impl_def_id, node);
         });
     }
 }
 
-fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: DefId) -> Option<Node> {
+fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId) -> Option<Node> {
     let trait_ref = tcx.impl_trait_ref(impl1_def_id)?;
     let trait_def = tcx.trait_def(trait_ref.def_id);
 
-    let impl2_node = trait_def.ancestors(tcx, impl1_def_id).ok()?.nth(1)?;
+    let impl2_node = trait_def.ancestors(tcx, impl1_def_id.to_def_id()).ok()?.nth(1)?;
 
     let always_applicable_trait =
         matches!(trait_def.specialization_kind, TraitSpecializationKind::AlwaysApplicable);
@@ -103,15 +103,8 @@ fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: DefId) -> Option<No
 }
 
 /// Check that `impl1` is a sound specialization
-fn check_always_applicable(
-    infcx: &InferCtxt<'_, '_>,
-    impl1_def_id: DefId,
-    impl2_node: Node,
-    span: Span,
-) {
-    if let Some((impl1_substs, impl2_substs)) =
-        get_impl_substs(infcx, impl1_def_id, impl2_node, span)
-    {
+fn check_always_applicable(infcx: &InferCtxt<'_, '_>, impl1_def_id: LocalDefId, impl2_node: Node) {
+    if let Some((impl1_substs, impl2_substs)) = get_impl_substs(infcx, impl1_def_id, impl2_node) {
         let impl2_def_id = impl2_node.def_id();
         debug!(
             "check_always_applicable(\nimpl1_def_id={:?},\nimpl2_def_id={:?},\nimpl2_substs={:?}\n)",
@@ -126,17 +119,10 @@ fn check_always_applicable(
             unconstrained_parent_impl_substs(tcx, impl2_def_id, impl2_substs)
         };
 
+        let span = tcx.def_span(impl1_def_id);
         check_static_lifetimes(tcx, &parent_substs, span);
         check_duplicate_params(tcx, impl1_substs, &parent_substs, span);
-
-        check_predicates(
-            infcx,
-            impl1_def_id.expect_local(),
-            impl1_substs,
-            impl2_node,
-            impl2_substs,
-            span,
-        );
+        check_predicates(infcx, impl1_def_id, impl1_substs, impl2_node, impl2_substs, span);
     }
 }
 
@@ -152,20 +138,21 @@ fn check_always_applicable(
 /// Would return `S1 = [C]` and `S2 = [Vec<C>, C]`.
 fn get_impl_substs<'tcx>(
     infcx: &InferCtxt<'_, 'tcx>,
-    impl1_def_id: DefId,
+    impl1_def_id: LocalDefId,
     impl2_node: Node,
-    span: Span,
 ) -> Option<(SubstsRef<'tcx>, SubstsRef<'tcx>)> {
     let tcx = infcx.tcx;
     let param_env = tcx.param_env(impl1_def_id);
 
-    let impl1_substs = InternalSubsts::identity_for_item(tcx, impl1_def_id);
-    let impl2_substs = translate_substs(infcx, param_env, impl1_def_id, impl1_substs, impl2_node);
+    let impl1_substs = InternalSubsts::identity_for_item(tcx, impl1_def_id.to_def_id());
+    let impl2_substs =
+        translate_substs(infcx, param_env, impl1_def_id.to_def_id(), impl1_substs, impl2_node);
 
     // Conservatively use an empty `ParamEnv`.
     let outlives_env = OutlivesEnvironment::new(ty::ParamEnv::empty());
-    infcx.resolve_regions_and_report_errors(impl1_def_id, &outlives_env);
+    infcx.resolve_regions_and_report_errors(impl1_def_id.to_def_id(), &outlives_env);
     let Ok(impl2_substs) = infcx.fully_resolve(impl2_substs) else {
+        let span = tcx.def_span(impl1_def_id);
         tcx.sess.emit_err(SubstsOnOverriddenImpl { span });
         return None;
     };
diff --git a/compiler/rustc_typeck/src/lib.rs b/compiler/rustc_typeck/src/lib.rs
index d613edf0ab0..b6d4f5fcda6 100644
--- a/compiler/rustc_typeck/src/lib.rs
+++ b/compiler/rustc_typeck/src/lib.rs
@@ -509,11 +509,21 @@ pub fn check_crate(tcx: TyCtxt<'_>) -> Result<(), ErrorGuaranteed> {
     }
 
     tcx.sess.track_errors(|| {
-        tcx.sess.time("impl_wf_inference", || impl_wf_check::impl_wf_check(tcx));
+        tcx.sess.time("impl_wf_inference", || {
+            tcx.hir().for_each_module(|module| tcx.ensure().check_mod_impl_wf(module))
+        });
     })?;
 
     tcx.sess.track_errors(|| {
-        tcx.sess.time("coherence_checking", || coherence::check_coherence(tcx));
+        tcx.sess.time("coherence_checking", || {
+            for &trait_def_id in tcx.all_local_trait_impls(()).keys() {
+                tcx.ensure().coherent_trait(trait_def_id);
+            }
+
+            // these queries are executed for side-effects (error reporting):
+            tcx.ensure().crate_inherent_impls(());
+            tcx.ensure().crate_inherent_impls_overlap_check(());
+        });
     })?;
 
     if tcx.features().rustc_attrs {
@@ -523,7 +533,9 @@ pub fn check_crate(tcx: TyCtxt<'_>) -> Result<(), ErrorGuaranteed> {
     }
 
     tcx.sess.track_errors(|| {
-        tcx.sess.time("wf_checking", || check::check_wf_new(tcx));
+        tcx.sess.time("wf_checking", || {
+            tcx.hir().par_for_each_module(|module| tcx.ensure().check_mod_type_wf(module))
+        });
     })?;
 
     // NOTE: This is copy/pasted in librustdoc/core.rs and should be kept in sync.