about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_ast/src/ast.rs11
-rw-r--r--compiler/rustc_ast_lowering/src/item.rs4
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs72
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs2
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs4
-rw-r--r--compiler/rustc_borrowck/src/borrowck_errors.rs2
-rw-r--r--compiler/rustc_borrowck/src/constraint_generation.rs6
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mod.rs7
-rw-r--r--compiler/rustc_borrowck/src/region_infer/mod.rs4
-rw-r--r--compiler/rustc_borrowck/src/region_infer/opaque_types.rs4
-rw-r--r--compiler/rustc_borrowck/src/type_check/constraint_conversion.rs2
-rw-r--r--compiler/rustc_borrowck/src/type_check/liveness/mod.rs51
-rw-r--r--compiler/rustc_borrowck/src/type_check/liveness/trace.rs43
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs127
-rw-r--r--compiler/rustc_codegen_cranelift/src/base.rs23
-rw-r--r--compiler/rustc_codegen_cranelift/src/lib.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/callee.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/mono_item.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/type_of.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/builder.rs6
-rw-r--r--compiler/rustc_codegen_llvm/src/callee.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/mod.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm/ffi.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/mono_item.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/type_of.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/intrinsic.rs4
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/mod.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/place.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/rvalue.rs81
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/statement.rs3
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs1
-rw-r--r--compiler/rustc_const_eval/src/interpret/cast.rs30
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs122
-rw-r--r--compiler/rustc_const_eval/src/interpret/intrinsics.rs18
-rw-r--r--compiler/rustc_const_eval/src/interpret/machine.rs31
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs48
-rw-r--r--compiler/rustc_const_eval/src/interpret/operand.rs67
-rw-r--r--compiler/rustc_const_eval/src/interpret/operator.rs28
-rw-r--r--compiler/rustc_const_eval/src/interpret/place.rs394
-rw-r--r--compiler/rustc_const_eval/src/interpret/step.rs10
-rw-r--r--compiler/rustc_const_eval/src/interpret/terminator.rs38
-rw-r--r--compiler/rustc_const_eval/src/interpret/traits.rs10
-rw-r--r--compiler/rustc_const_eval/src/interpret/util.rs4
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs18
-rw-r--r--compiler/rustc_const_eval/src/interpret/visitor.rs49
-rw-r--r--compiler/rustc_const_eval/src/lib.rs1
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs2
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs5
-rw-r--r--compiler/rustc_const_eval/src/transform/promote_consts.rs2
-rw-r--r--compiler/rustc_const_eval/src/transform/validate.rs34
-rw-r--r--compiler/rustc_driver/src/lib.rs20
-rw-r--r--compiler/rustc_error_messages/locales/en-US/lint.ftl400
-rw-r--r--compiler/rustc_error_messages/src/lib.rs5
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs110
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs25
-rw-r--r--compiler/rustc_errors/src/lib.rs48
-rw-r--r--compiler/rustc_expand/src/base.rs6
-rw-r--r--compiler/rustc_expand/src/build.rs13
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs545
-rw-r--r--compiler/rustc_infer/src/infer/canonical/query_response.rs2
-rw-r--r--compiler/rustc_infer/src/infer/combine.rs2
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs12
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs107
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs2
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs4
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs4
-rw-r--r--compiler/rustc_infer/src/infer/free_regions.rs8
-rw-r--r--compiler/rustc_infer/src/infer/freshen.rs2
-rw-r--r--compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs14
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs22
-rw-r--r--compiler/rustc_infer/src/infer/nll_relate/mod.rs4
-rw-r--r--compiler/rustc_infer/src/infer/opaque_types.rs5
-rw-r--r--compiler/rustc_infer/src/infer/outlives/components.rs2
-rw-r--r--compiler/rustc_infer/src/infer/outlives/env.rs88
-rw-r--r--compiler/rustc_infer/src/infer/outlives/obligations.rs57
-rw-r--r--compiler/rustc_infer/src/infer/outlives/test_type_match.rs2
-rw-r--r--compiler/rustc_infer/src/infer/region_constraints/mod.rs4
-rw-r--r--compiler/rustc_infer/src/infer/resolve.rs5
-rw-r--r--compiler/rustc_infer/src/infer/sub.rs2
-rw-r--r--compiler/rustc_infer/src/traits/error_reporting/mod.rs8
-rw-r--r--compiler/rustc_infer/src/traits/project.rs2
-rw-r--r--compiler/rustc_infer/src/traits/structural_impls.rs5
-rw-r--r--compiler/rustc_lint/Cargo.toml1
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs21
-rw-r--r--compiler/rustc_lint/src/builtin.rs366
-rw-r--r--compiler/rustc_lint/src/context.rs23
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs32
-rw-r--r--compiler/rustc_lint/src/expect.rs5
-rw-r--r--compiler/rustc_lint/src/hidden_unicode_codepoints.rs44
-rw-r--r--compiler/rustc_lint/src/internal.rs67
-rw-r--r--compiler/rustc_lint/src/levels.rs10
-rw-r--r--compiler/rustc_lint/src/methods.rs17
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs35
-rw-r--r--compiler/rustc_lint/src/non_fmt_panic.rs73
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs40
-rw-r--r--compiler/rustc_lint/src/noop_method_call.rs21
-rw-r--r--compiler/rustc_lint/src/pass_by_value.rs7
-rw-r--r--compiler/rustc_lint/src/redundant_semicolon.rs12
-rw-r--r--compiler/rustc_lint/src/traits.rs21
-rw-r--r--compiler/rustc_lint/src/types.rs285
-rw-r--r--compiler/rustc_lint/src/unused.rs100
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs704
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs590
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs22
-rw-r--r--compiler/rustc_macros/src/diagnostics/mod.rs55
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs9
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs41
-rw-r--r--compiler/rustc_macros/src/lib.rs21
-rw-r--r--compiler/rustc_macros/src/type_foldable.rs13
-rw-r--r--compiler/rustc_macros/src/type_visitable.rs33
-rw-r--r--compiler/rustc_middle/src/arena.rs1
-rw-r--r--compiler/rustc_middle/src/hir/map/mod.rs6
-rw-r--r--compiler/rustc_middle/src/hir/mod.rs9
-rw-r--r--compiler/rustc_middle/src/hir/place.rs48
-rw-r--r--compiler/rustc_middle/src/infer/canonical.rs12
-rw-r--r--compiler/rustc_middle/src/infer/mod.rs2
-rw-r--r--compiler/rustc_middle/src/lint.rs26
-rw-r--r--compiler/rustc_middle/src/macros.rs46
-rw-r--r--compiler/rustc_middle/src/mir/coverage.rs19
-rw-r--r--compiler/rustc_middle/src/mir/graph_cyclic_cache.rs2
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation.rs8
-rw-r--r--compiler/rustc_middle/src/mir/interpret/error.rs36
-rw-r--r--compiler/rustc_middle/src/mir/interpret/mod.rs12
-rw-r--r--compiler/rustc_middle/src/mir/interpret/pointer.rs21
-rw-r--r--compiler/rustc_middle/src/mir/interpret/queries.rs2
-rw-r--r--compiler/rustc_middle/src/mir/interpret/value.rs2
-rw-r--r--compiler/rustc_middle/src/mir/mod.rs78
-rw-r--r--compiler/rustc_middle/src/mir/predecessors.rs2
-rw-r--r--compiler/rustc_middle/src/mir/pretty.rs4
-rw-r--r--compiler/rustc_middle/src/mir/query.rs2
-rw-r--r--compiler/rustc_middle/src/mir/switch_sources.rs2
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs34
-rw-r--r--compiler/rustc_middle/src/mir/tcx.rs2
-rw-r--r--compiler/rustc_middle/src/mir/traversal.rs2
-rw-r--r--compiler/rustc_middle/src/mir/type_foldable.rs154
-rw-r--r--compiler/rustc_middle/src/mir/type_visitable.rs186
-rw-r--r--compiler/rustc_middle/src/mir/visit.rs25
-rw-r--r--compiler/rustc_middle/src/query/mod.rs5
-rw-r--r--compiler/rustc_middle/src/thir.rs14
-rw-r--r--compiler/rustc_middle/src/thir/abstract_const.rs2
-rw-r--r--compiler/rustc_middle/src/traits/chalk.rs2
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs35
-rw-r--r--compiler/rustc_middle/src/traits/query.rs21
-rw-r--r--compiler/rustc_middle/src/traits/select.rs4
-rw-r--r--compiler/rustc_middle/src/traits/specialization_graph.rs2
-rw-r--r--compiler/rustc_middle/src/traits/structural_impls.rs2
-rw-r--r--compiler/rustc_middle/src/ty/adjustment.rs10
-rw-r--r--compiler/rustc_middle/src/ty/binding.rs2
-rw-r--r--compiler/rustc_middle/src/ty/cast.rs6
-rw-r--r--compiler/rustc_middle/src/ty/closure.rs29
-rw-r--r--compiler/rustc_middle/src/ty/consts.rs2
-rw-r--r--compiler/rustc_middle/src/ty/consts/int.rs4
-rw-r--r--compiler/rustc_middle/src/ty/consts/kind.rs2
-rw-r--r--compiler/rustc_middle/src/ty/context.rs33
-rw-r--r--compiler/rustc_middle/src/ty/diagnostics.rs6
-rw-r--r--compiler/rustc_middle/src/ty/erase_regions.rs1
-rw-r--r--compiler/rustc_middle/src/ty/error.rs6
-rw-r--r--compiler/rustc_middle/src/ty/fast_reject.rs2
-rw-r--r--compiler/rustc_middle/src/ty/fold.rs768
-rw-r--r--compiler/rustc_middle/src/ty/instance.rs6
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs13
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs84
-rw-r--r--compiler/rustc_middle/src/ty/print/pretty.rs12
-rw-r--r--compiler/rustc_middle/src/ty/relate.rs2
-rw-r--r--compiler/rustc_middle/src/ty/structural_impls.rs89
-rw-r--r--compiler/rustc_middle/src/ty/sty.rs38
-rw-r--r--compiler/rustc_middle/src/ty/subst.rs15
-rw-r--r--compiler/rustc_middle/src/ty/trait_def.rs2
-rw-r--r--compiler/rustc_middle/src/ty/util.rs1
-rw-r--r--compiler/rustc_middle/src/ty/visit.rs745
-rw-r--r--compiler/rustc_mir_build/src/build/expr/as_rvalue.rs29
-rw-r--r--compiler/rustc_mir_build/src/build/mod.rs2
-rw-r--r--compiler/rustc_mir_build/src/thir/cx/expr.rs5
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs2
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/lattice.rs12
-rw-r--r--compiler/rustc_mir_transform/src/check_const_item_mutation.rs3
-rw-r--r--compiler/rustc_mir_transform/src/const_prop.rs42
-rw-r--r--compiler/rustc_mir_transform/src/const_prop_lint.rs42
-rw-r--r--compiler/rustc_mir_transform/src/dead_store_elimination.rs2
-rw-r--r--compiler/rustc_mir_transform/src/deaggregator.rs3
-rw-r--r--compiler/rustc_mir_transform/src/inline.rs14
-rw-r--r--compiler/rustc_mir_transform/src/inline/cycle.rs2
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs2
-rw-r--r--compiler/rustc_mir_transform/src/lower_slice_len.rs4
-rw-r--r--compiler/rustc_mir_transform/src/normalize_array_len.rs4
-rw-r--r--compiler/rustc_mir_transform/src/remove_storage_markers.rs2
-rw-r--r--compiler/rustc_mir_transform/src/remove_zsts.rs3
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs4
-rw-r--r--compiler/rustc_monomorphize/src/partitioning/default.rs2
-rw-r--r--compiler/rustc_monomorphize/src/polymorphize.rs2
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs4
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs11
-rw-r--r--compiler/rustc_passes/src/check_attr.rs2
-rw-r--r--compiler/rustc_passes/src/entry.rs26
-rw-r--r--compiler/rustc_privacy/src/lib.rs6
-rw-r--r--compiler/rustc_query_impl/src/plumbing.rs13
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs13
-rw-r--r--compiler/rustc_resolve/src/late/lifetimes.rs4
-rw-r--r--compiler/rustc_resolve/src/lib.rs1
-rw-r--r--compiler/rustc_session/src/config.rs48
-rw-r--r--compiler/rustc_session/src/lib.rs2
-rw-r--r--compiler/rustc_session/src/options.rs4
-rw-r--r--compiler/rustc_session/src/parse.rs4
-rw-r--r--compiler/rustc_session/src/session.rs46
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_symbol_mangling/src/legacy.rs2
-rw-r--r--compiler/rustc_symbol_mangling/src/v0.rs4
-rw-r--r--compiler/rustc_trait_selection/src/autoderef.rs2
-rw-r--r--compiler/rustc_trait_selection/src/infer.rs2
-rw-r--r--compiler/rustc_trait_selection/src/opaque_types.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/auto_trait.rs11
-rw-r--r--compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/coherence.rs30
-rw-r--r--compiler/rustc_trait_selection/src/traits/const_evaluatable.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs48
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/fulfill.rs9
-rw-r--r--compiler/rustc_trait_selection/src/traits/misc.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/mod.rs19
-rw-r--r--compiler/rustc_trait_selection/src/traits/object_safety.rs4
-rw-r--r--compiler/rustc_trait_selection/src/traits/project.rs5
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs70
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/normalize.rs3
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/mod.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/specialize/mod.rs26
-rw-r--r--compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/structural_match.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/util.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/wf.rs2
-rw-r--r--compiler/rustc_traits/src/chalk/lowering.rs5
-rw-r--r--compiler/rustc_traits/src/chalk/mod.rs2
-rw-r--r--compiler/rustc_traits/src/implied_outlives_bounds.rs2
-rw-r--r--compiler/rustc_ty_utils/src/instance.rs4
-rw-r--r--compiler/rustc_ty_utils/src/ty.rs2
-rw-r--r--compiler/rustc_type_ir/src/lib.rs1
-rw-r--r--compiler/rustc_typeck/src/astconv/mod.rs11
-rw-r--r--compiler/rustc_typeck/src/check/_match.rs2
-rw-r--r--compiler/rustc_typeck/src/check/callee.rs29
-rw-r--r--compiler/rustc_typeck/src/check/cast.rs2
-rw-r--r--compiler/rustc_typeck/src/check/check.rs37
-rw-r--r--compiler/rustc_typeck/src/check/closure.rs2
-rw-r--r--compiler/rustc_typeck/src/check/coercion.rs2
-rw-r--r--compiler/rustc_typeck/src/check/compare_method.rs54
-rw-r--r--compiler/rustc_typeck/src/check/dropck.rs25
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs2
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs14
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/checks.rs2
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/mod.rs2
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs5
-rw-r--r--compiler/rustc_typeck/src/check/inherited.rs1
-rw-r--r--compiler/rustc_typeck/src/check/intrinsicck.rs2
-rw-r--r--compiler/rustc_typeck/src/check/method/mod.rs2
-rw-r--r--compiler/rustc_typeck/src/check/method/probe.rs4
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs54
-rw-r--r--compiler/rustc_typeck/src/check/mod.rs23
-rw-r--r--compiler/rustc_typeck/src/check/op.rs2
-rw-r--r--compiler/rustc_typeck/src/check/pat.rs2
-rw-r--r--compiler/rustc_typeck/src/check/regionck.rs843
-rw-r--r--compiler/rustc_typeck/src/check/wfcheck.rs16
-rw-r--r--compiler/rustc_typeck/src/check/writeback.rs3
-rw-r--r--compiler/rustc_typeck/src/coherence/builtin.rs6
-rw-r--r--compiler/rustc_typeck/src/coherence/mod.rs43
-rw-r--r--compiler/rustc_typeck/src/coherence/orphan.rs4
-rw-r--r--compiler/rustc_typeck/src/collect/type_of.rs2
-rw-r--r--compiler/rustc_typeck/src/constrained_generic_params.rs4
-rw-r--r--compiler/rustc_typeck/src/impl_wf_check.rs2
-rw-r--r--compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs31
-rw-r--r--compiler/rustc_typeck/src/lib.rs12
-rw-r--r--compiler/rustc_typeck/src/mem_categorization.rs1
-rw-r--r--compiler/rustc_typeck/src/outlives/outlives_bounds.rs8
-rw-r--r--compiler/rustc_typeck/src/structured_errors/missing_cast_for_variadic_arg.rs2
-rw-r--r--compiler/rustc_typeck/src/structured_errors/sized_unsized_cast.rs2
-rw-r--r--compiler/rustc_typeck/src/variance/constraints.rs76
-rw-r--r--compiler/rustc_typeck/src/variance/solve.rs3
-rw-r--r--compiler/rustc_typeck/src/variance/terms.rs90
281 files changed, 5135 insertions, 5242 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index e5b61d7000a..2820d5e6e0c 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -2667,13 +2667,16 @@ impl Item {
 #[derive(Clone, Copy, Encodable, Decodable, Debug)]
 pub enum Extern {
     None,
-    Implicit,
-    Explicit(StrLit),
+    Implicit(Span),
+    Explicit(StrLit, Span),
 }
 
 impl Extern {
-    pub fn from_abi(abi: Option<StrLit>) -> Extern {
-        abi.map_or(Extern::Implicit, Extern::Explicit)
+    pub fn from_abi(abi: Option<StrLit>, span: Span) -> Extern {
+        match abi {
+            Some(name) => Extern::Explicit(name, span),
+            None => Extern::Implicit(span),
+        }
     }
 }
 
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index 0ef21371694..112197c6e39 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -1272,8 +1272,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
     pub(super) fn lower_extern(&mut self, ext: Extern) -> abi::Abi {
         match ext {
             Extern::None => abi::Abi::Rust,
-            Extern::Implicit => abi::Abi::FALLBACK,
-            Extern::Explicit(abi) => self.lower_abi(abi),
+            Extern::Implicit(_) => abi::Abi::FALLBACK,
+            Extern::Explicit(abi, _) => self.lower_abi(abi),
         }
     }
 
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 503bdbad258..3942062656f 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -13,7 +13,9 @@ use rustc_ast::walk_list;
 use rustc_ast::*;
 use rustc_ast_pretty::pprust::{self, State};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{error_code, pluralize, struct_span_err, Applicability};
+use rustc_errors::{
+    error_code, pluralize, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
+};
 use rustc_parse::validate_attr;
 use rustc_session::lint::builtin::{
     DEPRECATED_WHERE_CLAUSE_LOCATION, MISSING_ABI, PATTERNS_IN_FNS_WITHOUT_BODY,
@@ -476,6 +478,17 @@ impl<'a> AstValidator<'a> {
     }
 
     fn error_item_without_body(&self, sp: Span, ctx: &str, msg: &str, sugg: &str) {
+        self.error_item_without_body_with_help(sp, ctx, msg, sugg, |_| ());
+    }
+
+    fn error_item_without_body_with_help(
+        &self,
+        sp: Span,
+        ctx: &str,
+        msg: &str,
+        sugg: &str,
+        help: impl FnOnce(&mut DiagnosticBuilder<'_, ErrorGuaranteed>),
+    ) {
         let source_map = self.session.source_map();
         let end = source_map.end_point(sp);
         let replace_span = if source_map.span_to_snippet(end).map(|s| s == ";").unwrap_or(false) {
@@ -483,15 +496,15 @@ impl<'a> AstValidator<'a> {
         } else {
             sp.shrink_to_hi()
         };
-        self.err_handler()
-            .struct_span_err(sp, msg)
-            .span_suggestion(
-                replace_span,
-                &format!("provide a definition for the {}", ctx),
-                sugg,
-                Applicability::HasPlaceholders,
-            )
-            .emit();
+        let mut err = self.err_handler().struct_span_err(sp, msg);
+        err.span_suggestion(
+            replace_span,
+            &format!("provide a definition for the {}", ctx),
+            sugg,
+            Applicability::HasPlaceholders,
+        );
+        help(&mut err);
+        err.emit();
     }
 
     fn check_impl_item_provided<T>(&self, sp: Span, body: &Option<T>, ctx: &str, sugg: &str) {
@@ -630,7 +643,8 @@ impl<'a> AstValidator<'a> {
         match (fk.ctxt(), fk.header()) {
             (Some(FnCtxt::Foreign), _) => return,
             (Some(FnCtxt::Free), Some(header)) => match header.ext {
-                Extern::Explicit(StrLit { symbol_unescaped: sym::C, .. }) | Extern::Implicit
+                Extern::Explicit(StrLit { symbol_unescaped: sym::C, .. }, _)
+                | Extern::Implicit(_)
                     if matches!(header.unsafety, Unsafe::Yes(_)) =>
                 {
                     return;
@@ -842,7 +856,7 @@ impl<'a> AstValidator<'a> {
                     .emit();
                 });
                 self.check_late_bound_lifetime_defs(&bfty.generic_params);
-                if let Extern::Implicit = bfty.ext {
+                if let Extern::Implicit(_) = bfty.ext {
                     let sig_span = self.session.source_map().next_point(ty.span.shrink_to_lo());
                     self.maybe_lint_missing_abi(sig_span, ty.id);
                 }
@@ -1190,8 +1204,38 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
 
                 if body.is_none() {
                     let msg = "free function without a body";
-                    self.error_item_without_body(item.span, "function", msg, " { <body> }");
+                    let ext = sig.header.ext;
+
+                    let f = |e: &mut DiagnosticBuilder<'_, _>| {
+                        if let Extern::Implicit(start_span) | Extern::Explicit(_, start_span) = &ext
+                        {
+                            let start_suggestion = if let Extern::Explicit(abi, _) = ext {
+                                format!("extern \"{}\" {{", abi.symbol_unescaped)
+                            } else {
+                                "extern {".to_owned()
+                            };
+
+                            let end_suggestion = " }".to_owned();
+                            let end_span = item.span.shrink_to_hi();
+
+                            e
+                            .multipart_suggestion(
+                                "if you meant to declare an externally defined function, use an `extern` block",
+                                vec![(*start_span, start_suggestion), (end_span, end_suggestion)],
+                                Applicability::MaybeIncorrect,
+                             );
+                        }
+                    };
+
+                    self.error_item_without_body_with_help(
+                        item.span,
+                        "function",
+                        msg,
+                        " { <body> }",
+                        f,
+                    );
                 }
+
                 self.visit_vis(&item.vis);
                 self.visit_ident(item.ident);
                 let kind =
@@ -1556,7 +1600,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
         if let FnKind::Fn(
             _,
             _,
-            FnSig { span: sig_span, header: FnHeader { ext: Extern::Implicit, .. }, .. },
+            FnSig { span: sig_span, header: FnHeader { ext: Extern::Implicit(_), .. }, .. },
             _,
             _,
             _,
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 37c4f665415..fd2dd6cf6c7 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -283,7 +283,7 @@ impl<'a> PostExpansionVisitor<'a> {
     }
 
     fn check_extern(&self, ext: ast::Extern, constness: ast::Const) {
-        if let ast::Extern::Explicit(abi) = ext {
+        if let ast::Extern::Explicit(abi, _) = ext {
             self.check_abi(abi, constness);
         }
     }
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index ad8dbfd506d..c9e3a7edfa6 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -1734,10 +1734,10 @@ impl<'a> State<'a> {
 
         match header.ext {
             ast::Extern::None => {}
-            ast::Extern::Implicit => {
+            ast::Extern::Implicit(_) => {
                 self.word_nbsp("extern");
             }
-            ast::Extern::Explicit(abi) => {
+            ast::Extern::Explicit(abi, _) => {
                 self.word_nbsp("extern");
                 self.print_literal(&abi.as_lit());
                 self.nbsp();
diff --git a/compiler/rustc_borrowck/src/borrowck_errors.rs b/compiler/rustc_borrowck/src/borrowck_errors.rs
index 708fe8719a1..a5d9a2bc597 100644
--- a/compiler/rustc_borrowck/src/borrowck_errors.rs
+++ b/compiler/rustc_borrowck/src/borrowck_errors.rs
@@ -478,7 +478,7 @@ 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)]
+    #[rustc_lint_diagnostics]
     fn struct_span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
diff --git a/compiler/rustc_borrowck/src/constraint_generation.rs b/compiler/rustc_borrowck/src/constraint_generation.rs
index e4ffae38c33..5e9cec5c350 100644
--- a/compiler/rustc_borrowck/src/constraint_generation.rs
+++ b/compiler/rustc_borrowck/src/constraint_generation.rs
@@ -5,8 +5,8 @@ use rustc_middle::mir::{
     BasicBlock, BasicBlockData, Body, Local, Location, Place, PlaceRef, ProjectionElem, Rvalue,
     SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UserTypeProjection,
 };
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, RegionVid, Ty};
 
 use crate::{
@@ -149,7 +149,7 @@ impl<'cg, 'cx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'tcx> {
     fn visit_ascribe_user_ty(
         &mut self,
         _place: &Place<'tcx>,
-        _variance: &ty::Variance,
+        _variance: ty::Variance,
         _user_ty: &UserTypeProjection,
         _location: Location,
     ) {
@@ -163,7 +163,7 @@ impl<'cx, 'cg, 'tcx> ConstraintGeneration<'cx, 'cg, 'tcx> {
     /// `location`.
     fn add_regular_live_constraint<T>(&mut self, live_ty: T, location: Location)
     where
-        T: TypeFoldable<'tcx>,
+        T: TypeVisitable<'tcx>,
     {
         debug!("add_regular_live_constraint(live_ty={:?}, location={:?})", live_ty, location);
 
diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs
index fbc3a8cc088..6fea6941085 100644
--- a/compiler/rustc_borrowck/src/diagnostics/mod.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs
@@ -812,12 +812,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
             return FnSelfUse {
                 var_span: stmt.source_info.span,
                 fn_call_span: *fn_span,
-                fn_span: self
-                    .infcx
-                    .tcx
-                    .sess
-                    .source_map()
-                    .guess_head_span(self.infcx.tcx.def_span(method_did)),
+                fn_span: self.infcx.tcx.def_span(method_did),
                 kind,
             };
         }
diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs
index f5c9392948b..d0e0203bf8c 100644
--- a/compiler/rustc_borrowck/src/region_infer/mod.rs
+++ b/compiler/rustc_borrowck/src/region_infer/mod.rs
@@ -19,7 +19,9 @@ use rustc_middle::mir::{
 };
 use rustc_middle::traits::ObligationCause;
 use rustc_middle::traits::ObligationCauseCode;
-use rustc_middle::ty::{self, subst::SubstsRef, RegionVid, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{
+    self, subst::SubstsRef, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitable,
+};
 use rustc_span::Span;
 
 use crate::{
diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
index d182c0cf4e8..d129e845426 100644
--- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
+++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
@@ -2,7 +2,9 @@ use rustc_data_structures::vec_map::VecMap;
 use rustc_hir::def_id::DefId;
 use rustc_hir::OpaqueTyOrigin;
 use rustc_infer::infer::InferCtxt;
-use rustc_middle::ty::{self, OpaqueHiddenType, OpaqueTypeKey, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{
+    self, OpaqueHiddenType, OpaqueTypeKey, TyCtxt, TypeFoldable, TypeVisitable,
+};
 use rustc_trait_selection::opaque_types::InferCtxtExt;
 
 use super::RegionInferenceContext;
diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
index 5e33d9d25c2..3f9c0cecccc 100644
--- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
+++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
@@ -6,7 +6,7 @@ use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound};
 use rustc_infer::infer::{self, InferCtxt, SubregionOrigin};
 use rustc_middle::mir::ConstraintCategory;
 use rustc_middle::ty::subst::GenericArgKind;
-use rustc_middle::ty::TypeFoldable;
+use rustc_middle::ty::TypeVisitable;
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::{Span, DUMMY_SP};
 
diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs
index ac8670a5138..d5c401ae1c6 100644
--- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs
@@ -1,11 +1,11 @@
+use itertools::{Either, Itertools};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_middle::mir::{Body, Local};
 use rustc_middle::ty::{RegionVid, TyCtxt};
-use std::rc::Rc;
-
 use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
 use rustc_mir_dataflow::move_paths::MoveData;
 use rustc_mir_dataflow::ResultsCursor;
+use std::rc::Rc;
 
 use crate::{
     constraints::OutlivesConstraintSet,
@@ -46,7 +46,8 @@ pub(super) fn generate<'mir, 'tcx>(
         &typeck.borrowck_context.universal_regions,
         &typeck.borrowck_context.constraints.outlives_constraints,
     );
-    let live_locals = compute_live_locals(typeck.tcx(), &free_regions, &body);
+    let (relevant_live_locals, boring_locals) =
+        compute_relevant_live_locals(typeck.tcx(), &free_regions, &body);
     let facts_enabled = use_polonius || AllFacts::enabled(typeck.tcx());
 
     let polonius_drop_used = if facts_enabled {
@@ -57,48 +58,44 @@ pub(super) fn generate<'mir, 'tcx>(
         None
     };
 
-    if !live_locals.is_empty() || facts_enabled {
-        trace::trace(
-            typeck,
-            body,
-            elements,
-            flow_inits,
-            move_data,
-            live_locals,
-            polonius_drop_used,
-        );
-    }
+    trace::trace(
+        typeck,
+        body,
+        elements,
+        flow_inits,
+        move_data,
+        relevant_live_locals,
+        boring_locals,
+        polonius_drop_used,
+    );
 }
 
-// The purpose of `compute_live_locals` is to define the subset of `Local`
+// The purpose of `compute_relevant_live_locals` is to define the subset of `Local`
 // variables for which we need to do a liveness computation. We only need
 // to compute whether a variable `X` is live if that variable contains
 // some region `R` in its type where `R` is not known to outlive a free
 // region (i.e., where `R` may be valid for just a subset of the fn body).
-fn compute_live_locals<'tcx>(
+fn compute_relevant_live_locals<'tcx>(
     tcx: TyCtxt<'tcx>,
     free_regions: &FxHashSet<RegionVid>,
     body: &Body<'tcx>,
-) -> Vec<Local> {
-    let live_locals: Vec<Local> = body
-        .local_decls
-        .iter_enumerated()
-        .filter_map(|(local, local_decl)| {
+) -> (Vec<Local>, Vec<Local>) {
+    let (boring_locals, relevant_live_locals): (Vec<_>, Vec<_>) =
+        body.local_decls.iter_enumerated().partition_map(|(local, local_decl)| {
             if tcx.all_free_regions_meet(&local_decl.ty, |r| {
                 free_regions.contains(&r.to_region_vid())
             }) {
-                None
+                Either::Left(local)
             } else {
-                Some(local)
+                Either::Right(local)
             }
-        })
-        .collect();
+        });
 
     debug!("{} total variables", body.local_decls.len());
-    debug!("{} variables need liveness", live_locals.len());
+    debug!("{} variables need liveness", relevant_live_locals.len());
     debug!("{} regions outlive free regions", free_regions.len());
 
-    live_locals
+    (relevant_live_locals, boring_locals)
 }
 
 /// Computes all regions that are (currently) known to outlive free
diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs
index 169de23facc..d4e61ec213b 100644
--- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs
+++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs
@@ -3,7 +3,7 @@ use rustc_index::bit_set::HybridBitSet;
 use rustc_index::interval::IntervalSet;
 use rustc_infer::infer::canonical::QueryRegionConstraints;
 use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
-use rustc_middle::ty::{Ty, TypeFoldable};
+use rustc_middle::ty::{Ty, TypeVisitable};
 use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult;
 use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives;
 use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
@@ -41,12 +41,13 @@ pub(super) fn trace<'mir, 'tcx>(
     elements: &Rc<RegionValueElements>,
     flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
     move_data: &MoveData<'tcx>,
-    live_locals: Vec<Local>,
+    relevant_live_locals: Vec<Local>,
+    boring_locals: Vec<Local>,
     polonius_drop_used: Option<Vec<(Local, Location)>>,
 ) {
     debug!("trace()");
 
-    let local_use_map = &LocalUseMap::build(&live_locals, elements, body);
+    let local_use_map = &LocalUseMap::build(&relevant_live_locals, elements, body);
 
     let cx = LivenessContext {
         typeck,
@@ -61,10 +62,12 @@ pub(super) fn trace<'mir, 'tcx>(
     let mut results = LivenessResults::new(cx);
 
     if let Some(drop_used) = polonius_drop_used {
-        results.add_extra_drop_facts(drop_used, live_locals.iter().copied().collect())
+        results.add_extra_drop_facts(drop_used, relevant_live_locals.iter().copied().collect())
     }
 
-    results.compute_for_all_locals(live_locals);
+    results.compute_for_all_locals(relevant_live_locals);
+
+    results.dropck_boring_locals(boring_locals);
 }
 
 /// Contextual state for the type-liveness generator.
@@ -133,8 +136,8 @@ impl<'me, 'typeck, 'flow, 'tcx> LivenessResults<'me, 'typeck, 'flow, 'tcx> {
         }
     }
 
-    fn compute_for_all_locals(&mut self, live_locals: Vec<Local>) {
-        for local in live_locals {
+    fn compute_for_all_locals(&mut self, relevant_live_locals: Vec<Local>) {
+        for local in relevant_live_locals {
             self.reset_local_state();
             self.add_defs_for(local);
             self.compute_use_live_points_for(local);
@@ -157,6 +160,24 @@ impl<'me, 'typeck, 'flow, 'tcx> LivenessResults<'me, 'typeck, 'flow, 'tcx> {
         }
     }
 
+    // Runs dropck for locals whose liveness isn't relevant. This is
+    // necessary to eagerly detect unbound recursion during drop glue computation.
+    fn dropck_boring_locals(&mut self, boring_locals: Vec<Local>) {
+        for local in boring_locals {
+            let local_ty = self.cx.body.local_decls[local].ty;
+            let drop_data = self.cx.drop_data.entry(local_ty).or_insert_with({
+                let typeck = &mut self.cx.typeck;
+                move || LivenessContext::compute_drop_data(typeck, local_ty)
+            });
+
+            drop_data.dropck_result.report_overflows(
+                self.cx.typeck.infcx.tcx,
+                self.cx.body.local_decls[local].source_info.span,
+                local_ty,
+            );
+        }
+    }
+
     /// Add extra drop facts needed for Polonius.
     ///
     /// Add facts for all locals with free regions, since regions may outlive
@@ -164,12 +185,12 @@ impl<'me, 'typeck, 'flow, 'tcx> LivenessResults<'me, 'typeck, 'flow, 'tcx> {
     fn add_extra_drop_facts(
         &mut self,
         drop_used: Vec<(Local, Location)>,
-        live_locals: FxHashSet<Local>,
+        relevant_live_locals: FxHashSet<Local>,
     ) {
         let locations = IntervalSet::new(self.cx.elements.num_points());
 
         for (local, location) in drop_used {
-            if !live_locals.contains(&local) {
+            if !relevant_live_locals.contains(&local) {
                 let local_ty = self.cx.body.local_decls[local].ty;
                 if local_ty.has_free_regions() {
                     self.cx.add_drop_live_facts_for(local, local_ty, &[location], &locations);
@@ -456,7 +477,7 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> {
     /// points `live_at`.
     fn add_use_live_facts_for(
         &mut self,
-        value: impl TypeFoldable<'tcx>,
+        value: impl TypeVisitable<'tcx>,
         live_at: &IntervalSet<PointIndex>,
     ) {
         debug!("add_use_live_facts_for(value={:?})", value);
@@ -521,7 +542,7 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> {
     fn make_all_regions_live(
         elements: &RegionValueElements,
         typeck: &mut TypeChecker<'_, 'tcx>,
-        value: impl TypeFoldable<'tcx>,
+        value: impl TypeVisitable<'tcx>,
         live_at: &IntervalSet<PointIndex>,
     ) {
         debug!("make_all_regions_live(value={:?})", value);
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index a21a8dd48be..d71a4983a92 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -27,8 +27,8 @@ use rustc_middle::mir::AssertKind;
 use rustc_middle::mir::*;
 use rustc_middle::ty::adjustment::PointerCast;
 use rustc_middle::ty::cast::CastTy;
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::{GenericArgKind, SubstsRef, UserSubsts};
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{
     self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, OpaqueHiddenType,
     OpaqueTypeKey, RegionVid, ToPredicate, Ty, TyCtxt, UserType, UserTypeAnnotationIndex,
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
index ff431c8de5d..2a9e37081e0 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -66,7 +66,7 @@
 //!
 //! # "`cs`" functions
 //!
-//! The `cs_...` functions ("combine substructure) are designed to
+//! The `cs_...` functions ("combine substructure") are designed to
 //! make life easier by providing some pre-made recipes for common
 //! threads; mostly calling the function being derived on all the
 //! arguments and then combining them back together in some way (or
@@ -429,6 +429,7 @@ impl<'a> TraitDef<'a> {
                         generics,
                         from_scratch,
                         use_temporaries,
+                        is_packed,
                     ),
                     ast::ItemKind::Enum(ref enum_def, ref generics) => {
                         // We ignore `use_temporaries` here, because
@@ -448,6 +449,7 @@ impl<'a> TraitDef<'a> {
                                 generics,
                                 from_scratch,
                                 use_temporaries,
+                                is_packed,
                             )
                         } else {
                             cx.span_err(mitem.span, "this trait cannot be derived for unions");
@@ -729,6 +731,7 @@ impl<'a> TraitDef<'a> {
         generics: &Generics,
         from_scratch: bool,
         use_temporaries: bool,
+        is_packed: bool,
     ) -> P<ast::Item> {
         let field_tys: Vec<P<ast::Ty>> =
             struct_def.fields().iter().map(|field| field.ty.clone()).collect();
@@ -757,6 +760,7 @@ impl<'a> TraitDef<'a> {
                         &self_args,
                         &nonself_args,
                         use_temporaries,
+                        is_packed,
                     )
                 };
 
@@ -945,6 +949,7 @@ impl<'a> MethodDef<'a> {
         })
     }
 
+    /// The normal case uses field access.
     /// ```
     /// #[derive(PartialEq)]
     /// # struct Dummy;
@@ -953,33 +958,21 @@ impl<'a> MethodDef<'a> {
     /// // equivalent to:
     /// impl PartialEq for A {
     ///     fn eq(&self, other: &A) -> bool {
-    ///         match *self {
-    ///             A {x: ref __self_0_0, y: ref __self_0_1} => {
-    ///                 match *other {
-    ///                     A {x: ref __self_1_0, y: ref __self_1_1} => {
-    ///                         __self_0_0.eq(__self_1_0) && __self_0_1.eq(__self_1_1)
-    ///                     }
-    ///                 }
-    ///             }
-    ///         }
+    ///         self.x == other.x && self.y == other.y
     ///     }
     /// }
     /// ```
-    /// or if A is repr(packed) - note fields are matched by-value
-    /// instead of by-reference.
+    /// But if the struct is `repr(packed)`, we can't use something like
+    /// `&self.x` on a packed type (as required for e.g. `Debug` and `Hash`)
+    /// because that might cause an unaligned ref. So we use let-destructuring
+    /// instead.
     /// ```
     /// # struct A { x: i32, y: i32 }
     /// impl PartialEq for A {
     ///     fn eq(&self, other: &A) -> bool {
-    ///         match *self {
-    ///             A {x: __self_0_0, y: __self_0_1} => {
-    ///                 match other {
-    ///                     A {x: __self_1_0, y: __self_1_1} => {
-    ///                         __self_0_0.eq(&__self_1_0) && __self_0_1.eq(&__self_1_1)
-    ///                     }
-    ///                 }
-    ///             }
-    ///         }
+    ///         let Self { x: ref __self_0_0, y: ref __self_0_1 } = *self;
+    ///         let Self { x: ref __self_1_0, y: ref __self_1_1 } = *other;
+    ///         *__self_0_0 == *__self_1_0 && *__self_0_1 == *__self_1_1
     ///     }
     /// }
     /// ```
@@ -992,24 +985,33 @@ impl<'a> MethodDef<'a> {
         self_args: &[P<Expr>],
         nonself_args: &[P<Expr>],
         use_temporaries: bool,
+        is_packed: bool,
     ) -> P<Expr> {
         let mut raw_fields = Vec::new(); // Vec<[fields of self], [fields of next Self arg], [etc]>
         let span = trait_.span;
         let mut patterns = Vec::new();
-        for i in 0..self_args.len() {
-            // We could use `type_ident` instead of `Self`, but in the case of a type parameter
-            // shadowing the struct name, that causes a second, unnecessary E0578 error. #97343
-            let struct_path = cx.path(span, vec![Ident::new(kw::SelfUpper, type_ident.span)]);
-            let (pat, ident_expr) = trait_.create_struct_pattern(
-                cx,
-                struct_path,
-                struct_def,
-                &format!("__self_{}", i),
-                ast::Mutability::Not,
-                use_temporaries,
-            );
-            patterns.push(pat);
-            raw_fields.push(ident_expr);
+
+        for (i, self_arg) in self_args.iter().enumerate() {
+            let ident_exprs = if !is_packed {
+                trait_.create_struct_field_accesses(cx, self_arg, struct_def)
+            } else {
+                // Get the pattern for the let-destructuring.
+                //
+                // We could use `type_ident` instead of `Self`, but in the case of a type parameter
+                // shadowing the struct name, that causes a second, unnecessary E0578 error. #97343
+                let struct_path = cx.path(span, vec![Ident::new(kw::SelfUpper, type_ident.span)]);
+                let (pat, ident_exprs) = trait_.create_struct_pattern(
+                    cx,
+                    struct_path,
+                    struct_def,
+                    &format!("__self_{}", i),
+                    ast::Mutability::Not,
+                    use_temporaries,
+                );
+                patterns.push(pat);
+                ident_exprs
+            };
+            raw_fields.push(ident_exprs);
         }
 
         // transpose raw_fields
@@ -1036,7 +1038,6 @@ impl<'a> MethodDef<'a> {
             cx.span_bug(span, "no `self` parameter for method in generic `derive`")
         };
 
-        // body of the inner most destructuring match
         let mut body = self.call_substructure_method(
             cx,
             trait_,
@@ -1045,14 +1046,18 @@ impl<'a> MethodDef<'a> {
             &Struct(struct_def, fields),
         );
 
-        // make a series of nested matches, to destructure the
-        // structs. This is actually right-to-left, but it shouldn't
-        // matter.
-        for (arg_expr, pat) in iter::zip(self_args, patterns) {
-            body = cx.expr_match(span, arg_expr.clone(), vec![cx.arm(span, pat.clone(), body)])
-        }
+        if !is_packed {
+            body.span = span;
+            body
+        } else {
+            // Do the let-destructuring.
+            let mut stmts: Vec<_> = iter::zip(self_args, patterns)
+                .map(|(arg_expr, pat)| cx.stmt_let_pat(span, pat, arg_expr.clone()))
+                .collect();
+            stmts.push(cx.stmt_expr(body));
 
-        body
+            cx.expr_block(cx.block(span, stmts))
+        }
     }
 
     fn expand_static_struct_method_body(
@@ -1522,8 +1527,6 @@ impl<'a> TraitDef<'a> {
             paths.push(ident.with_span_pos(sp));
             let val = cx.expr_path(cx.path_ident(sp, ident));
             let val = if use_temporaries { val } else { cx.expr_deref(sp, val) };
-            let val = cx.expr(sp, ast::ExprKind::Paren(val));
-
             ident_exprs.push((sp, struct_field.ident, val, &struct_field.attrs[..]));
         }
 
@@ -1555,6 +1558,39 @@ impl<'a> TraitDef<'a> {
         (pattern, ident_exprs)
     }
 
+    fn create_struct_field_accesses(
+        &self,
+        cx: &mut ExtCtxt<'_>,
+        mut self_arg: &P<Expr>,
+        struct_def: &'a VariantData,
+    ) -> Vec<(Span, Option<Ident>, P<Expr>, &'a [ast::Attribute])> {
+        let mut ident_exprs = Vec::new();
+        for (i, struct_field) in struct_def.fields().iter().enumerate() {
+            let sp = struct_field.span.with_ctxt(self.span.ctxt());
+
+            // We don't the need the deref, if there is one.
+            if let ast::ExprKind::Unary(ast::UnOp::Deref, inner) = &self_arg.kind {
+                self_arg = inner;
+            }
+
+            // Note: we must use `struct_field.span` rather than `span` in the
+            // `unwrap_or_else` case otherwise the hygiene is wrong and we get
+            // "field `0` of struct `Point` is private" errors on tuple
+            // structs.
+            let val = cx.expr(
+                sp,
+                ast::ExprKind::Field(
+                    self_arg.clone(),
+                    struct_field.ident.unwrap_or_else(|| {
+                        Ident::from_str_and_span(&i.to_string(), struct_field.span)
+                    }),
+                ),
+            );
+            ident_exprs.push((sp, struct_field.ident, val, &struct_field.attrs[..]));
+        }
+        ident_exprs
+    }
+
     fn create_enum_variant_pattern(
         &self,
         cx: &mut ExtCtxt<'_>,
@@ -1643,7 +1679,6 @@ where
 /// fields.
 /// When the `substructure` is an `EnumNonMatchingCollapsed`, the result of `enum_nonmatch_f`
 /// is returned. Statics may not be folded over.
-/// See `cs_op` in `partial_ord.rs` for a model example.
 pub fn cs_fold1<F, B>(
     use_foldl: bool,
     f: F,
diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs
index fa4ea426385..f71d749df62 100644
--- a/compiler/rustc_codegen_cranelift/src/base.rs
+++ b/compiler/rustc_codegen_cranelift/src/base.rs
@@ -635,29 +635,6 @@ fn codegen_stmt<'tcx>(
                             let (ptr, _extra) = operand.load_scalar_pair(fx);
                             lval.write_cvalue(fx, CValue::by_val(ptr, dest_layout))
                         }
-                    } else if let ty::Adt(adt_def, _substs) = from_ty.kind() {
-                        // enum -> discriminant value
-                        assert!(adt_def.is_enum());
-                        match to_ty.kind() {
-                            ty::Uint(_) | ty::Int(_) => {}
-                            _ => unreachable!("cast adt {} -> {}", from_ty, to_ty),
-                        }
-                        let to_clif_ty = fx.clif_type(to_ty).unwrap();
-
-                        let discriminant = crate::discriminant::codegen_get_discriminant(
-                            fx,
-                            operand,
-                            fx.layout_of(operand.layout().ty.discriminant_ty(fx.tcx)),
-                        )
-                        .load_scalar(fx);
-
-                        let res = crate::cast::clif_intcast(
-                            fx,
-                            discriminant,
-                            to_clif_ty,
-                            to_ty.is_signed(),
-                        );
-                        lval.write_cvalue(fx, CValue::by_val(res, dest_layout));
                     } else {
                         let to_clif_ty = fx.clif_type(to_ty).unwrap();
                         let from = operand.load_scalar(fx);
diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs
index 9d2e12f9898..be2d3108c5f 100644
--- a/compiler/rustc_codegen_cranelift/src/lib.rs
+++ b/compiler/rustc_codegen_cranelift/src/lib.rs
@@ -79,7 +79,7 @@ mod prelude {
     pub(crate) use rustc_middle::ty::layout::{self, LayoutOf, TyAndLayout};
     pub(crate) use rustc_middle::ty::{
         self, FloatTy, Instance, InstanceDef, IntTy, ParamEnv, Ty, TyCtxt, TypeAndMut,
-        TypeFoldable, UintTy,
+        TypeFoldable, TypeVisitable, UintTy,
     };
     pub(crate) use rustc_target::abi::{Abi, Scalar, Size, VariantIdx};
 
diff --git a/compiler/rustc_codegen_gcc/src/callee.rs b/compiler/rustc_codegen_gcc/src/callee.rs
index 76419b103d0..c1041125ecc 100644
--- a/compiler/rustc_codegen_gcc/src/callee.rs
+++ b/compiler/rustc_codegen_gcc/src/callee.rs
@@ -1,6 +1,6 @@
 use gccjit::{FunctionType, RValue};
 use rustc_codegen_ssa::traits::BaseTypeMethods;
-use rustc_middle::ty::{self, Instance, TypeFoldable};
+use rustc_middle::ty::{self, Instance, TypeVisitable};
 use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt};
 
 use crate::abi::FnAbiGccExt;
diff --git a/compiler/rustc_codegen_gcc/src/mono_item.rs b/compiler/rustc_codegen_gcc/src/mono_item.rs
index e21d40b6c37..9468a1ef4bb 100644
--- a/compiler/rustc_codegen_gcc/src/mono_item.rs
+++ b/compiler/rustc_codegen_gcc/src/mono_item.rs
@@ -1,7 +1,7 @@
 use rustc_codegen_ssa::traits::PreDefineMethods;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::mir::mono::{Linkage, Visibility};
-use rustc_middle::ty::{self, Instance, TypeFoldable};
+use rustc_middle::ty::{self, Instance, TypeVisitable};
 use rustc_middle::ty::layout::{FnAbiOf, LayoutOf};
 use rustc_span::def_id::DefId;
 
diff --git a/compiler/rustc_codegen_gcc/src/type_of.rs b/compiler/rustc_codegen_gcc/src/type_of.rs
index 569ee2925b1..524d10fb5e2 100644
--- a/compiler/rustc_codegen_gcc/src/type_of.rs
+++ b/compiler/rustc_codegen_gcc/src/type_of.rs
@@ -3,7 +3,7 @@ use std::fmt::Write;
 use gccjit::{Struct, Type};
 use crate::rustc_codegen_ssa::traits::{BaseTypeMethods, DerivedTypeMethods, LayoutTypeMethods};
 use rustc_middle::bug;
-use rustc_middle::ty::{self, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TypeVisitable};
 use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_target::abi::{self, Abi, F32, F64, FieldsShape, Int, Integer, Pointer, PointeeInfo, Size, TyAbiInterface, Variants};
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index 8c1e865762c..4a4cccb490d 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -28,7 +28,7 @@ use std::ffi::CStr;
 use std::iter;
 use std::ops::Deref;
 use std::ptr;
-use tracing::debug;
+use tracing::{debug, instrument};
 
 // All Builders must have an llfn associated with them
 #[must_use]
@@ -464,15 +464,15 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         }
     }
 
+    #[instrument(level = "trace", skip(self))]
     fn load_operand(&mut self, place: PlaceRef<'tcx, &'ll Value>) -> OperandRef<'tcx, &'ll Value> {
-        debug!("PlaceRef::load: {:?}", place);
-
         assert_eq!(place.llextra.is_some(), place.layout.is_unsized());
 
         if place.layout.is_zst() {
             return OperandRef::new_zst(self, place.layout);
         }
 
+        #[instrument(level = "trace", skip(bx))]
         fn scalar_load_metadata<'a, 'll, 'tcx>(
             bx: &mut Builder<'a, 'll, 'tcx>,
             load: &'ll Value,
diff --git a/compiler/rustc_codegen_llvm/src/callee.rs b/compiler/rustc_codegen_llvm/src/callee.rs
index ac423a22703..72155d874a2 100644
--- a/compiler/rustc_codegen_llvm/src/callee.rs
+++ b/compiler/rustc_codegen_llvm/src/callee.rs
@@ -13,7 +13,7 @@ use rustc_codegen_ssa::traits::*;
 use tracing::debug;
 
 use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt};
-use rustc_middle::ty::{self, Instance, TypeFoldable};
+use rustc_middle::ty::{self, Instance, TypeVisitable};
 
 /// Codegens a reference to a fn/method item, monomorphizing and
 /// inlining as it goes.
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
index 71699b5cf38..64ecbc82c56 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
@@ -27,7 +27,7 @@ use rustc_index::vec::IndexVec;
 use rustc_middle::mir;
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
-use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TypeVisitable};
 use rustc_session::config::{self, DebugInfo};
 use rustc_session::Session;
 use rustc_span::symbol::Symbol;
diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
index d92d9d96fe2..5ebc2d6139f 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
@@ -1619,7 +1619,7 @@ extern "C" {
         B: &Builder<'a>,
         Val: &'a Value,
         DestTy: &'a Type,
-        IsSized: bool,
+        IsSigned: bool,
     ) -> &'a Value;
 
     // Comparisons
diff --git a/compiler/rustc_codegen_llvm/src/mono_item.rs b/compiler/rustc_codegen_llvm/src/mono_item.rs
index a3053742aad..6e94284852f 100644
--- a/compiler/rustc_codegen_llvm/src/mono_item.rs
+++ b/compiler/rustc_codegen_llvm/src/mono_item.rs
@@ -8,7 +8,7 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
 pub use rustc_middle::mir::mono::MonoItem;
 use rustc_middle::mir::mono::{Linkage, Visibility};
 use rustc_middle::ty::layout::{FnAbiOf, LayoutOf};
-use rustc_middle::ty::{self, Instance, TypeFoldable};
+use rustc_middle::ty::{self, Instance, TypeVisitable};
 use rustc_session::config::CrateType;
 use rustc_target::spec::RelocModel;
 use tracing::debug;
diff --git a/compiler/rustc_codegen_llvm/src/type_of.rs b/compiler/rustc_codegen_llvm/src/type_of.rs
index 86280523631..9f0e6c80b19 100644
--- a/compiler/rustc_codegen_llvm/src/type_of.rs
+++ b/compiler/rustc_codegen_llvm/src/type_of.rs
@@ -6,7 +6,7 @@ use rustc_codegen_ssa::traits::*;
 use rustc_middle::bug;
 use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout};
 use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
-use rustc_middle::ty::{self, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TypeVisitable};
 use rustc_target::abi::{Abi, AddressSpace, Align, FieldsShape};
 use rustc_target::abi::{Int, Pointer, F32, F64};
 use rustc_target::abi::{PointeeInfo, Scalar, Size, TyAbiInterface, Variants};
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index 0503d723240..f296a04dea1 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -17,7 +17,7 @@ use rustc_middle::mir::AssertKind;
 use rustc_middle::mir::{self, SwitchTargets};
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
 use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
-use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Instance, Ty, TypeVisitable};
 use rustc_span::source_map::Span;
 use rustc_span::{sym, Symbol};
 use rustc_symbol_mangling::typeid_for_fnabi;
diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
index 7f14b95317b..645afae30d8 100644
--- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
@@ -513,9 +513,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                         };
 
                         let ty = substs.type_at(0);
-                        if int_type_width_signed(ty, bx.tcx()).is_some()
-                            || (ty.is_unsafe_ptr() && op == "xchg")
-                        {
+                        if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() {
                             let mut ptr = args[0].immediate();
                             let mut val = args[1].immediate();
                             if ty.is_unsafe_ptr() {
diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs
index 0c958de64fa..ec3f7a2156a 100644
--- a/compiler/rustc_codegen_ssa/src/mir/mod.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs
@@ -2,7 +2,7 @@ use crate::traits::*;
 use rustc_middle::mir;
 use rustc_middle::mir::interpret::ErrorHandled;
 use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, TyAndLayout};
-use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Instance, Ty, TypeFoldable, TypeVisitable};
 use rustc_symbol_mangling::typeid_for_fnabi;
 use rustc_target::abi::call::{FnAbi, PassMode};
 
diff --git a/compiler/rustc_codegen_ssa/src/mir/place.rs b/compiler/rustc_codegen_ssa/src/mir/place.rs
index 5b88635982f..58cee0c8bb0 100644
--- a/compiler/rustc_codegen_ssa/src/mir/place.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/place.rs
@@ -204,6 +204,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
     }
 
     /// Obtain the actual discriminant of a value.
+    #[instrument(level = "trace", skip(bx))]
     pub fn codegen_get_discr<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
         self,
         bx: &mut Bx,
@@ -420,12 +421,12 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
 }
 
 impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
+    #[instrument(level = "trace", skip(self, bx))]
     pub fn codegen_place(
         &mut self,
         bx: &mut Bx,
         place_ref: mir::PlaceRef<'tcx>,
     ) -> PlaceRef<'tcx, Bx::Value> {
-        debug!("codegen_place(place_ref={:?})", place_ref);
         let cx = self.cx;
         let tcx = self.cx.tcx();
 
diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
index a5806d64d43..7ff12823bf7 100644
--- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
@@ -12,17 +12,15 @@ use rustc_middle::ty::cast::{CastTy, IntTy};
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
 use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
 use rustc_span::source_map::{Span, DUMMY_SP};
-use rustc_target::abi::{Abi, Int, Variants};
 
 impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
+    #[instrument(level = "trace", skip(self, bx))]
     pub fn codegen_rvalue(
         &mut self,
         mut bx: Bx,
         dest: PlaceRef<'tcx, Bx::Value>,
         rvalue: &mir::Rvalue<'tcx>,
     ) -> Bx {
-        debug!("codegen_rvalue(dest.llval={:?}, rvalue={:?})", dest.llval, rvalue);
-
         match *rvalue {
             mir::Rvalue::Use(ref operand) => {
                 let cg_operand = self.codegen_operand(&mut bx, operand);
@@ -285,74 +283,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                             CastTy::from_ty(operand.layout.ty).expect("bad input type for cast");
                         let r_t_out = CastTy::from_ty(cast.ty).expect("bad output type for cast");
                         let ll_t_in = bx.cx().immediate_backend_type(operand.layout);
-                        match operand.layout.variants {
-                            Variants::Single { index } => {
-                                if let Some(discr) =
-                                    operand.layout.ty.discriminant_for_variant(bx.tcx(), index)
-                                {
-                                    let discr_layout = bx.cx().layout_of(discr.ty);
-                                    let discr_t = bx.cx().immediate_backend_type(discr_layout);
-                                    let discr_val = bx.cx().const_uint_big(discr_t, discr.val);
-                                    let discr_val =
-                                        bx.intcast(discr_val, ll_t_out, discr.ty.is_signed());
-
-                                    return (
-                                        bx,
-                                        OperandRef {
-                                            val: OperandValue::Immediate(discr_val),
-                                            layout: cast,
-                                        },
-                                    );
-                                }
-                            }
-                            Variants::Multiple { .. } => {}
-                        }
                         let llval = operand.immediate();
 
-                        let mut signed = false;
-                        if let Abi::Scalar(scalar) = operand.layout.abi {
-                            if let Int(_, s) = scalar.primitive() {
-                                // We use `i1` for bytes that are always `0` or `1`,
-                                // e.g., `#[repr(i8)] enum E { A, B }`, but we can't
-                                // let LLVM interpret the `i1` as signed, because
-                                // then `i1 1` (i.e., E::B) is effectively `i8 -1`.
-                                signed = !scalar.is_bool() && s;
-
-                                if !scalar.is_always_valid(bx.cx())
-                                    && scalar.valid_range(bx.cx()).end
-                                        >= scalar.valid_range(bx.cx()).start
-                                {
-                                    // We want `table[e as usize ± k]` to not
-                                    // have bound checks, and this is the most
-                                    // convenient place to put the `assume`s.
-                                    if scalar.valid_range(bx.cx()).start > 0 {
-                                        let enum_value_lower_bound = bx.cx().const_uint_big(
-                                            ll_t_in,
-                                            scalar.valid_range(bx.cx()).start,
-                                        );
-                                        let cmp_start = bx.icmp(
-                                            IntPredicate::IntUGE,
-                                            llval,
-                                            enum_value_lower_bound,
-                                        );
-                                        bx.assume(cmp_start);
-                                    }
-
-                                    let enum_value_upper_bound = bx
-                                        .cx()
-                                        .const_uint_big(ll_t_in, scalar.valid_range(bx.cx()).end);
-                                    let cmp_end = bx.icmp(
-                                        IntPredicate::IntULE,
-                                        llval,
-                                        enum_value_upper_bound,
-                                    );
-                                    bx.assume(cmp_end);
-                                }
-                            }
-                        }
-
                         let newval = match (r_t_in, r_t_out) {
-                            (CastTy::Int(_), CastTy::Int(_)) => bx.intcast(llval, ll_t_out, signed),
+                            (CastTy::Int(i), CastTy::Int(_)) => {
+                                bx.intcast(llval, ll_t_out, i.is_signed())
+                            }
                             (CastTy::Float, CastTy::Float) => {
                                 let srcsz = bx.cx().float_width(ll_t_in);
                                 let dstsz = bx.cx().float_width(ll_t_out);
@@ -364,8 +300,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                     llval
                                 }
                             }
-                            (CastTy::Int(_), CastTy::Float) => {
-                                if signed {
+                            (CastTy::Int(i), CastTy::Float) => {
+                                if i.is_signed() {
                                     bx.sitofp(llval, ll_t_out)
                                 } else {
                                     bx.uitofp(llval, ll_t_out)
@@ -374,8 +310,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                             (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Ptr(_)) => {
                                 bx.pointercast(llval, ll_t_out)
                             }
-                            (CastTy::Int(_), CastTy::Ptr(_)) => {
-                                let usize_llval = bx.intcast(llval, bx.cx().type_isize(), signed);
+                            (CastTy::Int(i), CastTy::Ptr(_)) => {
+                                let usize_llval =
+                                    bx.intcast(llval, bx.cx().type_isize(), i.is_signed());
                                 bx.inttoptr(usize_llval, ll_t_out)
                             }
                             (CastTy::Float, CastTy::Int(IntTy::I)) => {
diff --git a/compiler/rustc_codegen_ssa/src/mir/statement.rs b/compiler/rustc_codegen_ssa/src/mir/statement.rs
index d9ebfc3e871..f452f29883f 100644
--- a/compiler/rustc_codegen_ssa/src/mir/statement.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/statement.rs
@@ -6,9 +6,8 @@ use crate::traits::BuilderMethods;
 use crate::traits::*;
 
 impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
+    #[instrument(level = "debug", skip(self, bx))]
     pub fn codegen_statement(&mut self, mut bx: Bx, statement: &mir::Statement<'tcx>) -> Bx {
-        debug!("codegen_statement(statement={:?})", statement);
-
         self.set_debug_loc(&mut bx, statement.source_info);
         match statement.kind {
             mir::StatementKind::Assign(box (ref place, ref rvalue)) => {
diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
index 0dac4f8978e..f84dd9521ee 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -189,6 +189,7 @@ pub(super) fn op_to_const<'tcx>(
                 let len: usize = len.try_into().unwrap();
                 ConstValue::Slice { data, start, end: start + len }
             }
+            Immediate::Uninit => to_const_value(&op.assert_mem_place()),
         },
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index d09ab5fa3e8..5d598b65c72 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -8,7 +8,7 @@ use rustc_middle::mir::CastKind;
 use rustc_middle::ty::adjustment::PointerCast;
 use rustc_middle::ty::layout::{IntegerExt, LayoutOf, TyAndLayout};
 use rustc_middle::ty::{self, FloatTy, Ty, TypeAndMut};
-use rustc_target::abi::{Integer, Variants};
+use rustc_target::abi::Integer;
 use rustc_type_ir::sty::TyKind::*;
 
 use super::{
@@ -128,12 +128,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             Float(FloatTy::F64) => {
                 return Ok(self.cast_from_float(src.to_scalar()?.to_f64()?, cast_ty).into());
             }
-            // The rest is integer/pointer-"like", including fn ptr casts and casts from enums that
-            // are represented as integers.
+            // The rest is integer/pointer-"like", including fn ptr casts
             _ => assert!(
                 src.layout.ty.is_bool()
                     || src.layout.ty.is_char()
-                    || src.layout.ty.is_enum()
                     || src.layout.ty.is_integral()
                     || src.layout.ty.is_any_ptr(),
                 "Unexpected cast from type {:?}",
@@ -143,25 +141,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
         // # First handle non-scalar source values.
 
-        // Handle cast from a ZST enum (0 or 1 variants).
-        match src.layout.variants {
-            Variants::Single { index } => {
-                if src.layout.abi.is_uninhabited() {
-                    // This is dead code, because an uninhabited enum is UB to
-                    // instantiate.
-                    throw_ub!(Unreachable);
-                }
-                if let Some(discr) = src.layout.ty.discriminant_for_variant(*self.tcx, index) {
-                    assert!(src.layout.is_zst());
-                    let discr_layout = self.layout_of(discr.ty)?;
-
-                    let scalar = Scalar::from_uint(discr.val, discr_layout.layout.size());
-                    return Ok(self.cast_from_int_like(scalar, discr_layout, cast_ty)?.into());
-                }
-            }
-            Variants::Multiple { .. } => {}
-        }
-
         // Handle casting any ptr to raw ptr (might be a fat ptr).
         if src.layout.ty.is_any_ptr() && cast_ty.is_unsafe_ptr() {
             let dest_layout = self.layout_of(cast_ty)?;
@@ -174,7 +153,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 assert_eq!(dest_layout.size, self.pointer_size());
                 assert!(src.layout.ty.is_unsafe_ptr());
                 return match **src {
-                    Immediate::ScalarPair(data, _) => Ok(data.into()),
+                    Immediate::ScalarPair(data, _) => Ok(data.check_init()?.into()),
                     Immediate::Scalar(..) => span_bug!(
                         self.cur_span(),
                         "{:?} input to a fat-to-thin cast ({:?} -> {:?})",
@@ -182,6 +161,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         src.layout.ty,
                         cast_ty
                     ),
+                    Immediate::Uninit => throw_ub!(InvalidUninitBytes(None)),
                 };
             }
         }
@@ -379,7 +359,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let src_field = self.operand_field(src, i)?;
                     let dst_field = self.place_field(dest, i)?;
                     if src_field.layout.ty == cast_ty_field.ty {
-                        self.copy_op(&src_field, &dst_field)?;
+                        self.copy_op(&src_field, &dst_field, /*allow_transmute*/ false)?;
                     } else {
                         self.unsize_into(&src_field, cast_ty_field, &dst_field)?;
                     }
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index 66c73624501..3892d1920ce 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -112,6 +112,8 @@ pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
     /// The locals are stored as `Option<Value>`s.
     /// `None` represents a local that is currently dead, while a live local
     /// can either directly contain `Scalar` or refer to some part of an `Allocation`.
+    ///
+    /// Do *not* access this directly; always go through the machine hook!
     pub locals: IndexVec<mir::Local, LocalState<'tcx, Tag>>,
 
     /// The span of the `tracing` crate is stored here.
@@ -179,10 +181,6 @@ pub struct LocalState<'tcx, Tag: Provenance = AllocId> {
 pub enum LocalValue<Tag: Provenance = AllocId> {
     /// This local is not currently alive, and cannot be used at all.
     Dead,
-    /// This local is alive but not yet allocated. It cannot be read from or have its address taken,
-    /// and will be allocated on the first write. This is to support unsized locals, where we cannot
-    /// know their size in advance.
-    Unallocated,
     /// A normal, live local.
     /// Mostly for convenience, we re-use the `Operand` type here.
     /// This is an optimization over just always having a pointer here;
@@ -196,12 +194,10 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
     ///
     /// Note: This may only be invoked from the `Machine::access_local` hook and not from
     /// anywhere else. You may be invalidating machine invariants if you do!
-    pub fn access(&self) -> InterpResult<'tcx, Operand<Tag>> {
-        match self.value {
-            LocalValue::Dead => throw_ub!(DeadLocal),
-            LocalValue::Unallocated => {
-                bug!("The type checker should prevent reading from a never-written local")
-            }
+    #[inline]
+    pub fn access(&self) -> InterpResult<'tcx, &Operand<Tag>> {
+        match &self.value {
+            LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
             LocalValue::Live(val) => Ok(val),
         }
     }
@@ -211,15 +207,11 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
     ///
     /// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
     /// anywhere else. You may be invalidating machine invariants if you do!
-    pub fn access_mut(
-        &mut self,
-    ) -> InterpResult<'tcx, Result<&mut LocalValue<Tag>, MemPlace<Tag>>> {
-        match self.value {
-            LocalValue::Dead => throw_ub!(DeadLocal),
-            LocalValue::Live(Operand::Indirect(mplace)) => Ok(Err(mplace)),
-            ref mut local @ (LocalValue::Live(Operand::Immediate(_)) | LocalValue::Unallocated) => {
-                Ok(Ok(local))
-            }
+    #[inline]
+    pub fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Tag>> {
+        match &mut self.value {
+            LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
+            LocalValue::Live(val) => Ok(val),
         }
     }
 }
@@ -710,16 +702,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             })?;
         }
 
-        // Locals are initially unallocated.
-        let dummy = LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
+        // Most locals are initially dead.
+        let dummy = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
         let mut locals = IndexVec::from_elem(dummy, &body.local_decls);
 
-        // Now mark those locals as dead that we do not want to initialize
-        // Mark locals that use `Storage*` annotations as dead on function entry.
+        // Now mark those locals as live that have no `Storage*` annotations.
         let always_live = always_live_locals(self.body());
         for local in locals.indices() {
-            if !always_live.contains(local) {
-                locals[local].value = LocalValue::Dead;
+            if always_live.contains(local) {
+                locals[local].value = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
             }
         }
         // done
@@ -791,7 +782,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             if unwinding { "during unwinding" } else { "returning from function" }
         );
 
-        // Sanity check `unwinding`.
+        // Check `unwinding`.
         assert_eq!(
             unwinding,
             match self.frame().loc {
@@ -799,51 +790,61 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 Err(_) => true,
             }
         );
-
         if unwinding && self.frame_idx() == 0 {
             throw_ub_format!("unwinding past the topmost frame of the stack");
         }
 
-        let frame =
-            self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
-
-        if !unwinding {
-            let op = self.access_local(&frame, mir::RETURN_PLACE, None)?;
-            self.copy_op_transmute(&op, &frame.return_place)?;
-            trace!("{:?}", self.dump_place(*frame.return_place));
-        }
-
-        let return_to_block = frame.return_to_block;
-
-        // Now where do we jump next?
+        // Copy return value. Must of course happen *before* we deallocate the locals.
+        let copy_ret_result = if !unwinding {
+            let op = self
+                .local_to_op(self.frame(), mir::RETURN_PLACE, None)
+                .expect("return place should always be live");
+            let dest = self.frame().return_place;
+            let err = self.copy_op(&op, &dest, /*allow_transmute*/ true);
+            trace!("return value: {:?}", self.dump_place(*dest));
+            // We delay actually short-circuiting on this error until *after* the stack frame is
+            // popped, since we want this error to be attributed to the caller, whose type defines
+            // this transmute.
+            err
+        } else {
+            Ok(())
+        };
 
+        // Cleanup: deallocate locals.
         // Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
-        // In that case, we return early. We also avoid validation in that case,
-        // because this is CTFE and the final value will be thoroughly validated anyway.
+        // We do this while the frame is still on the stack, so errors point to the callee.
+        let return_to_block = self.frame().return_to_block;
         let cleanup = match return_to_block {
             StackPopCleanup::Goto { .. } => true,
             StackPopCleanup::Root { cleanup, .. } => cleanup,
         };
+        if cleanup {
+            // We need to take the locals out, since we need to mutate while iterating.
+            let locals = mem::take(&mut self.frame_mut().locals);
+            for local in &locals {
+                self.deallocate_local(local.value)?;
+            }
+        }
+
+        // All right, now it is time to actually pop the frame.
+        // Note that its locals are gone already, but that's fine.
+        let frame =
+            self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
+        // Report error from return value copy, if any.
+        copy_ret_result?;
 
+        // If we are not doing cleanup, also skip everything else.
         if !cleanup {
             assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
             assert!(!unwinding, "tried to skip cleanup during unwinding");
-            // Leak the locals, skip validation, skip machine hook.
+            // Skip machine hook.
             return Ok(());
         }
-
-        trace!("locals: {:#?}", frame.locals);
-
-        // Cleanup: deallocate all locals that are backed by an allocation.
-        for local in &frame.locals {
-            self.deallocate_local(local.value)?;
-        }
-
         if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump {
             // The hook already did everything.
-            // We want to skip the `info!` below, hence early return.
             return Ok(());
         }
+
         // Normal return, figure out where to jump.
         if unwinding {
             // Follow the unwind edge.
@@ -874,7 +875,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
         trace!("{:?} is now live", local);
 
-        let local_val = LocalValue::Unallocated;
+        let local_val = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
         // StorageLive expects the local to be dead, and marks it live.
         let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
         if !matches!(old, LocalValue::Dead) {
@@ -977,12 +978,13 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
 
                 match self.ecx.stack()[frame].locals[local].value {
                     LocalValue::Dead => write!(fmt, " is dead")?,
-                    LocalValue::Unallocated => write!(fmt, " is unallocated")?,
+                    LocalValue::Live(Operand::Immediate(Immediate::Uninit)) => {
+                        write!(fmt, " is uninitialized")?
+                    }
                     LocalValue::Live(Operand::Indirect(mplace)) => {
                         write!(
                             fmt,
-                            " by align({}){} ref {:?}:",
-                            mplace.align.bytes(),
+                            " by {} ref {:?}:",
                             match mplace.meta {
                                 MemPlaceMeta::Meta(meta) => format!(" meta({:?})", meta),
                                 MemPlaceMeta::Poison | MemPlaceMeta::None => String::new(),
@@ -1011,13 +1013,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
                 write!(fmt, ": {:?}", self.ecx.dump_allocs(allocs.into_iter().flatten().collect()))
             }
             Place::Ptr(mplace) => match mplace.ptr.provenance.and_then(Provenance::get_alloc_id) {
-                Some(alloc_id) => write!(
-                    fmt,
-                    "by align({}) ref {:?}: {:?}",
-                    mplace.align.bytes(),
-                    mplace.ptr,
-                    self.ecx.dump_alloc(alloc_id)
-                ),
+                Some(alloc_id) => {
+                    write!(fmt, "by ref {:?}: {:?}", mplace.ptr, self.ecx.dump_alloc(alloc_id))
+                }
                 ptr => write!(fmt, " integral by ref: {:?}", ptr),
             },
         }
diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
index e51c51cf45e..93b64d9d37a 100644
--- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs
+++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
@@ -174,7 +174,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let val =
                     self.tcx.const_eval_global_id(self.param_env, gid, Some(self.tcx.span))?;
                 let val = self.const_val_to_op(val, ty, Some(dest.layout))?;
-                self.copy_op(&val, dest)?;
+                self.copy_op(&val, dest, /*allow_transmute*/ false)?;
             }
 
             sym::ctpop
@@ -217,7 +217,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     sym::mul_with_overflow => BinOp::Mul,
                     _ => bug!(),
                 };
-                self.binop_with_overflow(bin_op, &lhs, &rhs, dest)?;
+                self.binop_with_overflow(
+                    bin_op, /*force_overflow_checks*/ true, &lhs, &rhs, dest,
+                )?;
             }
             sym::saturating_add | sym::saturating_sub => {
                 let l = self.read_immediate(&args[0])?;
@@ -392,7 +394,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             }
 
             sym::transmute => {
-                self.copy_op_transmute(&args[0], dest)?;
+                self.copy_op(&args[0], dest, /*allow_transmute*/ true)?;
             }
             sym::assert_inhabited | sym::assert_zero_valid | sym::assert_uninit_valid => {
                 let ty = instance.substs.type_at(0);
@@ -459,7 +461,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let place = self.mplace_index(&dest, i)?;
                     let value =
                         if i == index { *elem } else { self.mplace_index(&input, i)?.into() };
-                    self.copy_op(&value, &place.into())?;
+                    self.copy_op(&value, &place.into(), /*allow_transmute*/ false)?;
                 }
             }
             sym::simd_extract => {
@@ -471,11 +473,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     index,
                     input_len
                 );
-                self.copy_op(&self.mplace_index(&input, index)?.into(), dest)?;
+                self.copy_op(
+                    &self.mplace_index(&input, index)?.into(),
+                    dest,
+                    /*allow_transmute*/ false,
+                )?;
             }
             sym::likely | sym::unlikely | sym::black_box => {
                 // These just return their argument
-                self.copy_op(&args[0], dest)?;
+                self.copy_op(&args[0], dest, /*allow_transmute*/ false)?;
             }
             sym::assume => {
                 let cond = self.read_scalar(&args[0])?.check_init()?.to_bool()?;
diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs
index c18ac84171d..b3461b414b6 100644
--- a/compiler/rustc_const_eval/src/interpret/machine.rs
+++ b/compiler/rustc_const_eval/src/interpret/machine.rs
@@ -14,8 +14,7 @@ use rustc_target::spec::abi::Abi;
 
 use super::{
     AllocId, AllocRange, Allocation, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
-    LocalValue, MemPlace, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar,
-    StackPopUnwind,
+    MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar, StackPopUnwind,
 };
 
 /// Data returned by Machine::stack_pop,
@@ -144,6 +143,9 @@ pub trait Machine<'mir, 'tcx>: Sized {
         true
     }
 
+    /// Whether CheckedBinOp MIR statements should actually check for overflow.
+    fn checked_binop_checks_overflow(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
+
     /// Entry point for obtaining the MIR of anything that should get evaluated.
     /// So not just functions and shims, but also const/static initializers, anonymous
     /// constants, ...
@@ -223,11 +225,13 @@ pub trait Machine<'mir, 'tcx>: Sized {
     /// Since reading a ZST is not actually accessing memory or locals, this is never invoked
     /// for ZST reads.
     #[inline]
-    fn access_local(
-        _ecx: &InterpCx<'mir, 'tcx, Self>,
-        frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
+    fn access_local<'a>(
+        frame: &'a Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
         local: mir::Local,
-    ) -> InterpResult<'tcx, Operand<Self::PointerTag>> {
+    ) -> InterpResult<'tcx, &'a Operand<Self::PointerTag>>
+    where
+        'tcx: 'mir,
+    {
         frame.locals[local].access()
     }
 
@@ -239,7 +243,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
         frame: usize,
         local: mir::Local,
-    ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
+    ) -> InterpResult<'tcx, &'a mut Operand<Self::PointerTag>>
     where
         'tcx: 'mir,
     {
@@ -415,12 +419,14 @@ pub trait Machine<'mir, 'tcx>: Sized {
     }
 
     /// Called immediately after a stack frame got popped, but before jumping back to the caller.
+    /// The `locals` have already been destroyed!
     fn after_stack_pop(
         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
         _frame: Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
-        _unwinding: bool,
+        unwinding: bool,
     ) -> InterpResult<'tcx, StackPopJump> {
         // By default, we do not support unwinding from panics
+        assert!(!unwinding);
         Ok(StackPopJump::Normal)
     }
 }
@@ -469,6 +475,11 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
     }
 
     #[inline(always)]
+    fn checked_binop_checks_overflow(_ecx: &InterpCx<$mir, $tcx, Self>) -> bool {
+        true
+    }
+
+    #[inline(always)]
     fn call_extra_fn(
         _ecx: &mut InterpCx<$mir, $tcx, Self>,
         fn_val: !,
@@ -513,7 +524,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
         _ecx: &InterpCx<$mir, $tcx, Self>,
         addr: u64,
     ) -> Pointer<Option<AllocId>> {
-        Pointer::new(None, Size::from_bytes(addr))
+        Pointer::from_addr(addr)
     }
 
     #[inline(always)]
@@ -523,7 +534,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
     ) -> InterpResult<$tcx, Pointer<Option<AllocId>>> {
         // Allow these casts, but make the pointer not dereferenceable.
         // (I.e., they behave like transmutation.)
-        Ok(Pointer::new(None, Size::from_bytes(addr)))
+        Ok(Pointer::from_addr(addr))
     }
 
     #[inline(always)]
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index c2a5b71b8f9..509fe576893 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -276,7 +276,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         kind: MemoryKind<M::MemoryKind>,
     ) -> InterpResult<'tcx> {
         let (alloc_id, offset, tag) = self.ptr_get_alloc_id(ptr)?;
-        trace!("deallocating: {}", alloc_id);
+        trace!("deallocating: {alloc_id:?}");
 
         if offset.bytes() != 0 {
             throw_ub_format!(
@@ -289,10 +289,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             // Deallocating global memory -- always an error
             return Err(match self.tcx.get_global_alloc(alloc_id) {
                 Some(GlobalAlloc::Function(..)) => {
-                    err_ub_format!("deallocating {}, which is a function", alloc_id)
+                    err_ub_format!("deallocating {alloc_id:?}, which is a function")
                 }
                 Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
-                    err_ub_format!("deallocating {}, which is static memory", alloc_id)
+                    err_ub_format!("deallocating {alloc_id:?}, which is static memory")
                 }
                 None => err_ub!(PointerUseAfterFree(alloc_id)),
             }
@@ -302,21 +302,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         debug!(?alloc);
 
         if alloc.mutability == Mutability::Not {
-            throw_ub_format!("deallocating immutable allocation {}", alloc_id);
+            throw_ub_format!("deallocating immutable allocation {alloc_id:?}");
         }
         if alloc_kind != kind {
             throw_ub_format!(
-                "deallocating {}, which is {} memory, using {} deallocation operation",
-                alloc_id,
-                alloc_kind,
-                kind
+                "deallocating {alloc_id:?}, which is {alloc_kind} memory, using {kind} deallocation operation"
             );
         }
         if let Some((size, align)) = old_size_and_align {
             if size != alloc.size() || align != alloc.align {
                 throw_ub_format!(
-                    "incorrect layout on deallocation: {} has size {} and alignment {}, but gave size {} and alignment {}",
-                    alloc_id,
+                    "incorrect layout on deallocation: {alloc_id:?} has size {} and alignment {}, but gave size {} and alignment {}",
                     alloc.size().bytes(),
                     alloc.align.bytes(),
                     size.bytes(),
@@ -815,7 +811,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a,
                 continue;
             }
 
-            write!(fmt, "{}", id)?;
+            write!(fmt, "{id:?}")?;
             match self.ecx.memory.alloc_map.get(id) {
                 Some(&(kind, ref alloc)) => {
                     // normal alloc
@@ -859,25 +855,21 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a,
 
 /// Reading and writing.
 impl<'tcx, 'a, Tag: Provenance, Extra> AllocRefMut<'a, 'tcx, Tag, Extra> {
+    /// `range` is relative to this allocation reference, not the base of the allocation.
     pub fn write_scalar(
         &mut self,
         range: AllocRange,
         val: ScalarMaybeUninit<Tag>,
     ) -> InterpResult<'tcx> {
         let range = self.range.subrange(range);
-        debug!(
-            "write_scalar in {} at {:#x}, size {}: {:?}",
-            self.alloc_id,
-            range.start.bytes(),
-            range.size.bytes(),
-            val
-        );
+        debug!("write_scalar at {:?}{range:?}: {val:?}", self.alloc_id);
         Ok(self
             .alloc
             .write_scalar(&self.tcx, range, val)
             .map_err(|e| e.to_interp_error(self.alloc_id))?)
     }
 
+    /// `offset` is relative to this allocation reference, not the base of the allocation.
     pub fn write_ptr_sized(
         &mut self,
         offset: Size,
@@ -896,6 +888,7 @@ impl<'tcx, 'a, Tag: Provenance, Extra> AllocRefMut<'a, 'tcx, Tag, Extra> {
 }
 
 impl<'tcx, 'a, Tag: Provenance, Extra> AllocRef<'a, 'tcx, Tag, Extra> {
+    /// `range` is relative to this allocation reference, not the base of the allocation.
     pub fn read_scalar(
         &self,
         range: AllocRange,
@@ -906,24 +899,16 @@ impl<'tcx, 'a, Tag: Provenance, Extra> AllocRef<'a, 'tcx, Tag, Extra> {
             .alloc
             .read_scalar(&self.tcx, range, read_provenance)
             .map_err(|e| e.to_interp_error(self.alloc_id))?;
-        debug!(
-            "read_scalar in {} at {:#x}, size {}: {:?}",
-            self.alloc_id,
-            range.start.bytes(),
-            range.size.bytes(),
-            res
-        );
+        debug!("read_scalar at {:?}{range:?}: {res:?}", self.alloc_id);
         Ok(res)
     }
 
-    pub fn read_integer(
-        &self,
-        offset: Size,
-        size: Size,
-    ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
-        self.read_scalar(alloc_range(offset, size), /*read_provenance*/ false)
+    /// `range` is relative to this allocation reference, not the base of the allocation.
+    pub fn read_integer(&self, range: AllocRange) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
+        self.read_scalar(range, /*read_provenance*/ false)
     }
 
+    /// `offset` is relative to this allocation reference, not the base of the allocation.
     pub fn read_pointer(&self, offset: Size) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
         self.read_scalar(
             alloc_range(offset, self.tcx.data_layout().pointer_size),
@@ -931,6 +916,7 @@ impl<'tcx, 'a, Tag: Provenance, Extra> AllocRef<'a, 'tcx, Tag, Extra> {
         )
     }
 
+    /// `range` is relative to this allocation reference, not the base of the allocation.
     pub fn check_bytes(
         &self,
         range: AllocRange,
diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs
index e7a08e05275..805dcb38895 100644
--- a/compiler/rustc_const_eval/src/interpret/operand.rs
+++ b/compiler/rustc_const_eval/src/interpret/operand.rs
@@ -10,11 +10,11 @@ use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt, TyAndLayout};
 use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Printer};
 use rustc_middle::ty::{ConstInt, DelaySpanBugEmitted, Ty};
 use rustc_middle::{mir, ty};
-use rustc_target::abi::{self, Abi, HasDataLayout, Size, TagEncoding};
+use rustc_target::abi::{self, Abi, Align, HasDataLayout, Size, TagEncoding};
 use rustc_target::abi::{VariantIdx, Variants};
 
 use super::{
-    alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, GlobalId,
+    alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, Frame, GlobalId,
     InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, Place, PlaceTy, Pointer,
     PointerArithmetic, Provenance, Scalar, ScalarMaybeUninit,
 };
@@ -28,8 +28,15 @@ use super::{
 /// defined on `Immediate`, and do not have to work with a `Place`.
 #[derive(Copy, Clone, PartialEq, Eq, HashStable, Hash, Debug)]
 pub enum Immediate<Tag: Provenance = AllocId> {
+    /// A single scalar value (must have *initialized* `Scalar` ABI).
+    /// FIXME: we also currently often use this for ZST.
+    /// `ScalarMaybeUninit` should reject ZST, and we should use `Uninit` for them instead.
     Scalar(ScalarMaybeUninit<Tag>),
+    /// A pair of two scalar value (must have `ScalarPair` ABI where both fields are
+    /// `Scalar::Initialized`).
     ScalarPair(ScalarMaybeUninit<Tag>, ScalarMaybeUninit<Tag>),
+    /// A value of fully uninitialized memory. Can have and size and layout.
+    Uninit,
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
@@ -75,6 +82,7 @@ impl<'tcx, Tag: Provenance> Immediate<Tag> {
         match self {
             Immediate::Scalar(val) => val,
             Immediate::ScalarPair(..) => bug!("Got a scalar pair where a scalar was expected"),
+            Immediate::Uninit => ScalarMaybeUninit::Uninit,
         }
     }
 
@@ -88,6 +96,7 @@ impl<'tcx, Tag: Provenance> Immediate<Tag> {
         match self {
             Immediate::ScalarPair(val1, val2) => (val1, val2),
             Immediate::Scalar(..) => bug!("Got a scalar where a scalar pair was expected"),
+            Immediate::Uninit => (ScalarMaybeUninit::Uninit, ScalarMaybeUninit::Uninit),
         }
     }
 
@@ -149,7 +158,10 @@ impl<Tag: Provenance> std::fmt::Display for ImmTy<'_, Tag> {
                 }
                 Immediate::ScalarPair(a, b) => {
                     // FIXME(oli-obk): at least print tuples and slices nicely
-                    write!(f, "({:x}, {:x}): {}", a, b, self.layout.ty,)
+                    write!(f, "({:x}, {:x}): {}", a, b, self.layout.ty)
+                }
+                Immediate::Uninit => {
+                    write!(f, "uninit: {}", self.layout.ty)
                 }
             }
         })
@@ -177,10 +189,18 @@ pub enum Operand<Tag: Provenance = AllocId> {
 pub struct OpTy<'tcx, Tag: Provenance = AllocId> {
     op: Operand<Tag>, // Keep this private; it helps enforce invariants.
     pub layout: TyAndLayout<'tcx>,
+    /// rustc does not have a proper way to represent the type of a field of a `repr(packed)` struct:
+    /// it needs to have a different alignment than the field type would usually have.
+    /// So we represent this here with a separate field that "overwrites" `layout.align`.
+    /// This means `layout.align` should never be used for an `OpTy`!
+    /// `None` means "alignment does not matter since this is a by-value operand"
+    /// (`Operand::Immediate`); this field is only relevant for `Operand::Indirect`.
+    /// Also CTFE ignores alignment anyway, so this is for Miri only.
+    pub align: Option<Align>,
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(OpTy<'_>, 80);
+rustc_data_structures::static_assert_size!(OpTy<'_>, 88);
 
 impl<'tcx, Tag: Provenance> std::ops::Deref for OpTy<'tcx, Tag> {
     type Target = Operand<Tag>;
@@ -193,28 +213,28 @@ impl<'tcx, Tag: Provenance> std::ops::Deref for OpTy<'tcx, Tag> {
 impl<'tcx, Tag: Provenance> From<MPlaceTy<'tcx, Tag>> for OpTy<'tcx, Tag> {
     #[inline(always)]
     fn from(mplace: MPlaceTy<'tcx, Tag>) -> Self {
-        OpTy { op: Operand::Indirect(*mplace), layout: mplace.layout }
+        OpTy { op: Operand::Indirect(*mplace), layout: mplace.layout, align: Some(mplace.align) }
     }
 }
 
 impl<'tcx, Tag: Provenance> From<&'_ MPlaceTy<'tcx, Tag>> for OpTy<'tcx, Tag> {
     #[inline(always)]
     fn from(mplace: &MPlaceTy<'tcx, Tag>) -> Self {
-        OpTy { op: Operand::Indirect(**mplace), layout: mplace.layout }
+        OpTy { op: Operand::Indirect(**mplace), layout: mplace.layout, align: Some(mplace.align) }
     }
 }
 
 impl<'tcx, Tag: Provenance> From<&'_ mut MPlaceTy<'tcx, Tag>> for OpTy<'tcx, Tag> {
     #[inline(always)]
     fn from(mplace: &mut MPlaceTy<'tcx, Tag>) -> Self {
-        OpTy { op: Operand::Indirect(**mplace), layout: mplace.layout }
+        OpTy { op: Operand::Indirect(**mplace), layout: mplace.layout, align: Some(mplace.align) }
     }
 }
 
 impl<'tcx, Tag: Provenance> From<ImmTy<'tcx, Tag>> for OpTy<'tcx, Tag> {
     #[inline(always)]
     fn from(val: ImmTy<'tcx, Tag>) -> Self {
-        OpTy { op: Operand::Immediate(val.imm), layout: val.layout }
+        OpTy { op: Operand::Immediate(val.imm), layout: val.layout, align: None }
     }
 }
 
@@ -298,9 +318,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             s.is_ptr() || (number_may_have_provenance && size == self.pointer_size())
         };
         if let Some(s) = scalar_layout {
-            //FIXME(#96185): let size = s.size(self);
-            //FIXME(#96185): assert_eq!(size, mplace.layout.size, "abi::Scalar size does not match layout size");
-            let size = mplace.layout.size; //FIXME(#96185): remove this line
+            let size = s.size(self);
+            assert_eq!(size, mplace.layout.size, "abi::Scalar size does not match layout size");
             let scalar =
                 alloc.read_scalar(alloc_range(Size::ZERO, size), read_provenance(s, size))?;
             return Ok(Some(ImmTy { imm: scalar.into(), layout: mplace.layout }));
@@ -390,7 +409,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         self.scalar_to_ptr(self.read_scalar(op)?.check_init()?)
     }
 
-    // Turn the wide MPlace into a string (must already be dereferenced!)
+    /// Turn the wide MPlace into a string (must already be dereferenced!)
     pub fn read_str(&self, mplace: &MPlaceTy<'tcx, M::PointerTag>) -> InterpResult<'tcx, &str> {
         let len = mplace.len(self)?;
         let bytes = self.read_bytes_ptr(mplace.ptr, Size::from_bytes(len))?;
@@ -450,7 +469,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             ),
         };
 
-        Ok(OpTy { op: Operand::Immediate(field_val), layout: field_layout })
+        Ok(OpTy { op: Operand::Immediate(field_val), layout: field_layout, align: None })
     }
 
     pub fn operand_index(
@@ -521,10 +540,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     /// Will not access memory, instead an indirect `Operand` is returned.
     ///
     /// This is public because it is used by [priroda](https://github.com/oli-obk/priroda) to get an
-    /// OpTy from a local
-    pub fn access_local(
+    /// OpTy from a local.
+    pub fn local_to_op(
         &self,
-        frame: &super::Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,
+        frame: &Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,
         local: mir::Local,
         layout: Option<TyAndLayout<'tcx>>,
     ) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
@@ -533,9 +552,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             // Do not read from ZST, they might not be initialized
             Operand::Immediate(Scalar::ZST.into())
         } else {
-            M::access_local(&self, frame, local)?
+            *M::access_local(frame, local)?
         };
-        Ok(OpTy { op, layout })
+        Ok(OpTy { op, layout, align: Some(layout.align.abi) })
     }
 
     /// Every place can be read from, so we can turn them into an operand.
@@ -549,10 +568,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         let op = match **place {
             Place::Ptr(mplace) => Operand::Indirect(mplace),
             Place::Local { frame, local } => {
-                *self.access_local(&self.stack()[frame], local, None)?
+                *self.local_to_op(&self.stack()[frame], local, None)?
             }
         };
-        Ok(OpTy { op, layout: place.layout })
+        Ok(OpTy { op, layout: place.layout, align: Some(place.align) })
     }
 
     /// Evaluate a place with the goal of reading from it.  This lets us sometimes
@@ -566,7 +585,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // here is not the entire place.
         let layout = if place.projection.is_empty() { layout } else { None };
 
-        let base_op = self.access_local(self.frame(), place.local, layout)?;
+        let base_op = self.local_to_op(self.frame(), place.local, layout)?;
 
         let op = place
             .projection
@@ -603,11 +622,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             Constant(ref constant) => {
                 let val =
                     self.subst_from_current_frame_and_normalize_erasing_regions(constant.literal)?;
+
                 // This can still fail:
                 // * During ConstProp, with `TooGeneric` or since the `required_consts` were not all
                 //   checked yet.
                 // * During CTFE, since promoteds in `const`/`static` initializer bodies can fail.
-
                 self.mir_const_to_op(&val, layout)?
             }
         };
@@ -683,7 +702,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 // We rely on mutability being set correctly in that allocation to prevent writes
                 // where none should happen.
                 let ptr = self.global_base_pointer(Pointer::new(id, offset))?;
-                Operand::Indirect(MemPlace::from_ptr(ptr.into(), layout.align.abi))
+                Operand::Indirect(MemPlace::from_ptr(ptr.into()))
             }
             ConstValue::Scalar(x) => Operand::Immediate(tag_scalar(x)?.into()),
             ConstValue::Slice { data, start, end } => {
@@ -700,7 +719,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 ))
             }
         };
-        Ok(OpTy { op, layout })
+        Ok(OpTy { op, layout, align: Some(layout.align.abi) })
     }
 
     /// Read discriminant, return the runtime value as well as the variant index.
diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs
index 85ee88e9e47..f0c113376ea 100644
--- a/compiler/rustc_const_eval/src/interpret/operator.rs
+++ b/compiler/rustc_const_eval/src/interpret/operator.rs
@@ -5,15 +5,20 @@ use rustc_middle::mir;
 use rustc_middle::mir::interpret::{InterpResult, Scalar};
 use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
 use rustc_middle::ty::{self, FloatTy, Ty};
+use rustc_target::abi::Abi;
 
 use super::{ImmTy, Immediate, InterpCx, Machine, PlaceTy};
 
 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     /// Applies the binary operation `op` to the two operands and writes a tuple of the result
     /// and a boolean signifying the potential overflow to the destination.
+    ///
+    /// `force_overflow_checks` indicates whether overflow checks should be done even when
+    /// `tcx.sess.overflow_checks()` is `false`.
     pub fn binop_with_overflow(
         &mut self,
         op: mir::BinOp,
+        force_overflow_checks: bool,
         left: &ImmTy<'tcx, M::PointerTag>,
         right: &ImmTy<'tcx, M::PointerTag>,
         dest: &PlaceTy<'tcx, M::PointerTag>,
@@ -25,8 +30,27 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             "type mismatch for result of {:?}",
             op,
         );
-        let val = Immediate::ScalarPair(val.into(), Scalar::from_bool(overflowed).into());
-        self.write_immediate(val, dest)
+        // As per https://github.com/rust-lang/rust/pull/98738, we always return `false` in the 2nd
+        // component when overflow checking is disabled.
+        let overflowed =
+            overflowed && (force_overflow_checks || M::checked_binop_checks_overflow(self));
+        // Write the result to `dest`.
+        if let Abi::ScalarPair(..) = dest.layout.abi {
+            // We can use the optimized path and avoid `place_field` (which might do
+            // `force_allocation`).
+            let pair = Immediate::ScalarPair(val.into(), Scalar::from_bool(overflowed).into());
+            self.write_immediate(pair, dest)?;
+        } else {
+            assert!(self.tcx.sess.opts.debugging_opts.randomize_layout);
+            // With randomized layout, `(int, bool)` might cease to be a `ScalarPair`, so we have to
+            // do a component-wise write here. This code path is slower than the above because
+            // `place_field` will have to `force_allocate` locals here.
+            let val_field = self.place_field(&dest, 0)?;
+            self.write_scalar(val, &val_field)?;
+            let overflowed_field = self.place_field(&dest, 1)?;
+            self.write_scalar(Scalar::from_bool(overflowed), &overflowed_field)?;
+        }
+        Ok(())
     }
 
     /// Applies the binary operation `op` to the arguments and writes the result to the
diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs
index 337fcd28c66..57ecad07b42 100644
--- a/compiler/rustc_const_eval/src/interpret/place.rs
+++ b/compiler/rustc_const_eval/src/interpret/place.rs
@@ -10,13 +10,14 @@ use rustc_macros::HashStable;
 use rustc_middle::mir;
 use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt, TyAndLayout};
 use rustc_middle::ty::{self, Ty};
-use rustc_target::abi::{Abi, Align, FieldsShape, TagEncoding};
-use rustc_target::abi::{HasDataLayout, Size, VariantIdx, Variants};
+use rustc_target::abi::{
+    Abi, Align, FieldsShape, HasDataLayout, Size, TagEncoding, VariantIdx, Variants,
+};
 
 use super::{
     alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, CheckInAllocMsg,
-    ConstAlloc, ImmTy, Immediate, InterpCx, InterpResult, LocalValue, Machine, MemoryKind, OpTy,
-    Operand, Pointer, Provenance, Scalar, ScalarMaybeUninit,
+    ConstAlloc, ImmTy, Immediate, InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand,
+    Pointer, Provenance, Scalar, ScalarMaybeUninit,
 };
 
 #[derive(Copy, Clone, Hash, PartialEq, Eq, HashStable, Debug)]
@@ -57,7 +58,6 @@ impl<Tag: Provenance> MemPlaceMeta<Tag> {
 pub struct MemPlace<Tag: Provenance = AllocId> {
     /// The pointer can be a pure integer, with the `None` tag.
     pub ptr: Pointer<Option<Tag>>,
-    pub align: Align,
     /// Metadata for unsized places. Interpretation is up to the type.
     /// Must not be present for sized types, but can be missing for unsized types
     /// (e.g., `extern type`).
@@ -65,7 +65,7 @@ pub struct MemPlace<Tag: Provenance = AllocId> {
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(MemPlace, 48);
+rustc_data_structures::static_assert_size!(MemPlace, 40);
 
 #[derive(Copy, Clone, Hash, PartialEq, Eq, HashStable, Debug)]
 pub enum Place<Tag: Provenance = AllocId> {
@@ -78,12 +78,17 @@ pub enum Place<Tag: Provenance = AllocId> {
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(Place, 56);
+rustc_data_structures::static_assert_size!(Place, 48);
 
 #[derive(Copy, Clone, Debug)]
 pub struct PlaceTy<'tcx, Tag: Provenance = AllocId> {
     place: Place<Tag>, // Keep this private; it helps enforce invariants.
     pub layout: TyAndLayout<'tcx>,
+    /// rustc does not have a proper way to represent the type of a field of a `repr(packed)` struct:
+    /// it needs to have a different alignment than the field type would usually have.
+    /// So we represent this here with a separate field that "overwrites" `layout.align`.
+    /// This means `layout.align` should never be used for a `PlaceTy`!
+    pub align: Align,
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
@@ -102,6 +107,11 @@ impl<'tcx, Tag: Provenance> std::ops::Deref for PlaceTy<'tcx, Tag> {
 pub struct MPlaceTy<'tcx, Tag: Provenance = AllocId> {
     mplace: MemPlace<Tag>,
     pub layout: TyAndLayout<'tcx>,
+    /// rustc does not have a proper way to represent the type of a field of a `repr(packed)` struct:
+    /// it needs to have a different alignment than the field type would usually have.
+    /// So we represent this here with a separate field that "overwrites" `layout.align`.
+    /// This means `layout.align` should never be used for a `MPlaceTy`!
+    pub align: Align,
 }
 
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
@@ -118,28 +128,28 @@ impl<'tcx, Tag: Provenance> std::ops::Deref for MPlaceTy<'tcx, Tag> {
 impl<'tcx, Tag: Provenance> From<MPlaceTy<'tcx, Tag>> for PlaceTy<'tcx, Tag> {
     #[inline(always)]
     fn from(mplace: MPlaceTy<'tcx, Tag>) -> Self {
-        PlaceTy { place: Place::Ptr(*mplace), layout: mplace.layout }
+        PlaceTy { place: Place::Ptr(*mplace), layout: mplace.layout, align: mplace.align }
     }
 }
 
 impl<'tcx, Tag: Provenance> From<&'_ MPlaceTy<'tcx, Tag>> for PlaceTy<'tcx, Tag> {
     #[inline(always)]
     fn from(mplace: &MPlaceTy<'tcx, Tag>) -> Self {
-        PlaceTy { place: Place::Ptr(**mplace), layout: mplace.layout }
+        PlaceTy { place: Place::Ptr(**mplace), layout: mplace.layout, align: mplace.align }
     }
 }
 
 impl<'tcx, Tag: Provenance> From<&'_ mut MPlaceTy<'tcx, Tag>> for PlaceTy<'tcx, Tag> {
     #[inline(always)]
     fn from(mplace: &mut MPlaceTy<'tcx, Tag>) -> Self {
-        PlaceTy { place: Place::Ptr(**mplace), layout: mplace.layout }
+        PlaceTy { place: Place::Ptr(**mplace), layout: mplace.layout, align: mplace.align }
     }
 }
 
 impl<Tag: Provenance> MemPlace<Tag> {
     #[inline(always)]
-    pub fn from_ptr(ptr: Pointer<Option<Tag>>, align: Align) -> Self {
-        MemPlace { ptr, align, meta: MemPlaceMeta::None }
+    pub fn from_ptr(ptr: Pointer<Option<Tag>>) -> Self {
+        MemPlace { ptr, meta: MemPlaceMeta::None }
     }
 
     /// Adjust the provenance of the main pointer (metadata is unaffected).
@@ -170,11 +180,19 @@ impl<Tag: Provenance> MemPlace<Tag> {
         meta: MemPlaceMeta<Tag>,
         cx: &impl HasDataLayout,
     ) -> InterpResult<'tcx, Self> {
-        Ok(MemPlace {
-            ptr: self.ptr.offset(offset, cx)?,
-            align: self.align.restrict_for_offset(offset),
-            meta,
-        })
+        Ok(MemPlace { ptr: self.ptr.offset(offset, cx)?, meta })
+    }
+}
+
+impl<Tag: Provenance> Place<Tag> {
+    /// Asserts that this points to some local variable.
+    /// Returns the frame idx and the variable idx.
+    #[inline]
+    pub fn assert_local(&self) -> (usize, mir::Local) {
+        match self {
+            Place::Local { frame, local } => (*frame, *local),
+            _ => bug!("assert_local: expected Place::Local, got {:?}", self),
+        }
     }
 }
 
@@ -183,9 +201,9 @@ impl<'tcx, Tag: Provenance> MPlaceTy<'tcx, Tag> {
     #[inline]
     pub fn dangling(layout: TyAndLayout<'tcx>) -> Self {
         let align = layout.align.abi;
-        let ptr = Pointer::new(None, Size::from_bytes(align.bytes())); // no provenance, absolute address
+        let ptr = Pointer::from_addr(align.bytes()); // no provenance, absolute address
         // `Poison` this to make sure that the pointer value `ptr` is never observable by the program.
-        MPlaceTy { mplace: MemPlace { ptr, align, meta: MemPlaceMeta::Poison }, layout }
+        MPlaceTy { mplace: MemPlace { ptr, meta: MemPlaceMeta::Poison }, layout, align }
     }
 
     #[inline]
@@ -196,12 +214,16 @@ impl<'tcx, Tag: Provenance> MPlaceTy<'tcx, Tag> {
         layout: TyAndLayout<'tcx>,
         cx: &impl HasDataLayout,
     ) -> InterpResult<'tcx, Self> {
-        Ok(MPlaceTy { mplace: self.mplace.offset(offset, meta, cx)?, layout })
+        Ok(MPlaceTy {
+            mplace: self.mplace.offset(offset, meta, cx)?,
+            align: self.align.restrict_for_offset(offset),
+            layout,
+        })
     }
 
     #[inline]
     pub fn from_aligned_ptr(ptr: Pointer<Option<Tag>>, layout: TyAndLayout<'tcx>) -> Self {
-        MPlaceTy { mplace: MemPlace::from_ptr(ptr, layout.align.abi), layout }
+        MPlaceTy { mplace: MemPlace::from_ptr(ptr), layout, align: layout.align.abi }
     }
 
     #[inline]
@@ -210,10 +232,10 @@ impl<'tcx, Tag: Provenance> MPlaceTy<'tcx, Tag> {
         layout: TyAndLayout<'tcx>,
         meta: MemPlaceMeta<Tag>,
     ) -> Self {
-        let mut mplace = MemPlace::from_ptr(ptr, layout.align.abi);
+        let mut mplace = MemPlace::from_ptr(ptr);
         mplace.meta = meta;
 
-        MPlaceTy { mplace, layout }
+        MPlaceTy { mplace, layout, align: layout.align.abi }
     }
 
     #[inline]
@@ -250,7 +272,9 @@ impl<'tcx, Tag: Provenance> OpTy<'tcx, Tag> {
     /// read from the resulting mplace, not to get its address back.
     pub fn try_as_mplace(&self) -> Result<MPlaceTy<'tcx, Tag>, ImmTy<'tcx, Tag>> {
         match **self {
-            Operand::Indirect(mplace) => Ok(MPlaceTy { mplace, layout: self.layout }),
+            Operand::Indirect(mplace) => {
+                Ok(MPlaceTy { mplace, layout: self.layout, align: self.align.unwrap() })
+            }
             Operand::Immediate(_) if self.layout.is_zst() => Ok(MPlaceTy::dangling(self.layout)),
             Operand::Immediate(imm) => Err(ImmTy::from_immediate(imm, self.layout)),
         }
@@ -264,20 +288,19 @@ impl<'tcx, Tag: Provenance> OpTy<'tcx, Tag> {
     }
 }
 
-impl<Tag: Provenance> Place<Tag> {
+impl<'tcx, Tag: Provenance> PlaceTy<'tcx, Tag> {
+    /// A place is either an mplace or some local.
     #[inline]
-    pub fn assert_mem_place(self) -> MemPlace<Tag> {
-        match self {
-            Place::Ptr(mplace) => mplace,
-            _ => bug!("assert_mem_place: expected Place::Ptr, got {:?}", self),
+    pub fn try_as_mplace(&self) -> Result<MPlaceTy<'tcx, Tag>, (usize, mir::Local)> {
+        match **self {
+            Place::Ptr(mplace) => Ok(MPlaceTy { mplace, layout: self.layout, align: self.align }),
+            Place::Local { frame, local } => Err((frame, local)),
         }
     }
-}
 
-impl<'tcx, Tag: Provenance> PlaceTy<'tcx, Tag> {
     #[inline]
-    pub fn assert_mem_place(self) -> MPlaceTy<'tcx, Tag> {
-        MPlaceTy { mplace: self.place.assert_mem_place(), layout: self.layout }
+    pub fn assert_mem_place(&self) -> MPlaceTy<'tcx, Tag> {
+        self.try_as_mplace().unwrap()
     }
 }
 
@@ -304,18 +327,13 @@ where
         let (ptr, meta) = match **val {
             Immediate::Scalar(ptr) => (ptr, MemPlaceMeta::None),
             Immediate::ScalarPair(ptr, meta) => (ptr, MemPlaceMeta::Meta(meta.check_init()?)),
+            Immediate::Uninit => throw_ub!(InvalidUninitBytes(None)),
         };
 
-        let mplace = MemPlace {
-            ptr: self.scalar_to_ptr(ptr.check_init()?)?,
-            // We could use the run-time alignment here. For now, we do not, because
-            // the point of tracking the alignment here is to make sure that the *static*
-            // alignment information emitted with the loads is correct. The run-time
-            // alignment can only be more restrictive.
-            align: layout.align.abi,
-            meta,
-        };
-        Ok(MPlaceTy { mplace, layout })
+        let mplace = MemPlace { ptr: self.scalar_to_ptr(ptr.check_init()?)?, meta };
+        // When deref'ing a pointer, the *static* alignment given by the type is what matters.
+        let align = layout.align.abi;
+        Ok(MPlaceTy { mplace, layout, align })
     }
 
     /// Take an operand, representing a pointer, and dereference it to a place -- that
@@ -368,7 +386,7 @@ where
         let (size, align) = self
             .size_and_align_of_mplace(&mplace)?
             .unwrap_or((mplace.layout.size, mplace.layout.align.abi));
-        assert!(mplace.mplace.align <= align, "dynamic alignment less strict than static one?");
+        assert!(mplace.align <= align, "dynamic alignment less strict than static one?");
         let align = M::enforce_alignment(self).then_some(align);
         self.check_ptr_access_align(mplace.ptr, size, align.unwrap_or(Align::ONE), msg)?;
         Ok(())
@@ -533,7 +551,7 @@ where
 
             Index(local) => {
                 let layout = self.layout_of(self.tcx.types.usize)?;
-                let n = self.access_local(self.frame(), local, Some(layout))?;
+                let n = self.local_to_op(self.frame(), local, Some(layout))?;
                 let n = self.read_scalar(&n)?;
                 let n = n.to_machine_usize(self)?;
                 self.mplace_index(base, n)?
@@ -608,11 +626,9 @@ where
         variant: VariantIdx,
     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
         // Downcast just changes the layout
-        Ok(match base.place {
-            Place::Ptr(mplace) => {
-                self.mplace_downcast(&MPlaceTy { mplace, layout: base.layout }, variant)?.into()
-            }
-            Place::Local { .. } => {
+        Ok(match base.try_as_mplace() {
+            Ok(mplace) => self.mplace_downcast(&mplace, variant)?.into(),
+            Err(..) => {
                 let layout = base.layout.for_variant(self, variant);
                 PlaceTy { layout, ..*base }
             }
@@ -649,6 +665,16 @@ where
         self.mplace_to_simd(&mplace)
     }
 
+    pub fn local_to_place(
+        &self,
+        frame: usize,
+        local: mir::Local,
+    ) -> InterpResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
+        let layout = self.layout_of_local(&self.stack()[frame], local, None)?;
+        let place = Place::Local { frame, local };
+        Ok(PlaceTy { place, layout, align: layout.align.abi })
+    }
+
     /// Computes a place. You should only use this if you intend to write into this
     /// place; for reading, a more efficient alternative is `eval_place_to_op`.
     #[instrument(skip(self), level = "debug")]
@@ -656,11 +682,7 @@ where
         &mut self,
         place: mir::Place<'tcx>,
     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
-        let mut place_ty = PlaceTy {
-            // This works even for dead/uninitialized locals; we check further when writing
-            place: Place::Local { frame: self.frame_idx(), local: place.local },
-            layout: self.layout_of_local(self.frame(), place.local, None)?,
-        };
+        let mut place_ty = self.local_to_place(self.frame_idx(), place.local)?;
 
         for elem in place.projection.iter() {
             place_ty = self.place_projection(&place_ty, &elem)?
@@ -668,14 +690,19 @@ where
 
         trace!("{:?}", self.dump_place(place_ty.place));
         // Sanity-check the type we ended up with.
-        debug_assert!(mir_assign_valid_types(
-            *self.tcx,
-            self.param_env,
-            self.layout_of(self.subst_from_current_frame_and_normalize_erasing_regions(
-                place.ty(&self.frame().body.local_decls, *self.tcx).ty
-            )?)?,
-            place_ty.layout,
-        ));
+        debug_assert!(
+            mir_assign_valid_types(
+                *self.tcx,
+                self.param_env,
+                self.layout_of(self.subst_from_current_frame_and_normalize_erasing_regions(
+                    place.ty(&self.frame().body.local_decls, *self.tcx).ty
+                )?)?,
+                place_ty.layout,
+            ),
+            "eval_place of a MIR place with type {:?} produced an interpret place with type {:?}",
+            place.ty(&self.frame().body.local_decls, *self.tcx).ty,
+            place_ty.layout.ty,
+        );
         Ok(place_ty)
     }
 
@@ -733,32 +760,33 @@ where
         let mplace = match dest.place {
             Place::Local { frame, local } => {
                 match M::access_local_mut(self, frame, local)? {
-                    Ok(local) => {
+                    Operand::Immediate(local) => {
                         // Local can be updated in-place.
-                        *local = LocalValue::Live(Operand::Immediate(src));
+                        *local = src;
                         return Ok(());
                     }
-                    Err(mplace) => {
+                    Operand::Indirect(mplace) => {
                         // The local is in memory, go on below.
-                        mplace
+                        *mplace
                     }
                 }
             }
             Place::Ptr(mplace) => mplace, // already referring to memory
         };
-        let dest = MPlaceTy { mplace, layout: dest.layout };
 
         // This is already in memory, write there.
-        self.write_immediate_to_mplace_no_validate(src, &dest)
+        self.write_immediate_to_mplace_no_validate(src, dest.layout, dest.align, mplace)
     }
 
     /// Write an immediate to memory.
     /// If you use this you are responsible for validating that things got copied at the
-    /// right type.
+    /// right layout.
     fn write_immediate_to_mplace_no_validate(
         &mut self,
         value: Immediate<M::PointerTag>,
-        dest: &MPlaceTy<'tcx, M::PointerTag>,
+        layout: TyAndLayout<'tcx>,
+        align: Align,
+        dest: MemPlace<M::PointerTag>,
     ) -> InterpResult<'tcx> {
         // Note that it is really important that the type here is the right one, and matches the
         // type things are read at. In case `value` is a `ScalarPair`, we don't do any magic here
@@ -766,31 +794,30 @@ where
         // wrong type.
 
         let tcx = *self.tcx;
-        let Some(mut alloc) = self.get_place_alloc_mut(dest)? else {
+        let Some(mut alloc) = self.get_place_alloc_mut(&MPlaceTy { mplace: dest, layout, align })? else {
             // zero-sized access
             return Ok(());
         };
 
         match value {
             Immediate::Scalar(scalar) => {
-                let Abi::Scalar(s) = dest.layout.abi else { span_bug!(
+                let Abi::Scalar(s) = layout.abi else { span_bug!(
                         self.cur_span(),
-                        "write_immediate_to_mplace: invalid Scalar layout: {:#?}",
-                        dest.layout
+                        "write_immediate_to_mplace: invalid Scalar layout: {layout:#?}",
                     )
                 };
                 let size = s.size(&tcx);
-                //FIXME(#96185): assert_eq!(dest.layout.size, size, "abi::Scalar size does not match layout size");
+                assert_eq!(size, layout.size, "abi::Scalar size does not match layout size");
                 alloc.write_scalar(alloc_range(Size::ZERO, size), scalar)
             }
             Immediate::ScalarPair(a_val, b_val) => {
                 // We checked `ptr_align` above, so all fields will have the alignment they need.
                 // We would anyway check against `ptr_align.restrict_for_offset(b_offset)`,
                 // which `ptr.offset(b_offset)` cannot possibly fail to satisfy.
-                let Abi::ScalarPair(a, b) = dest.layout.abi else { span_bug!(
+                let Abi::ScalarPair(a, b) = layout.abi else { span_bug!(
                         self.cur_span(),
                         "write_immediate_to_mplace: invalid ScalarPair layout: {:#?}",
-                        dest.layout
+                        layout
                     )
                 };
                 let (a_size, b_size) = (a.size(&tcx), b.size(&tcx));
@@ -804,33 +831,22 @@ where
                 alloc.write_scalar(alloc_range(Size::ZERO, a_size), a_val)?;
                 alloc.write_scalar(alloc_range(b_offset, b_size), b_val)
             }
+            Immediate::Uninit => alloc.write_uninit(),
         }
     }
 
     pub fn write_uninit(&mut self, dest: &PlaceTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
-        let mplace = match dest.place {
-            Place::Ptr(mplace) => MPlaceTy { mplace, layout: dest.layout },
-            Place::Local { frame, local } => {
+        let mplace = match dest.try_as_mplace() {
+            Ok(mplace) => mplace,
+            Err((frame, local)) => {
                 match M::access_local_mut(self, frame, local)? {
-                    Ok(local) => match dest.layout.abi {
-                        Abi::Scalar(_) => {
-                            *local = LocalValue::Live(Operand::Immediate(Immediate::Scalar(
-                                ScalarMaybeUninit::Uninit,
-                            )));
-                            return Ok(());
-                        }
-                        Abi::ScalarPair(..) => {
-                            *local = LocalValue::Live(Operand::Immediate(Immediate::ScalarPair(
-                                ScalarMaybeUninit::Uninit,
-                                ScalarMaybeUninit::Uninit,
-                            )));
-                            return Ok(());
-                        }
-                        _ => self.force_allocation(dest)?,
-                    },
-                    Err(mplace) => {
+                    Operand::Immediate(local) => {
+                        *local = Immediate::Uninit;
+                        return Ok(());
+                    }
+                    Operand::Indirect(mplace) => {
                         // The local is in memory, go on below.
-                        MPlaceTy { mplace, layout: dest.layout }
+                        MPlaceTy { mplace: *mplace, layout: dest.layout, align: dest.align }
                     }
                 }
             }
@@ -843,16 +859,17 @@ where
         Ok(())
     }
 
-    /// Copies the data from an operand to a place. This does not support transmuting!
-    /// Use `copy_op_transmute` if the layouts could disagree.
+    /// Copies the data from an operand to a place.
+    /// `allow_transmute` indicates whether the layouts may disagree.
     #[inline(always)]
     #[instrument(skip(self), level = "debug")]
     pub fn copy_op(
         &mut self,
         src: &OpTy<'tcx, M::PointerTag>,
         dest: &PlaceTy<'tcx, M::PointerTag>,
+        allow_transmute: bool,
     ) -> InterpResult<'tcx> {
-        self.copy_op_no_validate(src, dest)?;
+        self.copy_op_no_validate(src, dest, allow_transmute)?;
 
         if M::enforce_validity(self) {
             // Data got changed, better make sure it matches the type!
@@ -862,8 +879,8 @@ where
         Ok(())
     }
 
-    /// Copies the data from an operand to a place. This does not support transmuting!
-    /// Use `copy_op_transmute` if the layouts could disagree.
+    /// Copies the data from an operand to a place.
+    /// `allow_transmute` indicates whether the layouts may disagree.
     /// Also, if you use this you are responsible for validating that things get copied at the
     /// right type.
     #[instrument(skip(self), level = "debug")]
@@ -871,10 +888,13 @@ where
         &mut self,
         src: &OpTy<'tcx, M::PointerTag>,
         dest: &PlaceTy<'tcx, M::PointerTag>,
+        allow_transmute: bool,
     ) -> InterpResult<'tcx> {
         // We do NOT compare the types for equality, because well-typed code can
         // actually "transmute" `&mut T` to `&T` in an assignment without a cast.
-        if !mir_assign_valid_types(*self.tcx, self.param_env, src.layout, dest.layout) {
+        let layout_compat =
+            mir_assign_valid_types(*self.tcx, self.param_env, src.layout, dest.layout);
+        if !allow_transmute && !layout_compat {
             span_bug!(
                 self.cur_span(),
                 "type mismatch when copying!\nsrc: {:?},\ndest: {:?}",
@@ -883,100 +903,68 @@ where
             );
         }
 
-        // Let us see if the layout is simple so we take a shortcut, avoid force_allocation.
+        // Let us see if the layout is simple so we take a shortcut,
+        // avoid force_allocation.
         let src = match self.read_immediate_raw(src, /*force*/ false)? {
             Ok(src_val) => {
                 assert!(!src.layout.is_unsized(), "cannot have unsized immediates");
+                assert!(
+                    !dest.layout.is_unsized(),
+                    "the src is sized, so the dest must also be sized"
+                );
+                assert_eq!(src.layout.size, dest.layout.size);
                 // Yay, we got a value that we can write directly.
-                return self.write_immediate_no_validate(*src_val, dest);
+                return if layout_compat {
+                    self.write_immediate_no_validate(*src_val, dest)
+                } else {
+                    // This is tricky. The problematic case is `ScalarPair`: the `src_val` was
+                    // loaded using the offsets defined by `src.layout`. When we put this back into
+                    // the destination, we have to use the same offsets! So (a) we make sure we
+                    // write back to memory, and (b) we use `dest` *with the source layout*.
+                    let dest_mem = self.force_allocation(dest)?;
+                    self.write_immediate_to_mplace_no_validate(
+                        *src_val,
+                        src.layout,
+                        dest_mem.align,
+                        *dest_mem,
+                    )
+                };
             }
             Err(mplace) => mplace,
         };
         // Slow path, this does not fit into an immediate. Just memcpy.
         trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout.ty);
 
-        // This interprets `src.meta` with the `dest` local's layout, if an unsized local
-        // is being initialized!
-        let (dest, size) = self.force_allocation_maybe_sized(dest, src.meta)?;
-        let size = size.unwrap_or_else(|| {
-            assert!(
-                !dest.layout.is_unsized(),
-                "Cannot copy into already initialized unsized place"
-            );
-            dest.layout.size
-        });
-        assert_eq!(src.meta, dest.meta, "Can only copy between equally-sized instances");
-
-        self.mem_copy(src.ptr, src.align, dest.ptr, dest.align, size, /*nonoverlapping*/ false)
-    }
-
-    /// Copies the data from an operand to a place. The layouts may disagree, but they must
-    /// have the same size.
-    pub fn copy_op_transmute(
-        &mut self,
-        src: &OpTy<'tcx, M::PointerTag>,
-        dest: &PlaceTy<'tcx, M::PointerTag>,
-    ) -> InterpResult<'tcx> {
-        if mir_assign_valid_types(*self.tcx, self.param_env, src.layout, dest.layout) {
-            // Fast path: Just use normal `copy_op`
-            return self.copy_op(src, dest);
-        }
-        // We still require the sizes to match.
-        if src.layout.size != dest.layout.size {
-            span_bug!(
-                self.cur_span(),
-                "size-changing transmute, should have been caught by transmute checking: {:#?}\ndest: {:#?}",
-                src,
-                dest
-            );
-        }
-        // Unsized copies rely on interpreting `src.meta` with `dest.layout`, we want
-        // to avoid that here.
-        assert!(
-            !src.layout.is_unsized() && !dest.layout.is_unsized(),
-            "Cannot transmute unsized data"
-        );
-
-        // The hard case is `ScalarPair`.  `src` is already read from memory in this case,
-        // using `src.layout` to figure out which bytes to use for the 1st and 2nd field.
-        // We have to write them to `dest` at the offsets they were *read at*, which is
-        // not necessarily the same as the offsets in `dest.layout`!
-        // Hence we do the copy with the source layout on both sides.  We also make sure to write
-        // into memory, because if `dest` is a local we would not even have a way to write
-        // at the `src` offsets; the fact that we came from a different layout would
-        // just be lost.
-        let dest = self.force_allocation(dest)?;
-        self.copy_op_no_validate(
-            src,
-            &PlaceTy::from(MPlaceTy { mplace: *dest, layout: src.layout }),
-        )?;
-
-        if M::enforce_validity(self) {
-            // Data got changed, better make sure it matches the type!
-            self.validate_operand(&dest.into())?;
+        let dest = self.force_allocation(&dest)?;
+        let Some((dest_size, _)) = self.size_and_align_of_mplace(&dest)? else {
+            span_bug!(self.cur_span(), "copy_op needs (dynamically) sized values")
+        };
+        if cfg!(debug_assertions) {
+            let src_size = self.size_and_align_of_mplace(&src)?.unwrap().0;
+            assert_eq!(src_size, dest_size, "Cannot copy differently-sized data");
+        } else {
+            // As a cheap approximation, we compare the fixed parts of the size.
+            assert_eq!(src.layout.size, dest.layout.size);
         }
 
-        Ok(())
+        self.mem_copy(
+            src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ false,
+        )
     }
 
     /// Ensures that a place is in memory, and returns where it is.
     /// If the place currently refers to a local that doesn't yet have a matching allocation,
     /// create such an allocation.
     /// This is essentially `force_to_memplace`.
-    ///
-    /// This supports unsized types and returns the computed size to avoid some
-    /// redundant computation when copying; use `force_allocation` for a simpler, sized-only
-    /// version.
     #[instrument(skip(self), level = "debug")]
-    pub fn force_allocation_maybe_sized(
+    pub fn force_allocation(
         &mut self,
         place: &PlaceTy<'tcx, M::PointerTag>,
-        meta: MemPlaceMeta<M::PointerTag>,
-    ) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::PointerTag>, Option<Size>)> {
-        let (mplace, size) = match place.place {
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
+        let mplace = match place.place {
             Place::Local { frame, local } => {
                 match M::access_local_mut(self, frame, local)? {
-                    Ok(&mut local_val) => {
+                    &mut Operand::Immediate(local_val) => {
                         // We need to make an allocation.
 
                         // We need the layout of the local.  We can NOT use the layout we got,
@@ -984,40 +972,34 @@ where
                         // that has different alignment than the outer field.
                         let local_layout =
                             self.layout_of_local(&self.stack()[frame], local, None)?;
-                        // We also need to support unsized types, and hence cannot use `allocate`.
-                        let (size, align) = self
-                            .size_and_align_of(&meta, &local_layout)?
-                            .expect("Cannot allocate for non-dyn-sized type");
-                        let ptr = self.allocate_ptr(size, align, MemoryKind::Stack)?;
-                        let mplace = MemPlace { ptr: ptr.into(), align, meta };
-                        if let LocalValue::Live(Operand::Immediate(value)) = local_val {
-                            // Preserve old value.
+                        if local_layout.is_unsized() {
+                            throw_unsup_format!("unsized locals are not supported");
+                        }
+                        let mplace = *self.allocate(local_layout, MemoryKind::Stack)?;
+                        if !matches!(local_val, Immediate::Uninit) {
+                            // Preserve old value. (As an optimization, we can skip this if it was uninit.)
                             // We don't have to validate as we can assume the local
                             // was already valid for its type.
-                            let mplace = MPlaceTy { mplace, layout: local_layout };
-                            self.write_immediate_to_mplace_no_validate(value, &mplace)?;
+                            self.write_immediate_to_mplace_no_validate(
+                                local_val,
+                                local_layout,
+                                local_layout.align.abi,
+                                mplace,
+                            )?;
                         }
                         // Now we can call `access_mut` again, asserting it goes well,
                         // and actually overwrite things.
-                        *M::access_local_mut(self, frame, local).unwrap().unwrap() =
-                            LocalValue::Live(Operand::Indirect(mplace));
-                        (mplace, Some(size))
+                        *M::access_local_mut(self, frame, local).unwrap() =
+                            Operand::Indirect(mplace);
+                        mplace
                     }
-                    Err(mplace) => (mplace, None), // this already was an indirect local
+                    &mut Operand::Indirect(mplace) => mplace, // this already was an indirect local
                 }
             }
-            Place::Ptr(mplace) => (mplace, None),
+            Place::Ptr(mplace) => mplace,
         };
         // Return with the original layout, so that the caller can go on
-        Ok((MPlaceTy { mplace, layout: place.layout }, size))
-    }
-
-    #[inline(always)]
-    pub fn force_allocation(
-        &mut self,
-        place: &PlaceTy<'tcx, M::PointerTag>,
-    ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
-        Ok(self.force_allocation_maybe_sized(place, MemPlaceMeta::None)?.0)
+        Ok(MPlaceTy { mplace, layout: place.layout, align: place.align })
     }
 
     pub fn allocate(
@@ -1025,6 +1007,7 @@ where
         layout: TyAndLayout<'tcx>,
         kind: MemoryKind<M::MemoryKind>,
     ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
+        assert!(!layout.is_unsized());
         let ptr = self.allocate_ptr(layout.size, layout.align.abi, kind)?;
         Ok(MPlaceTy::from_aligned_ptr(ptr.into(), layout))
     }
@@ -1038,15 +1021,14 @@ where
     ) -> MPlaceTy<'tcx, M::PointerTag> {
         let ptr = self.allocate_bytes_ptr(str.as_bytes(), Align::ONE, kind, mutbl);
         let meta = Scalar::from_machine_usize(u64::try_from(str.len()).unwrap(), self);
-        let mplace =
-            MemPlace { ptr: ptr.into(), align: Align::ONE, meta: MemPlaceMeta::Meta(meta) };
+        let mplace = MemPlace { ptr: ptr.into(), meta: MemPlaceMeta::Meta(meta) };
 
         let ty = self.tcx.mk_ref(
             self.tcx.lifetimes.re_static,
             ty::TypeAndMut { ty: self.tcx.types.str_, mutbl },
         );
         let layout = self.layout_of(ty).unwrap();
-        MPlaceTy { mplace, layout }
+        MPlaceTy { mplace, layout, align: layout.align.abi }
     }
 
     /// Writes the discriminant of the given variant.
@@ -1166,7 +1148,11 @@ where
             assert_eq!(align, layout.align.abi);
         }
 
-        let mplace = MPlaceTy { mplace: MemPlace { meta: MemPlaceMeta::None, ..**mplace }, layout };
+        let mplace = MPlaceTy {
+            mplace: MemPlace { meta: MemPlaceMeta::None, ..**mplace },
+            layout,
+            align: layout.align.abi,
+        };
         Ok((instance, mplace))
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs
index 98f69456e49..240910c08b2 100644
--- a/compiler/rustc_const_eval/src/interpret/step.rs
+++ b/compiler/rustc_const_eval/src/interpret/step.rs
@@ -169,7 +169,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             Use(ref operand) => {
                 // Avoid recomputing the layout
                 let op = self.eval_operand(operand, Some(dest.layout))?;
-                self.copy_op(&op, &dest)?;
+                self.copy_op(&op, &dest, /*allow_transmute*/ false)?;
             }
 
             BinaryOp(bin_op, box (ref left, ref right)) => {
@@ -185,7 +185,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let left = self.read_immediate(&self.eval_operand(left, None)?)?;
                 let layout = binop_right_homogeneous(bin_op).then_some(left.layout);
                 let right = self.read_immediate(&self.eval_operand(right, layout)?)?;
-                self.binop_with_overflow(bin_op, &left, &right, &dest)?;
+                self.binop_with_overflow(
+                    bin_op, /*force_overflow_checks*/ false, &left, &right, &dest,
+                )?;
             }
 
             UnaryOp(un_op, ref operand) => {
@@ -202,7 +204,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 for (field_index, operand) in operands.iter().enumerate() {
                     let op = self.eval_operand(operand, None)?;
                     let field_dest = self.place_field(&dest, field_index)?;
-                    self.copy_op(&op, &field_dest)?;
+                    self.copy_op(&op, &field_dest, /*allow_transmute*/ false)?;
                 }
             }
 
@@ -218,7 +220,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 } else {
                     // Write the src to the first element.
                     let first = self.mplace_field(&dest, 0)?;
-                    self.copy_op(&src, &first.into())?;
+                    self.copy_op(&src, &first.into(), /*allow_transmute*/ false)?;
 
                     // This is performance-sensitive code for big static/const arrays! So we
                     // avoid writing each operand individually and instead just make many copies
diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs
index 57d06b48ca4..515cc222dc6 100644
--- a/compiler/rustc_const_eval/src/interpret/terminator.rs
+++ b/compiler/rustc_const_eval/src/interpret/terminator.rs
@@ -12,8 +12,8 @@ use rustc_target::abi::call::{ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMo
 use rustc_target::spec::abi::Abi;
 
 use super::{
-    FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Scalar,
-    StackPopCleanup, StackPopUnwind,
+    FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
+    PlaceTy, Scalar, StackPopCleanup, StackPopUnwind,
 };
 
 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
@@ -185,11 +185,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 // No question
                 return true;
             }
+            if caller_abi.layout.is_unsized() || callee_abi.layout.is_unsized() {
+                // No, no, no. We require the types to *exactly* match for unsized arguments. If
+                // these are somehow unsized "in a different way" (say, `dyn Trait` vs `[i32]`),
+                // then who knows what happens.
+                return false;
+            }
             if caller_abi.layout.size != callee_abi.layout.size
                 || caller_abi.layout.align.abi != callee_abi.layout.align.abi
             {
                 // This cannot go well...
-                // FIXME: What about unsized types?
                 return false;
             }
             // The rest *should* be okay, but we are extra conservative.
@@ -287,11 +292,36 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 caller_arg.layout.ty
             )
         }
+        // Special handling for unsized parameters.
+        if caller_arg.layout.is_unsized() {
+            // `check_argument_compat` ensures that both have the same type, so we know they will use the metadata the same way.
+            assert_eq!(caller_arg.layout.ty, callee_arg.layout.ty);
+            // We have to properly pre-allocate the memory for the callee.
+            // So let's tear down some wrappers.
+            // This all has to be in memory, there are no immediate unsized values.
+            let src = caller_arg.assert_mem_place();
+            // The destination cannot be one of these "spread args".
+            let (dest_frame, dest_local) = callee_arg.assert_local();
+            // We are just initializing things, so there can't be anything here yet.
+            assert!(matches!(
+                *self.local_to_op(&self.stack()[dest_frame], dest_local, None)?,
+                Operand::Immediate(Immediate::Uninit)
+            ));
+            // Allocate enough memory to hold `src`.
+            let Some((size, align)) = self.size_and_align_of_mplace(&src)? else {
+                span_bug!(self.cur_span(), "unsized fn arg with `extern` type tail should not be allowed")
+            };
+            let ptr = self.allocate_ptr(size, align, MemoryKind::Stack)?;
+            let dest_place =
+                MPlaceTy::from_aligned_ptr_with_meta(ptr.into(), callee_arg.layout, src.meta);
+            // Update the local to be that new place.
+            *M::access_local_mut(self, dest_frame, dest_local)? = Operand::Indirect(*dest_place);
+        }
         // We allow some transmutes here.
         // FIXME: Depending on the PassMode, this should reset some padding to uninitialized. (This
         // is true for all `copy_op`, but there are a lot of special cases for argument passing
         // specifically.)
-        self.copy_op_transmute(&caller_arg, callee_arg)
+        self.copy_op(&caller_arg, callee_arg, /*allow_transmute*/ true)
     }
 
     /// Call this function -- pushing the stack frame and initializing the arguments.
diff --git a/compiler/rustc_const_eval/src/interpret/traits.rs b/compiler/rustc_const_eval/src/interpret/traits.rs
index 9c48f3e8337..22c23df7b1a 100644
--- a/compiler/rustc_const_eval/src/interpret/traits.rs
+++ b/compiler/rustc_const_eval/src/interpret/traits.rs
@@ -1,6 +1,6 @@
 use std::convert::TryFrom;
 
-use rustc_middle::mir::interpret::{InterpResult, Pointer, PointerArithmetic};
+use rustc_middle::mir::interpret::{alloc_range, InterpResult, Pointer, PointerArithmetic};
 use rustc_middle::ty::{
     self, Ty, TyCtxt, COMMON_VTABLE_ENTRIES_ALIGN, COMMON_VTABLE_ENTRIES_DROPINPLACE,
     COMMON_VTABLE_ENTRIES_SIZE,
@@ -102,18 +102,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             )?
             .expect("cannot be a ZST");
         let size = vtable
-            .read_integer(
+            .read_integer(alloc_range(
                 pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_SIZE).unwrap(),
                 pointer_size,
-            )?
+            ))?
             .check_init()?;
         let size = size.to_machine_usize(self)?;
         let size = Size::from_bytes(size);
         let align = vtable
-            .read_integer(
+            .read_integer(alloc_range(
                 pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_ALIGN).unwrap(),
                 pointer_size,
-            )?
+            ))?
             .check_init()?;
         let align = align.to_machine_usize(self)?;
         let align = Align::from_bytes(align).map_err(|e| err_ub!(InvalidVtableAlignment(e)))?;
diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs
index 9d7905ed9a8..2bc521d5bbe 100644
--- a/compiler/rustc_const_eval/src/interpret/util.rs
+++ b/compiler/rustc_const_eval/src/interpret/util.rs
@@ -1,5 +1,5 @@
 use rustc_middle::mir::interpret::InterpResult;
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use std::convert::TryInto;
 use std::ops::ControlFlow;
 
@@ -10,7 +10,7 @@ use std::ops::ControlFlow;
 /// case these parameters are unused.
 pub(crate) fn ensure_monomorphic_enough<'tcx, T>(tcx: TyCtxt<'tcx>, ty: T) -> InterpResult<'tcx>
 where
-    T: TypeFoldable<'tcx>,
+    T: TypeVisitable<'tcx>,
 {
     debug!("ensure_monomorphic_enough: ty={:?}", ty);
     if !ty.needs_subst() {
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 847694cbd10..0bf78446e37 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -593,16 +593,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 self.check_safe_pointer(value, "reference")?;
                 Ok(true)
             }
-            ty::Adt(def, ..) if def.is_box() => {
-                let unique = self.ecx.operand_field(value, 0)?;
-                let nonnull = self.ecx.operand_field(&unique, 0)?;
-                let ptr = self.ecx.operand_field(&nonnull, 0)?;
-                self.check_safe_pointer(&ptr, "box")?;
-
-                // Check other fields of Box
-                self.walk_value(value)?;
-                Ok(true)
-            }
             ty::FnPtr(_sig) => {
                 let value = try_validation!(
                     self.ecx.read_scalar(value).and_then(|v| v.check_init()),
@@ -814,6 +804,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
     }
 
     #[inline]
+    fn visit_box(&mut self, op: &OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
+        self.check_safe_pointer(op, "box")?;
+        Ok(())
+    }
+
+    #[inline]
     fn visit_value(&mut self, op: &OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
         trace!("visit_value: {:?}, {:?}", *op, op.layout);
 
@@ -821,8 +817,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         if self.try_visit_primitive(op)? {
             return Ok(());
         }
-        // Sanity check: `builtin_deref` does not know any pointers that are not primitive.
-        assert!(op.layout.ty.builtin_deref(true).is_none());
 
         // Special check preventing `UnsafeCell` in the inner part of constants
         if let Some(def) = op.layout.ty.ty_adt_def() {
diff --git a/compiler/rustc_const_eval/src/interpret/visitor.rs b/compiler/rustc_const_eval/src/interpret/visitor.rs
index 2b77ed89893..ded4c6a557a 100644
--- a/compiler/rustc_const_eval/src/interpret/visitor.rs
+++ b/compiler/rustc_const_eval/src/interpret/visitor.rs
@@ -151,6 +151,14 @@ macro_rules! make_value_visitor {
             {
                 Ok(())
             }
+            /// Visits the given value as the pointer of a `Box`. There is nothing to recurse into.
+            /// The type of `v` will be a raw pointer, but this is a field of `Box<T>` and the
+            /// pointee type is the actual `T`.
+            #[inline(always)]
+            fn visit_box(&mut self, _v: &Self::V) -> InterpResult<'tcx>
+            {
+                Ok(())
+            }
             /// Visits this value as an aggregate, you are getting an iterator yielding
             /// all the fields (still in an `InterpResult`, you have to do error handling yourself).
             /// Recurses into the fields.
@@ -221,6 +229,47 @@ macro_rules! make_value_visitor {
                     // Slices do not need special handling here: they have `Array` field
                     // placement with length 0, so we enter the `Array` case below which
                     // indirectly uses the metadata to determine the actual length.
+
+                    // However, `Box`... let's talk about `Box`.
+                    ty::Adt(def, ..) if def.is_box() => {
+                        // `Box` is a hybrid primitive-library-defined type that one the one hand is
+                        // a dereferenceable pointer, on the other hand has *basically arbitrary
+                        // user-defined layout* since the user controls the 'allocator' field. So it
+                        // cannot be treated like a normal pointer, since it does not fit into an
+                        // `Immediate`. Yeah, it is quite terrible. But many visitors want to do
+                        // something with "all boxed pointers", so we handle this mess for them.
+                        //
+                        // When we hit a `Box`, we do not do the usual `visit_aggregate`; instead,
+                        // we (a) call `visit_box` on the pointer value, and (b) recurse on the
+                        // allocator field. We also assert tons of things to ensure we do not miss
+                        // any other fields.
+
+                        // `Box` has two fields: the pointer we care about, and the allocator.
+                        assert_eq!(v.layout().fields.count(), 2, "`Box` must have exactly 2 fields");
+                        let (unique_ptr, alloc) =
+                            (v.project_field(self.ecx(), 0)?, v.project_field(self.ecx(), 1)?);
+                        // Unfortunately there is some type junk in the way here: `unique_ptr` is a `Unique`...
+                        // (which means another 2 fields, the second of which is a `PhantomData`)
+                        assert_eq!(unique_ptr.layout().fields.count(), 2);
+                        let (nonnull_ptr, phantom) = (
+                            unique_ptr.project_field(self.ecx(), 0)?,
+                            unique_ptr.project_field(self.ecx(), 1)?,
+                        );
+                        assert!(
+                            phantom.layout().ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()),
+                            "2nd field of `Unique` should be PhantomData but is {:?}",
+                            phantom.layout().ty,
+                        );
+                        // ... that contains a `NonNull`... (gladly, only a single field here)
+                        assert_eq!(nonnull_ptr.layout().fields.count(), 1);
+                        let raw_ptr = nonnull_ptr.project_field(self.ecx(), 0)?; // the actual raw ptr
+                        // ... whose only field finally is a raw ptr we can dereference.
+                        self.visit_box(&raw_ptr)?;
+
+                        // The second `Box` field is the allocator, which we recursively check for validity
+                        // like in regular structs.
+                        self.visit_field(v, 1, &alloc)?;
+                    }
                     _ => {},
                 };
 
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index 5bf91879066..2d42ae236ad 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -21,6 +21,7 @@ Rust MIR: a lowered representation of Rust.
 #![feature(trusted_step)]
 #![feature(try_blocks)]
 #![feature(yeet_expr)]
+#![feature(is_some_with)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index dc46583d5af..f87de4f6a08 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -10,7 +10,7 @@ use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceC
 use rustc_middle::mir::*;
 use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts};
 use rustc_middle::ty::{self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt};
-use rustc_middle::ty::{Binder, TraitPredicate, TraitRef, TypeFoldable};
+use rustc_middle::ty::{Binder, TraitPredicate, TraitRef, TypeVisitable};
 use rustc_mir_dataflow::{self, Analysis};
 use rustc_span::{sym, Span, Symbol};
 use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index 0c587220cb7..9574661282b 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -155,8 +155,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                     });
 
                     if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
-                        let span =
-                            tcx.sess.source_map().guess_head_span(tcx.def_span(data.impl_def_id));
+                        let span = tcx.def_span(data.impl_def_id);
                         err.span_note(span, "impl defined here, but it is not `const`");
                     }
                 }
@@ -205,7 +204,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
 
                 match self_ty.kind() {
                     FnDef(def_id, ..) => {
-                        let span = tcx.sess.source_map().guess_head_span(tcx.def_span(*def_id));
+                        let span = tcx.def_span(*def_id);
                         if ccx.tcx.is_const_fn_raw(*def_id) {
                             span_bug!(span, "calling const FnDef errored when it shouldn't");
                         }
diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs
index 8eee13196fc..6298fa7f062 100644
--- a/compiler/rustc_const_eval/src/transform/promote_consts.rs
+++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs
@@ -17,7 +17,7 @@ use rustc_middle::mir::traversal::ReversePostorderIter;
 use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
 use rustc_middle::mir::*;
 use rustc_middle::ty::subst::InternalSubsts;
-use rustc_middle::ty::{self, List, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, List, TyCtxt, TypeVisitable};
 use rustc_span::Span;
 
 use rustc_index::vec::{Idx, IndexVec};
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index c9cb01701cf..7b9c6329d32 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -7,12 +7,12 @@ use rustc_middle::mir::interpret::Scalar;
 use rustc_middle::mir::visit::NonUseContext::VarDebugInfo;
 use rustc_middle::mir::visit::{PlaceContext, Visitor};
 use rustc_middle::mir::{
-    traversal, AggregateKind, BasicBlock, BinOp, Body, BorrowKind, Local, Location, MirPass,
-    MirPhase, Operand, Place, PlaceElem, PlaceRef, ProjectionElem, Rvalue, SourceScope, Statement,
-    StatementKind, Terminator, TerminatorKind, UnOp, START_BLOCK,
+    traversal, AggregateKind, BasicBlock, BinOp, Body, BorrowKind, CastKind, Local, Location,
+    MirPass, MirPhase, Operand, Place, PlaceElem, PlaceRef, ProjectionElem, Rvalue, SourceScope,
+    Statement, StatementKind, Terminator, TerminatorKind, UnOp, START_BLOCK,
 };
 use rustc_middle::ty::fold::BottomUpFolder;
-use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable, TypeVisitable};
 use rustc_mir_dataflow::impls::MaybeStorageLive;
 use rustc_mir_dataflow::storage::always_live_locals;
 use rustc_mir_dataflow::{Analysis, ResultsCursor};
@@ -361,6 +361,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
                     );
                 }
             }
+            Rvalue::Ref(..) => {}
             Rvalue::Len(p) => {
                 let pty = p.ty(&self.body.local_decls, self.tcx).ty;
                 check_kinds!(
@@ -503,7 +504,30 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
                 let a = operand.ty(&self.body.local_decls, self.tcx);
                 check_kinds!(a, "Cannot shallow init type {:?}", ty::RawPtr(..));
             }
-            _ => {}
+            Rvalue::Cast(kind, operand, target_type) => {
+                match kind {
+                    CastKind::Misc => {
+                        let op_ty = operand.ty(self.body, self.tcx);
+                        if op_ty.is_enum() {
+                            self.fail(
+                                location,
+                                format!(
+                                    "enum -> int casts should go through `Rvalue::Discriminant`: {operand:?}:{op_ty} as {target_type}",
+                                ),
+                            );
+                        }
+                    }
+                    // Nothing to check here
+                    CastKind::PointerFromExposedAddress
+                    | CastKind::PointerExposeAddress
+                    | CastKind::Pointer(_) => {}
+                }
+            }
+            Rvalue::Repeat(_, _)
+            | Rvalue::ThreadLocalRef(_)
+            | Rvalue::AddressOf(_, _)
+            | Rvalue::NullaryOp(_, _)
+            | Rvalue::Discriminant(_) => {}
         }
         self.super_rvalue(rvalue, location);
     }
diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs
index 3096af90d47..b71cdad718a 100644
--- a/compiler/rustc_driver/src/lib.rs
+++ b/compiler/rustc_driver/src/lib.rs
@@ -849,10 +849,10 @@ Available lint options:
     };
 
     println!("Lint checks provided by rustc:\n");
-    println!("    {}  {:7.7}  {}", padded("name"), "default", "meaning");
-    println!("    {}  {:7.7}  {}", padded("----"), "-------", "-------");
 
     let print_lints = |lints: Vec<&Lint>| {
+        println!("    {}  {:7.7}  {}", padded("name"), "default", "meaning");
+        println!("    {}  {:7.7}  {}", padded("----"), "-------", "-------");
         for lint in lints {
             let name = lint.name_lower().replace('_', "-");
             println!(
@@ -884,11 +884,15 @@ Available lint options:
     };
 
     println!("Lint groups provided by rustc:\n");
-    println!("    {}  sub-lints", padded("name"));
-    println!("    {}  ---------", padded("----"));
-    println!("    {}  all lints that are set to issue warnings", padded("warnings"));
 
-    let print_lint_groups = |lints: Vec<(&'static str, Vec<LintId>)>| {
+    let print_lint_groups = |lints: Vec<(&'static str, Vec<LintId>)>, all_warnings| {
+        println!("    {}  sub-lints", padded("name"));
+        println!("    {}  ---------", padded("----"));
+
+        if all_warnings {
+            println!("    {}  all lints that are set to issue warnings", padded("warnings"));
+        }
+
         for (name, to) in lints {
             let name = name.to_lowercase().replace('_', "-");
             let desc = to
@@ -901,7 +905,7 @@ Available lint options:
         println!("\n");
     };
 
-    print_lint_groups(builtin_groups);
+    print_lint_groups(builtin_groups, true);
 
     match (loaded_plugins, plugin.len(), plugin_groups.len()) {
         (false, 0, _) | (false, _, 0) => {
@@ -916,7 +920,7 @@ Available lint options:
             }
             if g > 0 {
                 println!("Lint groups provided by plugins loaded by this crate:\n");
-                print_lint_groups(plugin_groups);
+                print_lint_groups(plugin_groups, false);
             }
         }
     }
diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl
new file mode 100644
index 00000000000..e7e07093c03
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl
@@ -0,0 +1,400 @@
+lint-array-into-iter =
+    this method call resolves to `<&{$target} as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to <{$target} as IntoIterator>::into_iter in Rust 2021
+    .use-iter-suggestion = use `.iter()` instead of `.into_iter()` to avoid ambiguity
+    .remove-into-iter-suggestion = or remove `.into_iter()` to iterate by value
+    .use-explicit-into-iter-suggestion =
+        or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
+
+lint-enum-intrinsics-mem-discriminant =
+    the return value of `mem::discriminant` is unspecified when called with a non-enum type
+    .note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum.
+
+lint-enum-intrinsics-mem-variant =
+    the return value of `mem::variant_count` is unspecified when called with a non-enum type
+    .note = the type parameter of `variant_count` should be an enum, but it was instantiated with the type `{$ty_param}`, which is not an enum.
+
+lint-expectation = this lint expectation is unfulfilled
+    .note = the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+
+lint-hidden-unicode-codepoints = unicode codepoint changing visible direction of text present in {$label}
+    .label = this {$label} contains {$count ->
+        [one] an invisible
+        *[other] invisible
+    } unicode text flow control {$count ->
+        [one] codepoint
+        *[other] codepoints
+    }
+    .note = these kind of unicode codepoints change the way text flows on applications that support them, but can cause confusion because they change the order of characters on the screen
+    .suggestion-remove = if their presence wasn't intentional, you can remove them
+    .suggestion-escape = if you want to keep them but make them visible in your source code, you can escape them
+    .no-suggestion-note-escape = if you want to keep them but make them visible in your source code, you can escape them: {$escaped}
+
+lint-default-hash-types = prefer `{$preferred}` over `{$used}`, it has better performance
+    .note = a `use rustc_data_structures::fx::{$preferred}` may be necessary
+
+lint-query-instability = using `{$query}` can result in unstable query results
+    .note = if you believe this case to be fine, allow this lint and add a comment explaining your rationale
+
+lint-tykind-kind = usage of `ty::TyKind::<kind>`
+    .suggestion = try using `ty::<kind>` directly
+
+lint-tykind = usage of `ty::TyKind`
+    .help = try using `Ty` instead
+
+lint-ty-qualified = usage of qualified `ty::{$ty}`
+    .suggestion = try importing it and using it unqualified
+
+lint-lintpass-by-hand = implementing `LintPass` by hand
+    .help = try using `declare_lint_pass!` or `impl_lint_pass!` instead
+
+lint-non-existant-doc-keyword = found non-existing keyword `{$keyword}` used in `#[doc(keyword = \"...\")]`
+    .help = only existing keywords are allowed in core/std
+
+lint-diag-out-of-impl =
+    diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls
+
+lint-untranslatable-diag = diagnostics should be created using translatable messages
+
+lint-cstring-ptr = getting the inner pointer of a temporary `CString`
+    .as-ptr-label = this pointer will be invalid
+    .unwrap-label = this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime
+    .note = pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned
+    .help = for more information, see https://doc.rust-lang.org/reference/destructors.html
+
+lint-identifier-non-ascii-char = identifier contains non-ASCII characters
+
+lint-identifier-uncommon-codepoints = identifier contains uncommon Unicode codepoints
+
+lint-confusable-identifier-pair = identifier pair considered confusable between `{$existing_sym}` and `{$sym}`
+    .label = this is where the previous identifier occurred
+
+lint-mixed-script-confusables =
+    the usage of Script Group `{$set}` in this crate consists solely of mixed script confusables
+    .includes-note = the usage includes {$includes}
+    .note = please recheck to make sure their usages are indeed what you want
+
+lint-non-fmt-panic = panic message is not a string literal
+    .note = this usage of `{$name}!()` is deprecated; it will be a hard error in Rust 2021
+    .more-info-note = for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>
+    .supports-fmt-note = the `{$name}!()` macro supports formatting, so there's no need for the `format!()` macro here
+    .supports-fmt-suggestion = remove the `format!(..)` macro call
+    .display-suggestion = add a "{"{"}{"}"}" format string to `Display` the message
+    .debug-suggestion =
+        add a "{"{"}:?{"}"}" format string to use the `Debug` implementation of `{$ty}`
+    .panic-suggestion = {$already_suggested ->
+        [true] or use
+        *[false] use
+    } std::panic::panic_any instead
+
+lint-non-fmt-panic-unused =
+    panic message contains {$count ->
+        [one] an unused
+        *[other] unused
+    } formatting {$count ->
+        [one] placeholder
+        *[other] placeholders
+    }
+    .note = this message is not used as a format string when given without arguments, but will be in Rust 2021
+    .add-args-suggestion = add the missing {$count ->
+        [one] argument
+        *[other] arguments
+    }
+    .add-fmt-suggestion = or add a "{"{"}{"}"}" format string to use the message literally
+
+lint-non-fmt-panic-braces =
+    panic message contains {$count ->
+        [one] a brace
+        *[other] braces
+    }
+    .note = this message is not used as a format string, but will be in Rust 2021
+    .suggestion = add a "{"{"}{"}"}" format string to use the message literally
+
+lint-non-camel-case-type = {$sort} `{$name}` should have an upper camel case name
+    .suggestion = convert the identifier to upper camel case
+    .label = should have an UpperCamelCase name
+
+lint-non-snake-case = {$sort} `{$name}` should have a snake case name
+    .rename-or-convert-suggestion = rename the identifier or convert it to a snake case raw identifier
+    .cannot-convert-note = `{$sc}` cannot be used as a raw identifier
+    .rename-suggestion = rename the identifier
+    .convert-suggestion = convert the identifier to snake case
+    .help = convert the identifier to snake case: `{$sc}`
+    .label = should have a snake_case name
+
+lint-non-upper_case-global = {$sort} `{$name}` should have an upper case name
+    .suggestion = convert the identifier to upper case
+    .label = should have an UPPER_CASE name
+
+lint-noop-method-call = call to `.{$method}()` on a reference in this situation does nothing
+    .label = unnecessary method call
+    .note = the type `{$receiver_ty}` which `{$method}` is being called on is the same as the type returned from `{$method}`, so the method call does not do anything and can be removed
+
+lint-pass-by-value = passing `{$ty}` by reference
+    .suggestion = try passing by value
+
+lint-redundant-semicolons =
+    unnecessary trailing {$multiple ->
+        [true] semicolons
+        *[false] semicolon
+    }
+    .suggestion = remove {$multiple ->
+        [true] these semicolons
+        *[false] this semicolon
+    }
+
+lint-drop-trait-constraints =
+    bounds on `{$predicate}` are most likely incorrect, consider instead using `{$needs_drop}` to detect whether a type can be trivially dropped
+
+lint-drop-glue =
+    types that do not implement `Drop` can still have drop glue, consider instead using `{$needs_drop}` to detect whether a type is trivially dropped
+
+lint-range-endpoint-out-of-range = range endpoint is out of range for `{$ty}`
+    .suggestion = use an inclusive range instead
+
+lint-overflowing-bin-hex = literal out of range for `{$ty}`
+    .negative-note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}`
+    .negative-becomes-note = and the value `-{$lit}` will become `{$actually}{$ty}`
+    .positive-note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}` and will become `{$actually}{$ty}`
+    .suggestion = consider using the type `{$suggestion_ty}` instead
+    .help = consider using the type `{$suggestion_ty}` instead
+
+lint-overflowing-int = literal out of range for `{$ty}`
+    .note = the literal `{$lit}` does not fit into the type `{$ty}` whose range is `{$min}..={$max}`
+    .help = consider using the type `{$suggestion_ty}` instead
+
+lint-only-cast-u8-to-char = only `u8` can be cast into `char`
+    .suggestion = use a `char` literal instead
+
+lint-overflowing-uint = literal out of range for `{$ty}`
+    .note = the literal `{$lit}` does not fit into the type `{$ty}` whose range is `{$min}..={$max}`
+
+lint-overflowing-literal = literal out of range for `{$ty}`
+    .note = the literal `{$lit}` does not fit into the type `{$ty}` and will be converted to `{$ty}::INFINITY`
+
+lint-unused-comparisons = comparison is useless due to type limits
+
+lint-improper-ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe
+    .label = not FFI-safe
+    .note = the type is defined here
+
+lint-improper-ctypes-opaque = opaque types have no C equivalent
+
+lint-improper-ctypes-fnptr-reason = this function pointer has Rust-specific calling convention
+lint-improper-ctypes-fnptr-help = consider using an `extern fn(...) -> ...` function pointer instead
+
+lint-improper-ctypes-tuple-reason = tuples have unspecified layout
+lint-improper-ctypes-tuple-help = consider using a struct instead
+
+lint-improper-ctypes-str-reason = string slices have no C equivalent
+lint-improper-ctypes-str-help = consider using `*const u8` and a length instead
+
+lint-improper-ctypes-dyn = trait objects have no C equivalent
+
+lint-improper-ctypes-slice-reason = slices have no C equivalent
+lint-improper-ctypes-slice-help = consider using a raw pointer instead
+
+lint-improper-ctypes-128bit = 128-bit integers don't currently have a known stable ABI
+
+lint-improper-ctypes-char-reason = the `char` type has no C equivalent
+lint-improper-ctypes-char-help = consider using `u32` or `libc::wchar_t` instead
+
+lint-improper-ctypes-non-exhaustive = this enum is non-exhaustive
+lint-improper-ctypes-non-exhaustive-variant = this enum has non-exhaustive variants
+
+lint-improper-ctypes-enum-repr-reason = enum has no representation hint
+lint-improper-ctypes-enum-repr-help =
+    consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
+
+lint-improper-ctypes-struct-fieldless-reason = this struct has no fields
+lint-improper-ctypes-struct-fieldless-help = consider adding a member to this struct
+
+lint-improper-ctypes-union-fieldless-reason = this union has no fields
+lint-improper-ctypes-union-fieldless-help = consider adding a member to this union
+
+lint-improper-ctypes-struct-non-exhaustive = this struct is non-exhaustive
+lint-improper-ctypes-union-non-exhaustive = this union is non-exhaustive
+
+lint-improper-ctypes-struct-layout-reason = this struct has unspecified layout
+lint-improper-ctypes-struct-layout-help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
+
+lint-improper-ctypes-union-layout-reason = this union has unspecified layout
+lint-improper-ctypes-union-layout-help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union
+
+lint-improper-ctypes-box = box cannot be represented as a single pointer
+
+lint-improper-ctypes-enum-phantomdata = this enum contains a PhantomData field
+
+lint-improper-ctypes-struct-zst = this struct contains only zero-sized fields
+
+lint-improper-ctypes-array-reason = passing raw arrays by value is not FFI-safe
+lint-improper-ctypes-array-help = consider passing a pointer to the array
+
+lint-improper-ctypes-only-phantomdata = composed only of `PhantomData`
+
+lint-variant-size-differences =
+    enum variant is more than three times larger ({$largest} bytes) than the next largest
+
+lint-atomic-ordering-load = atomic loads cannot have `Release` or `AcqRel` ordering
+    .help = consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`
+
+lint-atomic-ordering-store = atomic stores cannot have `Acquire` or `AcqRel` ordering
+    .help = consider using ordering modes `Release`, `SeqCst` or `Relaxed`
+
+lint-atomic-ordering-fence = memory fences cannot have `Relaxed` ordering
+    .help = consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`
+
+lint-atomic-ordering-invalid = `{$method}`'s failure ordering may not be `Release` or `AcqRel`, since a failed `{$method}` does not result in a write
+    .label = invalid failure ordering
+    .help = consider using `Acquire` or `Relaxed` failure ordering instead
+
+lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering
+    .fail-label = `{$fail_ordering}` failure ordering
+    .success-label = `{$success_ordering}` success ordering
+    .suggestion = consider using `{$success_suggestion}` success ordering instead
+
+lint-unused-op = unused {$op} that must be used
+    .label = the {$op} produces a value
+    .suggestion = use `let _ = ...` to ignore the resulting value
+
+lint-unused-result = unused result of type `{$ty}`
+
+lint-unused-closure =
+    unused {$pre}{$count ->
+        [one] closure
+        *[other] closures
+    }{$post} that must be used
+    .note = closures are lazy and do nothing unless called
+
+lint-unused-generator =
+    unused {$pre}{$count ->
+        [one] generator
+        *[other] generator
+    }{$post} that must be used
+    .note = generators are lazy and do nothing unless resumed
+
+lint-unused-def = unused {$pre}`{$def}`{$post} that must be used
+
+lint-path-statement-drop = path statement drops value
+    .suggestion = use `drop` to clarify the intent
+
+lint-path-statement-no-effect = path statement with no effect
+
+lint-unused-delim = unnecessary {$delim} around {$item}
+    .suggestion = remove these {$delim}
+
+lint-unused-import-braces = braces around {$node} is unnecessary
+
+lint-unused-allocation = unnecessary allocation, use `&` instead
+lint-unused-allocation-mut = unnecessary allocation, use `&mut` instead
+
+lint-builtin-while-true = denote infinite loops with `loop {"{"} ... {"}"}`
+    .suggestion = use `loop`
+
+lint-builtin-box-pointers = type uses owned (Box type) pointers: {$ty}
+
+lint-builtin-non-shorthand-field-patterns = the `{$ident}:` in this pattern is redundant
+    .suggestion = use shorthand field pattern
+
+lint-builtin-overridden-symbol-name =
+    the linker's behavior with multiple libraries exporting duplicate symbol names is undefined and Rust cannot provide guarantees when you manually override them
+
+lint-builtin-overridden-symbol-section =
+    the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them
+
+lint-builtin-allow-internal-unsafe =
+    `allow_internal_unsafe` allows defining macros using unsafe without triggering the `unsafe_code` lint at their call site
+
+lint-builtin-unsafe-block = usage of an `unsafe` block
+
+lint-builtin-unsafe-trait = declaration of an `unsafe` trait
+
+lint-builtin-unsafe-impl = implementation of an `unsafe` trait
+
+lint-builtin-no-mangle-fn = declaration of a `no_mangle` function
+lint-builtin-export-name-fn = declaration of a function with `export_name`
+lint-builtin-link-section-fn = declaration of a function with `link_section`
+
+lint-builtin-no-mangle-static = declaration of a `no_mangle` static
+lint-builtin-export-name-static = declaration of a static with `export_name`
+lint-builtin-link-section-static = declaration of a static with `link_section`
+
+lint-builtin-no-mangle-method = declaration of a `no_mangle` method
+lint-builtin-export-name-method = declaration of a method with `export_name`
+
+lint-builtin-decl-unsafe-fn = declaration of an `unsafe` function
+lint-builtin-decl-unsafe-method = declaration of an `unsafe` method
+lint-builtin-impl-unsafe-method = implementation of an `unsafe` method
+
+lint-builtin-missing-doc = missing documentation for {$article} {$desc}
+
+lint-builtin-missing-copy-impl = type could implement `Copy`; consider adding `impl Copy`
+
+lint-builtin-missing-debug-impl =
+    type does not implement `{$debug}`; consider adding `#[derive(Debug)]` or a manual implementation
+
+lint-builtin-anonymous-params = anonymous parameters are deprecated and will be removed in the next edition
+    .suggestion = try naming the parameter or explicitly ignoring it
+
+lint-builtin-deprecated-attr-link = use of deprecated attribute `{$name}`: {$reason}. See {$link}
+lint-builtin-deprecated-attr-used = use of deprecated attribute `{$name}`: no longer used.
+lint-builtin-deprecated-attr-default-suggestion = remove this attribute
+
+lint-builtin-unused-doc-comment = unused doc comment
+    .label = rustdoc does not generate documentation for {$kind}
+    .plain-help = use `//` for a plain comment
+    .block-help = use `/* */` for a plain comment
+
+lint-builtin-no-mangle-generic = functions generic over types or consts must be mangled
+    .suggestion = remove this attribute
+
+lint-builtin-const-no-mangle = const items should never be `#[no_mangle]`
+    .suggestion = try a static value
+
+lint-builtin-mutable-transmutes =
+    transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
+
+lint-builtin-unstable-features = unstable feature
+
+lint-builtin-unreachable-pub = unreachable `pub` {$what}
+    .suggestion = consider restricting its visibility
+    .help = or consider exporting it for use by other crates
+
+lint-builtin-type-alias-bounds-help = use fully disambiguated paths (i.e., `<T as Trait>::Assoc`) to refer to associated types in type aliases
+
+lint-builtin-type-alias-where-clause = where clauses are not enforced in type aliases
+    .suggestion = the clause will not be checked when the type alias is used, and should be removed
+
+lint-builtin-type-alias-generic-bounds = bounds on generic parameters are not enforced in type aliases
+    .suggestion = the bound will not be checked when the type alias is used, and should be removed
+
+lint-builtin-trivial-bounds = {$predicate_kind_name} bound {$predicate} does not depend on any type or lifetime parameters
+
+lint-builtin-ellipsis-inclusive-range-patterns = `...` range patterns are deprecated
+    .suggestion = use `..=` for an inclusive range
+
+lint-builtin-unnameable-test-items = cannot test inner items
+
+lint-builtin-keyword-idents = `{$kw}` is a keyword in the {$next} edition
+    .suggestion = you can use a raw identifier to stay compatible
+
+lint-builtin-explicit-outlives = outlives requirements can be inferred
+    .suggestion = remove {$count ->
+        [one] this bound
+        *[other] these bounds
+    }
+
+lint-builtin-incomplete-features = the feature `{$name}` is incomplete and may not be safe to use and/or cause compiler crashes
+    .note = see issue #{$n} <https://github.com/rust-lang/rust/issues/{$n}> for more information
+    .help = consider using `min_{$name}` instead, which is more stable and complete
+
+lint-builtin-clashing-extern-same-name = `{$this_fi}` redeclared with a different signature
+    .previous-decl-label = `{$orig}` previously declared here
+    .mismatch-label = this signature doesn't match the previous declaration
+lint-builtin-clashing-extern-diff-name = `{$this_fi}` redeclares `{$orig}` with a different signature
+    .previous-decl-label = `{$orig}` previously declared here
+    .mismatch-label = this signature doesn't match the previous declaration
+
+lint-builtin-deref-nullptr = dereferencing a null pointer
+    .label = this code causes undefined behavior when executed
+
+lint-builtin-asm-labels = avoid using named labels in inline assembly
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index d52b94b78df..563d0534d8f 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -31,11 +31,12 @@ pub use unic_langid::{langid, LanguageIdentifier};
 
 // Generates `DEFAULT_LOCALE_RESOURCES` static and `fluent_generated` module.
 fluent_messages! {
+    borrowck => "../locales/en-US/borrowck.ftl",
+    builtin_macros => "../locales/en-US/builtin_macros.ftl",
+    lint => "../locales/en-US/lint.ftl",
     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",
-    borrowck => "../locales/en-US/borrowck.ftl",
 }
 
 pub use fluent_generated::{self as fluent, DEFAULT_LOCALE_RESOURCES};
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index b8545139cec..0d1d017d874 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -1,14 +1,14 @@
 use crate::snippet::Style;
 use crate::{
-    CodeSuggestion, DiagnosticMessage, Level, MultiSpan, SubdiagnosticMessage, Substitution,
-    SubstitutionPart, SuggestionStyle,
+    CodeSuggestion, DiagnosticMessage, EmissionGuarantee, Level, LintDiagnosticBuilder, MultiSpan,
+    SubdiagnosticMessage, Substitution, SubstitutionPart, SuggestionStyle,
 };
 use rustc_data_structures::stable_map::FxHashMap;
 use rustc_error_messages::FluentValue;
 use rustc_lint_defs::{Applicability, LintExpectationId};
 use rustc_span::edition::LATEST_STABLE_EDITION;
 use rustc_span::symbol::{Ident, Symbol};
-use rustc_span::{Span, DUMMY_SP};
+use rustc_span::{edition::Edition, Span, DUMMY_SP};
 use std::borrow::Cow;
 use std::fmt;
 use std::hash::{Hash, Hasher};
@@ -39,12 +39,94 @@ pub trait IntoDiagnosticArg {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>;
 }
 
+impl IntoDiagnosticArg for bool {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        if self {
+            DiagnosticArgValue::Str(Cow::Borrowed("true"))
+        } else {
+            DiagnosticArgValue::Str(Cow::Borrowed("false"))
+        }
+    }
+}
+
+impl IntoDiagnosticArg for i8 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for u8 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for i16 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for u16 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for i32 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for u32 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for i64 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for u64 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for i128 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for u128 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
 impl IntoDiagnosticArg for String {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Str(Cow::Owned(self))
     }
 }
 
+impl IntoDiagnosticArg for std::num::NonZeroU32 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for Edition {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
 impl IntoDiagnosticArg for Symbol {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         self.to_ident_string().into_diagnostic_arg()
@@ -86,6 +168,14 @@ pub trait AddSubdiagnostic {
     fn add_to_diagnostic(self, diag: &mut Diagnostic);
 }
 
+/// Trait implemented by lint types. This should not be implemented manually. Instead, use
+/// `#[derive(LintDiagnostic)]` -- see [rustc_macros::LintDiagnostic].
+#[rustc_diagnostic_item = "DecorateLint"]
+pub trait DecorateLint<'a, G: EmissionGuarantee> {
+    /// Decorate and emit a lint.
+    fn decorate_lint(self, diag: LintDiagnosticBuilder<'a, G>);
+}
+
 #[must_use]
 #[derive(Clone, Debug, Encodable, Decodable)]
 pub struct Diagnostic {
@@ -286,7 +376,7 @@ impl Diagnostic {
     ///
     /// This span is *not* considered a ["primary span"][`MultiSpan`]; only
     /// the `Span` supplied when creating the diagnostic is primary.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_label(&mut self, span: Span, label: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.span.push_span_label(span, self.subdiagnostic_message_to_diagnostic_message(label));
         self
@@ -405,7 +495,7 @@ impl Diagnostic {
     }
 
     /// Add a note attached to this diagnostic.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn note(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Note, msg, MultiSpan::new(), None);
         self
@@ -428,7 +518,7 @@ impl Diagnostic {
 
     /// Prints the span with a note above it.
     /// This is like [`Diagnostic::note()`], but it gets its own span.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_note<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
@@ -450,7 +540,7 @@ impl Diagnostic {
     }
 
     /// Add a warning attached to this diagnostic.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Warning(None), msg, MultiSpan::new(), None);
         self
@@ -458,7 +548,7 @@ impl Diagnostic {
 
     /// Prints the span with a warning above it.
     /// This is like [`Diagnostic::warn()`], but it gets its own span.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_warn<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
@@ -469,7 +559,7 @@ impl Diagnostic {
     }
 
     /// Add a help message attached to this diagnostic.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Help, msg, MultiSpan::new(), None);
         self
@@ -483,7 +573,7 @@ impl Diagnostic {
 
     /// Prints the span with some help above it.
     /// This is like [`Diagnostic::help()`], but it gets its own span.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_help<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 9e0a99849a3..c3341fd68f4 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -529,7 +529,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
         applicability: Applicability,
     ) -> &mut Self);
 
-    forward!(pub fn set_primary_message(&mut self, msg: impl Into<String>) -> &mut Self);
+    forward!(pub fn set_primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self);
     forward!(pub fn set_span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self);
     forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self);
     forward!(pub fn set_arg(
@@ -589,3 +589,26 @@ macro_rules! struct_span_err {
 macro_rules! error_code {
     ($code:ident) => {{ $crate::DiagnosticId::Error(stringify!($code).to_owned()) }};
 }
+
+/// Wrapper around a `DiagnosticBuilder` for creating lints.
+pub struct LintDiagnosticBuilder<'a, G: EmissionGuarantee>(DiagnosticBuilder<'a, G>);
+
+impl<'a, G: EmissionGuarantee> LintDiagnosticBuilder<'a, G> {
+    /// Return the inner `DiagnosticBuilder`, first setting the primary message to `msg`.
+    pub fn build(mut self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'a, G> {
+        self.0.set_primary_message(msg);
+        self.0.set_is_lint();
+        self.0
+    }
+
+    /// Create a `LintDiagnosticBuilder` from some existing `DiagnosticBuilder`.
+    pub fn new(err: DiagnosticBuilder<'a, G>) -> LintDiagnosticBuilder<'a, G> {
+        LintDiagnosticBuilder(err)
+    }
+}
+
+impl<'a> LintDiagnosticBuilder<'a, ErrorGuaranteed> {
+    pub fn forget_guarantee(self) -> LintDiagnosticBuilder<'a, ()> {
+        LintDiagnosticBuilder(self.0.forget_guarantee())
+    }
+}
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 9c42f6f26fc..ffe4ecebb2e 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -370,10 +370,10 @@ impl fmt::Display for ExplicitBug {
 impl error::Error for ExplicitBug {}
 
 pub use diagnostic::{
-    AddSubdiagnostic, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
+    AddSubdiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
     DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
 };
-pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee};
+pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee, LintDiagnosticBuilder};
 use std::backtrace::Backtrace;
 
 /// A handler deals with errors and other compiler output.
@@ -649,7 +649,7 @@ impl Handler {
     /// Attempting to `.emit()` the builder will only emit if either:
     /// * `can_emit_warnings` is `true`
     /// * `is_force_warn` was set in `DiagnosticId::Lint`
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_warn(
         &self,
         span: impl Into<MultiSpan>,
@@ -678,7 +678,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Allow` level at the given `span` and with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_allow(
         &self,
         span: impl Into<MultiSpan>,
@@ -691,7 +691,7 @@ impl Handler {
 
     /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
     /// Also include a code.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_warn_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -708,7 +708,7 @@ impl Handler {
     /// Attempting to `.emit()` the builder will only emit if either:
     /// * `can_emit_warnings` is `true`
     /// * `is_force_warn` was set in `DiagnosticId::Lint`
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Warning(None), msg)
     }
@@ -728,13 +728,13 @@ impl Handler {
     }
 
     /// Construct a builder at the `Allow` level with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Allow, msg)
     }
 
     /// Construct a builder at the `Expect` level with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_expect(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -744,7 +744,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Error` level at the given `span` and with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_err(
         &self,
         span: impl Into<MultiSpan>,
@@ -756,7 +756,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Error` level at the given `span`, with the `msg`, and `code`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_err_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -770,7 +770,7 @@ impl Handler {
 
     /// Construct a builder at the `Error` level with the `msg`.
     // FIXME: This method should be removed (every error should have an associated error code).
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_err(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -785,7 +785,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Error` level with the `msg` and the `code`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_err_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -797,7 +797,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Warn` level with the `msg` and the `code`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_warn_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -809,7 +809,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_fatal(
         &self,
         span: impl Into<MultiSpan>,
@@ -821,7 +821,7 @@ impl Handler {
     }
 
     /// Construct a builder at the `Fatal` level at the given `span`, with the `msg`, and `code`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_fatal_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -834,19 +834,19 @@ impl Handler {
     }
 
     /// Construct a builder at the `Error` level with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, !> {
         DiagnosticBuilder::new_fatal(self, msg)
     }
 
     /// Construct a builder at the `Help` level with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Help, msg)
     }
 
     /// Construct a builder at the `Note` level with the `msg`.
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_note_without_error(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -854,13 +854,13 @@ impl Handler {
         DiagnosticBuilder::new(self, Level::Note, msg)
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
         self.emit_diag_at_span(Diagnostic::new(Fatal, msg), span);
         FatalError.raise()
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_fatal_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -871,7 +871,7 @@ impl Handler {
         FatalError.raise()
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_err(
         &self,
         span: impl Into<MultiSpan>,
@@ -880,7 +880,7 @@ impl Handler {
         self.emit_diag_at_span(Diagnostic::new(Error { lint: false }, msg), span).unwrap()
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_err_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -893,12 +893,12 @@ impl Handler {
         );
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_warn(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
         self.emit_diag_at_span(Diagnostic::new(Warning(None), msg), span);
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_warn_with_code(
         &self,
         span: impl Into<MultiSpan>,
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 1e57d66dd9f..bfe2d773788 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1077,7 +1077,7 @@ impl<'a> ExtCtxt<'a> {
         self.current_expansion.id.expansion_cause()
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -1102,11 +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)]
+    #[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)]
+    #[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 1694a8865dd..74e9bbeeeaf 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -152,6 +152,19 @@ impl<'a> ExtCtxt<'a> {
         ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Expr(expr) }
     }
 
+    pub fn stmt_let_pat(&self, sp: Span, pat: P<ast::Pat>, ex: P<ast::Expr>) -> ast::Stmt {
+        let local = P(ast::Local {
+            pat,
+            ty: None,
+            id: ast::DUMMY_NODE_ID,
+            kind: LocalKind::Init(ex),
+            span: sp,
+            attrs: AttrVec::new(),
+            tokens: None,
+        });
+        self.stmt_local(local, sp)
+    }
+
     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)
     }
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index fd843b0c403..50acb0270b0 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -59,7 +59,7 @@ impl PpAnn for &dyn rustc_hir::intravisit::Map<'_> {
             Nested::ImplItem(id) => state.print_impl_item(self.impl_item(id)),
             Nested::ForeignItem(id) => state.print_foreign_item(self.foreign_item(id)),
             Nested::Body(id) => state.print_expr(&self.body(id).value),
-            Nested::BodyParamPat(id, i) => state.print_pat(&self.body(id).params[i].pat),
+            Nested::BodyParamPat(id, i) => state.print_pat(self.body(id).params[i].pat),
         }
     }
 }
@@ -74,37 +74,37 @@ pub struct State<'a> {
 impl<'a> State<'a> {
     pub fn print_node(&mut self, node: Node<'_>) {
         match node {
-            Node::Param(a) => self.print_param(&a),
-            Node::Item(a) => self.print_item(&a),
-            Node::ForeignItem(a) => self.print_foreign_item(&a),
+            Node::Param(a) => self.print_param(a),
+            Node::Item(a) => self.print_item(a),
+            Node::ForeignItem(a) => self.print_foreign_item(a),
             Node::TraitItem(a) => self.print_trait_item(a),
             Node::ImplItem(a) => self.print_impl_item(a),
-            Node::Variant(a) => self.print_variant(&a),
-            Node::AnonConst(a) => self.print_anon_const(&a),
-            Node::Expr(a) => self.print_expr(&a),
-            Node::Stmt(a) => self.print_stmt(&a),
-            Node::PathSegment(a) => self.print_path_segment(&a),
-            Node::Ty(a) => self.print_type(&a),
-            Node::TypeBinding(a) => self.print_type_binding(&a),
-            Node::TraitRef(a) => self.print_trait_ref(&a),
-            Node::Pat(a) => self.print_pat(&a),
-            Node::Arm(a) => self.print_arm(&a),
+            Node::Variant(a) => self.print_variant(a),
+            Node::AnonConst(a) => self.print_anon_const(a),
+            Node::Expr(a) => self.print_expr(a),
+            Node::Stmt(a) => self.print_stmt(a),
+            Node::PathSegment(a) => self.print_path_segment(a),
+            Node::Ty(a) => self.print_type(a),
+            Node::TypeBinding(a) => self.print_type_binding(a),
+            Node::TraitRef(a) => self.print_trait_ref(a),
+            Node::Pat(a) => self.print_pat(a),
+            Node::Arm(a) => self.print_arm(a),
             Node::Infer(_) => self.word("_"),
             Node::Block(a) => {
                 // Containing cbox, will be closed by print-block at `}`.
                 self.cbox(INDENT_UNIT);
                 // Head-ibox, will be closed by print-block after `{`.
                 self.ibox(0);
-                self.print_block(&a)
+                self.print_block(a);
             }
-            Node::Lifetime(a) => self.print_lifetime(&a),
+            Node::Lifetime(a) => self.print_lifetime(a),
             Node::GenericParam(_) => panic!("cannot print Node::GenericParam"),
             Node::Field(_) => panic!("cannot print Node::Field"),
             // These cases do not carry enough information in the
             // `hir_map` to reconstruct their full structure for pretty
             // printing.
             Node::Ctor(..) => panic!("cannot print isolated Ctor"),
-            Node::Local(a) => self.print_local_decl(&a),
+            Node::Local(a) => self.print_local_decl(a),
             Node::Crate(..) => panic!("cannot print Crate"),
         }
     }
@@ -266,7 +266,7 @@ impl<'a> State<'a> {
     }
 
     pub fn commasep_exprs(&mut self, b: Breaks, exprs: &[hir::Expr<'_>]) {
-        self.commasep_cmnt(b, exprs, |s, e| s.print_expr(&e), |e| e.span)
+        self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e), |e| e.span);
     }
 
     pub fn print_mod(&mut self, _mod: &hir::Mod<'_>, attrs: &[ast::Attribute]) {
@@ -287,9 +287,9 @@ impl<'a> State<'a> {
         self.maybe_print_comment(ty.span.lo());
         self.ibox(0);
         match ty.kind {
-            hir::TyKind::Slice(ref ty) => {
+            hir::TyKind::Slice(ty) => {
                 self.word("[");
-                self.print_type(&ty);
+                self.print_type(ty);
                 self.word("]");
             }
             hir::TyKind::Ptr(ref mt) => {
@@ -304,23 +304,16 @@ impl<'a> State<'a> {
             hir::TyKind::Never => {
                 self.word("!");
             }
-            hir::TyKind::Tup(ref elts) => {
+            hir::TyKind::Tup(elts) => {
                 self.popen();
-                self.commasep(Inconsistent, &elts, |s, ty| s.print_type(&ty));
+                self.commasep(Inconsistent, elts, |s, ty| s.print_type(ty));
                 if elts.len() == 1 {
                     self.word(",");
                 }
                 self.pclose();
             }
-            hir::TyKind::BareFn(ref f) => {
-                self.print_ty_fn(
-                    f.abi,
-                    f.unsafety,
-                    &f.decl,
-                    None,
-                    &f.generic_params,
-                    f.param_names,
-                );
+            hir::TyKind::BareFn(f) => {
+                self.print_ty_fn(f.abi, f.unsafety, f.decl, None, f.generic_params, f.param_names);
             }
             hir::TyKind::OpaqueDef(..) => self.word("/*impl Trait*/"),
             hir::TyKind::Path(ref qpath) => self.print_qpath(qpath, false),
@@ -344,9 +337,9 @@ impl<'a> State<'a> {
                     self.print_lifetime(lifetime);
                 }
             }
-            hir::TyKind::Array(ref ty, ref length) => {
+            hir::TyKind::Array(ty, ref length) => {
                 self.word("[");
-                self.print_type(&ty);
+                self.print_type(ty);
                 self.word("; ");
                 self.print_array_length(length);
                 self.word("]");
@@ -373,7 +366,7 @@ impl<'a> State<'a> {
         self.maybe_print_comment(item.span.lo());
         self.print_outer_attributes(self.attrs(item.hir_id()));
         match item.kind {
-            hir::ForeignItemKind::Fn(ref decl, ref arg_names, ref generics) => {
+            hir::ForeignItemKind::Fn(decl, arg_names, generics) => {
                 self.head("");
                 self.print_fn(
                     decl,
@@ -392,14 +385,14 @@ impl<'a> State<'a> {
                 self.word(";");
                 self.end() // end the outer fn box
             }
-            hir::ForeignItemKind::Static(ref t, m) => {
+            hir::ForeignItemKind::Static(t, m) => {
                 self.head("static");
                 if m == hir::Mutability::Mut {
                     self.word_space("mut");
                 }
                 self.print_ident(item.ident);
                 self.word_space(":");
-                self.print_type(&t);
+                self.print_type(t);
                 self.word(";");
                 self.end(); // end the head-ibox
                 self.end() // end the outer cbox
@@ -442,7 +435,7 @@ impl<'a> State<'a> {
     ) {
         self.word_space("type");
         self.print_ident(ident);
-        self.print_generic_params(&generics.params);
+        self.print_generic_params(generics.params);
         if let Some(bounds) = bounds {
             self.print_bounds(":", bounds);
         }
@@ -463,7 +456,7 @@ impl<'a> State<'a> {
     ) {
         self.head("type");
         self.print_ident(item.ident);
-        self.print_generic_params(&generics.params);
+        self.print_generic_params(generics.params);
         self.end(); // end the inner ibox
 
         self.print_where_clause(generics);
@@ -494,7 +487,7 @@ impl<'a> State<'a> {
                 self.end(); // end inner head-block
                 self.end(); // end outer head-block
             }
-            hir::ItemKind::Use(ref path, kind) => {
+            hir::ItemKind::Use(path, kind) => {
                 self.head("use");
                 self.print_path(path, false);
 
@@ -513,14 +506,14 @@ impl<'a> State<'a> {
                 self.end(); // end inner head-block
                 self.end(); // end outer head-block
             }
-            hir::ItemKind::Static(ref ty, m, expr) => {
+            hir::ItemKind::Static(ty, m, expr) => {
                 self.head("static");
                 if m == hir::Mutability::Mut {
                     self.word_space("mut");
                 }
                 self.print_ident(item.ident);
                 self.word_space(":");
-                self.print_type(&ty);
+                self.print_type(ty);
                 self.space();
                 self.end(); // end the head-ibox
 
@@ -529,11 +522,11 @@ impl<'a> State<'a> {
                 self.word(";");
                 self.end(); // end the outer cbox
             }
-            hir::ItemKind::Const(ref ty, expr) => {
+            hir::ItemKind::Const(ty, expr) => {
                 self.head("const");
                 self.print_ident(item.ident);
                 self.word_space(":");
-                self.print_type(&ty);
+                self.print_type(ty);
                 self.space();
                 self.end(); // end the head-ibox
 
@@ -542,10 +535,10 @@ impl<'a> State<'a> {
                 self.word(";");
                 self.end(); // end the outer cbox
             }
-            hir::ItemKind::Fn(ref sig, ref param_names, body) => {
+            hir::ItemKind::Fn(ref sig, param_names, body) => {
                 self.head("");
                 self.print_fn(
-                    &sig.decl,
+                    sig.decl,
                     sig.header,
                     Some(item.ident.name),
                     param_names,
@@ -578,22 +571,22 @@ impl<'a> State<'a> {
                 }
                 self.bclose(item.span);
             }
-            hir::ItemKind::GlobalAsm(ref asm) => {
+            hir::ItemKind::GlobalAsm(asm) => {
                 self.head("global_asm!");
                 self.print_inline_asm(asm);
                 self.end()
             }
-            hir::ItemKind::TyAlias(ref ty, ref generics) => {
-                self.print_item_type(item, &generics, |state| {
+            hir::ItemKind::TyAlias(ty, generics) => {
+                self.print_item_type(item, generics, |state| {
                     state.word_space("=");
-                    state.print_type(&ty);
+                    state.print_type(ty);
                 });
             }
             hir::ItemKind::OpaqueTy(ref opaque_ty) => {
-                self.print_item_type(item, &opaque_ty.generics, |state| {
+                self.print_item_type(item, opaque_ty.generics, |state| {
                     let mut real_bounds = Vec::with_capacity(opaque_ty.bounds.len());
-                    for b in opaque_ty.bounds.iter() {
-                        if let GenericBound::Trait(ref ptr, hir::TraitBoundModifier::Maybe) = *b {
+                    for b in opaque_ty.bounds {
+                        if let GenericBound::Trait(ptr, hir::TraitBoundModifier::Maybe) = b {
                             state.space();
                             state.word_space("for ?");
                             state.print_trait_ref(&ptr.trait_ref);
@@ -604,39 +597,39 @@ impl<'a> State<'a> {
                     state.print_bounds("= impl", real_bounds);
                 });
             }
-            hir::ItemKind::Enum(ref enum_definition, ref params) => {
+            hir::ItemKind::Enum(ref enum_definition, params) => {
                 self.print_enum_def(enum_definition, params, item.ident.name, item.span);
             }
-            hir::ItemKind::Struct(ref struct_def, ref generics) => {
+            hir::ItemKind::Struct(ref struct_def, generics) => {
                 self.head("struct");
                 self.print_struct(struct_def, generics, item.ident.name, item.span, true);
             }
-            hir::ItemKind::Union(ref struct_def, ref generics) => {
+            hir::ItemKind::Union(ref struct_def, generics) => {
                 self.head("union");
                 self.print_struct(struct_def, generics, item.ident.name, item.span, true);
             }
-            hir::ItemKind::Impl(hir::Impl {
+            hir::ItemKind::Impl(&hir::Impl {
                 unsafety,
                 polarity,
                 defaultness,
                 constness,
                 defaultness_span: _,
-                ref generics,
+                generics,
                 ref of_trait,
-                ref self_ty,
+                self_ty,
                 items,
             }) => {
                 self.head("");
-                self.print_defaultness(*defaultness);
-                self.print_unsafety(*unsafety);
+                self.print_defaultness(defaultness);
+                self.print_unsafety(unsafety);
                 self.word_nbsp("impl");
 
                 if !generics.params.is_empty() {
-                    self.print_generic_params(&generics.params);
+                    self.print_generic_params(generics.params);
                     self.space();
                 }
 
-                if *constness == hir::Constness::Const {
+                if constness == hir::Constness::Const {
                     self.word_nbsp("const");
                 }
 
@@ -644,33 +637,33 @@ impl<'a> State<'a> {
                     self.word("!");
                 }
 
-                if let Some(ref t) = of_trait {
+                if let Some(t) = of_trait {
                     self.print_trait_ref(t);
                     self.space();
                     self.word_space("for");
                 }
 
-                self.print_type(&self_ty);
+                self.print_type(self_ty);
                 self.print_where_clause(generics);
 
                 self.space();
                 self.bopen();
                 self.print_inner_attributes(attrs);
-                for impl_item in *items {
+                for impl_item in items {
                     self.ann.nested(self, Nested::ImplItem(impl_item.id));
                 }
                 self.bclose(item.span);
             }
-            hir::ItemKind::Trait(is_auto, unsafety, ref generics, ref bounds, trait_items) => {
+            hir::ItemKind::Trait(is_auto, unsafety, generics, bounds, trait_items) => {
                 self.head("");
                 self.print_is_auto(is_auto);
                 self.print_unsafety(unsafety);
                 self.word_nbsp("trait");
                 self.print_ident(item.ident);
-                self.print_generic_params(&generics.params);
+                self.print_generic_params(generics.params);
                 let mut real_bounds = Vec::with_capacity(bounds.len());
-                for b in bounds.iter() {
-                    if let GenericBound::Trait(ref ptr, hir::TraitBoundModifier::Maybe) = *b {
+                for b in bounds {
+                    if let GenericBound::Trait(ptr, hir::TraitBoundModifier::Maybe) = b {
                         self.space();
                         self.word_space("for ?");
                         self.print_trait_ref(&ptr.trait_ref);
@@ -687,14 +680,14 @@ impl<'a> State<'a> {
                 }
                 self.bclose(item.span);
             }
-            hir::ItemKind::TraitAlias(ref generics, ref bounds) => {
+            hir::ItemKind::TraitAlias(generics, bounds) => {
                 self.head("trait");
                 self.print_ident(item.ident);
-                self.print_generic_params(&generics.params);
+                self.print_generic_params(generics.params);
                 let mut real_bounds = Vec::with_capacity(bounds.len());
                 // FIXME(durka) this seems to be some quite outdated syntax
-                for b in bounds.iter() {
-                    if let GenericBound::Trait(ref ptr, hir::TraitBoundModifier::Maybe) = *b {
+                for b in bounds {
+                    if let GenericBound::Trait(ptr, hir::TraitBoundModifier::Maybe) = b {
                         self.space();
                         self.word_space("for ?");
                         self.print_trait_ref(&ptr.trait_ref);
@@ -714,7 +707,7 @@ impl<'a> State<'a> {
     }
 
     pub fn print_trait_ref(&mut self, t: &hir::TraitRef<'_>) {
-        self.print_path(&t.path, false)
+        self.print_path(t.path, false);
     }
 
     fn print_formal_generic_params(&mut self, generic_params: &[hir::GenericParam<'_>]) {
@@ -726,8 +719,8 @@ impl<'a> State<'a> {
     }
 
     fn print_poly_trait_ref(&mut self, t: &hir::PolyTraitRef<'_>) {
-        self.print_formal_generic_params(&t.bound_generic_params);
-        self.print_trait_ref(&t.trait_ref)
+        self.print_formal_generic_params(t.bound_generic_params);
+        self.print_trait_ref(&t.trait_ref);
     }
 
     pub fn print_enum_def(
@@ -739,10 +732,10 @@ impl<'a> State<'a> {
     ) {
         self.head("enum");
         self.print_name(name);
-        self.print_generic_params(&generics.params);
+        self.print_generic_params(generics.params);
         self.print_where_clause(generics);
         self.space();
-        self.print_variants(&enum_definition.variants, span)
+        self.print_variants(enum_definition.variants, span);
     }
 
     pub fn print_variants(&mut self, variants: &[hir::Variant<'_>], span: rustc_span::Span) {
@@ -776,7 +769,7 @@ impl<'a> State<'a> {
         print_finalizer: bool,
     ) {
         self.print_name(name);
-        self.print_generic_params(&generics.params);
+        self.print_generic_params(generics.params);
         match struct_def {
             hir::VariantData::Tuple(..) | hir::VariantData::Unit(..) => {
                 if let hir::VariantData::Tuple(..) = struct_def {
@@ -784,7 +777,7 @@ impl<'a> State<'a> {
                     self.commasep(Inconsistent, struct_def.fields(), |s, field| {
                         s.maybe_print_comment(field.span.lo());
                         s.print_outer_attributes(s.attrs(field.hir_id));
-                        s.print_type(&field.ty)
+                        s.print_type(field.ty);
                     });
                     self.pclose();
                 }
@@ -807,7 +800,7 @@ impl<'a> State<'a> {
                     self.print_outer_attributes(self.attrs(field.hir_id));
                     self.print_ident(field.ident);
                     self.word_nbsp(":");
-                    self.print_type(&field.ty);
+                    self.print_type(field.ty);
                     self.word(",");
                 }
 
@@ -819,7 +812,7 @@ impl<'a> State<'a> {
     pub fn print_variant(&mut self, v: &hir::Variant<'_>) {
         self.head("");
         let generics = hir::Generics::empty();
-        self.print_struct(&v.data, &generics, v.ident.name, v.span, false);
+        self.print_struct(&v.data, generics, v.ident.name, v.span, false);
         if let Some(ref d) = v.disr_expr {
             self.space();
             self.word_space("=");
@@ -834,7 +827,7 @@ impl<'a> State<'a> {
         arg_names: &[Ident],
         body_id: Option<hir::BodyId>,
     ) {
-        self.print_fn(&m.decl, m.header, Some(ident.name), generics, arg_names, body_id)
+        self.print_fn(m.decl, m.header, Some(ident.name), generics, arg_names, body_id);
     }
 
     pub fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) {
@@ -843,28 +836,23 @@ impl<'a> State<'a> {
         self.maybe_print_comment(ti.span.lo());
         self.print_outer_attributes(self.attrs(ti.hir_id()));
         match ti.kind {
-            hir::TraitItemKind::Const(ref ty, default) => {
-                self.print_associated_const(ti.ident, &ty, default);
+            hir::TraitItemKind::Const(ty, default) => {
+                self.print_associated_const(ti.ident, ty, default);
             }
-            hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(ref arg_names)) => {
-                self.print_method_sig(ti.ident, sig, &ti.generics, arg_names, None);
+            hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(arg_names)) => {
+                self.print_method_sig(ti.ident, sig, ti.generics, arg_names, None);
                 self.word(";");
             }
             hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => {
                 self.head("");
-                self.print_method_sig(ti.ident, sig, &ti.generics, &[], Some(body));
+                self.print_method_sig(ti.ident, sig, ti.generics, &[], Some(body));
                 self.nbsp();
                 self.end(); // need to close a box
                 self.end(); // need to close a box
                 self.ann.nested(self, Nested::Body(body));
             }
-            hir::TraitItemKind::Type(ref bounds, ref default) => {
-                self.print_associated_type(
-                    ti.ident,
-                    &ti.generics,
-                    Some(bounds),
-                    default.as_ref().map(|ty| &**ty),
-                );
+            hir::TraitItemKind::Type(bounds, default) => {
+                self.print_associated_type(ti.ident, ti.generics, Some(bounds), default);
             }
         }
         self.ann.post(self, AnnNode::SubItem(ti.hir_id()))
@@ -877,19 +865,19 @@ impl<'a> State<'a> {
         self.print_outer_attributes(self.attrs(ii.hir_id()));
 
         match ii.kind {
-            hir::ImplItemKind::Const(ref ty, expr) => {
-                self.print_associated_const(ii.ident, &ty, Some(expr));
+            hir::ImplItemKind::Const(ty, expr) => {
+                self.print_associated_const(ii.ident, ty, Some(expr));
             }
             hir::ImplItemKind::Fn(ref sig, body) => {
                 self.head("");
-                self.print_method_sig(ii.ident, sig, &ii.generics, &[], Some(body));
+                self.print_method_sig(ii.ident, sig, ii.generics, &[], Some(body));
                 self.nbsp();
                 self.end(); // need to close a box
                 self.end(); // need to close a box
                 self.ann.nested(self, Nested::Body(body));
             }
-            hir::ImplItemKind::TyAlias(ref ty) => {
-                self.print_associated_type(ii.ident, &ii.generics, None, Some(ty));
+            hir::ImplItemKind::TyAlias(ty) => {
+                self.print_associated_type(ii.ident, ii.generics, None, Some(ty));
             }
         }
         self.ann.post(self, AnnNode::SubItem(ii.hir_id()))
@@ -904,10 +892,10 @@ impl<'a> State<'a> {
         decl(self);
         self.end();
 
-        if let Some(ref init) = init {
+        if let Some(init) = init {
             self.nbsp();
             self.word_space("=");
-            self.print_expr(&init);
+            self.print_expr(init);
         }
         self.end()
     }
@@ -915,17 +903,17 @@ impl<'a> State<'a> {
     pub fn print_stmt(&mut self, st: &hir::Stmt<'_>) {
         self.maybe_print_comment(st.span.lo());
         match st.kind {
-            hir::StmtKind::Local(ref loc) => {
-                self.print_local(loc.init, |this| this.print_local_decl(&loc));
+            hir::StmtKind::Local(loc) => {
+                self.print_local(loc.init, |this| this.print_local_decl(loc));
             }
             hir::StmtKind::Item(item) => self.ann.nested(self, Nested::Item(item)),
-            hir::StmtKind::Expr(ref expr) => {
+            hir::StmtKind::Expr(expr) => {
                 self.space_if_not_bol();
-                self.print_expr(&expr);
+                self.print_expr(expr);
             }
-            hir::StmtKind::Semi(ref expr) => {
+            hir::StmtKind::Semi(expr) => {
                 self.space_if_not_bol();
-                self.print_expr(&expr);
+                self.print_expr(expr);
                 self.word(";");
             }
         }
@@ -966,9 +954,9 @@ impl<'a> State<'a> {
         for st in blk.stmts {
             self.print_stmt(st);
         }
-        if let Some(ref expr) = blk.expr {
+        if let Some(expr) = blk.expr {
             self.space_if_not_bol();
-            self.print_expr(&expr);
+            self.print_expr(expr);
             self.maybe_print_trailing_comment(expr.span, Some(blk.span.hi()));
         }
         self.bclose_maybe_open(blk.span, close_box);
@@ -979,21 +967,21 @@ impl<'a> State<'a> {
         if let Some(els_inner) = els {
             match els_inner.kind {
                 // Another `else if` block.
-                hir::ExprKind::If(ref i, ref then, ref e) => {
+                hir::ExprKind::If(i, then, e) => {
                     self.cbox(INDENT_UNIT - 1);
                     self.ibox(0);
                     self.word(" else if ");
-                    self.print_expr_as_cond(&i);
+                    self.print_expr_as_cond(i);
                     self.space();
-                    self.print_expr(&then);
-                    self.print_else(e.as_ref().map(|e| &**e))
+                    self.print_expr(then);
+                    self.print_else(e);
                 }
                 // Final `else` block.
-                hir::ExprKind::Block(ref b, _) => {
+                hir::ExprKind::Block(b, _) => {
                     self.cbox(INDENT_UNIT - 1);
                     self.ibox(0);
                     self.word(" else ");
-                    self.print_block(&b)
+                    self.print_block(b);
                 }
                 // Constraints would be great here!
                 _ => {
@@ -1048,7 +1036,7 @@ impl<'a> State<'a> {
         if needs_par {
             self.popen();
         }
-        if let hir::ExprKind::DropTemps(ref actual_expr) = expr.kind {
+        if let hir::ExprKind::DropTemps(actual_expr) = expr.kind {
             self.print_expr(actual_expr);
         } else {
             self.print_expr(expr);
@@ -1114,7 +1102,7 @@ impl<'a> State<'a> {
         &mut self,
         qpath: &hir::QPath<'_>,
         fields: &[hir::ExprField<'_>],
-        wth: &Option<&hir::Expr<'_>>,
+        wth: Option<&hir::Expr<'_>>,
     ) {
         self.print_qpath(qpath, true);
         self.word("{");
@@ -1127,28 +1115,24 @@ impl<'a> State<'a> {
                     s.print_ident(field.ident);
                     s.word_space(":");
                 }
-                s.print_expr(&field.expr);
+                s.print_expr(field.expr);
                 s.end()
             },
             |f| f.span,
         );
-        match *wth {
-            Some(ref expr) => {
-                self.ibox(INDENT_UNIT);
-                if !fields.is_empty() {
-                    self.word(",");
-                    self.space();
-                }
-                self.word("..");
-                self.print_expr(&expr);
-                self.end();
-            }
-            _ => {
-                if !fields.is_empty() {
-                    self.word(",")
-                }
+        if let Some(expr) = wth {
+            self.ibox(INDENT_UNIT);
+            if !fields.is_empty() {
+                self.word(",");
+                self.space();
             }
+            self.word("..");
+            self.print_expr(expr);
+            self.end();
+        } else if !fields.is_empty() {
+            self.word(",");
         }
+
         self.word("}");
     }
 
@@ -1249,18 +1233,17 @@ impl<'a> State<'a> {
             Options(ast::InlineAsmOptions),
         }
 
-        let mut args =
-            vec![AsmArg::Template(ast::InlineAsmTemplatePiece::to_string(&asm.template))];
+        let mut args = vec![AsmArg::Template(ast::InlineAsmTemplatePiece::to_string(asm.template))];
         args.extend(asm.operands.iter().map(|(o, _)| AsmArg::Operand(o)));
         if !asm.options.is_empty() {
             args.push(AsmArg::Options(asm.options));
         }
 
         self.popen();
-        self.commasep(Consistent, &args, |s, arg| match arg {
-            AsmArg::Template(template) => s.print_string(&template, ast::StrStyle::Cooked),
-            AsmArg::Operand(op) => match op {
-                hir::InlineAsmOperand::In { reg, expr } => {
+        self.commasep(Consistent, &args, |s, arg| match *arg {
+            AsmArg::Template(ref template) => s.print_string(template, ast::StrStyle::Cooked),
+            AsmArg::Operand(op) => match *op {
+                hir::InlineAsmOperand::In { reg, ref expr } => {
                     s.word("in");
                     s.popen();
                     s.word(format!("{}", reg));
@@ -1268,8 +1251,8 @@ impl<'a> State<'a> {
                     s.space();
                     s.print_expr(expr);
                 }
-                hir::InlineAsmOperand::Out { reg, late, expr } => {
-                    s.word(if *late { "lateout" } else { "out" });
+                hir::InlineAsmOperand::Out { reg, late, ref expr } => {
+                    s.word(if late { "lateout" } else { "out" });
                     s.popen();
                     s.word(format!("{}", reg));
                     s.pclose();
@@ -1279,16 +1262,16 @@ impl<'a> State<'a> {
                         None => s.word("_"),
                     }
                 }
-                hir::InlineAsmOperand::InOut { reg, late, expr } => {
-                    s.word(if *late { "inlateout" } else { "inout" });
+                hir::InlineAsmOperand::InOut { reg, late, ref expr } => {
+                    s.word(if late { "inlateout" } else { "inout" });
                     s.popen();
                     s.word(format!("{}", reg));
                     s.pclose();
                     s.space();
                     s.print_expr(expr);
                 }
-                hir::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => {
-                    s.word(if *late { "inlateout" } else { "inout" });
+                hir::InlineAsmOperand::SplitInOut { reg, late, ref in_expr, ref out_expr } => {
+                    s.word(if late { "inlateout" } else { "inout" });
                     s.popen();
                     s.word(format!("{}", reg));
                     s.pclose();
@@ -1301,17 +1284,17 @@ impl<'a> State<'a> {
                         None => s.word("_"),
                     }
                 }
-                hir::InlineAsmOperand::Const { anon_const } => {
+                hir::InlineAsmOperand::Const { ref anon_const } => {
                     s.word("const");
                     s.space();
                     s.print_anon_const(anon_const);
                 }
-                hir::InlineAsmOperand::SymFn { anon_const } => {
+                hir::InlineAsmOperand::SymFn { ref anon_const } => {
                     s.word("sym_fn");
                     s.space();
                     s.print_anon_const(anon_const);
                 }
-                hir::InlineAsmOperand::SymStatic { path, def_id: _ } => {
+                hir::InlineAsmOperand::SymStatic { ref path, def_id: _ } => {
                     s.word("sym_static");
                     s.space();
                     s.print_qpath(path, true);
@@ -1363,57 +1346,57 @@ impl<'a> State<'a> {
         self.ibox(INDENT_UNIT);
         self.ann.pre(self, AnnNode::Expr(expr));
         match expr.kind {
-            hir::ExprKind::Box(ref expr) => {
+            hir::ExprKind::Box(expr) => {
                 self.word_space("box");
                 self.print_expr_maybe_paren(expr, parser::PREC_PREFIX);
             }
-            hir::ExprKind::Array(ref exprs) => {
+            hir::ExprKind::Array(exprs) => {
                 self.print_expr_vec(exprs);
             }
             hir::ExprKind::ConstBlock(ref anon_const) => {
                 self.print_expr_anon_const(anon_const);
             }
-            hir::ExprKind::Repeat(ref element, ref count) => {
-                self.print_expr_repeat(&element, count);
+            hir::ExprKind::Repeat(element, ref count) => {
+                self.print_expr_repeat(element, count);
             }
-            hir::ExprKind::Struct(ref qpath, fields, ref wth) => {
+            hir::ExprKind::Struct(qpath, fields, wth) => {
                 self.print_expr_struct(qpath, fields, wth);
             }
-            hir::ExprKind::Tup(ref exprs) => {
+            hir::ExprKind::Tup(exprs) => {
                 self.print_expr_tup(exprs);
             }
-            hir::ExprKind::Call(ref func, ref args) => {
-                self.print_expr_call(&func, args);
+            hir::ExprKind::Call(func, args) => {
+                self.print_expr_call(func, args);
             }
-            hir::ExprKind::MethodCall(ref segment, ref args, _) => {
+            hir::ExprKind::MethodCall(segment, args, _) => {
                 self.print_expr_method_call(segment, args);
             }
-            hir::ExprKind::Binary(op, ref lhs, ref rhs) => {
-                self.print_expr_binary(op, &lhs, &rhs);
+            hir::ExprKind::Binary(op, lhs, rhs) => {
+                self.print_expr_binary(op, lhs, rhs);
             }
-            hir::ExprKind::Unary(op, ref expr) => {
-                self.print_expr_unary(op, &expr);
+            hir::ExprKind::Unary(op, expr) => {
+                self.print_expr_unary(op, expr);
             }
-            hir::ExprKind::AddrOf(k, m, ref expr) => {
-                self.print_expr_addr_of(k, m, &expr);
+            hir::ExprKind::AddrOf(k, m, expr) => {
+                self.print_expr_addr_of(k, m, expr);
             }
             hir::ExprKind::Lit(ref lit) => {
-                self.print_literal(&lit);
+                self.print_literal(lit);
             }
-            hir::ExprKind::Cast(ref expr, ref ty) => {
+            hir::ExprKind::Cast(expr, ty) => {
                 let prec = AssocOp::As.precedence() as i8;
-                self.print_expr_maybe_paren(&expr, prec);
+                self.print_expr_maybe_paren(expr, prec);
                 self.space();
                 self.word_space("as");
-                self.print_type(&ty);
+                self.print_type(ty);
             }
-            hir::ExprKind::Type(ref expr, ref ty) => {
+            hir::ExprKind::Type(expr, ty) => {
                 let prec = AssocOp::Colon.precedence() as i8;
-                self.print_expr_maybe_paren(&expr, prec);
+                self.print_expr_maybe_paren(expr, prec);
                 self.word_space(":");
-                self.print_type(&ty);
+                self.print_type(ty);
             }
-            hir::ExprKind::DropTemps(ref init) => {
+            hir::ExprKind::DropTemps(init) => {
                 // Print `{`:
                 self.cbox(INDENT_UNIT);
                 self.ibox(0);
@@ -1431,25 +1414,25 @@ impl<'a> State<'a> {
                 // Print `}`:
                 self.bclose_maybe_open(expr.span, true);
             }
-            hir::ExprKind::Let(hir::Let { pat, ty, init, .. }) => {
-                self.print_let(pat, *ty, init);
+            hir::ExprKind::Let(&hir::Let { pat, ty, init, .. }) => {
+                self.print_let(pat, ty, init);
             }
-            hir::ExprKind::If(ref test, ref blk, ref elseopt) => {
-                self.print_if(&test, &blk, elseopt.as_ref().map(|e| &**e));
+            hir::ExprKind::If(test, blk, elseopt) => {
+                self.print_if(test, blk, elseopt);
             }
-            hir::ExprKind::Loop(ref blk, opt_label, _, _) => {
+            hir::ExprKind::Loop(blk, opt_label, _, _) => {
                 if let Some(label) = opt_label {
                     self.print_ident(label.ident);
                     self.word_space(":");
                 }
                 self.head("loop");
-                self.print_block(&blk);
+                self.print_block(blk);
             }
-            hir::ExprKind::Match(ref expr, arms, _) => {
+            hir::ExprKind::Match(expr, arms, _) => {
                 self.cbox(INDENT_UNIT);
                 self.ibox(INDENT_UNIT);
                 self.word_nbsp("match");
-                self.print_expr_as_cond(&expr);
+                self.print_expr_as_cond(expr);
                 self.space();
                 self.bopen();
                 for arm in arms {
@@ -1460,7 +1443,7 @@ impl<'a> State<'a> {
             hir::ExprKind::Closure {
                 capture_clause,
                 bound_generic_params,
-                ref fn_decl,
+                fn_decl,
                 body,
                 fn_decl_span: _,
                 movability: _,
@@ -1468,7 +1451,7 @@ impl<'a> State<'a> {
                 self.print_formal_generic_params(bound_generic_params);
                 self.print_capture_clause(capture_clause);
 
-                self.print_closure_params(&fn_decl, body);
+                self.print_closure_params(fn_decl, body);
                 self.space();
 
                 // This is a bare expression.
@@ -1480,7 +1463,7 @@ impl<'a> State<'a> {
                 // empty box to satisfy the close.
                 self.ibox(0);
             }
-            hir::ExprKind::Block(ref blk, opt_label) => {
+            hir::ExprKind::Block(blk, opt_label) => {
                 if let Some(label) = opt_label {
                     self.print_ident(label.ident);
                     self.word_space(":");
@@ -1489,42 +1472,42 @@ impl<'a> State<'a> {
                 self.cbox(INDENT_UNIT);
                 // head-box, will be closed by print-block after `{`
                 self.ibox(0);
-                self.print_block(&blk);
+                self.print_block(blk);
             }
-            hir::ExprKind::Assign(ref lhs, ref rhs, _) => {
+            hir::ExprKind::Assign(lhs, rhs, _) => {
                 let prec = AssocOp::Assign.precedence() as i8;
-                self.print_expr_maybe_paren(&lhs, prec + 1);
+                self.print_expr_maybe_paren(lhs, prec + 1);
                 self.space();
                 self.word_space("=");
-                self.print_expr_maybe_paren(&rhs, prec);
+                self.print_expr_maybe_paren(rhs, prec);
             }
-            hir::ExprKind::AssignOp(op, ref lhs, ref rhs) => {
+            hir::ExprKind::AssignOp(op, lhs, rhs) => {
                 let prec = AssocOp::Assign.precedence() as i8;
-                self.print_expr_maybe_paren(&lhs, prec + 1);
+                self.print_expr_maybe_paren(lhs, prec + 1);
                 self.space();
                 self.word(op.node.as_str());
                 self.word_space("=");
-                self.print_expr_maybe_paren(&rhs, prec);
+                self.print_expr_maybe_paren(rhs, prec);
             }
-            hir::ExprKind::Field(ref expr, ident) => {
+            hir::ExprKind::Field(expr, ident) => {
                 self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX);
                 self.word(".");
                 self.print_ident(ident);
             }
-            hir::ExprKind::Index(ref expr, ref index) => {
-                self.print_expr_maybe_paren(&expr, parser::PREC_POSTFIX);
+            hir::ExprKind::Index(expr, index) => {
+                self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX);
                 self.word("[");
-                self.print_expr(&index);
+                self.print_expr(index);
                 self.word("]");
             }
             hir::ExprKind::Path(ref qpath) => self.print_qpath(qpath, true),
-            hir::ExprKind::Break(destination, ref opt_expr) => {
+            hir::ExprKind::Break(destination, opt_expr) => {
                 self.word("break");
                 if let Some(label) = destination.label {
                     self.space();
                     self.print_ident(label.ident);
                 }
-                if let Some(ref expr) = *opt_expr {
+                if let Some(expr) = opt_expr {
                     self.space();
                     self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
                 }
@@ -1536,20 +1519,20 @@ impl<'a> State<'a> {
                     self.print_ident(label.ident);
                 }
             }
-            hir::ExprKind::Ret(ref result) => {
+            hir::ExprKind::Ret(result) => {
                 self.word("return");
-                if let Some(ref expr) = *result {
+                if let Some(expr) = result {
                     self.word(" ");
-                    self.print_expr_maybe_paren(&expr, parser::PREC_JUMP);
+                    self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
                 }
             }
-            hir::ExprKind::InlineAsm(ref asm) => {
+            hir::ExprKind::InlineAsm(asm) => {
                 self.word("asm!");
                 self.print_inline_asm(asm);
             }
-            hir::ExprKind::Yield(ref expr, _) => {
+            hir::ExprKind::Yield(expr, _) => {
                 self.word_space("yield");
-                self.print_expr_maybe_paren(&expr, parser::PREC_JUMP);
+                self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
             }
             hir::ExprKind::Err => {
                 self.popen();
@@ -1562,10 +1545,10 @@ impl<'a> State<'a> {
     }
 
     pub fn print_local_decl(&mut self, loc: &hir::Local<'_>) {
-        self.print_pat(&loc.pat);
-        if let Some(ref ty) = loc.ty {
+        self.print_pat(loc.pat);
+        if let Some(ty) = loc.ty {
             self.word_space(":");
-            self.print_type(&ty);
+            self.print_type(ty);
         }
     }
 
@@ -1596,8 +1579,8 @@ impl<'a> State<'a> {
 
     pub fn print_qpath(&mut self, qpath: &hir::QPath<'_>, colons_before_params: bool) {
         match *qpath {
-            hir::QPath::Resolved(None, ref path) => self.print_path(path, colons_before_params),
-            hir::QPath::Resolved(Some(ref qself), ref path) => {
+            hir::QPath::Resolved(None, path) => self.print_path(path, colons_before_params),
+            hir::QPath::Resolved(Some(qself), path) => {
                 self.word("<");
                 self.print_type(qself);
                 self.space();
@@ -1627,11 +1610,11 @@ impl<'a> State<'a> {
                     colons_before_params,
                 )
             }
-            hir::QPath::TypeRelative(ref qself, ref item_segment) => {
+            hir::QPath::TypeRelative(qself, item_segment) => {
                 // If we've got a compound-qualified-path, let's push an additional pair of angle
                 // brackets, so that we pretty-print `<<A::B>::C>` as `<A::B>::C`, instead of just
                 // `A::B::C` (since the latter could be ambiguous to the user)
-                if let hir::TyKind::Path(hir::QPath::Resolved(None, _)) = &qself.kind {
+                if let hir::TyKind::Path(hir::QPath::Resolved(None, _)) = qself.kind {
                     self.print_type(qself);
                 } else {
                     self.word("<");
@@ -1663,7 +1646,7 @@ impl<'a> State<'a> {
     ) {
         if generic_args.parenthesized {
             self.word("(");
-            self.commasep(Inconsistent, generic_args.inputs(), |s, ty| s.print_type(&ty));
+            self.commasep(Inconsistent, generic_args.inputs(), |s, ty| s.print_type(ty));
             self.word(")");
 
             self.space_if_not_bol();
@@ -1694,7 +1677,7 @@ impl<'a> State<'a> {
                 start_or_comma(self);
                 self.commasep(
                     Inconsistent,
-                    &generic_args.args,
+                    generic_args.args,
                     |s, generic_arg| match generic_arg {
                         GenericArg::Lifetime(lt) if !elide_lifetimes => s.print_lifetime(lt),
                         GenericArg::Lifetime(_) => {}
@@ -1712,7 +1695,7 @@ impl<'a> State<'a> {
                 self.word("..");
             }
 
-            for binding in generic_args.bindings.iter() {
+            for binding in generic_args.bindings {
                 start_or_comma(self);
                 self.print_type_binding(binding);
             }
@@ -1731,7 +1714,7 @@ impl<'a> State<'a> {
             hir::TypeBindingKind::Equality { ref term } => {
                 self.word_space("=");
                 match term {
-                    Term::Ty(ref ty) => self.print_type(ty),
+                    Term::Ty(ty) => self.print_type(ty),
                     Term::Const(ref c) => self.print_anon_const(c),
                 }
             }
@@ -1748,7 +1731,7 @@ impl<'a> State<'a> {
         // is that it doesn't matter
         match pat.kind {
             PatKind::Wild => self.word("_"),
-            PatKind::Binding(binding_mode, _, ident, ref sub) => {
+            PatKind::Binding(binding_mode, _, ident, sub) => {
                 match binding_mode {
                     hir::BindingAnnotation::Ref => {
                         self.word_nbsp("ref");
@@ -1764,33 +1747,33 @@ impl<'a> State<'a> {
                     }
                 }
                 self.print_ident(ident);
-                if let Some(ref p) = *sub {
+                if let Some(p) = sub {
                     self.word("@");
-                    self.print_pat(&p);
+                    self.print_pat(p);
                 }
             }
-            PatKind::TupleStruct(ref qpath, ref elts, ddpos) => {
+            PatKind::TupleStruct(ref qpath, elts, ddpos) => {
                 self.print_qpath(qpath, true);
                 self.popen();
                 if let Some(ddpos) = ddpos {
-                    self.commasep(Inconsistent, &elts[..ddpos], |s, p| s.print_pat(&p));
+                    self.commasep(Inconsistent, &elts[..ddpos], |s, p| s.print_pat(p));
                     if ddpos != 0 {
                         self.word_space(",");
                     }
                     self.word("..");
                     if ddpos != elts.len() {
                         self.word(",");
-                        self.commasep(Inconsistent, &elts[ddpos..], |s, p| s.print_pat(&p));
+                        self.commasep(Inconsistent, &elts[ddpos..], |s, p| s.print_pat(p));
                     }
                 } else {
-                    self.commasep(Inconsistent, &elts, |s, p| s.print_pat(&p));
+                    self.commasep(Inconsistent, elts, |s, p| s.print_pat(p));
                 }
                 self.pclose();
             }
             PatKind::Path(ref qpath) => {
                 self.print_qpath(qpath, true);
             }
-            PatKind::Struct(ref qpath, ref fields, etc) => {
+            PatKind::Struct(ref qpath, fields, etc) => {
                 self.print_qpath(qpath, true);
                 self.nbsp();
                 self.word("{");
@@ -1800,14 +1783,14 @@ impl<'a> State<'a> {
                 }
                 self.commasep_cmnt(
                     Consistent,
-                    &fields,
+                    fields,
                     |s, f| {
                         s.cbox(INDENT_UNIT);
                         if !f.is_shorthand {
                             s.print_ident(f.ident);
                             s.word_nbsp(":");
                         }
-                        s.print_pat(&f.pat);
+                        s.print_pat(f.pat);
                         s.end()
                     },
                     |f| f.pat.span,
@@ -1823,58 +1806,58 @@ impl<'a> State<'a> {
                 }
                 self.word("}");
             }
-            PatKind::Or(ref pats) => {
-                self.strsep("|", true, Inconsistent, &pats, |s, p| s.print_pat(&p));
+            PatKind::Or(pats) => {
+                self.strsep("|", true, Inconsistent, pats, |s, p| s.print_pat(p));
             }
-            PatKind::Tuple(ref elts, ddpos) => {
+            PatKind::Tuple(elts, ddpos) => {
                 self.popen();
                 if let Some(ddpos) = ddpos {
-                    self.commasep(Inconsistent, &elts[..ddpos], |s, p| s.print_pat(&p));
+                    self.commasep(Inconsistent, &elts[..ddpos], |s, p| s.print_pat(p));
                     if ddpos != 0 {
                         self.word_space(",");
                     }
                     self.word("..");
                     if ddpos != elts.len() {
                         self.word(",");
-                        self.commasep(Inconsistent, &elts[ddpos..], |s, p| s.print_pat(&p));
+                        self.commasep(Inconsistent, &elts[ddpos..], |s, p| s.print_pat(p));
                     }
                 } else {
-                    self.commasep(Inconsistent, &elts[..], |s, p| s.print_pat(&p));
+                    self.commasep(Inconsistent, elts, |s, p| s.print_pat(p));
                     if elts.len() == 1 {
                         self.word(",");
                     }
                 }
                 self.pclose();
             }
-            PatKind::Box(ref inner) => {
+            PatKind::Box(inner) => {
                 let is_range_inner = matches!(inner.kind, PatKind::Range(..));
                 self.word("box ");
                 if is_range_inner {
                     self.popen();
                 }
-                self.print_pat(&inner);
+                self.print_pat(inner);
                 if is_range_inner {
                     self.pclose();
                 }
             }
-            PatKind::Ref(ref inner, mutbl) => {
+            PatKind::Ref(inner, mutbl) => {
                 let is_range_inner = matches!(inner.kind, PatKind::Range(..));
                 self.word("&");
                 self.word(mutbl.prefix_str());
                 if is_range_inner {
                     self.popen();
                 }
-                self.print_pat(&inner);
+                self.print_pat(inner);
                 if is_range_inner {
                     self.pclose();
                 }
             }
-            PatKind::Lit(ref e) => self.print_expr(&e),
-            PatKind::Range(ref begin, ref end, ref end_kind) => {
+            PatKind::Lit(e) => self.print_expr(e),
+            PatKind::Range(begin, end, end_kind) => {
                 if let Some(expr) = begin {
                     self.print_expr(expr);
                 }
-                match *end_kind {
+                match end_kind {
                     RangeEnd::Included => self.word("..."),
                     RangeEnd::Excluded => self.word(".."),
                 }
@@ -1882,24 +1865,24 @@ impl<'a> State<'a> {
                     self.print_expr(expr);
                 }
             }
-            PatKind::Slice(ref before, ref slice, ref after) => {
+            PatKind::Slice(before, slice, after) => {
                 self.word("[");
-                self.commasep(Inconsistent, &before, |s, p| s.print_pat(&p));
-                if let Some(ref p) = *slice {
+                self.commasep(Inconsistent, before, |s, p| s.print_pat(p));
+                if let Some(p) = slice {
                     if !before.is_empty() {
                         self.word_space(",");
                     }
                     if let PatKind::Wild = p.kind {
                         // Print nothing.
                     } else {
-                        self.print_pat(&p);
+                        self.print_pat(p);
                     }
                     self.word("..");
                     if !after.is_empty() {
                         self.word_space(",");
                     }
                 }
-                self.commasep(Inconsistent, &after, |s, p| s.print_pat(&p));
+                self.commasep(Inconsistent, after, |s, p| s.print_pat(p));
                 self.word("]");
             }
         }
@@ -1908,7 +1891,7 @@ impl<'a> State<'a> {
 
     pub fn print_param(&mut self, arg: &hir::Param<'_>) {
         self.print_outer_attributes(self.attrs(arg.hir_id));
-        self.print_pat(&arg.pat);
+        self.print_pat(arg.pat);
     }
 
     pub fn print_arm(&mut self, arm: &hir::Arm<'_>) {
@@ -1920,32 +1903,32 @@ impl<'a> State<'a> {
         self.cbox(INDENT_UNIT);
         self.ann.pre(self, AnnNode::Arm(arm));
         self.ibox(0);
-        self.print_outer_attributes(&self.attrs(arm.hir_id));
-        self.print_pat(&arm.pat);
+        self.print_outer_attributes(self.attrs(arm.hir_id));
+        self.print_pat(arm.pat);
         self.space();
         if let Some(ref g) = arm.guard {
-            match g {
+            match *g {
                 hir::Guard::If(e) => {
                     self.word_space("if");
-                    self.print_expr(&e);
+                    self.print_expr(e);
                     self.space();
                 }
-                hir::Guard::IfLet(hir::Let { pat, ty, init, .. }) => {
+                hir::Guard::IfLet(&hir::Let { pat, ty, init, .. }) => {
                     self.word_nbsp("if");
-                    self.print_let(pat, *ty, init);
+                    self.print_let(pat, ty, init);
                 }
             }
         }
         self.word_space("=>");
 
         match arm.body.kind {
-            hir::ExprKind::Block(ref blk, opt_label) => {
+            hir::ExprKind::Block(blk, opt_label) => {
                 if let Some(label) = opt_label {
                     self.print_ident(label.ident);
                     self.word_space(":");
                 }
                 // the block will close the pattern's ibox
-                self.print_block_unclosed(&blk);
+                self.print_block_unclosed(blk);
 
                 // If it is a user-provided unsafe block, print a comma after it
                 if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = blk.rules
@@ -1955,7 +1938,7 @@ impl<'a> State<'a> {
             }
             _ => {
                 self.end(); // close the ibox for the pattern
-                self.print_expr(&arm.body);
+                self.print_expr(arm.body);
                 self.word(",");
             }
         }
@@ -1978,13 +1961,13 @@ impl<'a> State<'a> {
             self.nbsp();
             self.print_name(name);
         }
-        self.print_generic_params(&generics.params);
+        self.print_generic_params(generics.params);
 
         self.popen();
         let mut i = 0;
         // Make sure we aren't supplied *both* `arg_names` and `body_id`.
         assert!(arg_names.is_empty() || body_id.is_none());
-        self.commasep(Inconsistent, &decl.inputs, |s, ty| {
+        self.commasep(Inconsistent, decl.inputs, |s, ty| {
             s.ibox(INDENT_UNIT);
             if let Some(arg_name) = arg_names.get(i) {
                 s.word(arg_name.to_string());
@@ -2011,7 +1994,7 @@ impl<'a> State<'a> {
     fn print_closure_params(&mut self, decl: &hir::FnDecl<'_>, body_id: hir::BodyId) {
         self.word("|");
         let mut i = 0;
-        self.commasep(Inconsistent, &decl.inputs, |s, ty| {
+        self.commasep(Inconsistent, decl.inputs, |s, ty| {
             s.ibox(INDENT_UNIT);
 
             s.ann.nested(s, Nested::BodyParamPat(body_id, i));
@@ -2035,8 +2018,8 @@ impl<'a> State<'a> {
         self.space_if_not_bol();
         self.word_space("->");
         match decl.output {
-            hir::FnRetTy::Return(ref ty) => {
-                self.print_type(&ty);
+            hir::FnRetTy::Return(ty) => {
+                self.print_type(ty);
                 self.maybe_print_comment(ty.span.lo());
             }
             hir::FnRetTy::DefaultReturn(..) => unreachable!(),
@@ -2107,20 +2090,20 @@ impl<'a> State<'a> {
 
         match param.kind {
             GenericParamKind::Lifetime { .. } => {}
-            GenericParamKind::Type { ref default, .. } => {
+            GenericParamKind::Type { default, .. } => {
                 if let Some(default) = default {
                     self.space();
                     self.word_space("=");
-                    self.print_type(&default)
+                    self.print_type(default);
                 }
             }
-            GenericParamKind::Const { ref ty, ref default } => {
+            GenericParamKind::Const { ty, ref default } => {
                 self.word_space(":");
                 self.print_type(ty);
-                if let Some(ref default) = default {
+                if let Some(default) = default {
                     self.space();
                     self.word_space("=");
-                    self.print_anon_const(&default)
+                    self.print_anon_const(default);
                 }
             }
         }
@@ -2143,7 +2126,7 @@ impl<'a> State<'a> {
                 self.word_space(",");
             }
 
-            match predicate {
+            match *predicate {
                 hir::WherePredicate::BoundPredicate(hir::WhereBoundPredicate {
                     bound_generic_params,
                     bounded_ty,
@@ -2151,11 +2134,11 @@ impl<'a> State<'a> {
                     ..
                 }) => {
                     self.print_formal_generic_params(bound_generic_params);
-                    self.print_type(&bounded_ty);
-                    self.print_bounds(":", *bounds);
+                    self.print_type(bounded_ty);
+                    self.print_bounds(":", bounds);
                 }
                 hir::WherePredicate::RegionPredicate(hir::WhereRegionPredicate {
-                    lifetime,
+                    ref lifetime,
                     bounds,
                     ..
                 }) => {
@@ -2200,7 +2183,7 @@ impl<'a> State<'a> {
 
     pub fn print_mt(&mut self, mt: &hir::MutTy<'_>, print_const: bool) {
         self.print_mutability(mt.mutbl, print_const);
-        self.print_type(&mt.ty)
+        self.print_type(mt.ty);
     }
 
     pub fn print_fn_output(&mut self, decl: &hir::FnDecl<'_>) {
@@ -2213,11 +2196,11 @@ impl<'a> State<'a> {
         self.word_space("->");
         match decl.output {
             hir::FnRetTy::DefaultReturn(..) => unreachable!(),
-            hir::FnRetTy::Return(ref ty) => self.print_type(&ty),
+            hir::FnRetTy::Return(ty) => self.print_type(ty),
         }
         self.end();
 
-        if let hir::FnRetTy::Return(ref output) = decl.output {
+        if let hir::FnRetTy::Return(output) = decl.output {
             self.maybe_print_comment(output.span.lo());
         }
     }
@@ -2243,7 +2226,7 @@ impl<'a> State<'a> {
                 asyncness: hir::IsAsync::NotAsync,
             },
             name,
-            &generics,
+            generics,
             arg_names,
             None,
         );
@@ -2312,7 +2295,7 @@ fn stmt_ends_with_semi(stmt: &hir::StmtKind<'_>) -> bool {
     match *stmt {
         hir::StmtKind::Local(_) => true,
         hir::StmtKind::Item(_) => false,
-        hir::StmtKind::Expr(ref e) => expr_requires_semi_to_be_stmt(&e),
+        hir::StmtKind::Expr(e) => expr_requires_semi_to_be_stmt(e),
         hir::StmtKind::Semi(..) => false,
     }
 }
@@ -2351,22 +2334,22 @@ fn contains_exterior_struct_lit(value: &hir::Expr<'_>) -> bool {
     match value.kind {
         hir::ExprKind::Struct(..) => true,
 
-        hir::ExprKind::Assign(ref lhs, ref rhs, _)
-        | hir::ExprKind::AssignOp(_, ref lhs, ref rhs)
-        | hir::ExprKind::Binary(_, ref lhs, ref rhs) => {
+        hir::ExprKind::Assign(lhs, rhs, _)
+        | hir::ExprKind::AssignOp(_, lhs, rhs)
+        | hir::ExprKind::Binary(_, lhs, rhs) => {
             // `X { y: 1 } + X { y: 2 }`
-            contains_exterior_struct_lit(&lhs) || contains_exterior_struct_lit(&rhs)
+            contains_exterior_struct_lit(lhs) || contains_exterior_struct_lit(rhs)
         }
-        hir::ExprKind::Unary(_, ref x)
-        | hir::ExprKind::Cast(ref x, _)
-        | hir::ExprKind::Type(ref x, _)
-        | hir::ExprKind::Field(ref x, _)
-        | hir::ExprKind::Index(ref x, _) => {
+        hir::ExprKind::Unary(_, x)
+        | hir::ExprKind::Cast(x, _)
+        | hir::ExprKind::Type(x, _)
+        | hir::ExprKind::Field(x, _)
+        | hir::ExprKind::Index(x, _) => {
             // `&X { y: 1 }, X { y: 1 }.y`
-            contains_exterior_struct_lit(&x)
+            contains_exterior_struct_lit(x)
         }
 
-        hir::ExprKind::MethodCall(.., ref exprs, _) => {
+        hir::ExprKind::MethodCall(.., exprs, _) => {
             // `X { y: 1 }.bar(...)`
             contains_exterior_struct_lit(&exprs[0])
         }
diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs
index 8938ed78a94..7120d5ad934 100644
--- a/compiler/rustc_infer/src/infer/canonical/query_response.rs
+++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs
@@ -128,7 +128,7 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
         let region_constraints = self.with_region_constraints(|region_constraints| {
             make_query_region_constraints(
                 tcx,
-                region_obligations.iter().map(|(_, r_o)| (r_o.sup_type, r_o.sub_region)),
+                region_obligations.iter().map(|r_o| (r_o.sup_type, r_o.sub_region)),
                 region_constraints,
             )
         });
diff --git a/compiler/rustc_infer/src/infer/combine.rs b/compiler/rustc_infer/src/infer/combine.rs
index 67dcb6e708b..8bf1de34a9b 100644
--- a/compiler/rustc_infer/src/infer/combine.rs
+++ b/compiler/rustc_infer/src/infer/combine.rs
@@ -37,7 +37,7 @@ use rustc_middle::traits::ObligationCause;
 use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation};
 use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::{self, InferConst, ToPredicate, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, InferConst, ToPredicate, Ty, TyCtxt, TypeVisitable};
 use rustc_middle::ty::{IntType, UintType};
 use rustc_span::{Span, DUMMY_SP};
 
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index e319f17b0e6..05556f7d0f9 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -68,7 +68,7 @@ use rustc_middle::dep_graph::DepContext;
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{
     self, error::TypeError, Binder, List, Region, Subst, Ty, TyCtxt, TypeFoldable,
-    TypeSuperFoldable,
+    TypeSuperVisitable, TypeVisitable,
 };
 use rustc_span::{sym, symbol::kw, BytePos, DesugaringKind, Pos, Span};
 use rustc_target::spec::abi;
@@ -148,12 +148,10 @@ fn msg_span_from_early_bound_and_free_regions<'tcx>(
     tcx: TyCtxt<'tcx>,
     region: ty::Region<'tcx>,
 ) -> (String, Span) {
-    let sm = tcx.sess.source_map();
-
     let scope = region.free_region_binding_scope(tcx).expect_local();
     match *region {
         ty::ReEarlyBound(ref br) => {
-            let mut sp = sm.guess_head_span(tcx.def_span(scope));
+            let mut sp = tcx.def_span(scope);
             if let Some(param) =
                 tcx.hir().get_generics(scope).and_then(|generics| generics.get_named(br.name))
             {
@@ -174,7 +172,7 @@ fn msg_span_from_early_bound_and_free_regions<'tcx>(
             } else {
                 match fr.bound_region {
                     ty::BoundRegionKind::BrNamed(_, name) => {
-                        let mut sp = sm.guess_head_span(tcx.def_span(scope));
+                        let mut sp = tcx.def_span(scope);
                         if let Some(param) =
                             tcx.hir().get_generics(scope).and_then(|generics| generics.get_named(name))
                         {
@@ -193,7 +191,7 @@ fn msg_span_from_early_bound_and_free_regions<'tcx>(
                     ),
                     _ => (
                         format!("the lifetime `{}` as defined here", region),
-                        sm.guess_head_span(tcx.def_span(scope)),
+                        tcx.def_span(scope),
                     ),
                 }
             }
@@ -1540,7 +1538,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
             }
         }
 
-        impl<'tcx> ty::fold::TypeVisitor<'tcx> for OpaqueTypesVisitor<'tcx> {
+        impl<'tcx> ty::visit::TypeVisitor<'tcx> for OpaqueTypesVisitor<'tcx> {
             fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
                 if let Some((kind, def_id)) = TyCategory::from_ty(self.tcx, t) {
                     let span = self.tcx.def_span(def_id);
diff --git a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
index 07dcf3876c8..4d29fc46946 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
@@ -315,8 +315,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
         body_id: Option<hir::BodyId>,
         failure_span: Span,
         arg: GenericArg<'tcx>,
-        // FIXME(#94483): Either use this or remove it.
-        _impl_candidates: Vec<ty::TraitRef<'tcx>>,
         error_code: TypeAnnotationNeeded,
         should_label_span: bool,
     ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
@@ -534,6 +532,23 @@ enum InferSourceKind<'tcx> {
     },
 }
 
+impl<'tcx> InferSource<'tcx> {
+    fn from_expansion(&self) -> bool {
+        let source_from_expansion = match self.kind {
+            InferSourceKind::LetBinding { insert_span, .. }
+            | InferSourceKind::ClosureArg { insert_span, .. }
+            | InferSourceKind::GenericArg { insert_span, .. } => insert_span.from_expansion(),
+            InferSourceKind::FullyQualifiedMethodCall { receiver, .. } => {
+                receiver.span.from_expansion()
+            }
+            InferSourceKind::ClosureReturn { data, should_wrap_expr, .. } => {
+                data.span().from_expansion() || should_wrap_expr.map_or(false, Span::from_expansion)
+            }
+        };
+        source_from_expansion || self.span.from_expansion()
+    }
+}
+
 impl<'tcx> InferSourceKind<'tcx> {
     fn ty_msg(&self, infcx: &InferCtxt<'_, 'tcx>) -> String {
         match *self {
@@ -604,43 +619,65 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
     /// Sources with a small cost are prefer and should result
     /// in a clearer and idiomatic suggestion.
     fn source_cost(&self, source: &InferSource<'tcx>) -> usize {
-        let tcx = self.infcx.tcx;
-
-        fn arg_cost<'tcx>(arg: GenericArg<'tcx>) -> usize {
-            match arg.unpack() {
-                GenericArgKind::Lifetime(_) => 0, // erased
-                GenericArgKind::Type(ty) => ty_cost(ty),
-                GenericArgKind::Const(_) => 3, // some non-zero value
-            }
+        #[derive(Clone, Copy)]
+        struct CostCtxt<'tcx> {
+            tcx: TyCtxt<'tcx>,
         }
-        fn ty_cost<'tcx>(ty: Ty<'tcx>) -> usize {
-            match ty.kind() {
-                ty::Closure(..) => 100,
-                ty::FnDef(..) => 20,
-                ty::FnPtr(..) => 10,
-                ty::Infer(..) => 0,
-                _ => 1,
+        impl<'tcx> CostCtxt<'tcx> {
+            fn arg_cost(self, arg: GenericArg<'tcx>) -> usize {
+                match arg.unpack() {
+                    GenericArgKind::Lifetime(_) => 0, // erased
+                    GenericArgKind::Type(ty) => self.ty_cost(ty),
+                    GenericArgKind::Const(_) => 3, // some non-zero value
+                }
+            }
+            fn ty_cost(self, ty: Ty<'tcx>) -> usize {
+                match *ty.kind() {
+                    ty::Closure(..) => 1000,
+                    ty::FnDef(..) => 150,
+                    ty::FnPtr(..) => 30,
+                    ty::Adt(def, substs) => {
+                        5 + self
+                            .tcx
+                            .generics_of(def.did())
+                            .own_substs_no_defaults(self.tcx, substs)
+                            .iter()
+                            .map(|&arg| self.arg_cost(arg))
+                            .sum::<usize>()
+                    }
+                    ty::Tuple(args) => 5 + args.iter().map(|arg| self.ty_cost(arg)).sum::<usize>(),
+                    ty::Ref(_, ty, _) => 2 + self.ty_cost(ty),
+                    ty::Infer(..) => 0,
+                    _ => 1,
+                }
             }
         }
 
         // The sources are listed in order of preference here.
-        match source.kind {
-            InferSourceKind::LetBinding { ty, .. } => ty_cost(ty),
-            InferSourceKind::ClosureArg { ty, .. } => 5 + ty_cost(ty),
+        let tcx = self.infcx.tcx;
+        let ctx = CostCtxt { tcx };
+        let base_cost = match source.kind {
+            InferSourceKind::LetBinding { ty, .. } => ctx.ty_cost(ty),
+            InferSourceKind::ClosureArg { ty, .. } => ctx.ty_cost(ty),
             InferSourceKind::GenericArg { def_id, generic_args, .. } => {
                 let variant_cost = match tcx.def_kind(def_id) {
-                    DefKind::Variant | DefKind::Ctor(CtorOf::Variant, _) => 15, // `None::<u32>` and friends are ugly.
-                    _ => 12,
+                    // `None::<u32>` and friends are ugly.
+                    DefKind::Variant | DefKind::Ctor(CtorOf::Variant, _) => 15,
+                    _ => 10,
                 };
-                variant_cost + generic_args.iter().map(|&arg| arg_cost(arg)).sum::<usize>()
+                variant_cost + generic_args.iter().map(|&arg| ctx.arg_cost(arg)).sum::<usize>()
             }
             InferSourceKind::FullyQualifiedMethodCall { substs, .. } => {
-                20 + substs.iter().map(|arg| arg_cost(arg)).sum::<usize>()
+                20 + substs.iter().map(|arg| ctx.arg_cost(arg)).sum::<usize>()
             }
             InferSourceKind::ClosureReturn { ty, should_wrap_expr, .. } => {
-                30 + ty_cost(ty) + if should_wrap_expr.is_some() { 10 } else { 0 }
+                30 + ctx.ty_cost(ty) + if should_wrap_expr.is_some() { 10 } else { 0 }
             }
-        }
+        };
+
+        let suggestion_may_apply = if source.from_expansion() { 10000 } else { 0 };
+
+        base_cost + suggestion_may_apply
     }
 
     /// Uses `fn source_cost` to determine whether this inference source is preferable to
@@ -648,6 +685,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
     #[instrument(level = "debug", skip(self))]
     fn update_infer_source(&mut self, new_source: InferSource<'tcx>) {
         let cost = self.source_cost(&new_source) + self.attempt;
+        debug!(?cost);
         self.attempt += 1;
         if cost < self.infer_source_cost {
             self.infer_source_cost = cost;
@@ -655,6 +693,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
         }
     }
 
+    fn node_substs_opt(&self, hir_id: HirId) -> Option<SubstsRef<'tcx>> {
+        let substs = self.typeck_results.node_substs_opt(hir_id);
+        self.infcx.resolve_vars_if_possible(substs)
+    }
+
     fn opt_node_type(&self, hir_id: HirId) -> Option<Ty<'tcx>> {
         let ty = self.typeck_results.node_type_opt(hir_id);
         self.infcx.resolve_vars_if_possible(ty)
@@ -737,7 +780,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
         let tcx = self.infcx.tcx;
         match expr.kind {
             hir::ExprKind::Path(ref path) => {
-                if let Some(substs) = self.typeck_results.node_substs_opt(expr.hir_id) {
+                if let Some(substs) = self.node_substs_opt(expr.hir_id) {
                     return self.path_inferred_subst_iter(expr.hir_id, substs, path);
                 }
             }
@@ -765,7 +808,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
                         if generics.has_impl_trait() {
                             None?
                         }
-                        let substs = self.typeck_results.node_substs_opt(expr.hir_id)?;
+                        let substs = self.node_substs_opt(expr.hir_id)?;
                         let span = tcx.hir().span(segment.hir_id?);
                         let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi());
                         InsertableGenericArgs {
@@ -980,8 +1023,10 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
             debug!(?args);
             let InsertableGenericArgs { insert_span, substs, generics_def_id, def_id } = args;
             let generics = tcx.generics_of(generics_def_id);
-            if let Some(argument_index) =
-                generics.own_substs(substs).iter().position(|&arg| self.generic_arg_is_target(arg))
+            if let Some(argument_index) = generics
+                .own_substs(substs)
+                .iter()
+                .position(|&arg| self.generic_arg_contains_target(arg))
             {
                 let substs = self.infcx.resolve_vars_if_possible(substs);
                 let generic_args = &generics.own_substs_no_defaults(tcx, substs)
@@ -1037,7 +1082,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
             .any(|generics| generics.has_impl_trait())
         };
         if let ExprKind::MethodCall(path, args, span) = expr.kind
-            && let Some(substs) = self.typeck_results.node_substs_opt(expr.hir_id)
+            && let Some(substs) = self.node_substs_opt(expr.hir_id)
             && substs.iter().any(|arg| self.generic_arg_contains_target(arg))
             && let Some(def_id) = self.typeck_results.type_dependent_def_id(expr.hir_id)
             && self.infcx.tcx.trait_of_item(def_id).is_some()
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
index 09430a135a3..02928c4aa57 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
@@ -10,7 +10,7 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::{walk_ty, Visitor};
 use rustc_hir::{self as hir, GenericBound, Item, ItemKind, Lifetime, LifetimeName, Node, TyKind};
 use rustc_middle::ty::{
-    self, AssocItemContainer, StaticLifetimeVisitor, Ty, TyCtxt, TypeSuperFoldable, TypeVisitor,
+    self, AssocItemContainer, StaticLifetimeVisitor, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor,
 };
 use rustc_span::symbol::Ident;
 use rustc_span::Span;
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs
index f6b49e41d4c..91bf9695dfc 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs
@@ -11,7 +11,7 @@ use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::intravisit::Visitor;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::print::RegionHighlightMode;
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperFoldable, TypeVisitor};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor};
 use rustc_span::{Span, Symbol};
 
 use std::ops::ControlFlow;
@@ -88,7 +88,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
             }
         }
 
-        impl<'tcx> ty::fold::TypeVisitor<'tcx> for HighlightBuilder<'tcx> {
+        impl<'tcx> ty::visit::TypeVisitor<'tcx> for HighlightBuilder<'tcx> {
             fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
                 if !r.has_name() && self.counter <= 3 {
                     self.highlight.highlighting_region(r, self.counter);
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 42d52446ab6..b356da0be55 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
@@ -5,7 +5,7 @@ use crate::infer::error_reporting::nice_region_error::NiceRegionError;
 use crate::infer::TyCtxt;
 use rustc_hir as hir;
 use rustc_hir::def_id::LocalDefId;
-use rustc_middle::ty::{self, Binder, DefIdTree, Region, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Binder, DefIdTree, Region, Ty, TypeVisitable};
 use rustc_span::Span;
 
 /// Information about the anonymous region we are searching for.
@@ -142,7 +142,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
 
     fn includes_region(
         &self,
-        ty: Binder<'tcx, impl TypeFoldable<'tcx>>,
+        ty: Binder<'tcx, impl TypeVisitable<'tcx>>,
         region: ty::BoundRegionKind,
     ) -> bool {
         let late_bound_regions = self.tcx().collect_referenced_late_bound_regions(&ty);
diff --git a/compiler/rustc_infer/src/infer/free_regions.rs b/compiler/rustc_infer/src/infer/free_regions.rs
index 082eb1bf111..fad949a3bc6 100644
--- a/compiler/rustc_infer/src/infer/free_regions.rs
+++ b/compiler/rustc_infer/src/infer/free_regions.rs
@@ -4,7 +4,6 @@
 //! and use that to decide when one free region outlives another, and so forth.
 
 use rustc_data_structures::transitive_relation::TransitiveRelation;
-use rustc_hir::def_id::DefId;
 use rustc_middle::ty::{self, Lift, Region, TyCtxt};
 
 /// Combines a `FreeRegionMap` and a `TyCtxt`.
@@ -14,16 +13,13 @@ use rustc_middle::ty::{self, Lift, Region, TyCtxt};
 pub(crate) struct RegionRelations<'a, 'tcx> {
     pub tcx: TyCtxt<'tcx>,
 
-    /// The context used for debug messages
-    pub context: DefId,
-
     /// Free-region relationships.
     pub free_regions: &'a FreeRegionMap<'tcx>,
 }
 
 impl<'a, 'tcx> RegionRelations<'a, 'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>, context: DefId, free_regions: &'a FreeRegionMap<'tcx>) -> Self {
-        Self { tcx, context, free_regions }
+    pub fn new(tcx: TyCtxt<'tcx>, free_regions: &'a FreeRegionMap<'tcx>) -> Self {
+        Self { tcx, free_regions }
     }
 
     pub fn lub_free_regions(&self, r_a: Region<'tcx>, r_b: Region<'tcx>) -> Region<'tcx> {
diff --git a/compiler/rustc_infer/src/infer/freshen.rs b/compiler/rustc_infer/src/infer/freshen.rs
index 024f7409947..84004d2b21f 100644
--- a/compiler/rustc_infer/src/infer/freshen.rs
+++ b/compiler/rustc_infer/src/infer/freshen.rs
@@ -34,7 +34,7 @@ use super::InferCtxt;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_middle::infer::unify_key::ToType;
 use rustc_middle::ty::fold::TypeFolder;
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitable};
 use std::collections::hash_map::Entry;
 
 pub struct TypeFreshener<'a, 'tcx> {
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 68c709a2e24..87fa22b3835 100644
--- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
+++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
@@ -120,13 +120,9 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
     ) -> LexicalRegionResolutions<'tcx> {
         let mut var_data = self.construct_var_data(self.tcx());
 
-        // Dorky hack to cause `dump_constraints` to only get called
-        // if debug mode is enabled:
-        debug!(
-            "----() End constraint listing (context={:?}) {:?}---",
-            self.region_rels.context,
-            self.dump_constraints(self.region_rels)
-        );
+        if cfg!(debug_assertions) {
+            self.dump_constraints();
+        }
 
         let graph = self.construct_graph();
         self.expand_givens(&graph);
@@ -156,8 +152,8 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
         }
     }
 
-    fn dump_constraints(&self, free_regions: &RegionRelations<'_, 'tcx>) {
-        debug!("----() Start constraint listing (context={:?}) ()----", free_regions.context);
+    #[instrument(level = "debug", skip(self))]
+    fn dump_constraints(&self) {
         for (idx, (constraint, _)) in self.data.constraints.iter().enumerate() {
             debug!("Constraint {} => {:?}", idx, constraint);
         }
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 6f88b83a473..991fd23ab43 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -15,7 +15,6 @@ use rustc_data_structures::sync::Lrc;
 use rustc_data_structures::undo_log::Rollback;
 use rustc_data_structures::unify as ut;
 use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
-use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::infer::canonical::{Canonical, CanonicalVarValues};
 use rustc_middle::infer::unify_key::{ConstVarValue, ConstVariableValue};
@@ -26,6 +25,7 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
 use rustc_middle::ty::relate::RelateResult;
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, InternalSubsts, SubstsRef};
+use rustc_middle::ty::visit::TypeVisitable;
 pub use rustc_middle::ty::IntVarValue;
 use rustc_middle::ty::{self, GenericParamDefKind, InferConst, Ty, TyCtxt};
 use rustc_middle::ty::{ConstVid, FloatVid, IntVid, TyVid};
@@ -147,7 +147,7 @@ pub struct InferCtxtInner<'tcx> {
     /// for each body-id in this map, which will process the
     /// obligations within. This is expected to be done 'late enough'
     /// that all type inference variables have been bound and so forth.
-    region_obligations: Vec<(hir::HirId, RegionObligation<'tcx>)>,
+    region_obligations: Vec<RegionObligation<'tcx>>,
 
     undo_log: InferCtxtUndoLogs<'tcx>,
 
@@ -171,7 +171,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
     }
 
     #[inline]
-    pub fn region_obligations(&self) -> &[(hir::HirId, RegionObligation<'tcx>)] {
+    pub fn region_obligations(&self) -> &[RegionObligation<'tcx>] {
         &self.region_obligations
     }
 
@@ -319,7 +319,7 @@ pub struct InferCtxt<'a, 'tcx> {
 }
 
 /// See the `error_reporting` module for more details.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, TypeFoldable)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, TypeFoldable, TypeVisitable)]
 pub enum ValuePairs<'tcx> {
     Regions(ExpectedFound<ty::Region<'tcx>>),
     Terms(ExpectedFound<ty::Term<'tcx>>),
@@ -1267,7 +1267,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
     /// `resolve_vars_if_possible` as well as `fully_resolve`.
     pub fn resolve_regions(
         &self,
-        region_context: DefId,
         outlives_env: &OutlivesEnvironment<'tcx>,
     ) -> Vec<RegionResolutionError<'tcx>> {
         let (var_infos, data) = {
@@ -1286,8 +1285,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
                 .into_infos_and_data()
         };
 
-        let region_rels =
-            &RegionRelations::new(self.tcx, region_context, outlives_env.free_region_map());
+        let region_rels = &RegionRelations::new(self.tcx, outlives_env.free_region_map());
 
         let (lexical_region_resolutions, errors) =
             lexical_region_resolve::resolve(outlives_env.param_env, region_rels, var_infos, data);
@@ -1302,12 +1300,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
     /// result. After this, no more unification operations should be
     /// done -- or the compiler will panic -- but it is legal to use
     /// `resolve_vars_if_possible` as well as `fully_resolve`.
-    pub fn resolve_regions_and_report_errors(
-        &self,
-        region_context: DefId,
-        outlives_env: &OutlivesEnvironment<'tcx>,
-    ) {
-        let errors = self.resolve_regions(region_context, outlives_env);
+    pub fn resolve_regions_and_report_errors(&self, outlives_env: &OutlivesEnvironment<'tcx>) {
+        let errors = self.resolve_regions(outlives_env);
 
         if !self.is_tainted_by_errors() {
             // As a heuristic, just skip reporting region errors
@@ -1445,7 +1439,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
     /// `resolve_vars_if_possible()`.
     pub fn unresolved_type_vars<T>(&self, value: &T) -> Option<(Ty<'tcx>, Option<Span>)>
     where
-        T: TypeFoldable<'tcx>,
+        T: TypeVisitable<'tcx>,
     {
         value.visit_with(&mut resolve::UnresolvedTypeFinder::new(self)).break_value()
     }
diff --git a/compiler/rustc_infer/src/infer/nll_relate/mod.rs b/compiler/rustc_infer/src/infer/nll_relate/mod.rs
index 846e7f7b921..bab4f3e9e36 100644
--- a/compiler/rustc_infer/src/infer/nll_relate/mod.rs
+++ b/compiler/rustc_infer/src/infer/nll_relate/mod.rs
@@ -27,8 +27,8 @@ use crate::infer::{ConstVarValue, ConstVariableValue};
 use crate::infer::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_middle::ty::error::TypeError;
-use rustc_middle::ty::fold::{TypeFoldable, TypeSuperFoldable, TypeVisitor};
 use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation};
+use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use rustc_middle::ty::{self, InferConst, Ty, TyCtxt};
 use rustc_span::Span;
 use std::fmt::Debug;
@@ -810,7 +810,7 @@ struct ScopeInstantiator<'me, 'tcx> {
 }
 
 impl<'me, 'tcx> TypeVisitor<'tcx> for ScopeInstantiator<'me, 'tcx> {
-    fn visit_binder<T: TypeFoldable<'tcx>>(
+    fn visit_binder<T: TypeVisitable<'tcx>>(
         &mut self,
         t: &ty::Binder<'tcx, T>,
     ) -> ControlFlow<Self::BreakTy> {
diff --git a/compiler/rustc_infer/src/infer/opaque_types.rs b/compiler/rustc_infer/src/infer/opaque_types.rs
index cc36d6a0a4f..26d689f29ee 100644
--- a/compiler/rustc_infer/src/infer/opaque_types.rs
+++ b/compiler/rustc_infer/src/infer/opaque_types.rs
@@ -9,7 +9,8 @@ use rustc_middle::traits::ObligationCause;
 use rustc_middle::ty::fold::BottomUpFolder;
 use rustc_middle::ty::subst::{GenericArgKind, Subst};
 use rustc_middle::ty::{
-    self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor,
+    self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
+    TypeVisitable, TypeVisitor,
 };
 use rustc_span::Span;
 
@@ -461,7 +462,7 @@ impl<'tcx, OP> TypeVisitor<'tcx> for ConstrainOpaqueTypeRegionVisitor<OP>
 where
     OP: FnMut(ty::Region<'tcx>),
 {
-    fn visit_binder<T: TypeFoldable<'tcx>>(
+    fn visit_binder<T: TypeVisitable<'tcx>>(
         &mut self,
         t: &ty::Binder<'tcx, T>,
     ) -> ControlFlow<Self::BreakTy> {
diff --git a/compiler/rustc_infer/src/infer/outlives/components.rs b/compiler/rustc_infer/src/infer/outlives/components.rs
index 5ea096f5cc2..7234660dbcd 100644
--- a/compiler/rustc_infer/src/infer/outlives/components.rs
+++ b/compiler/rustc_infer/src/infer/outlives/components.rs
@@ -4,7 +4,7 @@
 
 use rustc_data_structures::sso::SsoHashSet;
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
 use smallvec::{smallvec, SmallVec};
 
 #[derive(Debug)]
diff --git a/compiler/rustc_infer/src/infer/outlives/env.rs b/compiler/rustc_infer/src/infer/outlives/env.rs
index 9ddda7b92eb..b897de7315a 100644
--- a/compiler/rustc_infer/src/infer/outlives/env.rs
+++ b/compiler/rustc_infer/src/infer/outlives/env.rs
@@ -1,8 +1,6 @@
 use crate::infer::free_regions::FreeRegionMap;
 use crate::infer::{GenericKind, InferCtxt};
 use crate::traits::query::OutlivesBound;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_hir as hir;
 use rustc_middle::ty::{self, ReEarlyBound, ReFree, ReVar, Region};
 
 use super::explicit_outlives_bounds;
@@ -31,9 +29,7 @@ pub struct OutlivesEnvironment<'tcx> {
     pub param_env: ty::ParamEnv<'tcx>,
     free_region_map: FreeRegionMap<'tcx>,
 
-    // Contains, for each body B that we are checking (that is, the fn
-    // item, but also any nested closures), the set of implied region
-    // bounds that are in scope in that particular body.
+    // Contains the implied region bounds in scope for our current body.
     //
     // Example:
     //
@@ -43,24 +39,15 @@ pub struct OutlivesEnvironment<'tcx> {
     // } // body B0
     // ```
     //
-    // Here, for body B0, the list would be `[T: 'a]`, because we
+    // Here, when checking the body B0, the list would be `[T: 'a]`, because we
     // infer that `T` must outlive `'a` from the implied bounds on the
     // fn declaration.
     //
-    // For the body B1, the list would be `[T: 'a, T: 'b]`, because we
+    // For the body B1 however, the list would be `[T: 'a, T: 'b]`, because we
     // also can see that -- within the closure body! -- `T` must
     // outlive `'b`. This is not necessarily true outside the closure
     // body, since the closure may never be called.
-    //
-    // We collect this map as we descend the tree. We then use the
-    // results when proving outlives obligations like `T: 'x` later
-    // (e.g., if `T: 'x` must be proven within the body B1, then we
-    // know it is true if either `'a: 'x` or `'b: 'x`).
-    region_bound_pairs_map: FxHashMap<hir::HirId, RegionBoundPairs<'tcx>>,
-
-    // Used to compute `region_bound_pairs_map`: contains the set of
-    // in-scope region-bound pairs thus far.
-    region_bound_pairs_accum: RegionBoundPairs<'tcx>,
+    region_bound_pairs: RegionBoundPairs<'tcx>,
 }
 
 /// "Region-bound pairs" tracks outlives relations that are known to
@@ -73,8 +60,7 @@ impl<'a, 'tcx> OutlivesEnvironment<'tcx> {
         let mut env = OutlivesEnvironment {
             param_env,
             free_region_map: Default::default(),
-            region_bound_pairs_map: Default::default(),
-            region_bound_pairs_accum: vec![],
+            region_bound_pairs: Default::default(),
         };
 
         env.add_outlives_bounds(None, explicit_outlives_bounds(param_env));
@@ -87,62 +73,9 @@ impl<'a, 'tcx> OutlivesEnvironment<'tcx> {
         &self.free_region_map
     }
 
-    /// Borrows current value of the `region_bound_pairs`.
-    pub fn region_bound_pairs_map(&self) -> &FxHashMap<hir::HirId, RegionBoundPairs<'tcx>> {
-        &self.region_bound_pairs_map
-    }
-
-    /// This is a hack to support the old-school regionck, which
-    /// processes region constraints from the main function and the
-    /// closure together. In that context, when we enter a closure, we
-    /// want to be able to "save" the state of the surrounding a
-    /// function. We can then add implied bounds and the like from the
-    /// closure arguments into the environment -- these should only
-    /// apply in the closure body, so once we exit, we invoke
-    /// `pop_snapshot_post_typeck_child` to remove them.
-    ///
-    /// Example:
-    ///
-    /// ```ignore (pseudo-rust)
-    /// fn foo<T>() {
-    ///    callback(for<'a> |x: &'a T| {
-    ///         // ^^^^^^^ not legal syntax, but probably should be
-    ///         // within this closure body, `T: 'a` holds
-    ///    })
-    /// }
-    /// ```
-    ///
-    /// This "containment" of closure's effects only works so well. In
-    /// particular, we (intentionally) leak relationships between free
-    /// regions that are created by the closure's bounds. The case
-    /// where this is useful is when you have (e.g.) a closure with a
-    /// signature like `for<'a, 'b> fn(x: &'a &'b u32)` -- in this
-    /// case, we want to keep the relationship `'b: 'a` in the
-    /// free-region-map, so that later if we have to take `LUB('b,
-    /// 'a)` we can get the result `'b`.
-    ///
-    /// I have opted to keep **all modifications** to the
-    /// free-region-map, however, and not just those that concern free
-    /// variables bound in the closure. The latter seems more correct,
-    /// but it is not the existing behavior, and I could not find a
-    /// case where the existing behavior went wrong. In any case, it
-    /// seems like it'd be readily fixed if we wanted. There are
-    /// similar leaks around givens that seem equally suspicious, to
-    /// be honest. --nmatsakis
-    pub fn push_snapshot_pre_typeck_child(&self) -> usize {
-        self.region_bound_pairs_accum.len()
-    }
-
-    /// See `push_snapshot_pre_typeck_child`.
-    pub fn pop_snapshot_post_typeck_child(&mut self, len: usize) {
-        self.region_bound_pairs_accum.truncate(len);
-    }
-
-    /// Save the current set of region-bound pairs under the given `body_id`.
-    pub fn save_implied_bounds(&mut self, body_id: hir::HirId) {
-        let old =
-            self.region_bound_pairs_map.insert(body_id, self.region_bound_pairs_accum.clone());
-        assert!(old.is_none());
+    /// Borrows current `region_bound_pairs`.
+    pub fn region_bound_pairs(&self) -> &RegionBoundPairs<'tcx> {
+        &self.region_bound_pairs
     }
 
     /// Processes outlives bounds that are known to hold, whether from implied or other sources.
@@ -164,11 +97,10 @@ impl<'a, 'tcx> OutlivesEnvironment<'tcx> {
             debug!("add_outlives_bounds: outlives_bound={:?}", outlives_bound);
             match outlives_bound {
                 OutlivesBound::RegionSubParam(r_a, param_b) => {
-                    self.region_bound_pairs_accum.push((r_a, GenericKind::Param(param_b)));
+                    self.region_bound_pairs.push((r_a, GenericKind::Param(param_b)));
                 }
                 OutlivesBound::RegionSubProjection(r_a, projection_b) => {
-                    self.region_bound_pairs_accum
-                        .push((r_a, GenericKind::Projection(projection_b)));
+                    self.region_bound_pairs.push((r_a, GenericKind::Projection(projection_b)));
                 }
                 OutlivesBound::RegionSubRegion(r_a, r_b) => {
                     if let (ReEarlyBound(_) | ReFree(_), ReVar(vid_b)) = (r_a.kind(), r_b.kind()) {
diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs
index 1c1906f3375..59cf39abe64 100644
--- a/compiler/rustc_infer/src/infer/outlives/obligations.rs
+++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs
@@ -60,18 +60,16 @@
 //! imply that `'b: 'a`.
 
 use crate::infer::outlives::components::{push_outlives_components, Component};
+use crate::infer::outlives::env::OutlivesEnvironment;
 use crate::infer::outlives::env::RegionBoundPairs;
 use crate::infer::outlives::verify::VerifyBoundCx;
 use crate::infer::{
     self, GenericKind, InferCtxt, RegionObligation, SubregionOrigin, UndoLog, VerifyBound,
 };
 use crate::traits::{ObligationCause, ObligationCauseCode};
-use rustc_middle::ty::subst::GenericArgKind;
-use rustc_middle::ty::{self, Region, Ty, TyCtxt, TypeFoldable};
-
-use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::undo_log::UndoLogs;
-use rustc_hir as hir;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{self, Region, Ty, TyCtxt, TypeVisitable};
 use smallvec::smallvec;
 
 impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
@@ -80,16 +78,11 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
     /// and later processed by regionck, when full type information is
     /// available (see `region_obligations` field for more
     /// information).
-    pub fn register_region_obligation(
-        &self,
-        body_id: hir::HirId,
-        obligation: RegionObligation<'tcx>,
-    ) {
-        debug!("register_region_obligation(body_id={:?}, obligation={:?})", body_id, obligation);
-
+    #[instrument(level = "debug", skip(self))]
+    pub fn register_region_obligation(&self, obligation: RegionObligation<'tcx>) {
         let mut inner = self.inner.borrow_mut();
         inner.undo_log.push(UndoLog::PushRegionObligation);
-        inner.region_obligations.push((body_id, obligation));
+        inner.region_obligations.push(obligation);
     }
 
     pub fn register_region_obligation_with_cause(
@@ -109,14 +102,11 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
             )
         });
 
-        self.register_region_obligation(
-            cause.body_id,
-            RegionObligation { sup_type, sub_region, origin },
-        );
+        self.register_region_obligation(RegionObligation { sup_type, sub_region, origin });
     }
 
     /// Trait queries just want to pass back type obligations "as is"
-    pub fn take_registered_region_obligations(&self) -> Vec<(hir::HirId, RegionObligation<'tcx>)> {
+    pub fn take_registered_region_obligations(&self) -> Vec<RegionObligation<'tcx>> {
         std::mem::take(&mut self.inner.borrow_mut().region_obligations)
     }
 
@@ -144,10 +134,10 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
     /// - `param_env` is the parameter environment for the enclosing function.
     /// - `body_id` is the body-id whose region obligations are being
     ///   processed.
-    #[instrument(level = "debug", skip(self, region_bound_pairs_map))]
+    #[instrument(level = "debug", skip(self, region_bound_pairs))]
     pub fn process_registered_region_obligations(
         &self,
-        region_bound_pairs_map: &FxHashMap<hir::HirId, RegionBoundPairs<'tcx>>,
+        region_bound_pairs: &RegionBoundPairs<'tcx>,
         param_env: ty::ParamEnv<'tcx>,
     ) {
         assert!(
@@ -157,7 +147,7 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
 
         let my_region_obligations = self.take_registered_region_obligations();
 
-        for (body_id, RegionObligation { sup_type, sub_region, origin }) in my_region_obligations {
+        for RegionObligation { sup_type, sub_region, origin } in my_region_obligations {
             debug!(
                 "process_registered_region_obligations: sup_type={:?} sub_region={:?} origin={:?}",
                 sup_type, sub_region, origin
@@ -165,18 +155,23 @@ 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, None, param_env);
-                outlives.type_must_outlive(origin, sup_type, sub_region);
-            } else {
-                self.tcx.sess.delay_span_bug(
-                    origin.span(),
-                    &format!("no region-bound-pairs for {:?}", body_id),
-                );
-            }
+            let outlives =
+                &mut TypeOutlives::new(self, self.tcx, &region_bound_pairs, None, param_env);
+            outlives.type_must_outlive(origin, sup_type, sub_region);
         }
     }
+
+    pub fn check_region_obligations_and_report_errors(
+        &self,
+        outlives_env: &OutlivesEnvironment<'tcx>,
+    ) {
+        self.process_registered_region_obligations(
+            outlives_env.region_bound_pairs(),
+            outlives_env.param_env,
+        );
+
+        self.resolve_regions_and_report_errors(outlives_env)
+    }
 }
 
 /// The `TypeOutlives` struct has the job of "lowering" a `T: 'a`
diff --git a/compiler/rustc_infer/src/infer/outlives/test_type_match.rs b/compiler/rustc_infer/src/infer/outlives/test_type_match.rs
index 9f71ebae99e..772e297b7b4 100644
--- a/compiler/rustc_infer/src/infer/outlives/test_type_match.rs
+++ b/compiler/rustc_infer/src/infer/outlives/test_type_match.rs
@@ -1,7 +1,7 @@
 use std::collections::hash_map::Entry;
 
 use rustc_data_structures::fx::FxHashMap;
-use rustc_middle::ty::TypeFoldable;
+use rustc_middle::ty::TypeVisitable;
 use rustc_middle::ty::{
     self,
     error::TypeError,
diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs
index 19f83e3377a..c5747ecf702 100644
--- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs
+++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs
@@ -165,7 +165,7 @@ pub struct Verify<'tcx> {
     pub bound: VerifyBound<'tcx>,
 }
 
-#[derive(Copy, Clone, PartialEq, Eq, Hash, TypeFoldable)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, TypeFoldable, TypeVisitable)]
 pub enum GenericKind<'tcx> {
     Param(ty::ParamTy),
     Projection(ty::ProjectionTy<'tcx>),
@@ -272,7 +272,7 @@ pub enum VerifyBound<'tcx> {
 ///     }
 /// }
 /// ```
-#[derive(Debug, Copy, Clone, TypeFoldable)]
+#[derive(Debug, Copy, Clone, TypeFoldable, TypeVisitable)]
 pub struct VerifyIfEq<'tcx> {
     /// Type which must match the generic `G`
     pub ty: Ty<'tcx>,
diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs
index 1f3cb401314..a6145687429 100644
--- a/compiler/rustc_infer/src/infer/resolve.rs
+++ b/compiler/rustc_infer/src/infer/resolve.rs
@@ -1,8 +1,9 @@
 use super::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use super::{FixupError, FixupResult, InferCtxt, Span};
 use rustc_middle::mir;
-use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFolder, TypeSuperFoldable, TypeVisitor};
-use rustc_middle::ty::{self, Const, InferConst, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitor};
+use rustc_middle::ty::{self, Const, InferConst, Ty, TyCtxt, TypeFoldable, TypeVisitable};
 
 use std::ops::ControlFlow;
 
diff --git a/compiler/rustc_infer/src/infer/sub.rs b/compiler/rustc_infer/src/infer/sub.rs
index d0c6d8d16eb..b27571275b7 100644
--- a/compiler/rustc_infer/src/infer/sub.rs
+++ b/compiler/rustc_infer/src/infer/sub.rs
@@ -4,8 +4,8 @@ use super::SubregionOrigin;
 use crate::infer::combine::ConstEquateRelation;
 use crate::infer::{TypeVariableOrigin, TypeVariableOriginKind};
 use crate::traits::Obligation;
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::relate::{Cause, Relate, RelateResult, TypeRelation};
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::TyVar;
 use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt};
 use std::mem;
diff --git a/compiler/rustc_infer/src/traits/error_reporting/mod.rs b/compiler/rustc_infer/src/traits/error_reporting/mod.rs
index 4eafa3329c3..7e42458fda3 100644
--- a/compiler/rustc_infer/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/traits/error_reporting/mod.rs
@@ -23,10 +23,12 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
 
         let mut err = struct_span_err!(self.tcx.sess, sp, E0276, "{}", msg);
 
-        if let Some(trait_item_span) = self.tcx.hir().span_if_local(trait_item_def_id) {
-            let span = self.tcx.sess.source_map().guess_head_span(trait_item_span);
+        if trait_item_def_id.is_local() {
             let item_name = self.tcx.item_name(impl_item_def_id.to_def_id());
-            err.span_label(span, format!("definition of `{}` from trait", item_name));
+            err.span_label(
+                self.tcx.def_span(trait_item_def_id),
+                format!("definition of `{}` from trait", item_name),
+            );
         }
 
         err.span_label(sp, format!("impl has extra requirement {}", requirement));
diff --git a/compiler/rustc_infer/src/traits/project.rs b/compiler/rustc_infer/src/traits/project.rs
index b84ed3dc689..932e597509f 100644
--- a/compiler/rustc_infer/src/traits/project.rs
+++ b/compiler/rustc_infer/src/traits/project.rs
@@ -20,7 +20,7 @@ pub struct MismatchedProjectionTypes<'tcx> {
     pub err: ty::error::TypeError<'tcx>,
 }
 
-#[derive(Clone, TypeFoldable)]
+#[derive(Clone, TypeFoldable, TypeVisitable)]
 pub struct Normalized<'tcx, T> {
     pub value: T,
     pub obligations: Vec<PredicateObligation<'tcx>>,
diff --git a/compiler/rustc_infer/src/traits/structural_impls.rs b/compiler/rustc_infer/src/traits/structural_impls.rs
index 82ee4bb29e8..573d2d1e330 100644
--- a/compiler/rustc_infer/src/traits/structural_impls.rs
+++ b/compiler/rustc_infer/src/traits/structural_impls.rs
@@ -1,7 +1,8 @@
 use crate::traits;
 use crate::traits::project::Normalized;
 use rustc_middle::ty;
-use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeVisitor};
+use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFoldable};
+use rustc_middle::ty::visit::{TypeVisitable, TypeVisitor};
 
 use std::fmt;
 use std::ops::ControlFlow;
@@ -68,7 +69,9 @@ impl<'tcx, O: TypeFoldable<'tcx>> TypeFoldable<'tcx> for traits::Obligation<'tcx
             param_env: self.param_env.try_fold_with(folder)?,
         })
     }
+}
 
+impl<'tcx, O: TypeVisitable<'tcx>> TypeVisitable<'tcx> for traits::Obligation<'tcx, O> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.predicate.visit_with(visitor)?;
         self.param_env.visit_with(visitor)
diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml
index fab60b6f609..7c0f2c440d5 100644
--- a/compiler/rustc_lint/Cargo.toml
+++ b/compiler/rustc_lint/Cargo.toml
@@ -22,3 +22,4 @@ rustc_trait_selection = { path = "../rustc_trait_selection" }
 rustc_parse_format = { path = "../rustc_parse_format" }
 rustc_infer = { path = "../rustc_infer" }
 rustc_type_ir = { path = "../rustc_type_ir" }
+rustc_macros = { path = "../rustc_macros" }
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
index b33ab40eb39..121fefdc620 100644
--- a/compiler/rustc_lint/src/array_into_iter.rs
+++ b/compiler/rustc_lint/src/array_into_iter.rs
@@ -1,5 +1,5 @@
 use crate::{LateContext, LateLintPass, LintContext};
-use rustc_errors::Applicability;
+use rustc_errors::{fluent, Applicability};
 use rustc_hir as hir;
 use rustc_middle::ty;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment};
@@ -120,31 +120,30 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
                 _ => bug!("array type coerced to something other than array or slice"),
             };
             cx.struct_span_lint(ARRAY_INTO_ITER, call.ident.span, |lint| {
-                let mut diag = lint.build(&format!(
-                    "this method call resolves to `<&{} as IntoIterator>::into_iter` \
-                    (due to backwards compatibility), \
-                    but will resolve to <{} as IntoIterator>::into_iter in Rust 2021",
-                    target, target,
-                ));
+                let mut diag = lint.build(fluent::lint::array_into_iter);
+                diag.set_arg("target", target);
                 diag.span_suggestion(
                     call.ident.span,
-                    "use `.iter()` instead of `.into_iter()` to avoid ambiguity",
+                    fluent::lint::use_iter_suggestion,
                     "iter",
                     Applicability::MachineApplicable,
                 );
                 if self.for_expr_span == expr.span {
                     diag.span_suggestion(
                         receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
-                        "or remove `.into_iter()` to iterate by value",
+                        fluent::lint::remove_into_iter_suggestion,
                         "",
                         Applicability::MaybeIncorrect,
                     );
                 } else if receiver_ty.is_array() {
                     diag.multipart_suggestion(
-                        "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value",
+                        fluent::lint::use_explicit_into_iter_suggestion,
                         vec![
                             (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()),
-                            (receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), ")".into()),
+                            (
+                                receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+                                ")".into(),
+                            ),
                         ],
                         Applicability::MaybeIncorrect,
                     );
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index c0cf8c6b76b..a0472f98d72 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -31,14 +31,17 @@ use rustc_ast::{self as ast, *};
 use rustc_ast_pretty::pprust::{self, expr_to_string};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_errors::{Applicability, Diagnostic, DiagnosticStyledString, MultiSpan};
+use rustc_errors::{
+    fluent, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString,
+    LintDiagnosticBuilder, MultiSpan,
+};
 use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID};
 use rustc_hir::{ForeignItemKind, GenericParamKind, HirId, PatKind, PredicateOrigin};
 use rustc_index::vec::Idx;
-use rustc_middle::lint::{in_external_macro, LintDiagnosticBuilder};
+use rustc_middle::lint::in_external_macro;
 use rustc_middle::ty::layout::{LayoutError, LayoutOf};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::subst::GenericArgKind;
@@ -99,13 +102,12 @@ impl EarlyLintPass for WhileTrue {
             if let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind {
                 if let ast::LitKind::Bool(true) = lit.kind {
                     if !lit.span.from_expansion() {
-                        let msg = "denote infinite loops with `loop { ... }`";
                         let condition_span = e.span.with_hi(cond.span.hi());
                         cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
-                            lint.build(msg)
+                            lint.build(fluent::lint::builtin_while_true)
                                 .span_suggestion_short(
                                     condition_span,
-                                    "use `loop`",
+                                    fluent::lint::suggestion,
                                     format!(
                                         "{}loop",
                                         label.map_or_else(String::new, |label| format!(
@@ -156,7 +158,7 @@ impl BoxPointers {
             if let GenericArgKind::Type(leaf_ty) = leaf.unpack() {
                 if leaf_ty.is_box() {
                     cx.struct_span_lint(BOX_POINTERS, span, |lint| {
-                        lint.build(&format!("type uses owned (Box type) pointers: {}", ty)).emit();
+                        lint.build(fluent::lint::builtin_box_pointers).set_arg("ty", ty).emit();
                     });
                 }
             }
@@ -257,26 +259,26 @@ impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns {
                         == Some(cx.tcx.field_index(fieldpat.hir_id, cx.typeck_results()))
                     {
                         cx.struct_span_lint(NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, |lint| {
-                            let mut err = lint
-                                .build(&format!("the `{}:` in this pattern is redundant", ident));
                             let binding = match binding_annot {
                                 hir::BindingAnnotation::Unannotated => None,
                                 hir::BindingAnnotation::Mutable => Some("mut"),
                                 hir::BindingAnnotation::Ref => Some("ref"),
                                 hir::BindingAnnotation::RefMut => Some("ref mut"),
                             };
-                            let ident = if let Some(binding) = binding {
+                            let suggested_ident = if let Some(binding) = binding {
                                 format!("{} {}", binding, ident)
                             } else {
                                 ident.to_string()
                             };
-                            err.span_suggestion(
-                                fieldpat.span,
-                                "use shorthand field pattern",
-                                ident,
-                                Applicability::MachineApplicable,
-                            );
-                            err.emit();
+                            lint.build(fluent::lint::builtin_non_shorthand_field_patterns)
+                                .set_arg("ident", ident.clone())
+                                .span_suggestion(
+                                    fieldpat.span,
+                                    fluent::lint::suggestion,
+                                    suggested_ident,
+                                    Applicability::MachineApplicable,
+                                )
+                                .emit();
                         });
                     }
                 }
@@ -327,26 +329,25 @@ impl UnsafeCode {
         cx.struct_span_lint(UNSAFE_CODE, span, decorate);
     }
 
-    fn report_overridden_symbol_name(&self, cx: &EarlyContext<'_>, span: Span, msg: &str) {
+    fn report_overridden_symbol_name(
+        &self,
+        cx: &EarlyContext<'_>,
+        span: Span,
+        msg: DiagnosticMessage,
+    ) {
         self.report_unsafe(cx, span, |lint| {
-            lint.build(msg)
-                .note(
-                    "the linker's behavior with multiple libraries exporting duplicate symbol \
-                    names is undefined and Rust cannot provide guarantees when you manually \
-                    override them",
-                )
-                .emit();
+            lint.build(msg).note(fluent::lint::builtin_overridden_symbol_name).emit();
         })
     }
 
-    fn report_overridden_symbol_section(&self, cx: &EarlyContext<'_>, span: Span, msg: &str) {
+    fn report_overridden_symbol_section(
+        &self,
+        cx: &EarlyContext<'_>,
+        span: Span,
+        msg: DiagnosticMessage,
+    ) {
         self.report_unsafe(cx, span, |lint| {
-            lint.build(msg)
-                .note(
-                    "the program's behavior with overridden link sections on items is unpredictable \
-                    and Rust cannot provide guarantees when you manually override them",
-                )
-                .emit();
+            lint.build(msg).note(fluent::lint::builtin_overridden_symbol_section).emit();
         })
     }
 }
@@ -355,12 +356,7 @@ impl EarlyLintPass for UnsafeCode {
     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
         if attr.has_name(sym::allow_internal_unsafe) {
             self.report_unsafe(cx, attr.span, |lint| {
-                lint.build(
-                    "`allow_internal_unsafe` allows defining \
-                                               macros using unsafe without triggering \
-                                               the `unsafe_code` lint at their call site",
-                )
-                .emit();
+                lint.build(fluent::lint::builtin_allow_internal_unsafe).emit();
             });
         }
     }
@@ -370,7 +366,7 @@ impl EarlyLintPass for UnsafeCode {
             // Don't warn about generated blocks; that'll just pollute the output.
             if blk.rules == ast::BlockCheckMode::Unsafe(ast::UserProvided) {
                 self.report_unsafe(cx, blk.span, |lint| {
-                    lint.build("usage of an `unsafe` block").emit();
+                    lint.build(fluent::lint::builtin_unsafe_block).emit();
                 });
             }
         }
@@ -380,12 +376,12 @@ impl EarlyLintPass for UnsafeCode {
         match it.kind {
             ast::ItemKind::Trait(box ast::Trait { unsafety: ast::Unsafe::Yes(_), .. }) => self
                 .report_unsafe(cx, it.span, |lint| {
-                    lint.build("declaration of an `unsafe` trait").emit();
+                    lint.build(fluent::lint::builtin_unsafe_trait).emit();
                 }),
 
             ast::ItemKind::Impl(box ast::Impl { unsafety: ast::Unsafe::Yes(_), .. }) => self
                 .report_unsafe(cx, it.span, |lint| {
-                    lint.build("implementation of an `unsafe` trait").emit();
+                    lint.build(fluent::lint::builtin_unsafe_impl).emit();
                 }),
 
             ast::ItemKind::Fn(..) => {
@@ -393,7 +389,7 @@ impl EarlyLintPass for UnsafeCode {
                     self.report_overridden_symbol_name(
                         cx,
                         attr.span,
-                        "declaration of a `no_mangle` function",
+                        fluent::lint::builtin_no_mangle_fn,
                     );
                 }
 
@@ -401,7 +397,7 @@ impl EarlyLintPass for UnsafeCode {
                     self.report_overridden_symbol_name(
                         cx,
                         attr.span,
-                        "declaration of a function with `export_name`",
+                        fluent::lint::builtin_export_name_fn,
                     );
                 }
 
@@ -409,7 +405,7 @@ impl EarlyLintPass for UnsafeCode {
                     self.report_overridden_symbol_section(
                         cx,
                         attr.span,
-                        "declaration of a function with `link_section`",
+                        fluent::lint::builtin_link_section_fn,
                     );
                 }
             }
@@ -419,7 +415,7 @@ impl EarlyLintPass for UnsafeCode {
                     self.report_overridden_symbol_name(
                         cx,
                         attr.span,
-                        "declaration of a `no_mangle` static",
+                        fluent::lint::builtin_no_mangle_static,
                     );
                 }
 
@@ -427,7 +423,7 @@ impl EarlyLintPass for UnsafeCode {
                     self.report_overridden_symbol_name(
                         cx,
                         attr.span,
-                        "declaration of a static with `export_name`",
+                        fluent::lint::builtin_export_name_static,
                     );
                 }
 
@@ -435,7 +431,7 @@ impl EarlyLintPass for UnsafeCode {
                     self.report_overridden_symbol_section(
                         cx,
                         attr.span,
-                        "declaration of a static with `link_section`",
+                        fluent::lint::builtin_link_section_static,
                     );
                 }
             }
@@ -450,14 +446,14 @@ impl EarlyLintPass for UnsafeCode {
                 self.report_overridden_symbol_name(
                     cx,
                     attr.span,
-                    "declaration of a `no_mangle` method",
+                    fluent::lint::builtin_no_mangle_method,
                 );
             }
             if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) {
                 self.report_overridden_symbol_name(
                     cx,
                     attr.span,
-                    "declaration of a method with `export_name`",
+                    fluent::lint::builtin_export_name_method,
                 );
             }
         }
@@ -475,9 +471,9 @@ impl EarlyLintPass for UnsafeCode {
         {
             let msg = match ctxt {
                 FnCtxt::Foreign => return,
-                FnCtxt::Free => "declaration of an `unsafe` function",
-                FnCtxt::Assoc(_) if body.is_none() => "declaration of an `unsafe` method",
-                FnCtxt::Assoc(_) => "implementation of an `unsafe` method",
+                FnCtxt::Free => fluent::lint::builtin_decl_unsafe_fn,
+                FnCtxt::Assoc(_) if body.is_none() => fluent::lint::builtin_decl_unsafe_method,
+                FnCtxt::Assoc(_) => fluent::lint::builtin_impl_unsafe_method,
             };
             self.report_unsafe(cx, span, |lint| {
                 lint.build(msg).emit();
@@ -556,7 +552,6 @@ impl MissingDoc {
         &self,
         cx: &LateContext<'_>,
         def_id: LocalDefId,
-        sp: Span,
         article: &'static str,
         desc: &'static str,
     ) {
@@ -583,13 +578,12 @@ impl MissingDoc {
         let attrs = cx.tcx.hir().attrs(cx.tcx.hir().local_def_id_to_hir_id(def_id));
         let has_doc = attrs.iter().any(has_doc);
         if !has_doc {
-            cx.struct_span_lint(
-                MISSING_DOCS,
-                cx.tcx.sess.source_map().guess_head_span(sp),
-                |lint| {
-                    lint.build(&format!("missing documentation for {} {}", article, desc)).emit();
-                },
-            );
+            cx.struct_span_lint(MISSING_DOCS, cx.tcx.def_span(def_id), |lint| {
+                lint.build(fluent::lint::builtin_missing_doc)
+                    .set_arg("article", article)
+                    .set_arg("desc", desc)
+                    .emit();
+            });
         }
     }
 }
@@ -612,13 +606,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
     }
 
     fn check_crate(&mut self, cx: &LateContext<'_>) {
-        self.check_missing_docs_attrs(
-            cx,
-            CRATE_DEF_ID,
-            cx.tcx.def_span(CRATE_DEF_ID),
-            "the",
-            "crate",
-        );
+        self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate");
     }
 
     fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
@@ -648,13 +636,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
 
         let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id());
 
-        self.check_missing_docs_attrs(cx, it.def_id, it.span, article, desc);
+        self.check_missing_docs_attrs(cx, it.def_id, article, desc);
     }
 
     fn check_trait_item(&mut self, cx: &LateContext<'_>, trait_item: &hir::TraitItem<'_>) {
         let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id());
 
-        self.check_missing_docs_attrs(cx, trait_item.def_id, trait_item.span, article, desc);
+        self.check_missing_docs_attrs(cx, trait_item.def_id, article, desc);
     }
 
     fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
@@ -682,23 +670,23 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
         }
 
         let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id());
-        self.check_missing_docs_attrs(cx, impl_item.def_id, impl_item.span, article, desc);
+        self.check_missing_docs_attrs(cx, impl_item.def_id, article, desc);
     }
 
     fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'_>) {
         let (article, desc) = cx.tcx.article_and_description(foreign_item.def_id.to_def_id());
-        self.check_missing_docs_attrs(cx, foreign_item.def_id, foreign_item.span, article, desc);
+        self.check_missing_docs_attrs(cx, foreign_item.def_id, article, desc);
     }
 
     fn check_field_def(&mut self, cx: &LateContext<'_>, sf: &hir::FieldDef<'_>) {
         if !sf.is_positional() {
             let def_id = cx.tcx.hir().local_def_id(sf.hir_id);
-            self.check_missing_docs_attrs(cx, def_id, sf.span, "a", "struct field")
+            self.check_missing_docs_attrs(cx, def_id, "a", "struct field")
         }
     }
 
     fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) {
-        self.check_missing_docs_attrs(cx, cx.tcx.hir().local_def_id(v.id), v.span, "a", "variant");
+        self.check_missing_docs_attrs(cx, cx.tcx.hir().local_def_id(v.id), "a", "variant");
     }
 }
 
@@ -783,11 +771,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
         .is_ok()
         {
             cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| {
-                lint.build(
-                    "type could implement `Copy`; consider adding `impl \
-                          Copy`",
-                )
-                .emit();
+                lint.build(fluent::lint::builtin_missing_copy_impl).emit();
             })
         }
     }
@@ -863,12 +847,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
 
         if !self.impling_types.as_ref().unwrap().contains(&item.def_id) {
             cx.struct_span_lint(MISSING_DEBUG_IMPLEMENTATIONS, item.span, |lint| {
-                lint.build(&format!(
-                    "type does not implement `{}`; consider adding `#[derive(Debug)]` \
-                     or a manual implementation",
-                    cx.tcx.def_path_str(debug)
-                ))
-                .emit();
+                lint.build(fluent::lint::builtin_missing_debug_impl)
+                    .set_arg("debug", cx.tcx.def_path_str(debug))
+                    .emit();
             });
         }
     }
@@ -946,18 +927,14 @@ impl EarlyLintPass for AnonymousParameters {
                                 ("<type>", Applicability::HasPlaceholders)
                             };
 
-                            lint.build(
-                                "anonymous parameters are deprecated and will be \
-                                     removed in the next edition",
-                            )
-                            .span_suggestion(
-                                arg.pat.span,
-                                "try naming the parameter or explicitly \
-                                            ignoring it",
-                                format!("_: {}", ty_snip),
-                                appl,
-                            )
-                            .emit();
+                            lint.build(fluent::lint::builtin_anonymous_params)
+                                .span_suggestion(
+                                    arg.pat.span,
+                                    fluent::lint::suggestion,
+                                    format!("_: {}", ty_snip),
+                                    appl,
+                                )
+                                .emit();
                         })
                     }
                 }
@@ -982,24 +959,6 @@ impl DeprecatedAttr {
     }
 }
 
-fn lint_deprecated_attr(
-    cx: &EarlyContext<'_>,
-    attr: &ast::Attribute,
-    msg: &str,
-    suggestion: Option<&str>,
-) {
-    cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
-        lint.build(msg)
-            .span_suggestion_short(
-                attr.span,
-                suggestion.unwrap_or("remove this attribute"),
-                "",
-                Applicability::MachineApplicable,
-            )
-            .emit();
-    })
-}
-
 impl EarlyLintPass for DeprecatedAttr {
     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
         for BuiltinAttribute { name, gate, .. } in &self.depr_attrs {
@@ -1011,17 +970,38 @@ impl EarlyLintPass for DeprecatedAttr {
                     _,
                 ) = gate
                 {
-                    let msg =
-                        format!("use of deprecated attribute `{}`: {}. See {}", name, reason, link);
-                    lint_deprecated_attr(cx, attr, &msg, suggestion);
+                    cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
+                        // FIXME(davidtwco) translatable deprecated attr
+                        lint.build(fluent::lint::builtin_deprecated_attr_link)
+                            .set_arg("name", name)
+                            .set_arg("reason", reason)
+                            .set_arg("link", link)
+                            .span_suggestion_short(
+                                attr.span,
+                                suggestion.map(|s| s.into()).unwrap_or(
+                                    fluent::lint::builtin_deprecated_attr_default_suggestion,
+                                ),
+                                "",
+                                Applicability::MachineApplicable,
+                            )
+                            .emit();
+                    });
                 }
                 return;
             }
         }
         if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) {
-            let path_str = pprust::path_to_string(&attr.get_normal_item().path);
-            let msg = format!("use of deprecated attribute `{}`: no longer used.", path_str);
-            lint_deprecated_attr(cx, attr, &msg, None);
+            cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
+                lint.build(fluent::lint::builtin_deprecated_attr_used)
+                    .set_arg("name", pprust::path_to_string(&attr.get_normal_item().path))
+                    .span_suggestion_short(
+                        attr.span,
+                        fluent::lint::builtin_deprecated_attr_default_suggestion,
+                        "",
+                        Applicability::MachineApplicable,
+                    )
+                    .emit();
+            });
         }
     }
 }
@@ -1049,17 +1029,15 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
 
         if is_doc_comment || attr.has_name(sym::doc) {
             cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| {
-                let mut err = lint.build("unused doc comment");
-                err.span_label(
-                    node_span,
-                    format!("rustdoc does not generate documentation for {}", node_kind),
-                );
+                let mut err = lint.build(fluent::lint::builtin_unused_doc_comment);
+                err.set_arg("kind", node_kind);
+                err.span_label(node_span, fluent::lint::label);
                 match attr.kind {
                     AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
-                        err.help("use `//` for a plain comment");
+                        err.help(fluent::lint::plain_help);
                     }
                     AttrKind::DocComment(CommentKind::Block, _) => {
-                        err.help("use `/* */` for a plain comment");
+                        err.help(fluent::lint::block_help);
                     }
                 }
                 err.emit();
@@ -1178,10 +1156,10 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                     GenericParamKind::Lifetime { .. } => {}
                     GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
                         cx.struct_span_lint(NO_MANGLE_GENERIC_ITEMS, span, |lint| {
-                            lint.build("functions generic over types or consts must be mangled")
+                            lint.build(fluent::lint::builtin_no_mangle_generic)
                                 .span_suggestion_short(
                                     no_mangle_attr.span,
-                                    "remove this attribute",
+                                    fluent::lint::suggestion,
                                     "",
                                     // Use of `#[no_mangle]` suggests FFI intent; correct
                                     // fix may be to monomorphize source by hand
@@ -1205,8 +1183,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                     // Const items do not refer to a particular location in memory, and therefore
                     // don't have anything to attach a symbol to
                     cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, |lint| {
-                        let msg = "const items should never be `#[no_mangle]`";
-                        let mut err = lint.build(msg);
+                        let mut err = lint.build(fluent::lint::builtin_const_no_mangle);
 
                         // account for "pub const" (#45562)
                         let start = cx
@@ -1220,7 +1197,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                         let const_span = it.span.with_hi(BytePos(it.span.lo().0 + start + 5));
                         err.span_suggestion(
                             const_span,
-                            "try a static value",
+                            fluent::lint::suggestion,
                             "pub static",
                             Applicability::MachineApplicable,
                         );
@@ -1285,10 +1262,8 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
             get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind()))
         {
             if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not {
-                let msg = "transmuting &T to &mut T is undefined behavior, \
-                    even if the reference is unused, consider instead using an UnsafeCell";
                 cx.struct_span_lint(MUTABLE_TRANSMUTES, expr.span, |lint| {
-                    lint.build(msg).emit();
+                    lint.build(fluent::lint::builtin_mutable_transmutes).emit();
                 });
             }
         }
@@ -1338,7 +1313,7 @@ impl<'tcx> LateLintPass<'tcx> for UnstableFeatures {
             if let Some(items) = attr.meta_item_list() {
                 for item in items {
                     cx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| {
-                        lint.build("unstable feature").emit();
+                        lint.build(fluent::lint::builtin_unstable_features).emit();
                     });
                 }
             }
@@ -1400,16 +1375,17 @@ impl UnreachablePub {
             }
             let def_span = cx.tcx.sess.source_map().guess_head_span(span);
             cx.struct_span_lint(UNREACHABLE_PUB, def_span, |lint| {
-                let mut err = lint.build(&format!("unreachable `pub` {}", what));
+                let mut err = lint.build(fluent::lint::builtin_unreachable_pub);
+                err.set_arg("what", what);
 
                 err.span_suggestion(
                     vis_span,
-                    "consider restricting its visibility",
+                    fluent::lint::suggestion,
                     "pub(crate)",
                     applicability,
                 );
                 if exportable {
-                    err.help("or consider exporting it for use by other crates");
+                    err.help(fluent::lint::help);
                 }
                 err.emit();
             });
@@ -1513,11 +1489,7 @@ impl TypeAliasBounds {
         impl Visitor<'_> for WalkAssocTypes<'_> {
             fn visit_qpath(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, span: Span) {
                 if TypeAliasBounds::is_type_variable_assoc(qpath) {
-                    self.err.span_help(
-                        span,
-                        "use fully disambiguated paths (i.e., `<T as Trait>::Assoc`) to refer to \
-                         associated types in type aliases",
-                    );
+                    self.err.span_help(span, fluent::lint::builtin_type_alias_bounds_help);
                 }
                 intravisit::walk_qpath(self, qpath, id, span)
             }
@@ -1561,11 +1533,11 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
         let mut suggested_changing_assoc_types = false;
         if !where_spans.is_empty() {
             cx.lint(TYPE_ALIAS_BOUNDS, |lint| {
-                let mut err = lint.build("where clauses are not enforced in type aliases");
+                let mut err = lint.build(fluent::lint::builtin_type_alias_where_clause);
                 err.set_span(where_spans);
                 err.span_suggestion(
                     type_alias_generics.where_clause_span,
-                    "the clause will not be checked when the type alias is used, and should be removed",
+                    fluent::lint::suggestion,
                     "",
                     Applicability::MachineApplicable,
                 );
@@ -1579,11 +1551,10 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
 
         if !inline_spans.is_empty() {
             cx.lint(TYPE_ALIAS_BOUNDS, |lint| {
-                let mut err =
-                    lint.build("bounds on generic parameters are not enforced in type aliases");
+                let mut err = lint.build(fluent::lint::builtin_type_alias_generic_bounds);
                 err.set_span(inline_spans);
                 err.multipart_suggestion(
-                    "the bound will not be checked when the type alias is used, and should be removed",
+                    fluent::lint::suggestion,
                     inline_sugg,
                     Applicability::MachineApplicable,
                 );
@@ -1664,7 +1635,7 @@ declare_lint_pass!(
 
 impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
-        use rustc_middle::ty::fold::TypeFoldable;
+        use rustc_middle::ty::visit::TypeVisitable;
         use rustc_middle::ty::PredicateKind::*;
 
         if cx.tcx.features().trivial_bounds {
@@ -1690,12 +1661,10 @@ impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
                 };
                 if predicate.is_global() {
                     cx.struct_span_lint(TRIVIAL_BOUNDS, span, |lint| {
-                        lint.build(&format!(
-                            "{} bound {} does not depend on any type \
-                                or lifetime parameters",
-                            predicate_kind_name, predicate
-                        ))
-                        .emit();
+                        lint.build(fluent::lint::builtin_trivial_bounds)
+                            .set_arg("predicate_kind_name", predicate_kind_name)
+                            .set_arg("predicate", predicate)
+                            .emit();
                     });
                 }
             }
@@ -1796,8 +1765,8 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
         };
 
         if let Some((start, end, join)) = endpoints {
-            let msg = "`...` range patterns are deprecated";
-            let suggestion = "use `..=` for an inclusive range";
+            let msg = fluent::lint::builtin_ellipsis_inclusive_range_patterns;
+            let suggestion = fluent::lint::suggestion;
             if parenthesise {
                 self.node_id = Some(pat.id);
                 let end = expr_to_string(&end);
@@ -1806,8 +1775,11 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
                     None => format!("&(..={})", end),
                 };
                 if join.edition() >= Edition::Edition2021 {
-                    let mut err =
-                        rustc_errors::struct_span_err!(cx.sess(), pat.span, E0783, "{}", msg,);
+                    let mut err = cx.sess().struct_span_err_with_code(
+                        pat.span,
+                        msg,
+                        rustc_errors::error_code!(E0783),
+                    );
                     err.span_suggestion(
                         pat.span,
                         suggestion,
@@ -1830,8 +1802,11 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
             } else {
                 let replace = "..=";
                 if join.edition() >= Edition::Edition2021 {
-                    let mut err =
-                        rustc_errors::struct_span_err!(cx.sess(), pat.span, E0783, "{}", msg,);
+                    let mut err = cx.sess().struct_span_err_with_code(
+                        pat.span,
+                        msg,
+                        rustc_errors::error_code!(E0783),
+                    );
                     err.span_suggestion_short(
                         join,
                         suggestion,
@@ -1930,7 +1905,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems {
         let attrs = cx.tcx.hir().attrs(it.hir_id());
         if let Some(attr) = cx.sess().find_by_name(attrs, sym::rustc_test_marker) {
             cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| {
-                lint.build("cannot test inner items").emit();
+                lint.build(fluent::lint::builtin_unnameable_test_items).emit();
             });
         }
     }
@@ -2048,10 +2023,12 @@ impl KeywordIdents {
         }
 
         cx.struct_span_lint(KEYWORD_IDENTS, ident.span, |lint| {
-            lint.build(&format!("`{}` is a keyword in the {} edition", ident, next_edition))
+            lint.build(fluent::lint::builtin_keyword_idents)
+                .set_arg("kw", ident.clone())
+                .set_arg("next", next_edition)
                 .span_suggestion(
                     ident.span,
-                    "you can use a raw identifier to stay compatible",
+                    fluent::lint::suggestion,
                     format!("r#{}", ident),
                     Applicability::MachineApplicable,
                 )
@@ -2301,13 +2278,10 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
 
             if !lint_spans.is_empty() {
                 cx.struct_span_lint(EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), |lint| {
-                    lint.build("outlives requirements can be inferred")
+                    lint.build(fluent::lint::builtin_explicit_outlives)
+                        .set_arg("count", bound_count)
                         .multipart_suggestion(
-                            if bound_count == 1 {
-                                "remove this bound"
-                            } else {
-                                "remove these bounds"
-                            },
+                            fluent::lint::suggestion,
                             lint_spans
                                 .into_iter()
                                 .map(|span| (span, String::new()))
@@ -2363,23 +2337,14 @@ impl EarlyLintPass for IncompleteFeatures {
             .filter(|(&name, _)| features.incomplete(name))
             .for_each(|(&name, &span)| {
                 cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| {
-                    let mut builder = lint.build(&format!(
-                        "the feature `{}` is incomplete and may not be safe to use \
-                         and/or cause compiler crashes",
-                        name,
-                    ));
+                    let mut builder = lint.build(fluent::lint::builtin_incomplete_features);
+                    builder.set_arg("name", name);
                     if let Some(n) = rustc_feature::find_feature_issue(name, GateIssue::Language) {
-                        builder.note(&format!(
-                            "see issue #{} <https://github.com/rust-lang/rust/issues/{}> \
-                             for more information",
-                            n, n,
-                        ));
+                        builder.set_arg("n", n);
+                        builder.note(fluent::lint::note);
                     }
                     if HAS_MIN_FEATURES.contains(&name) {
-                        builder.help(&format!(
-                            "consider using `min_{}` instead, which is more stable and complete",
-                            name,
-                        ));
+                        builder.help(fluent::lint::help);
                     }
                     builder.emit();
                 })
@@ -2620,6 +2585,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
             if let Some((msg, span)) =
                 with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init))
             {
+                // FIXME(davidtwco): make translatable
                 cx.struct_span_lint(INVALID_VALUE, expr.span, |lint| {
                     let mut err = lint.build(&format!(
                         "the type `{}` does not permit {}",
@@ -2996,23 +2962,19 @@ impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations {
                             let mut found_str = DiagnosticStyledString::new();
                             found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true);
 
-                            lint.build(&format!(
-                                "`{}` redeclare{} with a different signature",
-                                this_fi.ident.name,
-                                if orig.get_name() == this_fi.ident.name {
-                                    "d".to_string()
-                                } else {
-                                    format!("s `{}`", orig.get_name())
-                                }
-                            ))
+                            lint.build(if orig.get_name() == this_fi.ident.name {
+                                fluent::lint::builtin_clashing_extern_same_name
+                            } else {
+                                fluent::lint::builtin_clashing_extern_diff_name
+                            })
+                            .set_arg("this_fi", this_fi.ident.name)
+                            .set_arg("orig", orig.get_name())
                             .span_label(
                                 get_relevant_span(orig_fi),
-                                &format!("`{}` previously declared here", orig.get_name()),
-                            )
-                            .span_label(
-                                get_relevant_span(this_fi),
-                                "this signature doesn't match the previous declaration",
+                                fluent::lint::previous_decl_label,
                             )
+                            .span_label(get_relevant_span(this_fi), fluent::lint::mismatch_label)
+                            // FIXME(davidtwco): translatable expected/found
                             .note_expected_found(&"", expected_str, &"", found_str)
                             .emit();
                         },
@@ -3096,8 +3058,8 @@ impl<'tcx> LateLintPass<'tcx> for DerefNullPtr {
         if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind {
             if is_null_ptr(cx, expr_deref) {
                 cx.struct_span_lint(DEREF_NULLPTR, expr.span, |lint| {
-                    let mut err = lint.build("dereferencing a null pointer");
-                    err.span_label(expr.span, "this code causes undefined behavior when executed");
+                    let mut err = lint.build(fluent::lint::builtin_deref_nullptr);
+                    err.span_label(expr.span, fluent::lint::label);
                     err.emit();
                 });
             }
@@ -3210,9 +3172,7 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
                             NAMED_ASM_LABELS,
                             Some(target_spans),
                             |diag| {
-                                let mut err =
-                                    diag.build("avoid using named labels in inline assembly");
-                                err.emit();
+                                diag.build(fluent::lint::builtin_asm_labels).emit();
                             },
                             BuiltinLintDiagnostics::NamedAsmLabel(
                                 "only local labels of the form `<number>:` should be used in inline asm"
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index eeb66f2d738..83328093e9f 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -22,12 +22,13 @@ use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync;
 use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err};
-use rustc_errors::{Applicability, MultiSpan, SuggestionStyle};
+use rustc_errors::{
+    Applicability, DecorateLint, LintDiagnosticBuilder, MultiSpan, SuggestionStyle,
+};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{CrateNum, DefId};
 use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
-use rustc_middle::lint::LintDiagnosticBuilder;
 use rustc_middle::middle::privacy::AccessLevels;
 use rustc_middle::middle::stability;
 use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
@@ -871,6 +872,17 @@ pub trait LintContext: Sized {
         decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
     );
 
+    /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`,
+    /// typically generated by `#[derive(LintDiagnostic)]`).
+    fn emit_spanned_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: S,
+        decorator: impl for<'a> DecorateLint<'a, ()>,
+    ) {
+        self.lookup(lint, Some(span), |diag| decorator.decorate_lint(diag));
+    }
+
     fn struct_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
@@ -879,6 +891,13 @@ pub trait LintContext: Sized {
     ) {
         self.lookup(lint, Some(span), decorate);
     }
+
+    /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically
+    /// generated by `#[derive(LintDiagnostic)]`).
+    fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) {
+        self.lookup(lint, None as Option<Span>, |diag| decorator.decorate_lint(diag));
+    }
+
     /// Emit a lint at the appropriate level, with no associated span.
     fn lint(
         &self,
diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
index c5e15a88fdf..f41ee640499 100644
--- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
+++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
@@ -1,6 +1,7 @@
 use crate::{context::LintContext, LateContext, LateLintPass};
+use rustc_errors::fluent;
 use rustc_hir as hir;
-use rustc_middle::ty::{fold::TypeFoldable, Ty};
+use rustc_middle::ty::{visit::TypeVisitable, Ty};
 use rustc_span::{symbol::sym, Span};
 
 declare_lint! {
@@ -51,19 +52,9 @@ fn enforce_mem_discriminant(
     if is_non_enum(ty_param) {
         cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| {
             builder
-                .build(
-                    "the return value of `mem::discriminant` is \
-                        unspecified when called with a non-enum type",
-                )
-                .span_note(
-                    args_span,
-                    &format!(
-                        "the argument to `discriminant` should be a \
-                            reference to an enum, but it was passed \
-                            a reference to a `{}`, which is not an enum.",
-                        ty_param,
-                    ),
-                )
+                .build(fluent::lint::enum_intrinsics_mem_discriminant)
+                .set_arg("ty_param", ty_param)
+                .span_note(args_span, fluent::lint::note)
                 .emit();
         });
     }
@@ -74,16 +65,9 @@ fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, sp
     if is_non_enum(ty_param) {
         cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| {
             builder
-                .build(
-                    "the return value of `mem::variant_count` is \
-                        unspecified when called with a non-enum type",
-                )
-                .note(&format!(
-                    "the type parameter of `variant_count` should \
-                            be an enum, but it was instantiated with \
-                            the type `{}`, which is not an enum.",
-                    ty_param,
-                ))
+                .build(fluent::lint::enum_intrinsics_mem_variant)
+                .set_arg("ty_param", ty_param)
+                .note(fluent::lint::note)
                 .emit();
         });
     }
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index 95e3125045d..699e8154318 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -1,4 +1,5 @@
 use crate::builtin;
+use rustc_errors::fluent;
 use rustc_hir::HirId;
 use rustc_middle::ty::query::Providers;
 use rustc_middle::{lint::LintExpectation, ty::TyCtxt};
@@ -43,13 +44,13 @@ fn emit_unfulfilled_expectation_lint(
         hir_id,
         expectation.emission_span,
         |diag| {
-            let mut diag = diag.build("this lint expectation is unfulfilled");
+            let mut diag = diag.build(fluent::lint::expectation);
             if let Some(rationale) = expectation.reason {
                 diag.note(rationale.as_str());
             }
 
             if expectation.is_unfulfilled_lint_expectations {
-                diag.note("the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message");
+                diag.note(fluent::lint::note);
             }
 
             diag.emit();
diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
index fc99d759a03..fe2712525ee 100644
--- a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
+++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
@@ -1,7 +1,7 @@
 use crate::{EarlyContext, EarlyLintPass, LintContext};
 use ast::util::unicode::{contains_text_flow_control_chars, TEXT_FLOW_CONTROL_CHARS};
 use rustc_ast as ast;
-use rustc_errors::{Applicability, SuggestionStyle};
+use rustc_errors::{fluent, Applicability, SuggestionStyle};
 use rustc_span::{BytePos, Span, Symbol};
 
 declare_lint! {
@@ -61,41 +61,25 @@ impl HiddenUnicodeCodepoints {
             .collect();
 
         cx.struct_span_lint(TEXT_DIRECTION_CODEPOINT_IN_LITERAL, span, |lint| {
-            let mut err = lint.build(&format!(
-                "unicode codepoint changing visible direction of text present in {}",
-                label
-            ));
-            let (an, s) = match spans.len() {
-                1 => ("an ", ""),
-                _ => ("", "s"),
-            };
-            err.span_label(
-                span,
-                &format!(
-                    "this {} contains {}invisible unicode text flow control codepoint{}",
-                    label, an, s,
-                ),
-            );
+            let mut err = lint.build(fluent::lint::hidden_unicode_codepoints);
+            err.set_arg("label", label);
+            err.set_arg("count", spans.len());
+            err.span_label(span, fluent::lint::label);
+            err.note(fluent::lint::note);
             if point_at_inner_spans {
                 for (c, span) in &spans {
                     err.span_label(*span, format!("{:?}", c));
                 }
             }
-            err.note(
-                "these kind of unicode codepoints change the way text flows on applications that \
-                 support them, but can cause confusion because they change the order of \
-                 characters on the screen",
-            );
             if point_at_inner_spans && !spans.is_empty() {
                 err.multipart_suggestion_with_style(
-                    "if their presence wasn't intentional, you can remove them",
+                    fluent::lint::suggestion_remove,
                     spans.iter().map(|(_, span)| (*span, "".to_string())).collect(),
                     Applicability::MachineApplicable,
                     SuggestionStyle::HideCodeAlways,
                 );
                 err.multipart_suggestion(
-                    "if you want to keep them but make them visible in your source code, you can \
-                    escape them",
+                    fluent::lint::suggestion_escape,
                     spans
                         .into_iter()
                         .map(|(c, span)| {
@@ -109,16 +93,16 @@ impl HiddenUnicodeCodepoints {
                 // FIXME: in other suggestions we've reversed the inner spans of doc comments. We
                 // should do the same here to provide the same good suggestions as we do for
                 // literals above.
-                err.note("if their presence wasn't intentional, you can remove them");
-                err.note(&format!(
-                    "if you want to keep them but make them visible in your source code, you can \
-                     escape them: {}",
+                err.set_arg(
+                    "escaped",
                     spans
                         .into_iter()
-                        .map(|(c, _)| { format!("{:?}", c) })
+                        .map(|(c, _)| format!("{:?}", c))
                         .collect::<Vec<String>>()
                         .join(", "),
-                ));
+                );
+                err.note(fluent::lint::suggestion_remove);
+                err.note(fluent::lint::no_suggestion_note_escape);
             }
             err.emit();
         });
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
index 56c8635a189..738f475983e 100644
--- a/compiler/rustc_lint/src/internal.rs
+++ b/compiler/rustc_lint/src/internal.rs
@@ -3,7 +3,7 @@
 
 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
-use rustc_errors::Applicability;
+use rustc_errors::{fluent, Applicability};
 use rustc_hir::def::Res;
 use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
 use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
@@ -36,13 +36,10 @@ impl LateLintPass<'_> for DefaultHashTypes {
             _ => return,
         };
         cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
-            let msg = format!(
-                "prefer `{}` over `{}`, it has better performance",
-                replace,
-                cx.tcx.item_name(def_id)
-            );
-            lint.build(&msg)
-                .note(&format!("a `use rustc_data_structures::fx::{}` may be necessary", replace))
+            lint.build(fluent::lint::default_hash_types)
+                .set_arg("preferred", replace)
+                .set_arg("used", cx.tcx.item_name(def_id))
+                .note(fluent::lint::note)
                 .emit();
         });
     }
@@ -99,12 +96,9 @@ impl LateLintPass<'_> for QueryStability {
             let def_id = instance.def_id();
             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
                 cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| {
-                    let msg = format!(
-                        "using `{}` can result in unstable query results",
-                        cx.tcx.item_name(def_id)
-                    );
-                    lint.build(&msg)
-                        .note("if you believe this case to be fine, allow this lint and add a comment explaining your rationale")
+                    lint.build(fluent::lint::query_instability)
+                        .set_arg("query", cx.tcx.item_name(def_id))
+                        .note(fluent::lint::note)
                         .emit();
                 })
             }
@@ -146,10 +140,10 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                 segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
             );
             cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
-                lint.build("usage of `ty::TyKind::<kind>`")
+                lint.build(fluent::lint::tykind_kind)
                     .span_suggestion(
                         span,
-                        "try using `ty::<kind>` directly",
+                        fluent::lint::suggestion,
                         "ty",
                         Applicability::MaybeIncorrect, // ty maybe needs an import
                     )
@@ -175,10 +169,10 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
                                     && qpath_ty.hir_id == ty.hir_id
                                 {
-                                    lint.build("usage of `ty::TyKind::<kind>`")
+                                    lint.build(fluent::lint::tykind_kind)
                                         .span_suggestion(
                                             path.span,
-                                            "try using `ty::<kind>` directly",
+                                            fluent::lint::suggestion,
                                             "ty",
                                             Applicability::MaybeIncorrect, // ty maybe needs an import
                                         )
@@ -193,10 +187,10 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
                                     && qpath_ty.hir_id == ty.hir_id
                                 {
-                                    lint.build("usage of `ty::TyKind::<kind>`")
+                                    lint.build(fluent::lint::tykind_kind)
                                         .span_suggestion(
                                             path.span,
-                                            "try using `ty::<kind>` directly",
+                                            fluent::lint::suggestion,
                                             "ty",
                                             Applicability::MaybeIncorrect, // ty maybe needs an import
                                         )
@@ -213,10 +207,10 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
                                     && qpath_ty.hir_id == ty.hir_id
                                 {
-                                    lint.build("usage of `ty::TyKind::<kind>`")
+                                    lint.build(fluent::lint::tykind_kind)
                                         .span_suggestion(
                                             path.span,
-                                            "try using `ty::<kind>` directly",
+                                            fluent::lint::suggestion,
                                             "ty",
                                             Applicability::MaybeIncorrect, // ty maybe needs an import
                                         )
@@ -226,15 +220,16 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                             }
                             _ => {}
                         }
-                        lint.build("usage of `ty::TyKind`").help("try using `Ty` instead").emit();
+                        lint.build(fluent::lint::tykind).help(fluent::lint::help).emit();
                     })
                 } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
                     if path.segments.len() > 1 {
                         cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
-                            lint.build(&format!("usage of qualified `ty::{}`", t))
+                            lint.build(fluent::lint::ty_qualified)
+                                .set_arg("ty", t.clone())
                                 .span_suggestion(
                                     path.span,
-                                    "try importing it and using it unqualified",
+                                    fluent::lint::suggestion,
                                     t,
                                     // The import probably needs to be changed
                                     Applicability::MaybeIncorrect,
@@ -330,8 +325,8 @@ impl EarlyLintPass for LintPassImpl {
                             LINT_PASS_IMPL_WITHOUT_MACRO,
                             lint_pass.path.span,
                             |lint| {
-                                lint.build("implementing `LintPass` by hand")
-                                    .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
+                                lint.build(fluent::lint::lintpass_by_hand)
+                                    .help(fluent::lint::help)
                                     .emit();
                             },
                         )
@@ -371,13 +366,10 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
                             return;
                         }
                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
-                            lint.build(&format!(
-                                "Found non-existing keyword `{}` used in \
-                                     `#[doc(keyword = \"...\")]`",
-                                v,
-                            ))
-                            .help("only existing keywords are allowed in core/std")
-                            .emit();
+                            lint.build(fluent::lint::non_existant_doc_keyword)
+                                .set_arg("keyword", v)
+                                .help(fluent::lint::help)
+                                .emit();
                         });
                     }
                 }
@@ -422,7 +414,7 @@ impl LateLintPass<'_> for Diagnostics {
                 let Impl { of_trait: Some(of_trait), .. } = impl_ &&
                 let Some(def_id) = of_trait.trait_def_id() &&
                 let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
-                matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic)
+                matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic | sym::DecorateLint)
             {
                 found_impl = true;
                 break;
@@ -431,8 +423,7 @@ impl LateLintPass<'_> for Diagnostics {
         debug!(?found_impl);
         if !found_impl {
             cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
-                lint.build("diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls")
-                    .emit();
+                lint.build(fluent::lint::diag_out_of_impl).emit();
             })
         }
 
@@ -450,7 +441,7 @@ impl LateLintPass<'_> for Diagnostics {
         debug!(?found_diagnostic_message);
         if !found_diagnostic_message {
             cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
-                lint.build("diagnostics should be created using translatable messages").emit();
+                lint.build(fluent::lint::untranslatable_diag).emit();
             })
         }
     }
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 4773feded12..00e96f20d1a 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -3,13 +3,13 @@ use crate::late::unerased_lint_store;
 use rustc_ast as ast;
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{struct_span_err, Applicability, Diagnostic, MultiSpan};
+use rustc_errors::{struct_span_err, Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::{intravisit, HirId};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::lint::{
-    struct_lint_level, LevelAndSource, LintDiagnosticBuilder, LintExpectation, LintLevelMap,
-    LintLevelSets, LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
+    struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets,
+    LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
 };
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{RegisteredTools, TyCtxt};
@@ -521,7 +521,7 @@ impl<'s> LintLevelsBuilder<'s> {
                             src,
                             Some(sp.into()),
                             |lint| {
-                                let mut err = lint.build(&msg);
+                                let mut err = lint.build(msg);
                                 if let Some(new_name) = &renamed {
                                     err.span_suggestion(
                                         sp,
@@ -548,7 +548,7 @@ impl<'s> LintLevelsBuilder<'s> {
                             } else {
                                 name.to_string()
                             };
-                            let mut db = lint.build(&format!("unknown lint: `{}`", name));
+                            let mut db = lint.build(format!("unknown lint: `{}`", name));
                             if let Some(suggestion) = suggestion {
                                 db.span_suggestion(
                                     sp,
diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs
index b6a45676a30..ff5a01749c1 100644
--- a/compiler/rustc_lint/src/methods.rs
+++ b/compiler/rustc_lint/src/methods.rs
@@ -1,6 +1,7 @@
 use crate::LateContext;
 use crate::LateLintPass;
 use crate::LintContext;
+use rustc_errors::fluent;
 use rustc_hir::{Expr, ExprKind, PathSegment};
 use rustc_middle::ty;
 use rustc_span::{symbol::sym, ExpnKind, Span};
@@ -88,16 +89,12 @@ fn lint_cstring_as_ptr(
             if let ty::Adt(adt, _) = substs.type_at(0).kind() {
                 if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) {
                     cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, as_ptr_span, |diag| {
-                        let mut diag = diag
-                            .build("getting the inner pointer of a temporary `CString`");
-                        diag.span_label(as_ptr_span, "this pointer will be invalid");
-                        diag.span_label(
-                            unwrap.span,
-                            "this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime",
-                        );
-                        diag.note("pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned");
-                        diag.help("for more information, see https://doc.rust-lang.org/reference/destructors.html");
-                        diag.emit();
+                        diag.build(fluent::lint::cstring_ptr)
+                            .span_label(as_ptr_span, fluent::lint::as_ptr_label)
+                            .span_label(unwrap.span, fluent::lint::unwrap_label)
+                            .note(fluent::lint::note)
+                            .help(fluent::lint::help)
+                            .emit();
                     });
                 }
             }
diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs
index 6182d2b10ed..764003e61a6 100644
--- a/compiler/rustc_lint/src/non_ascii_idents.rs
+++ b/compiler/rustc_lint/src/non_ascii_idents.rs
@@ -1,6 +1,7 @@
 use crate::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_ast as ast;
 use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::fluent;
 use rustc_span::symbol::Symbol;
 
 declare_lint! {
@@ -180,13 +181,13 @@ impl EarlyLintPass for NonAsciiIdents {
             }
             has_non_ascii_idents = true;
             cx.struct_span_lint(NON_ASCII_IDENTS, sp, |lint| {
-                lint.build("identifier contains non-ASCII characters").emit();
+                lint.build(fluent::lint::identifier_non_ascii_char).emit();
             });
             if check_uncommon_codepoints
                 && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed)
             {
                 cx.struct_span_lint(UNCOMMON_CODEPOINTS, sp, |lint| {
-                    lint.build("identifier contains uncommon Unicode codepoints").emit();
+                    lint.build(fluent::lint::identifier_uncommon_codepoints).emit();
                 })
             }
         }
@@ -216,15 +217,11 @@ impl EarlyLintPass for NonAsciiIdents {
                     .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| {
                         if !*existing_is_ascii || !is_ascii {
                             cx.struct_span_lint(CONFUSABLE_IDENTS, sp, |lint| {
-                                lint.build(&format!(
-                                    "identifier pair considered confusable between `{}` and `{}`",
-                                    existing_symbol, symbol
-                                ))
-                                .span_label(
-                                    *existing_span,
-                                    "this is where the previous identifier occurred",
-                                )
-                                .emit();
+                                lint.build(fluent::lint::confusable_identifier_pair)
+                                    .set_arg("existing_sym", *existing_symbol)
+                                    .set_arg("sym", symbol)
+                                    .span_label(*existing_span, fluent::lint::label)
+                                    .emit();
                             });
                         }
                         if *existing_is_ascii && !is_ascii {
@@ -326,18 +323,20 @@ impl EarlyLintPass for NonAsciiIdents {
 
                 for ((sp, ch_list), script_set) in lint_reports {
                     cx.struct_span_lint(MIXED_SCRIPT_CONFUSABLES, sp, |lint| {
-                        let message = format!(
-                            "the usage of Script Group `{}` in this crate consists solely of mixed script confusables",
-                            script_set);
-                        let mut note = "the usage includes ".to_string();
+                        let mut includes = String::new();
                         for (idx, ch) in ch_list.into_iter().enumerate() {
                             if idx != 0 {
-                                note += ", ";
+                                includes += ", ";
                             }
                             let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
-                            note += &char_info;
+                            includes += &char_info;
                         }
-                        lint.build(&message).note(&note).note("please recheck to make sure their usages are indeed what you want").emit();
+                        lint.build(fluent::lint::mixed_script_confusables)
+                            .set_arg("set", script_set.to_string())
+                            .set_arg("includes", includes)
+                            .note(fluent::lint::includes_note)
+                            .note(fluent::lint::note)
+                            .emit();
                     });
                 }
             }
diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs
index 4e7aeca9ce1..cdad2d2e8f9 100644
--- a/compiler/rustc_lint/src/non_fmt_panic.rs
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -1,6 +1,6 @@
 use crate::{LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
-use rustc_errors::{pluralize, Applicability};
+use rustc_errors::{fluent, Applicability};
 use rustc_hir as hir;
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_middle::lint::in_external_macro;
@@ -120,9 +120,10 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
     }
 
     cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
-        let mut l = lint.build("panic message is not a string literal");
-        l.note(&format!("this usage of {}!() is deprecated; it will be a hard error in Rust 2021", symbol));
-        l.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>");
+        let mut l = lint.build(fluent::lint::non_fmt_panic);
+        l.set_arg("name", symbol);
+        l.note(fluent::lint::note);
+        l.note(fluent::lint::more_info_note);
         if !is_arg_inside_call(arg_span, span) {
             // No clue where this argument is coming from.
             l.emit();
@@ -130,10 +131,10 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
         }
         if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
             // A case of `panic!(format!(..))`.
-            l.note(format!("the {}!() macro supports formatting, so there's no need for the format!() macro here", symbol).as_str());
+            l.note(fluent::lint::supports_fmt_note);
             if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                 l.multipart_suggestion(
-                    "remove the `format!(..)` macro call",
+                    fluent::lint::supports_fmt_suggestion,
                     vec![
                         (arg_span.until(open.shrink_to_hi()), "".into()),
                         (close.until(arg_span.shrink_to_hi()), "".into()),
@@ -153,12 +154,18 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
             );
 
             let (suggest_display, suggest_debug) = cx.tcx.infer_ctxt().enter(|infcx| {
-                let display = is_str || cx.tcx.get_diagnostic_item(sym::Display).map(|t| {
-                    infcx.type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env).may_apply()
-                }) == Some(true);
-                let debug = !display && cx.tcx.get_diagnostic_item(sym::Debug).map(|t| {
-                    infcx.type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env).may_apply()
-                }) == Some(true);
+                let display = is_str
+                    || cx.tcx.get_diagnostic_item(sym::Display).map(|t| {
+                        infcx
+                            .type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env)
+                            .may_apply()
+                    }) == Some(true);
+                let debug = !display
+                    && cx.tcx.get_diagnostic_item(sym::Debug).map(|t| {
+                        infcx
+                            .type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env)
+                            .may_apply()
+                    }) == Some(true);
                 (display, debug)
             });
 
@@ -175,17 +182,15 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
             if suggest_display {
                 l.span_suggestion_verbose(
                     arg_span.shrink_to_lo(),
-                    "add a \"{}\" format string to Display the message",
+                    fluent::lint::display_suggestion,
                     "\"{}\", ",
                     fmt_applicability,
                 );
             } else if suggest_debug {
+                l.set_arg("ty", ty);
                 l.span_suggestion_verbose(
                     arg_span.shrink_to_lo(),
-                    &format!(
-                        "add a \"{{:?}}\" format string to use the Debug implementation of `{}`",
-                        ty,
-                    ),
+                    fluent::lint::debug_suggestion,
                     "\"{:?}\", ",
                     fmt_applicability,
                 );
@@ -193,15 +198,9 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
 
             if suggest_panic_any {
                 if let Some((open, close, del)) = find_delimiters(cx, span) {
+                    l.set_arg("already_suggested", suggest_display || suggest_debug);
                     l.multipart_suggestion(
-                        &format!(
-                            "{}use std::panic::panic_any instead",
-                            if suggest_display || suggest_debug {
-                                "or "
-                            } else {
-                                ""
-                            },
-                        ),
+                        fluent::lint::panic_suggestion,
                         if del == '(' {
                             vec![(span.until(open), "std::panic::panic_any".into())]
                         } else {
@@ -260,21 +259,19 @@ fn check_panic_str<'tcx>(
                 .collect(),
         };
         cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
-            let mut l = lint.build(match n_arguments {
-                1 => "panic message contains an unused formatting placeholder",
-                _ => "panic message contains unused formatting placeholders",
-            });
-            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
+            let mut l = lint.build(fluent::lint::non_fmt_panic_unused);
+            l.set_arg("count", n_arguments);
+            l.note(fluent::lint::note);
             if is_arg_inside_call(arg.span, span) {
                 l.span_suggestion(
                     arg.span.shrink_to_hi(),
-                    &format!("add the missing argument{}", pluralize!(n_arguments)),
+                    fluent::lint::add_args_suggestion,
                     ", ...",
                     Applicability::HasPlaceholders,
                 );
                 l.span_suggestion(
                     arg.span.shrink_to_lo(),
-                    "or add a \"{}\" format string to use the message literally",
+                    fluent::lint::add_fmt_suggestion,
                     "\"{}\", ",
                     Applicability::MachineApplicable,
                 );
@@ -289,17 +286,15 @@ fn check_panic_str<'tcx>(
                     .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                     .collect()
             });
-        let msg = match &brace_spans {
-            Some(v) if v.len() == 1 => "panic message contains a brace",
-            _ => "panic message contains braces",
-        };
+        let count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2);
         cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
-            let mut l = lint.build(msg);
-            l.note("this message is not used as a format string, but will be in Rust 2021");
+            let mut l = lint.build(fluent::lint::non_fmt_panic_braces);
+            l.set_arg("count", count);
+            l.note(fluent::lint::note);
             if is_arg_inside_call(arg.span, span) {
                 l.span_suggestion(
                     arg.span.shrink_to_lo(),
-                    "add a \"{}\" format string to use the message literally",
+                    fluent::lint::suggestion,
                     "\"{}\", ",
                     Applicability::MachineApplicable,
                 );
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
index e1507d0fbb4..33ac2ed02aa 100644
--- a/compiler/rustc_lint/src/nonstandard_style.rs
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -1,7 +1,7 @@
 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
 use rustc_attr as attr;
-use rustc_errors::Applicability;
+use rustc_errors::{fluent, Applicability};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::intravisit::FnKind;
@@ -137,22 +137,23 @@ impl NonCamelCaseTypes {
 
         if !is_camel_case(name) {
             cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| {
-                let msg = format!("{} `{}` should have an upper camel case name", sort, name);
-                let mut err = lint.build(&msg);
+                let mut err = lint.build(fluent::lint::non_camel_case_type);
                 let cc = to_camel_case(name);
                 // We cannot provide meaningful suggestions
                 // if the characters are in the category of "Lowercase Letter".
                 if *name != cc {
                     err.span_suggestion(
                         ident.span,
-                        "convert the identifier to upper camel case",
+                        fluent::lint::suggestion,
                         to_camel_case(name),
                         Applicability::MaybeIncorrect,
                     );
                 } else {
-                    err.span_label(ident.span, "should have an UpperCamelCase name");
+                    err.span_label(ident.span, fluent::lint::label);
                 }
 
+                err.set_arg("sort", sort);
+                err.set_arg("name", name);
                 err.emit();
             })
         }
@@ -281,11 +282,10 @@ impl NonSnakeCase {
         if !is_snake_case(name) {
             cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| {
                 let sc = NonSnakeCase::to_snake_case(name);
-                let msg = format!("{} `{}` should have a snake case name", sort, name);
-                let mut err = lint.build(&msg);
+                let mut err = lint.build(fluent::lint::non_snake_case);
                 // We cannot provide meaningful suggestions
                 // if the characters are in the category of "Uppercase Letter".
-                if *name != sc {
+                if name != sc {
                     // We have a valid span in almost all cases, but we don't have one when linting a crate
                     // name provided via the command line.
                     if !ident.span.is_dummy() {
@@ -295,13 +295,13 @@ impl NonSnakeCase {
                             // Instead, recommend renaming the identifier entirely or, if permitted,
                             // escaping it to create a raw identifier.
                             if sc_ident.name.can_be_raw() {
-                                ("rename the identifier or convert it to a snake case raw identifier", sc_ident.to_string())
+                                (fluent::lint::rename_or_convert_suggestion, sc_ident.to_string())
                             } else {
-                                err.note(&format!("`{}` cannot be used as a raw identifier", sc));
-                                ("rename the identifier", String::new())
+                                err.note(fluent::lint::cannot_convert_note);
+                                (fluent::lint::rename_suggestion, String::new())
                             }
                         } else {
-                            ("convert the identifier to snake case", sc)
+                            (fluent::lint::convert_suggestion, sc.clone())
                         };
 
                         err.span_suggestion(
@@ -311,12 +311,15 @@ impl NonSnakeCase {
                             Applicability::MaybeIncorrect,
                         );
                     } else {
-                        err.help(&format!("convert the identifier to snake case: `{}`", sc));
+                        err.help(fluent::lint::help);
                     }
                 } else {
-                    err.span_label(ident.span, "should have a snake_case name");
+                    err.span_label(ident.span, fluent::lint::label);
                 }
 
+                err.set_arg("sort", sort);
+                err.set_arg("name", name);
+                err.set_arg("sc", sc);
                 err.emit();
             });
         }
@@ -488,21 +491,22 @@ impl NonUpperCaseGlobals {
         if name.chars().any(|c| c.is_lowercase()) {
             cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| {
                 let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
-                let mut err =
-                    lint.build(&format!("{} `{}` should have an upper case name", sort, name));
+                let mut err = lint.build(fluent::lint::non_upper_case_global);
                 // We cannot provide meaningful suggestions
                 // if the characters are in the category of "Lowercase Letter".
                 if *name != uc {
                     err.span_suggestion(
                         ident.span,
-                        "convert the identifier to upper case",
+                        fluent::lint::suggestion,
                         uc,
                         Applicability::MaybeIncorrect,
                     );
                 } else {
-                    err.span_label(ident.span, "should have an UPPER_CASE name");
+                    err.span_label(ident.span, fluent::lint::label);
                 }
 
+                err.set_arg("sort", sort);
+                err.set_arg("name", name);
                 err.emit();
             })
         }
diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs
index 675bee738a6..11a752ff097 100644
--- a/compiler/rustc_lint/src/noop_method_call.rs
+++ b/compiler/rustc_lint/src/noop_method_call.rs
@@ -1,7 +1,8 @@
 use crate::context::LintContext;
-use crate::rustc_middle::ty::TypeFoldable;
+use crate::rustc_middle::ty::TypeVisitable;
 use crate::LateContext;
 use crate::LateLintPass;
+use rustc_errors::fluent;
 use rustc_hir::def::DefKind;
 use rustc_hir::{Expr, ExprKind};
 use rustc_middle::ty;
@@ -80,7 +81,6 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
         ) {
             return;
         }
-        let method = &call.ident.name;
         let receiver = &elements[0];
         let receiver_ty = cx.typeck_results().expr_ty(receiver);
         let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
@@ -90,19 +90,14 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
             return;
         }
         let expr_span = expr.span;
-        let note = format!(
-            "the type `{:?}` which `{}` is being called on is the same as \
-             the type returned from `{}`, so the method call does not do \
-             anything and can be removed",
-            receiver_ty, method, method,
-        );
-
         let span = expr_span.with_lo(receiver.span.hi());
         cx.struct_span_lint(NOOP_METHOD_CALL, span, |lint| {
-            let method = &call.ident.name;
-            let message =
-                format!("call to `.{}()` on a reference in this situation does nothing", &method,);
-            lint.build(&message).span_label(span, "unnecessary method call").note(&note).emit();
+            lint.build(fluent::lint::noop_method_call)
+                .set_arg("method", call.ident.name)
+                .set_arg("receiver_ty", receiver_ty)
+                .span_label(span, fluent::lint::label)
+                .note(fluent::lint::note)
+                .emit();
         });
     }
 }
diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs
index 2c8b41d7214..af5e5faf1f5 100644
--- a/compiler/rustc_lint/src/pass_by_value.rs
+++ b/compiler/rustc_lint/src/pass_by_value.rs
@@ -1,5 +1,5 @@
 use crate::{LateContext, LateLintPass, LintContext};
-use rustc_errors::Applicability;
+use rustc_errors::{fluent, Applicability};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::{GenericArg, PathSegment, QPath, TyKind};
@@ -30,10 +30,11 @@ impl<'tcx> LateLintPass<'tcx> for PassByValue {
                 }
                 if let Some(t) = path_for_pass_by_value(cx, &inner_ty) {
                     cx.struct_span_lint(PASS_BY_VALUE, ty.span, |lint| {
-                        lint.build(&format!("passing `{}` by reference", t))
+                        lint.build(fluent::lint::pass_by_value)
+                            .set_arg("ty", t.clone())
                             .span_suggestion(
                                 ty.span,
-                                "try passing by value",
+                                fluent::lint::suggestion,
                                 t,
                                 // Changing type of function argument
                                 Applicability::MaybeIncorrect,
diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs
index f06a8b8f4b0..26f41345383 100644
--- a/compiler/rustc_lint/src/redundant_semicolon.rs
+++ b/compiler/rustc_lint/src/redundant_semicolon.rs
@@ -1,6 +1,6 @@
 use crate::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_ast::{Block, StmtKind};
-use rustc_errors::Applicability;
+use rustc_errors::{fluent, Applicability};
 use rustc_span::Span;
 
 declare_lint! {
@@ -49,12 +49,10 @@ fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, boo
         }
 
         cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
-            let (msg, rem) = if multiple {
-                ("unnecessary trailing semicolons", "remove these semicolons")
-            } else {
-                ("unnecessary trailing semicolon", "remove this semicolon")
-            };
-            lint.build(msg).span_suggestion(span, rem, "", Applicability::MaybeIncorrect).emit();
+            lint.build(fluent::lint::redundant_semicolons)
+                .set_arg("multiple", multiple)
+                .span_suggestion(span, fluent::lint::suggestion, "", Applicability::MaybeIncorrect)
+                .emit();
         });
     }
 }
diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs
index 81d308ee347..df1587c5948 100644
--- a/compiler/rustc_lint/src/traits.rs
+++ b/compiler/rustc_lint/src/traits.rs
@@ -1,6 +1,7 @@
 use crate::LateContext;
 use crate::LateLintPass;
 use crate::LintContext;
+use rustc_errors::fluent;
 use rustc_hir as hir;
 use rustc_span::symbol::sym;
 
@@ -103,13 +104,10 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
                     let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
                         return
                     };
-                    let msg = format!(
-                        "bounds on `{}` are most likely incorrect, consider instead \
-                         using `{}` to detect whether a type can be trivially dropped",
-                        predicate,
-                        cx.tcx.def_path_str(needs_drop)
-                    );
-                    lint.build(&msg).emit();
+                    lint.build(fluent::lint::drop_trait_constraints)
+                        .set_arg("predicate", predicate)
+                        .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
+                        .emit();
                 });
             }
         }
@@ -126,12 +124,9 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
                     let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
                         return
                     };
-                    let msg = format!(
-                        "types that do not implement `Drop` can still have drop glue, consider \
-                        instead using `{}` to detect whether a type is trivially dropped",
-                        cx.tcx.def_path_str(needs_drop)
-                    );
-                    lint.build(&msg).emit();
+                    lint.build(fluent::lint::drop_glue)
+                        .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
+                        .emit();
                 });
             }
         }
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index 5579e4d19cf..be4843c7ff1 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -2,12 +2,13 @@ use crate::{LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
 use rustc_attr as attr;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::Applicability;
+use rustc_errors::{fluent, Applicability, DiagnosticMessage};
 use rustc_hir as hir;
 use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
+use rustc_macros::LintDiagnostic;
 use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
 use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable};
+use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
 use rustc_span::source_map;
 use rustc_span::symbol::sym;
 use rustc_span::{Span, Symbol, DUMMY_SP};
@@ -139,7 +140,8 @@ fn lint_overflowing_range_endpoint<'tcx>(
         // overflowing and only by 1.
         if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max {
             cx.struct_span_lint(OVERFLOWING_LITERALS, parent_expr.span, |lint| {
-                let mut err = lint.build(&format!("range endpoint is out of range for `{}`", ty));
+                let mut err = lint.build(fluent::lint::range_endpoint_out_of_range);
+                err.set_arg("ty", ty);
                 if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) {
                     use ast::{LitIntType, LitKind};
                     // We need to preserve the literal's suffix,
@@ -153,7 +155,7 @@ fn lint_overflowing_range_endpoint<'tcx>(
                     let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix);
                     err.span_suggestion(
                         parent_expr.span,
-                        "use an inclusive range instead",
+                        fluent::lint::suggestion,
                         suggestion,
                         Applicability::MachineApplicable,
                     );
@@ -229,38 +231,35 @@ fn report_bin_hex_error(
                 (t.name_str(), actually.to_string())
             }
         };
-        let mut err = lint.build(&format!("literal out of range for `{}`", t));
+        let mut err = lint.build(fluent::lint::overflowing_bin_hex);
         if negative {
             // If the value is negative,
             // emits a note about the value itself, apart from the literal.
-            err.note(&format!(
-                "the literal `{}` (decimal `{}`) does not fit into \
-                 the type `{}`",
-                repr_str, val, t
-            ));
-            err.note(&format!("and the value `-{}` will become `{}{}`", repr_str, actually, t));
+            err.note(fluent::lint::negative_note);
+            err.note(fluent::lint::negative_becomes_note);
         } else {
-            err.note(&format!(
-                "the literal `{}` (decimal `{}`) does not fit into \
-                 the type `{}` and will become `{}{}`",
-                repr_str, val, t, actually, t
-            ));
+            err.note(fluent::lint::positive_note);
         }
         if let Some(sugg_ty) =
             get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative)
         {
+            err.set_arg("suggestion_ty", sugg_ty);
             if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
                 let (sans_suffix, _) = repr_str.split_at(pos);
                 err.span_suggestion(
                     expr.span,
-                    &format!("consider using the type `{}` instead", sugg_ty),
+                    fluent::lint::suggestion,
                     format!("{}{}", sans_suffix, sugg_ty),
                     Applicability::MachineApplicable,
                 );
             } else {
-                err.help(&format!("consider using the type `{}` instead", sugg_ty));
+                err.help(fluent::lint::help);
             }
         }
+        err.set_arg("ty", t);
+        err.set_arg("lit", repr_str);
+        err.set_arg("dec", val);
+        err.set_arg("actually", actually);
         err.emit();
     });
 }
@@ -353,21 +352,23 @@ fn lint_int_literal<'tcx>(
         }
 
         cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
-            let mut err = lint.build(&format!("literal out of range for `{}`", t.name_str()));
-            err.note(&format!(
-                "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`",
+            let mut err = lint.build(fluent::lint::overflowing_int);
+            err.set_arg("ty", t.name_str());
+            err.set_arg(
+                "lit",
                 cx.sess()
                     .source_map()
                     .span_to_snippet(lit.span)
                     .expect("must get snippet from literal"),
-                t.name_str(),
-                min,
-                max,
-            ));
+            );
+            err.set_arg("min", min);
+            err.set_arg("max", max);
+            err.note(fluent::lint::note);
             if let Some(sugg_ty) =
                 get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
             {
-                err.help(&format!("consider using the type `{}` instead", sugg_ty));
+                err.set_arg("suggestion_ty", sugg_ty);
+                err.help(fluent::lint::help);
             }
             err.emit();
         });
@@ -395,10 +396,10 @@ fn lint_uint_literal<'tcx>(
                 hir::ExprKind::Cast(..) => {
                     if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
                         cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| {
-                            lint.build("only `u8` can be cast into `char`")
+                            lint.build(fluent::lint::only_cast_u8_to_char)
                                 .span_suggestion(
                                     par_e.span,
-                                    "use a `char` literal instead",
+                                    fluent::lint::suggestion,
                                     format!("'\\u{{{:X}}}'", lit_val),
                                     Applicability::MachineApplicable,
                                 )
@@ -429,17 +430,18 @@ fn lint_uint_literal<'tcx>(
             return;
         }
         cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
-            lint.build(&format!("literal out of range for `{}`", t.name_str()))
-                .note(&format!(
-                    "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`",
+            lint.build(fluent::lint::overflowing_uint)
+                .set_arg("ty", t.name_str())
+                .set_arg(
+                    "lit",
                     cx.sess()
                         .source_map()
                         .span_to_snippet(lit.span)
                         .expect("must get snippet from literal"),
-                    t.name_str(),
-                    min,
-                    max,
-                ))
+                )
+                .set_arg("min", min)
+                .set_arg("max", max)
+                .note(fluent::lint::note)
                 .emit();
         });
     }
@@ -471,16 +473,16 @@ fn lint_literal<'tcx>(
             };
             if is_infinite == Ok(true) {
                 cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
-                    lint.build(&format!("literal out of range for `{}`", t.name_str()))
-                        .note(&format!(
-                            "the literal `{}` does not fit into the type `{}` and will be converted to `{}::INFINITY`",
+                    lint.build(fluent::lint::overflowing_literal)
+                        .set_arg("ty", t.name_str())
+                        .set_arg(
+                            "lit",
                             cx.sess()
                                 .source_map()
                                 .span_to_snippet(lit.span)
                                 .expect("must get snippet from literal"),
-                            t.name_str(),
-                            t.name_str(),
-                        ))
+                        )
+                        .note(fluent::lint::note)
                         .emit();
                 });
             }
@@ -501,7 +503,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
             hir::ExprKind::Binary(binop, ref l, ref r) => {
                 if is_comparison(binop) && !check_limits(cx, binop, &l, &r) {
                     cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| {
-                        lint.build("comparison is useless due to type limits").emit();
+                        lint.build(fluent::lint::unused_comparisons).emit();
                     });
                 }
             }
@@ -663,7 +665,7 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
 enum FfiResult<'tcx> {
     FfiSafe,
     FfiPhantom(Ty<'tcx>),
-    FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> },
+    FfiUnsafe { ty: Ty<'tcx>, reason: DiagnosticMessage, help: Option<DiagnosticMessage> },
 }
 
 pub(crate) fn nonnull_optimization_guaranteed<'tcx>(
@@ -823,8 +825,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
             self.emit_ffi_unsafe_type_lint(
                 ty,
                 sp,
-                "passing raw arrays by value is not FFI-safe",
-                Some("consider passing a pointer to the array"),
+                fluent::lint::improper_ctypes_array_reason,
+                Some(fluent::lint::improper_ctypes_array_help),
             );
             true
         } else {
@@ -867,11 +869,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
             } else {
                 // All fields are ZSTs; this means that the type should behave
                 // like (), which is FFI-unsafe
-                FfiUnsafe {
-                    ty,
-                    reason: "this struct contains only zero-sized fields".into(),
-                    help: None,
-                }
+                FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_struct_zst, help: None }
             }
         } else {
             // We can't completely trust repr(C) markings; make sure the fields are
@@ -885,7 +883,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                     FfiPhantom(..) if def.is_enum() => {
                         return FfiUnsafe {
                             ty,
-                            reason: "this enum contains a PhantomData field".into(),
+                            reason: fluent::lint::improper_ctypes_enum_phantomdata,
                             help: None,
                         };
                     }
@@ -921,7 +919,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                     } else {
                         return FfiUnsafe {
                             ty,
-                            reason: "box cannot be represented as a single pointer".to_string(),
+                            reason: fluent::lint::improper_ctypes_box,
                             help: None,
                         };
                     }
@@ -931,17 +929,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                 }
                 match def.adt_kind() {
                     AdtKind::Struct | AdtKind::Union => {
-                        let kind = if def.is_struct() { "struct" } else { "union" };
-
                         if !def.repr().c() && !def.repr().transparent() {
                             return FfiUnsafe {
                                 ty,
-                                reason: format!("this {} has unspecified layout", kind),
-                                help: Some(format!(
-                                    "consider adding a `#[repr(C)]` or \
-                                             `#[repr(transparent)]` attribute to this {}",
-                                    kind
-                                )),
+                                reason: if def.is_struct() {
+                                    fluent::lint::improper_ctypes_struct_layout_reason
+                                } else {
+                                    fluent::lint::improper_ctypes_union_layout_reason
+                                },
+                                help: if def.is_struct() {
+                                    Some(fluent::lint::improper_ctypes_struct_layout_help)
+                                } else {
+                                    Some(fluent::lint::improper_ctypes_union_layout_help)
+                                },
                             };
                         }
 
@@ -950,7 +950,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                         if is_non_exhaustive && !def.did().is_local() {
                             return FfiUnsafe {
                                 ty,
-                                reason: format!("this {} is non-exhaustive", kind),
+                                reason: if def.is_struct() {
+                                    fluent::lint::improper_ctypes_struct_non_exhaustive
+                                } else {
+                                    fluent::lint::improper_ctypes_union_non_exhaustive
+                                },
                                 help: None,
                             };
                         }
@@ -958,8 +962,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                         if def.non_enum_variant().fields.is_empty() {
                             return FfiUnsafe {
                                 ty,
-                                reason: format!("this {} has no fields", kind),
-                                help: Some(format!("consider adding a member to this {}", kind)),
+                                reason: if def.is_struct() {
+                                    fluent::lint::improper_ctypes_struct_fieldless_reason
+                                } else {
+                                    fluent::lint::improper_ctypes_union_fieldless_reason
+                                },
+                                help: if def.is_struct() {
+                                    Some(fluent::lint::improper_ctypes_struct_fieldless_help)
+                                } else {
+                                    Some(fluent::lint::improper_ctypes_union_fieldless_help)
+                                },
                             };
                         }
 
@@ -979,13 +991,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                             if repr_nullable_ptr(self.cx, ty, self.mode).is_none() {
                                 return FfiUnsafe {
                                     ty,
-                                    reason: "enum has no representation hint".into(),
-                                    help: Some(
-                                        "consider adding a `#[repr(C)]`, \
-                                                `#[repr(transparent)]`, or integer `#[repr(...)]` \
-                                                attribute to this enum"
-                                            .into(),
-                                    ),
+                                    reason: fluent::lint::improper_ctypes_enum_repr_reason,
+                                    help: Some(fluent::lint::improper_ctypes_enum_repr_help),
                                 };
                             }
                         }
@@ -993,7 +1000,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                         if def.is_variant_list_non_exhaustive() && !def.did().is_local() {
                             return FfiUnsafe {
                                 ty,
-                                reason: "this enum is non-exhaustive".into(),
+                                reason: fluent::lint::improper_ctypes_non_exhaustive,
                                 help: None,
                             };
                         }
@@ -1004,7 +1011,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                             if is_non_exhaustive && !variant.def_id.is_local() {
                                 return FfiUnsafe {
                                     ty,
-                                    reason: "this enum has non-exhaustive variants".into(),
+                                    reason: fluent::lint::improper_ctypes_non_exhaustive_variant,
                                     help: None,
                                 };
                             }
@@ -1022,39 +1029,37 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
 
             ty::Char => FfiUnsafe {
                 ty,
-                reason: "the `char` type has no C equivalent".into(),
-                help: Some("consider using `u32` or `libc::wchar_t` instead".into()),
+                reason: fluent::lint::improper_ctypes_char_reason,
+                help: Some(fluent::lint::improper_ctypes_char_help),
             },
 
-            ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => FfiUnsafe {
-                ty,
-                reason: "128-bit integers don't currently have a known stable ABI".into(),
-                help: None,
-            },
+            ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => {
+                FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_128bit, help: None }
+            }
 
             // Primitive types with a stable representation.
             ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe,
 
             ty::Slice(_) => FfiUnsafe {
                 ty,
-                reason: "slices have no C equivalent".into(),
-                help: Some("consider using a raw pointer instead".into()),
+                reason: fluent::lint::improper_ctypes_slice_reason,
+                help: Some(fluent::lint::improper_ctypes_slice_help),
             },
 
             ty::Dynamic(..) => {
-                FfiUnsafe { ty, reason: "trait objects have no C equivalent".into(), help: None }
+                FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_dyn, help: None }
             }
 
             ty::Str => FfiUnsafe {
                 ty,
-                reason: "string slices have no C equivalent".into(),
-                help: Some("consider using `*const u8` and a length instead".into()),
+                reason: fluent::lint::improper_ctypes_str_reason,
+                help: Some(fluent::lint::improper_ctypes_str_help),
             },
 
             ty::Tuple(..) => FfiUnsafe {
                 ty,
-                reason: "tuples have unspecified layout".into(),
-                help: Some("consider using a struct instead".into()),
+                reason: fluent::lint::improper_ctypes_tuple_reason,
+                help: Some(fluent::lint::improper_ctypes_tuple_help),
             },
 
             ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _)
@@ -1085,12 +1090,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                 if self.is_internal_abi(sig.abi()) {
                     return FfiUnsafe {
                         ty,
-                        reason: "this function pointer has Rust-specific calling convention".into(),
-                        help: Some(
-                            "consider using an `extern fn(...) -> ...` \
-                                    function pointer instead"
-                                .into(),
-                        ),
+                        reason: fluent::lint::improper_ctypes_fnptr_reason,
+                        help: Some(fluent::lint::improper_ctypes_fnptr_help),
                     };
                 }
 
@@ -1121,7 +1122,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
             // While opaque types are checked for earlier, if a projection in a struct field
             // normalizes to an opaque type, then it will reach this branch.
             ty::Opaque(..) => {
-                FfiUnsafe { ty, reason: "opaque types have no C equivalent".into(), help: None }
+                FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_opaque, help: None }
             }
 
             // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe,
@@ -1147,8 +1148,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         &mut self,
         ty: Ty<'tcx>,
         sp: Span,
-        note: &str,
-        help: Option<&str>,
+        note: DiagnosticMessage,
+        help: Option<DiagnosticMessage>,
     ) {
         let lint = match self.mode {
             CItemKind::Declaration => IMPROPER_CTYPES,
@@ -1160,18 +1161,17 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                 CItemKind::Declaration => "block",
                 CItemKind::Definition => "fn",
             };
-            let mut diag = lint.build(&format!(
-                "`extern` {} uses type `{}`, which is not FFI-safe",
-                item_description, ty
-            ));
-            diag.span_label(sp, "not FFI-safe");
+            let mut diag = lint.build(fluent::lint::improper_ctypes);
+            diag.set_arg("ty", ty);
+            diag.set_arg("desc", item_description);
+            diag.span_label(sp, fluent::lint::label);
             if let Some(help) = help {
                 diag.help(help);
             }
             diag.note(note);
             if let ty::Adt(def, _) = ty.kind() {
                 if let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) {
-                    diag.span_note(sp, "the type is defined here");
+                    diag.span_note(sp, fluent::lint::note);
                 }
             }
             diag.emit();
@@ -1183,7 +1183,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
             cx: &'a LateContext<'tcx>,
         }
 
-        impl<'a, 'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> {
+        impl<'a, 'tcx> ty::visit::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> {
             type BreakTy = Ty<'tcx>;
 
             fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
@@ -1208,7 +1208,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         }
 
         if let Some(ty) = ty.visit_with(&mut ProhibitOpaqueTypes { cx: self.cx }).break_value() {
-            self.emit_ffi_unsafe_type_lint(ty, sp, "opaque types have no C equivalent", None);
+            self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint::improper_ctypes_opaque, None);
             true
         } else {
             false
@@ -1250,13 +1250,18 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         match self.check_type_for_ffi(&mut FxHashSet::default(), ty) {
             FfiResult::FfiSafe => {}
             FfiResult::FfiPhantom(ty) => {
-                self.emit_ffi_unsafe_type_lint(ty, sp, "composed only of `PhantomData`", None);
+                self.emit_ffi_unsafe_type_lint(
+                    ty,
+                    sp,
+                    fluent::lint::improper_ctypes_only_phantomdata,
+                    None,
+                );
             }
             // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic
             // argument, which after substitution, is `()`, then this branch can be hit.
             FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {}
             FfiResult::FfiUnsafe { ty, reason, help } => {
-                self.emit_ffi_unsafe_type_lint(ty, sp, &reason, help.as_deref());
+                self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
             }
         }
     }
@@ -1383,12 +1388,9 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
                     VARIANT_SIZE_DIFFERENCES,
                     enum_definition.variants[largest_index].span,
                     |lint| {
-                        lint.build(&format!(
-                            "enum variant is more than three times \
-                                          larger ({} bytes) than the next largest",
-                            largest
-                        ))
-                        .emit();
+                        lint.build(fluent::lint::variant_size_differences)
+                            .set_arg("largest", largest)
+                            .emit();
                     },
                 );
             }
@@ -1511,13 +1513,13 @@ impl InvalidAtomicOrdering {
         {
             cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| {
                 if method == sym::load {
-                    diag.build("atomic loads cannot have `Release` or `AcqRel` ordering")
-                        .help("consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`")
+                    diag.build(fluent::lint::atomic_ordering_load)
+                        .help(fluent::lint::help)
                         .emit()
                 } else {
                     debug_assert_eq!(method, sym::store);
-                    diag.build("atomic stores cannot have `Acquire` or `AcqRel` ordering")
-                        .help("consider using ordering modes `Release`, `SeqCst` or `Relaxed`")
+                    diag.build(fluent::lint::atomic_ordering_store)
+                        .help(fluent::lint::help)
                         .emit();
                 }
             });
@@ -1532,8 +1534,8 @@ impl InvalidAtomicOrdering {
             && 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")
-                    .help("consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`")
+                diag.build(fluent::lint::atomic_ordering_fence)
+                    .help(fluent::lint::help)
                     .emit();
             });
         }
@@ -1552,15 +1554,20 @@ impl InvalidAtomicOrdering {
         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();
-            });
+            #[derive(LintDiagnostic)]
+            #[lint(lint::atomic_ordering_invalid)]
+            #[help]
+            struct InvalidAtomicOrderingDiag {
+                method: Symbol,
+                #[label]
+                fail_order_arg_span: Span,
+            }
+
+            cx.emit_spanned_lint(
+                INVALID_ATOMIC_ORDERING,
+                fail_order_arg.span,
+                InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span },
+            );
         }
 
         let Some(success_ordering) = Self::match_ordering(cx, success_order_arg) else { return };
@@ -1577,18 +1584,20 @@ impl InvalidAtomicOrdering {
                     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();
+                diag.build(fluent::lint::atomic_ordering_invalid_fail_success)
+                    .set_arg("method", method)
+                    .set_arg("fail_ordering", fail_ordering)
+                    .set_arg("success_ordering", success_ordering)
+                    .set_arg("success_suggestion", success_suggestion)
+                    .span_label(fail_order_arg.span, fluent::lint::fail_label)
+                    .span_label(success_order_arg.span, fluent::lint::success_label)
+                    .span_suggestion_short(
+                        success_order_arg.span,
+                        fluent::lint::suggestion,
+                        format!("std::sync::atomic::Ordering::{success_suggestion}"),
+                        Applicability::MaybeIncorrect,
+                    )
+                    .emit();
             });
         }
     }
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 73f353e62c1..53269d18527 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -3,7 +3,7 @@ use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
 use rustc_ast as ast;
 use rustc_ast::util::{classify, parser};
 use rustc_ast::{ExprKind, StmtKind};
-use rustc_errors::{pluralize, Applicability, MultiSpan};
+use rustc_errors::{fluent, pluralize, Applicability, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
@@ -155,22 +155,23 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
 
         if let Some(must_use_op) = must_use_op {
             cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| {
-                let mut lint = lint.build(&format!("unused {} that must be used", must_use_op));
-                lint.span_label(expr.span, &format!("the {} produces a value", must_use_op));
-                lint.span_suggestion_verbose(
-                    expr.span.shrink_to_lo(),
-                    "use `let _ = ...` to ignore the resulting value",
-                    "let _ = ",
-                    Applicability::MachineApplicable,
-                );
-                lint.emit();
+                lint.build(fluent::lint::unused_op)
+                    .set_arg("op", must_use_op)
+                    .span_label(expr.span, fluent::lint::label)
+                    .span_suggestion_verbose(
+                        expr.span.shrink_to_lo(),
+                        fluent::lint::suggestion,
+                        "let _ = ",
+                        Applicability::MachineApplicable,
+                    )
+                    .emit();
             });
             op_warned = true;
         }
 
         if !(type_permits_lack_of_use || fn_warned || op_warned) {
             cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| {
-                lint.build(&format!("unused result of type `{}`", ty)).emit();
+                lint.build(fluent::lint::unused_result).set_arg("ty", ty).emit();
             });
         }
 
@@ -267,23 +268,27 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
                 },
                 ty::Closure(..) => {
                     cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
-                        let mut err = lint.build(&format!(
-                            "unused {}closure{}{} that must be used",
-                            descr_pre, plural_suffix, descr_post,
-                        ));
-                        err.note("closures are lazy and do nothing unless called");
-                        err.emit();
+                        // FIXME(davidtwco): this isn't properly translatable becauses of the
+                        // pre/post strings
+                        lint.build(fluent::lint::unused_closure)
+                            .set_arg("count", plural_len)
+                            .set_arg("pre", descr_pre)
+                            .set_arg("post", descr_post)
+                            .note(fluent::lint::note)
+                            .emit();
                     });
                     true
                 }
                 ty::Generator(..) => {
                     cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
-                        let mut err = lint.build(&format!(
-                            "unused {}generator{}{} that must be used",
-                            descr_pre, plural_suffix, descr_post,
-                        ));
-                        err.note("generators are lazy and do nothing unless resumed");
-                        err.emit();
+                        // FIXME(davidtwco): this isn't properly translatable becauses of the
+                        // pre/post strings
+                        lint.build(fluent::lint::unused_generator)
+                            .set_arg("count", plural_len)
+                            .set_arg("pre", descr_pre)
+                            .set_arg("post", descr_post)
+                            .note(fluent::lint::note)
+                            .emit();
                     });
                     true
                 }
@@ -305,13 +310,12 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
         ) -> bool {
             if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) {
                 cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
-                    let msg = format!(
-                        "unused {}`{}`{} that must be used",
-                        descr_pre_path,
-                        cx.tcx.def_path_str(def_id),
-                        descr_post_path
-                    );
-                    let mut err = lint.build(&msg);
+                    // FIXME(davidtwco): this isn't properly translatable becauses of the pre/post
+                    // strings
+                    let mut err = lint.build(fluent::lint::unused_def);
+                    err.set_arg("pre", descr_pre_path);
+                    err.set_arg("post", descr_post_path);
+                    err.set_arg("def", cx.tcx.def_path_str(def_id));
                     // check for #[must_use = "..."]
                     if let Some(note) = attr.value_str() {
                         err.note(note.as_str());
@@ -356,20 +360,20 @@ impl<'tcx> LateLintPass<'tcx> for PathStatements {
                 cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| {
                     let ty = cx.typeck_results().expr_ty(expr);
                     if ty.needs_drop(cx.tcx, cx.param_env) {
-                        let mut lint = lint.build("path statement drops value");
+                        let mut lint = lint.build(fluent::lint::path_statement_drop);
                         if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) {
                             lint.span_suggestion(
                                 s.span,
-                                "use `drop` to clarify the intent",
+                                fluent::lint::suggestion,
                                 format!("drop({});", snippet),
                                 Applicability::MachineApplicable,
                             );
                         } else {
-                            lint.span_help(s.span, "use `drop` to clarify the intent");
+                            lint.span_help(s.span, fluent::lint::suggestion);
                         }
                         lint.emit();
                     } else {
-                        lint.build("path statement with no effect").emit();
+                        lint.build(fluent::lint::path_statement_no_effect).emit();
                     }
                 });
             }
@@ -540,15 +544,19 @@ trait UnusedDelimLint {
         }
 
         cx.struct_span_lint(self.lint(), MultiSpan::from(vec![spans.0, spans.1]), |lint| {
-            let span_msg = format!("unnecessary {} around {}", Self::DELIM_STR, msg);
-            let mut err = lint.build(&span_msg);
             let replacement = vec![
                 (spans.0, if keep_space.0 { " ".into() } else { "".into() }),
                 (spans.1, if keep_space.1 { " ".into() } else { "".into() }),
             ];
-            let suggestion = format!("remove these {}", Self::DELIM_STR);
-            err.multipart_suggestion(&suggestion, replacement, Applicability::MachineApplicable);
-            err.emit();
+            lint.build(fluent::lint::unused_delim)
+                .set_arg("delim", Self::DELIM_STR)
+                .set_arg("item", msg)
+                .multipart_suggestion(
+                    fluent::lint::suggestion,
+                    replacement,
+                    Applicability::MachineApplicable,
+                )
+                .emit();
         });
     }
 
@@ -1110,7 +1118,7 @@ impl UnusedImportBraces {
             };
 
             cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| {
-                lint.build(&format!("braces around {} is unnecessary", node_name)).emit();
+                lint.build(fluent::lint::unused_import_braces).set_arg("node", node_name).emit();
             });
         }
     }
@@ -1161,15 +1169,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
         for adj in cx.typeck_results().expr_adjustments(e) {
             if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
                 cx.struct_span_lint(UNUSED_ALLOCATION, e.span, |lint| {
-                    let msg = match m {
-                        adjustment::AutoBorrowMutability::Not => {
-                            "unnecessary allocation, use `&` instead"
-                        }
+                    lint.build(match m {
+                        adjustment::AutoBorrowMutability::Not => fluent::lint::unused_allocation,
                         adjustment::AutoBorrowMutability::Mut { .. } => {
-                            "unnecessary allocation, use `&mut` instead"
+                            fluent::lint::unused_allocation_mut
                         }
-                    };
-                    lint.build(msg).emit();
+                    })
+                    .emit();
                 });
             }
         }
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index d0c86527189..027f377b0ac 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -1,125 +1,54 @@
 #![deny(unused_must_use)]
 
-use crate::diagnostics::error::{
-    invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
-    SessionDiagnosticDeriveError,
-};
-use crate::diagnostics::utils::{
-    report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
-    Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
-};
-use proc_macro2::{Ident, TokenStream};
-use quote::{format_ident, quote};
-use std::collections::HashMap;
-use std::str::FromStr;
-use syn::{
-    parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
-};
-use synstructure::{BindingInfo, Structure};
+use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
+use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
+use crate::diagnostics::utils::{build_field_mapping, SetOnce};
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::spanned::Spanned;
+use synstructure::Structure;
 
 /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
 pub(crate) struct SessionDiagnosticDerive<'a> {
     structure: Structure<'a>,
-    builder: SessionDiagnosticDeriveBuilder,
+    sess: syn::Ident,
+    builder: DiagnosticDeriveBuilder,
 }
 
 impl<'a> SessionDiagnosticDerive<'a> {
     pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
-        // Build the mapping of field names to fields. This allows attributes to peek values from
-        // other fields.
-        let mut fields_map = HashMap::new();
-
-        // Convenience bindings.
-        let ast = structure.ast();
-
-        if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
-            for field in fields.iter() {
-                if let Some(ident) = &field.ident {
-                    fields_map.insert(ident.to_string(), quote! { &self.#ident });
-                }
-            }
-        }
-
         Self {
-            builder: SessionDiagnosticDeriveBuilder {
+            builder: DiagnosticDeriveBuilder {
                 diag,
-                sess,
-                fields: fields_map,
+                fields: build_field_mapping(&structure),
                 kind: None,
                 code: None,
                 slug: None,
             },
+            sess,
             structure,
         }
     }
 
     pub(crate) fn into_tokens(self) -> TokenStream {
-        let SessionDiagnosticDerive { mut structure, mut builder } = self;
+        let SessionDiagnosticDerive { mut structure, sess, mut builder } = self;
 
         let ast = structure.ast();
-        let attrs = &ast.attrs;
-
         let (implementation, param_ty) = {
             if let syn::Data::Struct(..) = ast.data {
-                let preamble = {
-                    let preamble = attrs.iter().map(|attr| {
-                        builder
-                            .generate_structure_code(attr)
-                            .unwrap_or_else(|v| v.to_compile_error())
-                    });
-
-                    quote! {
-                        #(#preamble)*;
-                    }
-                };
-
-                // Keep track of which fields are subdiagnostics or have no attributes.
-                let mut subdiagnostics_or_empty = std::collections::HashSet::new();
-
-                // Generates calls to `span_label` and similar functions based on the attributes
-                // on fields. Code for suggestions uses formatting machinery and the value of
-                // other fields - because any given field can be referenced multiple times, it
-                // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
-                // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
-                // has to happen in a separate pass over the fields.
-                let attrs = structure
-                    .clone()
-                    .filter(|field_binding| {
-                        let attrs = &field_binding.ast().attrs;
-
-                        (!attrs.is_empty()
-                            && attrs.iter().all(|attr| {
-                                "subdiagnostic"
-                                    != attr.path.segments.last().unwrap().ident.to_string()
-                            }))
-                            || {
-                                subdiagnostics_or_empty.insert(field_binding.binding.clone());
-                                false
-                            }
-                    })
-                    .each(|field_binding| builder.generate_field_attrs_code(field_binding));
-
-                structure.bind_with(|_| synstructure::BindStyle::Move);
-                // When a field has attributes like `#[label]` or `#[note]` then it doesn't
-                // need to be passed as an argument to the diagnostic. But when a field has no
-                // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
-                // argument to the diagnostic so that it can be referred to by Fluent messages.
-                let args = structure
-                    .filter(|field_binding| {
-                        subdiagnostics_or_empty.contains(&field_binding.binding)
-                    })
-                    .each(|field_binding| builder.generate_field_attrs_code(field_binding));
+                let preamble = builder.preamble(&structure);
+                let (attrs, args) = builder.body(&mut structure);
 
                 let span = ast.span().unwrap();
-                let (diag, sess) = (&builder.diag, &builder.sess);
-                let init = match (builder.kind, builder.slug) {
+                let diag = &builder.diag;
+                let init = match (builder.kind.value(), builder.slug.value()) {
                     (None, _) => {
                         span_err(span, "diagnostic kind not specified")
                             .help("use the `#[error(...)]` attribute to create an error")
                             .emit();
-                        return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
+                        return DiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
-                    (Some((kind, _)), None) => {
+                    (Some(kind), None) => {
                         span_err(span, "diagnostic slug not specified")
                             .help(&format!(
                                 "specify the slug as the first argument to the attribute, such as \
@@ -127,14 +56,20 @@ impl<'a> SessionDiagnosticDerive<'a> {
                                 kind.descr()
                             ))
                             .emit();
-                        return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
+                        return DiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
-                    (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
+                    (Some(DiagnosticDeriveKind::Lint), _) => {
+                        span_err(span, "only `#[error(..)]` and `#[warn(..)]` are supported")
+                            .help("use the `#[error(...)]` attribute to create a error")
+                            .emit();
+                        return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+                    }
+                    (Some(DiagnosticDeriveKind::Error), Some(slug)) => {
                         quote! {
                             let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug);
                         }
                     }
-                    (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
+                    (Some(DiagnosticDeriveKind::Warn), Some(slug)) => {
                         quote! {
                             let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug);
                         }
@@ -153,10 +88,12 @@ impl<'a> SessionDiagnosticDerive<'a> {
                     #diag
                 };
                 let param_ty = match builder.kind {
-                    Some((SessionDiagnosticKind::Error, _)) => {
+                    Some((DiagnosticDeriveKind::Error, _)) => {
                         quote! { rustc_errors::ErrorGuaranteed }
                     }
-                    Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
+                    Some((DiagnosticDeriveKind::Lint | DiagnosticDeriveKind::Warn, _)) => {
+                        quote! { () }
+                    }
                     _ => unreachable!(),
                 };
 
@@ -168,13 +105,12 @@ impl<'a> SessionDiagnosticDerive<'a> {
                 )
                 .emit();
 
-                let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
+                let implementation = DiagnosticDeriveError::ErrorHandled.to_compile_error();
                 let param_ty = quote! { rustc_errors::ErrorGuaranteed };
                 (implementation, param_ty)
             }
         };
 
-        let sess = &builder.sess;
         structure.gen_impl(quote! {
             gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
                     for @Self
@@ -191,525 +127,99 @@ impl<'a> SessionDiagnosticDerive<'a> {
     }
 }
 
-/// What kind of session diagnostic is being derived - an error or a warning?
-#[derive(Copy, Clone)]
-enum SessionDiagnosticKind {
-    /// `#[error(..)]`
-    Error,
-    /// `#[warn(..)]`
-    Warn,
+/// The central struct for constructing the `decorate_lint` method from an annotated struct.
+pub(crate) struct LintDiagnosticDerive<'a> {
+    structure: Structure<'a>,
+    builder: DiagnosticDeriveBuilder,
 }
 
-impl SessionDiagnosticKind {
-    /// Returns human-readable string corresponding to the kind.
-    fn descr(&self) -> &'static str {
-        match self {
-            SessionDiagnosticKind::Error => "error",
-            SessionDiagnosticKind::Warn => "warning",
+impl<'a> LintDiagnosticDerive<'a> {
+    pub(crate) fn new(diag: syn::Ident, structure: Structure<'a>) -> Self {
+        Self {
+            builder: DiagnosticDeriveBuilder {
+                diag,
+                fields: build_field_mapping(&structure),
+                kind: None,
+                code: None,
+                slug: None,
+            },
+            structure,
         }
     }
-}
-
-/// Tracks persistent information required for building up the individual calls to diagnostic
-/// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
-/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
-/// double mut borrow later on.
-struct SessionDiagnosticDeriveBuilder {
-    /// Name of the session parameter that's passed in to the `as_error` method.
-    sess: syn::Ident,
-    /// The identifier to use for the generated `DiagnosticBuilder` instance.
-    diag: syn::Ident,
-
-    /// Store a map of field name to its corresponding field. This is built on construction of the
-    /// derive builder.
-    fields: HashMap<String, TokenStream>,
-
-    /// Kind of diagnostic requested via the struct attribute.
-    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<(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)>,
-}
-
-impl HasFieldMap for SessionDiagnosticDeriveBuilder {
-    fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
-        self.fields.get(field)
-    }
-}
-
-impl SessionDiagnosticDeriveBuilder {
-    /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
-    /// 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()?;
-
-        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),
-        };
-
-        // 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`, `warning`, `help` and `note` are valid attributes")
-            }),
-        }
 
-        // 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")
-                }),
-            };
-        }
+    pub(crate) fn into_tokens(self) -> TokenStream {
+        let LintDiagnosticDerive { mut structure, mut builder } = self;
 
-        // Remaining attributes are optional, only `code = ".."` at the moment.
-        let mut tokens = Vec::new();
-        for nested_attr in nested_iter {
-            let meta = match nested_attr {
-                syn::NestedMeta::Meta(meta) => meta,
-                _ => throw_invalid_nested_attr!(attr, &nested_attr),
-            };
+        let ast = structure.ast();
+        let implementation = {
+            if let syn::Data::Struct(..) = ast.data {
+                let preamble = builder.preamble(&structure);
+                let (attrs, args) = builder.body(&mut structure);
 
-            let path = meta.path();
-            let nested_name = path.segments.last().unwrap().ident.to_string();
-            // 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()));
-                        });
+                let diag = &builder.diag;
+                let span = ast.span().unwrap();
+                let init = match (builder.kind.value(), builder.slug.value()) {
+                    (None, _) => {
+                        span_err(span, "diagnostic kind not specified")
+                            .help("use the `#[error(...)]` attribute to create an error")
+                            .emit();
+                        return DiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
-                    _ => invalid_nested_attr(attr, &nested_attr)
-                        .help("only `code` is a valid nested attributes following the slug")
-                        .emit(),
-                }
-            } else {
-                invalid_nested_attr(attr, &nested_attr).emit()
-            }
-        }
-
-        Ok(tokens.drain(..).collect())
-    }
-
-    fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
-        let field = binding_info.ast();
-        let field_binding = &binding_info.binding;
-
-        let inner_ty = FieldInnerTy::from_type(&field.ty);
-
-        // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
-        // borrow it to avoid requiring clones - this must therefore be the last use of
-        // each field (for example, any formatting machinery that might refer to a field
-        // should be generated already).
-        if field.attrs.is_empty() {
-            let diag = &self.diag;
-            let ident = field.ident.as_ref().unwrap();
-            quote! {
-                #diag.set_arg(
-                    stringify!(#ident),
-                    #field_binding
-                );
-            }
-        } else {
-            field
-                .attrs
-                .iter()
-                .map(move |attr| {
-                    let name = attr.path.segments.last().unwrap().ident.to_string();
-                    let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
-                        // `primary_span` can accept a `Vec<Span>` so don't destructure that.
-                        ("primary_span", FieldInnerTy::Vec(_)) => {
-                            (quote! { #field_binding.clone() }, false)
+                    (Some(kind), None) => {
+                        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 DiagnosticDeriveError::ErrorHandled.to_compile_error();
+                    }
+                    (Some(DiagnosticDeriveKind::Error | DiagnosticDeriveKind::Warn), _) => {
+                        span_err(span, "only `#[lint(..)]` is supported")
+                            .help("use the `#[lint(...)]` attribute to create a lint")
+                            .emit();
+                        return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+                    }
+                    (Some(DiagnosticDeriveKind::Lint), Some(slug)) => {
+                        quote! {
+                            let mut #diag = #diag.build(rustc_errors::fluent::#slug);
                         }
-                        // `subdiagnostics` are not derefed because they are bound by value.
-                        ("subdiagnostic", _) => (quote! { #field_binding }, true),
-                        _ => (quote! { *#field_binding }, true),
-                    };
-
-                    let generated_code = self
-                        .generate_inner_field_code(
-                            attr,
-                            FieldInfo {
-                                binding: binding_info,
-                                ty: inner_ty.inner_type().unwrap_or(&field.ty),
-                                span: &field.span(),
-                            },
-                            binding,
-                        )
-                        .unwrap_or_else(|v| v.to_compile_error());
-
-                    if needs_destructure {
-                        inner_ty.with(field_binding, generated_code)
-                    } else {
-                        generated_code
                     }
-                })
-                .collect()
-        }
-    }
-
-    fn generate_inner_field_code(
-        &mut self,
-        attr: &Attribute,
-        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()?;
-        let Meta::List(MetaList { ref path, ref nested, .. }) = meta  else { unreachable!() };
-
-        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",
-                )
-            }),
-        }
 
-        // 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),
-        };
-
-        // None of these attributes should have anything following the slug.
-        if nested_iter.next().is_some() {
-            throw_invalid_attr!(attr, &meta);
-        }
-
-        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)
-                                }
-                                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",
-                            )
-                        }),
+                let implementation = quote! {
+                    #init
+                    #preamble
+                    match self {
+                        #attrs
                     }
-                }
-                _ => 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
+                    match self {
+                        #args
                     }
-                }),
-            }
-        }
-
-        let applicability =
-            applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
-
-        let name = path.segments.last().unwrap().ident.to_string();
-        let method = format_ident!("span_{}", name);
-
-        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); })
-    }
-
-    /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
-    /// and `fluent_attr_identifier`.
-    fn add_spanned_subdiagnostic(
-        &self,
-        field_binding: TokenStream,
-        kind: &Ident,
-        fluent_attr_identifier: Path,
-    ) -> TokenStream {
-        let diag = &self.diag;
-        let fn_name = format_ident!("span_{}", kind);
-        quote! {
-            #diag.#fn_name(
-                #field_binding,
-                rustc_errors::fluent::#fluent_attr_identifier
-            );
-        }
-    }
+                    #diag.emit();
+                };
 
-    /// 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: Path) -> TokenStream {
-        let diag = &self.diag;
-        quote! {
-            #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
-        }
-    }
+                implementation
+            } else {
+                span_err(
+                    ast.span().unwrap(),
+                    "`#[derive(LintDiagnostic)]` can only be used on structs",
+                )
+                .emit();
 
-    fn span_and_applicability_of_ty(
-        &self,
-        info: FieldInfo<'_>,
-    ) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> {
-        match &info.ty {
-            // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
-            ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
-                let binding = &info.binding.binding;
-                Ok((quote!(*#binding), None))
+                DiagnosticDeriveError::ErrorHandled.to_compile_error()
             }
-            // If `ty` is `(Span, Applicability)` then return tokens accessing those.
-            Type::Tuple(tup) => {
-                let mut span_idx = None;
-                let mut applicability_idx = None;
-
-                for (idx, elem) in tup.elems.iter().enumerate() {
-                    if type_matches_path(elem, &["rustc_span", "Span"]) {
-                        if span_idx.is_none() {
-                            span_idx = Some(syn::Index::from(idx));
-                        } else {
-                            throw_span_err!(
-                                info.span.unwrap(),
-                                "type of field annotated with `#[suggestion(...)]` contains more \
-                                 than one `Span`"
-                            );
-                        }
-                    } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
-                        if applicability_idx.is_none() {
-                            applicability_idx = Some(syn::Index::from(idx));
-                        } else {
-                            throw_span_err!(
-                                info.span.unwrap(),
-                                "type of field annotated with `#[suggestion(...)]` contains more \
-                                 than one Applicability"
-                            );
-                        }
-                    }
-                }
-
-                if let Some(span_idx) = span_idx {
-                    let binding = &info.binding.binding;
-                    let span = quote!(#binding.#span_idx);
-                    let applicability = applicability_idx
-                        .map(|applicability_idx| quote!(#binding.#applicability_idx))
-                        .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
+        };
 
-                    return Ok((span, Some(applicability)));
+        let diag = &builder.diag;
+        structure.gen_impl(quote! {
+            gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
+                fn decorate_lint(self, #diag: rustc_errors::LintDiagnosticBuilder<'__a, ()>) {
+                    use rustc_errors::IntoDiagnosticArg;
+                    #implementation
                 }
-
-                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)`",
-                    )
-                });
             }
-            // 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)`",
-                )
-            }),
-        }
+        })
     }
 }
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
new file mode 100644
index 00000000000..74ce1ab08c2
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -0,0 +1,590 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::error::{
+    invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
+    DiagnosticDeriveError,
+};
+use crate::diagnostics::utils::{
+    report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
+    Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
+};
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote};
+use std::collections::HashMap;
+use std::str::FromStr;
+use syn::{
+    parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
+};
+use synstructure::{BindingInfo, Structure};
+
+/// What kind of diagnostic is being derived - an error, a warning or a lint?
+#[derive(Copy, Clone)]
+pub(crate) enum DiagnosticDeriveKind {
+    /// `#[error(..)]`
+    Error,
+    /// `#[warn(..)]`
+    Warn,
+    /// `#[lint(..)]`
+    Lint,
+}
+
+impl DiagnosticDeriveKind {
+    /// Returns human-readable string corresponding to the kind.
+    pub fn descr(&self) -> &'static str {
+        match self {
+            DiagnosticDeriveKind::Error => "error",
+            DiagnosticDeriveKind::Warn => "warning",
+            DiagnosticDeriveKind::Lint => "lint",
+        }
+    }
+}
+
+/// Tracks persistent information required for building up individual calls to diagnostic methods
+/// for generated diagnostic derives - both `SessionDiagnostic` for errors/warnings and
+/// `LintDiagnostic` for lints.
+pub(crate) struct DiagnosticDeriveBuilder {
+    /// The identifier to use for the generated `DiagnosticBuilder` instance.
+    pub diag: syn::Ident,
+
+    /// Store a map of field name to its corresponding field. This is built on construction of the
+    /// derive builder.
+    pub fields: HashMap<String, TokenStream>,
+
+    /// Kind of diagnostic requested via the struct attribute.
+    pub kind: Option<(DiagnosticDeriveKind, proc_macro::Span)>,
+    /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
+    /// has the actual diagnostic message.
+    pub slug: Option<(Path, proc_macro::Span)>,
+    /// Error codes are a optional part of the struct attribute - this is only set to detect
+    /// multiple specifications.
+    pub code: Option<(String, proc_macro::Span)>,
+}
+
+impl HasFieldMap for DiagnosticDeriveBuilder {
+    fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+        self.fields.get(field)
+    }
+}
+
+impl DiagnosticDeriveBuilder {
+    pub fn preamble<'s>(&mut self, structure: &Structure<'s>) -> TokenStream {
+        let ast = structure.ast();
+        let attrs = &ast.attrs;
+        let preamble = attrs.iter().map(|attr| {
+            self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error())
+        });
+
+        quote! {
+            #(#preamble)*;
+        }
+    }
+
+    pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
+        // Keep track of which fields are subdiagnostics or have no attributes.
+        let mut subdiagnostics_or_empty = std::collections::HashSet::new();
+
+        // Generates calls to `span_label` and similar functions based on the attributes
+        // on fields. Code for suggestions uses formatting machinery and the value of
+        // other fields - because any given field can be referenced multiple times, it
+        // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
+        // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
+        // has to happen in a separate pass over the fields.
+        let attrs = structure
+            .clone()
+            .filter(|field_binding| {
+                let attrs = &field_binding.ast().attrs;
+
+                (!attrs.is_empty()
+                    && attrs.iter().all(|attr| {
+                        "subdiagnostic" != attr.path.segments.last().unwrap().ident.to_string()
+                    }))
+                    || {
+                        subdiagnostics_or_empty.insert(field_binding.binding.clone());
+                        false
+                    }
+            })
+            .each(|field_binding| self.generate_field_attrs_code(field_binding));
+
+        structure.bind_with(|_| synstructure::BindStyle::Move);
+        // When a field has attributes like `#[label]` or `#[note]` then it doesn't
+        // need to be passed as an argument to the diagnostic. But when a field has no
+        // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
+        // argument to the diagnostic so that it can be referred to by Fluent messages.
+        let args = structure
+            .filter(|field_binding| subdiagnostics_or_empty.contains(&field_binding.binding))
+            .each(|field_binding| self.generate_field_attrs_code(field_binding));
+
+        (attrs, args)
+    }
+
+    /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
+    /// 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_for_attr(
+        &mut self,
+        attr: &Attribute,
+    ) -> Result<TokenStream, DiagnosticDeriveError> {
+        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()?;
+
+        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),
+        };
+
+        // 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((DiagnosticDeriveKind::Error, span)),
+            "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)),
+            "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)),
+            "help" | "note" => (),
+            _ => throw_invalid_attr!(attr, &meta, |diag| {
+                diag.help("only `error`, `warning`, `help` and `note` are valid attributes")
+            }),
+        }
+
+        // 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_iter {
+            let meta = match nested_attr {
+                syn::NestedMeta::Meta(meta) => meta,
+                _ => throw_invalid_nested_attr!(attr, &nested_attr),
+            };
+
+            let path = meta.path();
+            let nested_name = path.segments.last().unwrap().ident.to_string();
+            // 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(),
+                }
+            } else {
+                invalid_nested_attr(attr, &nested_attr).emit()
+            }
+        }
+
+        Ok(tokens.drain(..).collect())
+    }
+
+    fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
+        let field = binding_info.ast();
+        let field_binding = &binding_info.binding;
+
+        let inner_ty = FieldInnerTy::from_type(&field.ty);
+
+        // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
+        // borrow it to avoid requiring clones - this must therefore be the last use of
+        // each field (for example, any formatting machinery that might refer to a field
+        // should be generated already).
+        if field.attrs.is_empty() {
+            let diag = &self.diag;
+            let ident = field.ident.as_ref().unwrap();
+            quote! {
+                #diag.set_arg(
+                    stringify!(#ident),
+                    #field_binding
+                );
+            }
+        } else {
+            field
+                .attrs
+                .iter()
+                .map(move |attr| {
+                    let name = attr.path.segments.last().unwrap().ident.to_string();
+                    let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
+                        // `primary_span` can accept a `Vec<Span>` so don't destructure that.
+                        ("primary_span", FieldInnerTy::Vec(_)) => {
+                            (quote! { #field_binding.clone() }, false)
+                        }
+                        // `subdiagnostics` are not derefed because they are bound by value.
+                        ("subdiagnostic", _) => (quote! { #field_binding }, true),
+                        _ => (quote! { *#field_binding }, true),
+                    };
+
+                    let generated_code = self
+                        .generate_inner_field_code(
+                            attr,
+                            FieldInfo {
+                                binding: binding_info,
+                                ty: inner_ty.inner_type().unwrap_or(&field.ty),
+                                span: &field.span(),
+                            },
+                            binding,
+                        )
+                        .unwrap_or_else(|v| v.to_compile_error());
+
+                    if needs_destructure {
+                        inner_ty.with(field_binding, generated_code)
+                    } else {
+                        generated_code
+                    }
+                })
+                .collect()
+        }
+    }
+
+    fn generate_inner_field_code(
+        &mut self,
+        attr: &Attribute,
+        info: FieldInfo<'_>,
+        binding: TokenStream,
+    ) -> Result<TokenStream, DiagnosticDeriveError> {
+        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, DiagnosticDeriveError> {
+        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, DiagnosticDeriveError> {
+        let meta = attr.parse_meta()?;
+        let Meta::List(MetaList { ref path, ref nested, .. }) = meta  else { unreachable!() };
+
+        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",
+                )
+            }),
+        }
+
+        // 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),
+        };
+
+        // None of these attributes should have anything following the slug.
+        if nested_iter.next().is_some() {
+            throw_invalid_attr!(attr, &meta);
+        }
+
+        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, DiagnosticDeriveError> {
+        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)
+                                }
+                                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, |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 name = path.segments.last().unwrap().ident.to_string();
+        let method = format_ident!("span_{}", name);
+
+        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); })
+    }
+
+    /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
+    /// and `fluent_attr_identifier`.
+    fn add_spanned_subdiagnostic(
+        &self,
+        field_binding: TokenStream,
+        kind: &Ident,
+        fluent_attr_identifier: Path,
+    ) -> TokenStream {
+        let diag = &self.diag;
+        let fn_name = format_ident!("span_{}", kind);
+        quote! {
+            #diag.#fn_name(
+                #field_binding,
+                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: Path) -> TokenStream {
+        let diag = &self.diag;
+        quote! {
+            #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
+        }
+    }
+
+    fn span_and_applicability_of_ty(
+        &self,
+        info: FieldInfo<'_>,
+    ) -> Result<(TokenStream, Option<TokenStream>), DiagnosticDeriveError> {
+        match &info.ty {
+            // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
+            ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
+                let binding = &info.binding.binding;
+                Ok((quote!(*#binding), None))
+            }
+            // If `ty` is `(Span, Applicability)` then return tokens accessing those.
+            Type::Tuple(tup) => {
+                let mut span_idx = None;
+                let mut applicability_idx = None;
+
+                for (idx, elem) in tup.elems.iter().enumerate() {
+                    if type_matches_path(elem, &["rustc_span", "Span"]) {
+                        if span_idx.is_none() {
+                            span_idx = Some(syn::Index::from(idx));
+                        } else {
+                            throw_span_err!(
+                                info.span.unwrap(),
+                                "type of field annotated with `#[suggestion(...)]` contains more \
+                                 than one `Span`"
+                            );
+                        }
+                    } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
+                        if applicability_idx.is_none() {
+                            applicability_idx = Some(syn::Index::from(idx));
+                        } else {
+                            throw_span_err!(
+                                info.span.unwrap(),
+                                "type of field annotated with `#[suggestion(...)]` contains more \
+                                 than one Applicability"
+                            );
+                        }
+                    }
+                }
+
+                if let Some(span_idx) = span_idx {
+                    let binding = &info.binding.binding;
+                    let span = quote!(#binding.#span_idx);
+                    let applicability = applicability_idx
+                        .map(|applicability_idx| quote!(#binding.#applicability_idx))
+                        .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
+
+                    return Ok((span, Some(applicability)));
+                }
+
+                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)`",
+                    )
+                });
+            }
+            // 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)`",
+                )
+            }),
+        }
+    }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
index d088402abc6..0b1ededa775 100644
--- a/compiler/rustc_macros/src/diagnostics/error.rs
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -4,16 +4,16 @@ use quote::quote;
 use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta};
 
 #[derive(Debug)]
-pub(crate) enum SessionDiagnosticDeriveError {
+pub(crate) enum DiagnosticDeriveError {
     SynError(SynError),
     ErrorHandled,
 }
 
-impl SessionDiagnosticDeriveError {
+impl DiagnosticDeriveError {
     pub(crate) fn to_compile_error(self) -> TokenStream {
         match self {
-            SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
-            SessionDiagnosticDeriveError::ErrorHandled => {
+            DiagnosticDeriveError::SynError(e) => e.to_compile_error(),
+            DiagnosticDeriveError::ErrorHandled => {
                 // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
                 // error has already been emitted to the compiler.
                 quote! {
@@ -24,9 +24,9 @@ impl SessionDiagnosticDeriveError {
     }
 }
 
-impl From<SynError> for SessionDiagnosticDeriveError {
+impl From<SynError> for DiagnosticDeriveError {
     fn from(e: SynError) -> Self {
-        SessionDiagnosticDeriveError::SynError(e)
+        DiagnosticDeriveError::SynError(e)
     }
 }
 
@@ -34,9 +34,9 @@ impl From<SynError> for SessionDiagnosticDeriveError {
 pub(crate) fn _throw_err(
     diag: Diagnostic,
     f: impl FnOnce(Diagnostic) -> Diagnostic,
-) -> SessionDiagnosticDeriveError {
+) -> DiagnosticDeriveError {
     f(diag).emit();
-    SessionDiagnosticDeriveError::ErrorHandled
+    DiagnosticDeriveError::ErrorHandled
 }
 
 /// Helper function for printing `syn::Path` - doesn't handle arguments in paths and these are
@@ -60,7 +60,7 @@ pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
 /// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
 /// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
 ///
-/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
 macro_rules! throw_span_err {
     ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
     ($span:expr, $msg:expr, $f:expr) => {{
@@ -87,7 +87,7 @@ pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
 /// Emit a error diagnostic for an invalid attribute (optionally performing additional decoration
 /// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
 ///
-/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
 macro_rules! throw_invalid_attr {
     ($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }};
     ($attr:expr, $meta:expr, $f:expr) => {{
@@ -129,7 +129,7 @@ pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diag
 /// Emit a error diagnostic for an invalid nested attribute (optionally performing additional
 /// decoration using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
 ///
-/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
 macro_rules! throw_invalid_nested_attr {
     ($attr:expr, $nested_attr:expr) => {{ throw_invalid_nested_attr!($attr, $nested_attr, |diag| diag) }};
     ($attr:expr, $nested_attr:expr, $f:expr) => {{
diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs
index 2eee4bfb5dd..39979002666 100644
--- a/compiler/rustc_macros/src/diagnostics/mod.rs
+++ b/compiler/rustc_macros/src/diagnostics/mod.rs
@@ -1,10 +1,11 @@
 mod diagnostic;
+mod diagnostic_builder;
 mod error;
 mod fluent;
 mod subdiagnostic;
 mod utils;
 
-use diagnostic::SessionDiagnosticDerive;
+use diagnostic::{LintDiagnosticDerive, SessionDiagnosticDerive};
 pub(crate) use fluent::fluent_messages;
 use proc_macro2::TokenStream;
 use quote::format_ident;
@@ -56,13 +57,55 @@ use synstructure::Structure;
 /// ```
 ///
 /// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`:
-/// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
+/// <https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html>
 pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
-    // Names for the diagnostic we build and the session we build it from.
-    let diag = format_ident!("diag");
-    let sess = format_ident!("sess");
+    SessionDiagnosticDerive::new(format_ident!("diag"), format_ident!("sess"), s).into_tokens()
+}
 
-    SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
+/// Implements `#[derive(LintDiagnostic)]`, which allows for lints to be specified as a struct,
+/// independent from the actual lint emitting code.
+///
+/// ```ignore (rust)
+/// #[derive(LintDiagnostic)]
+/// #[lint(lint::atomic_ordering_invalid_fail_success)]
+/// pub struct AtomicOrderingInvalidLint {
+///     method: Symbol,
+///     success_ordering: Symbol,
+///     fail_ordering: Symbol,
+///     #[label(lint::fail_label)]
+///     fail_order_arg_span: Span,
+///     #[label(lint::success_label)]
+///     #[suggestion(
+///         code = "std::sync::atomic::Ordering::{success_suggestion}",
+///         applicability = "maybe-incorrect"
+///     )]
+///     success_order_arg_span: Span,
+/// }
+/// ```
+///
+/// ```fluent
+/// lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering
+///     .fail-label = `{$fail_ordering}` failure ordering
+///     .success-label = `{$success_ordering}` success ordering
+///     .suggestion = consider using `{$success_suggestion}` success ordering instead
+/// ```
+///
+/// Then, later, to emit the error:
+///
+/// ```ignore (rust)
+/// cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint {
+///     method,
+///     success_ordering,
+///     fail_ordering,
+///     fail_order_arg_span,
+///     success_order_arg_span,
+/// });
+/// ```
+///
+/// See rustc dev guide for more examples on using the `#[derive(LintDiagnostic)]`:
+/// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
+pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
+    LintDiagnosticDerive::new(format_ident!("diag"), s).into_tokens()
 }
 
 /// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index eab954a9c1b..2a5b6beba94 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -1,8 +1,7 @@
 #![deny(unused_must_use)]
 
 use crate::diagnostics::error::{
-    span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
-    SessionDiagnosticDeriveError,
+    span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
 };
 use crate::diagnostics::utils::{
     report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
@@ -214,7 +213,7 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
 }
 
 impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
-    fn identify_kind(&mut self) -> Result<(), SessionDiagnosticDeriveError> {
+    fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> {
         for attr in self.variant.ast().attrs {
             let span = attr.span().unwrap();
 
@@ -351,7 +350,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
         &mut self,
         binding: &BindingInfo<'_>,
         is_suggestion: bool,
-    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+    ) -> Result<TokenStream, DiagnosticDeriveError> {
         let ast = binding.ast();
 
         let inner_ty = FieldInnerTy::from_type(&ast.ty);
@@ -411,7 +410,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
         Ok(inner_ty.with(binding, generated))
     }
 
-    fn into_tokens(&mut self) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+    fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
         self.identify_kind()?;
         let Some(kind) = self.kind.map(|(kind, _)| kind) else {
             throw_span_err!(
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index 636bcf1f7b1..8977db4606c 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -1,11 +1,11 @@
-use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriveError};
+use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
 use proc_macro::Span;
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote, ToTokens};
-use std::collections::BTreeSet;
+use std::collections::{BTreeSet, HashMap};
 use std::str::FromStr;
 use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
-use synstructure::BindingInfo;
+use synstructure::{BindingInfo, Structure};
 
 /// Checks whether the type name of `ty` matches `name`.
 ///
@@ -34,7 +34,7 @@ pub(crate) fn type_is_unit(ty: &Type) -> bool {
 pub(crate) fn report_type_error(
     attr: &Attribute,
     ty_name: &str,
-) -> Result<!, SessionDiagnosticDeriveError> {
+) -> Result<!, DiagnosticDeriveError> {
     let name = attr.path.segments.last().unwrap().ident.to_string();
     let meta = attr.parse_meta()?;
 
@@ -59,7 +59,7 @@ fn report_error_if_not_applied_to_ty(
     info: &FieldInfo<'_>,
     path: &[&str],
     ty_name: &str,
-) -> Result<(), SessionDiagnosticDeriveError> {
+) -> Result<(), DiagnosticDeriveError> {
     if !type_matches_path(&info.ty, path) {
         report_type_error(attr, ty_name)?;
     }
@@ -71,7 +71,7 @@ fn report_error_if_not_applied_to_ty(
 pub(crate) fn report_error_if_not_applied_to_applicability(
     attr: &Attribute,
     info: &FieldInfo<'_>,
-) -> Result<(), SessionDiagnosticDeriveError> {
+) -> Result<(), DiagnosticDeriveError> {
     report_error_if_not_applied_to_ty(
         attr,
         info,
@@ -84,7 +84,7 @@ pub(crate) fn report_error_if_not_applied_to_applicability(
 pub(crate) fn report_error_if_not_applied_to_span(
     attr: &Attribute,
     info: &FieldInfo<'_>,
-) -> Result<(), SessionDiagnosticDeriveError> {
+) -> Result<(), DiagnosticDeriveError> {
     report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
 }
 
@@ -166,10 +166,12 @@ pub(crate) struct FieldInfo<'a> {
 /// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
 /// for error reporting if they are set more than once.
 pub(crate) trait SetOnce<T> {
-    fn set_once(&mut self, value: T);
+    fn set_once(&mut self, _: (T, Span));
+
+    fn value(self) -> Option<T>;
 }
 
-impl<T> SetOnce<(T, Span)> for Option<(T, Span)> {
+impl<T> SetOnce<T> for Option<(T, Span)> {
     fn set_once(&mut self, (value, span): (T, Span)) {
         match self {
             None => {
@@ -182,6 +184,10 @@ impl<T> SetOnce<(T, Span)> for Option<(T, Span)> {
             }
         }
     }
+
+    fn value(self) -> Option<T> {
+        self.map(|(v, _)| v)
+    }
 }
 
 pub(crate) trait HasFieldMap {
@@ -325,3 +331,20 @@ impl quote::ToTokens for Applicability {
         });
     }
 }
+
+/// Build the mapping of field names to fields. This allows attributes to peek values from
+/// other fields.
+pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<String, TokenStream> {
+    let mut fields_map = HashMap::new();
+
+    let ast = structure.ast();
+    if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
+        for field in fields.iter() {
+            if let Some(ident) = &field.ident {
+                fields_map.insert(ident.to_string(), quote! { &self.#ident });
+            }
+        }
+    }
+
+    fields_map
+}
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index 7c8e3c6d140..168530c54b9 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -18,6 +18,7 @@ mod query;
 mod serialize;
 mod symbols;
 mod type_foldable;
+mod type_visitable;
 
 #[proc_macro]
 pub fn rustc_queries(input: TokenStream) -> TokenStream {
@@ -121,12 +122,14 @@ decl_derive!([TyEncodable] => serialize::type_encodable_derive);
 decl_derive!([MetadataDecodable] => serialize::meta_decodable_derive);
 decl_derive!([MetadataEncodable] => serialize::meta_encodable_derive);
 decl_derive!([TypeFoldable, attributes(type_foldable)] => type_foldable::type_foldable_derive);
+decl_derive!([TypeVisitable, attributes(type_visitable)] => type_visitable::type_visitable_derive);
 decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
 decl_derive!(
     [SessionDiagnostic, attributes(
         // struct attributes
         warning,
         error,
+        lint,
         note,
         help,
         // field attributes
@@ -140,6 +143,24 @@ decl_derive!(
         suggestion_verbose)] => diagnostics::session_diagnostic_derive
 );
 decl_derive!(
+    [LintDiagnostic, attributes(
+        // struct attributes
+        warning,
+        error,
+        lint,
+        note,
+        help,
+        // field attributes
+        skip_arg,
+        primary_span,
+        label,
+        subdiagnostic,
+        suggestion,
+        suggestion_short,
+        suggestion_hidden,
+        suggestion_verbose)] => diagnostics::lint_diagnostic_derive
+);
+decl_derive!(
     [SessionSubdiagnostic, attributes(
         // struct/variant attributes
         label,
diff --git a/compiler/rustc_macros/src/type_foldable.rs b/compiler/rustc_macros/src/type_foldable.rs
index 9e834d3ba1c..23e619221aa 100644
--- a/compiler/rustc_macros/src/type_foldable.rs
+++ b/compiler/rustc_macros/src/type_foldable.rs
@@ -11,11 +11,6 @@ pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::
     }
 
     s.add_bounds(synstructure::AddBounds::Generics);
-    let body_visit = s.each(|bind| {
-        quote! {
-            ::rustc_middle::ty::fold::TypeFoldable::visit_with(#bind, __folder)?;
-        }
-    });
     s.bind_with(|_| synstructure::BindStyle::Move);
     let body_fold = s.each_variant(|vi| {
         let bindings = vi.bindings();
@@ -36,14 +31,6 @@ pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::
             ) -> Result<Self, __F::Error> {
                 Ok(match self { #body_fold })
             }
-
-            fn visit_with<__F: ::rustc_middle::ty::fold::TypeVisitor<'tcx>>(
-                &self,
-                __folder: &mut __F
-            ) -> ::std::ops::ControlFlow<__F::BreakTy> {
-                match *self { #body_visit }
-                ::std::ops::ControlFlow::CONTINUE
-            }
         },
     )
 }
diff --git a/compiler/rustc_macros/src/type_visitable.rs b/compiler/rustc_macros/src/type_visitable.rs
new file mode 100644
index 00000000000..14e6aa6e0c1
--- /dev/null
+++ b/compiler/rustc_macros/src/type_visitable.rs
@@ -0,0 +1,33 @@
+use quote::quote;
+use syn::parse_quote;
+
+pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+    if let syn::Data::Union(_) = s.ast().data {
+        panic!("cannot derive on union")
+    }
+
+    if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+        s.add_impl_generic(parse_quote! { 'tcx });
+    }
+
+    s.add_bounds(synstructure::AddBounds::Generics);
+    let body_visit = s.each(|bind| {
+        quote! {
+            ::rustc_middle::ty::visit::TypeVisitable::visit_with(#bind, __visitor)?;
+        }
+    });
+    s.bind_with(|_| synstructure::BindStyle::Move);
+
+    s.bound_impl(
+        quote!(::rustc_middle::ty::visit::TypeVisitable<'tcx>),
+        quote! {
+            fn visit_with<__V: ::rustc_middle::ty::visit::TypeVisitor<'tcx>>(
+                &self,
+                __visitor: &mut __V
+            ) -> ::std::ops::ControlFlow<__V::BreakTy> {
+                match *self { #body_visit }
+                ::std::ops::ControlFlow::CONTINUE
+            }
+        },
+    )
+}
diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs
index 984c95b314b..661a9b1944c 100644
--- a/compiler/rustc_middle/src/arena.rs
+++ b/compiler/rustc_middle/src/arena.rs
@@ -96,6 +96,7 @@ macro_rules! arena_types {
             // (during lowering) and the `librustc_middle` arena (for decoding MIR)
             [decode] asm_template: rustc_ast::InlineAsmTemplatePiece,
             [decode] used_trait_imports: rustc_data_structures::fx::FxHashSet<rustc_hir::def_id::LocalDefId>,
+            [decode] is_late_bound_map: rustc_data_structures::fx::FxIndexSet<rustc_hir::def_id::LocalDefId>,
             [decode] impl_source: rustc_middle::traits::ImplSource<'tcx, ()>,
 
             [] dep_kind: rustc_middle::dep_graph::DepKindStruct,
diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs
index 26b43488408..cda0a60fa4e 100644
--- a/compiler/rustc_middle/src/hir/map/mod.rs
+++ b/compiler/rustc_middle/src/hir/map/mod.rs
@@ -225,7 +225,8 @@ impl<'hir> Map<'hir> {
         self.tcx.definitions_untracked().iter_local_def_id()
     }
 
-    pub fn opt_def_kind(self, local_def_id: LocalDefId) -> Option<DefKind> {
+    /// Do not call this function directly. The query should be called.
+    pub(super) fn opt_def_kind(self, local_def_id: LocalDefId) -> Option<DefKind> {
         let hir_id = self.local_def_id_to_hir_id(local_def_id);
         let def_kind = match self.find(hir_id)? {
             Node::Item(item) => match item.kind {
@@ -1012,12 +1013,13 @@ impl<'hir> Map<'hir> {
                 ItemKind::Use(path, _) => path.span,
                 _ => named_span(item.span, item.ident, item.kind.generics()),
             },
+            Node::Variant(variant) => named_span(variant.span, variant.ident, None),
             Node::ImplItem(item) => named_span(item.span, item.ident, Some(item.generics)),
             Node::ForeignItem(item) => match item.kind {
                 ForeignItemKind::Fn(decl, _, _) => until_within(item.span, decl.output.span()),
                 _ => named_span(item.span, item.ident, None),
             },
-            Node::Ctor(..) => return self.opt_span(self.get_parent_node(hir_id)),
+            Node::Ctor(_) => return self.opt_span(self.get_parent_node(hir_id)),
             _ => self.span_with_body(hir_id),
         };
         Some(span)
diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs
index 8622a620721..12209d6725c 100644
--- a/compiler/rustc_middle/src/hir/mod.rs
+++ b/compiler/rustc_middle/src/hir/mod.rs
@@ -63,6 +63,15 @@ impl ModuleItems {
         self.foreign_items.iter().copied()
     }
 
+    pub fn definitions(&self) -> impl Iterator<Item = LocalDefId> + '_ {
+        self.items
+            .iter()
+            .map(|id| id.def_id)
+            .chain(self.trait_items.iter().map(|id| id.def_id))
+            .chain(self.impl_items.iter().map(|id| id.def_id))
+            .chain(self.foreign_items.iter().map(|id| id.def_id))
+    }
+
     pub fn par_items(&self, f: impl Fn(ItemId) + Send + Sync) {
         par_for_each_in(&self.items[..], |&id| f(id))
     }
diff --git a/compiler/rustc_middle/src/hir/place.rs b/compiler/rustc_middle/src/hir/place.rs
index 00db19019c4..83d3b0100b8 100644
--- a/compiler/rustc_middle/src/hir/place.rs
+++ b/compiler/rustc_middle/src/hir/place.rs
@@ -4,18 +4,8 @@ use crate::ty::Ty;
 use rustc_hir::HirId;
 use rustc_target::abi::VariantIdx;
 
-#[derive(
-    Clone,
-    Copy,
-    Debug,
-    PartialEq,
-    Eq,
-    Hash,
-    TyEncodable,
-    TyDecodable,
-    TypeFoldable,
-    HashStable
-)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum PlaceBase {
     /// A temporary variable.
     Rvalue,
@@ -27,18 +17,8 @@ pub enum PlaceBase {
     Upvar(ty::UpvarId),
 }
 
-#[derive(
-    Clone,
-    Copy,
-    Debug,
-    PartialEq,
-    Eq,
-    Hash,
-    TyEncodable,
-    TyDecodable,
-    TypeFoldable,
-    HashStable
-)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum ProjectionKind {
     /// A dereference of a pointer, reference or `Box<T>` of the given type.
     Deref,
@@ -58,18 +38,8 @@ pub enum ProjectionKind {
     Subslice,
 }
 
-#[derive(
-    Clone,
-    Copy,
-    Debug,
-    PartialEq,
-    Eq,
-    Hash,
-    TyEncodable,
-    TyDecodable,
-    TypeFoldable,
-    HashStable
-)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct Projection<'tcx> {
     /// Type after the projection is applied.
     pub ty: Ty<'tcx>,
@@ -81,7 +51,8 @@ pub struct Projection<'tcx> {
 /// A `Place` represents how a value is located in memory.
 ///
 /// This is an HIR version of [`rustc_middle::mir::Place`].
-#[derive(Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct Place<'tcx> {
     /// The type of the `PlaceBase`
     pub base_ty: Ty<'tcx>,
@@ -94,7 +65,8 @@ pub struct Place<'tcx> {
 /// A `PlaceWithHirId` represents how a value is located in memory.
 ///
 /// This is an HIR version of [`rustc_middle::mir::Place`].
-#[derive(Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct PlaceWithHirId<'tcx> {
     /// `HirId` of the expression or pattern producing this value.
     pub hir_id: HirId,
diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs
index 79f94802d20..c6fe3e72103 100644
--- a/compiler/rustc_middle/src/infer/canonical.rs
+++ b/compiler/rustc_middle/src/infer/canonical.rs
@@ -34,7 +34,7 @@ use std::ops::Index;
 /// variables have been rewritten to "canonical vars". These are
 /// numbered starting from 0 in order of first appearance.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyDecodable, TyEncodable)]
-#[derive(HashStable, TypeFoldable, Lift)]
+#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct Canonical<'tcx, V> {
     pub max_universe: ty::UniverseIndex,
     pub variables: CanonicalVarInfos<'tcx>,
@@ -53,7 +53,7 @@ pub type CanonicalVarInfos<'tcx> = &'tcx List<CanonicalVarInfo<'tcx>>;
 /// variables. You will need to supply it later to instantiate the
 /// canonicalized query response.
 #[derive(Clone, Debug, PartialEq, Eq, Hash, TyDecodable, TyEncodable)]
-#[derive(HashStable, TypeFoldable, Lift)]
+#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct CanonicalVarValues<'tcx> {
     pub var_values: IndexVec<BoundVar, GenericArg<'tcx>>,
 }
@@ -173,7 +173,7 @@ pub enum CanonicalTyVarKind {
 /// After we execute a query with a canonicalized key, we get back a
 /// `Canonical<QueryResponse<..>>`. You can use
 /// `instantiate_query_result` to access the data in this result.
-#[derive(Clone, Debug, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct QueryResponse<'tcx, R> {
     pub var_values: CanonicalVarValues<'tcx>,
     pub region_constraints: QueryRegionConstraints<'tcx>,
@@ -187,7 +187,7 @@ pub struct QueryResponse<'tcx, R> {
     pub value: R,
 }
 
-#[derive(Clone, Debug, Default, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, Debug, Default, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct QueryRegionConstraints<'tcx> {
     pub outlives: Vec<QueryOutlivesConstraint<'tcx>>,
     pub member_constraints: Vec<MemberConstraint<'tcx>>,
@@ -293,7 +293,7 @@ impl<'tcx, V> Canonical<'tcx, V> {
 pub type QueryOutlivesConstraint<'tcx> =
     ty::Binder<'tcx, ty::OutlivesPredicate<GenericArg<'tcx>, Region<'tcx>>>;
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     for <'tcx> {
         crate::infer::canonical::Certainty,
         crate::infer::canonical::CanonicalVarInfo<'tcx>,
@@ -301,7 +301,7 @@ TrivialTypeFoldableAndLiftImpls! {
     }
 }
 
-TrivialTypeFoldableImpls! {
+TrivialTypeTraversalImpls! {
     for <'tcx> {
         crate::infer::canonical::CanonicalVarInfos<'tcx>,
     }
diff --git a/compiler/rustc_middle/src/infer/mod.rs b/compiler/rustc_middle/src/infer/mod.rs
index 2350a6ab155..55e00c4c0d8 100644
--- a/compiler/rustc_middle/src/infer/mod.rs
+++ b/compiler/rustc_middle/src/infer/mod.rs
@@ -13,7 +13,7 @@ use rustc_span::Span;
 /// ```text
 /// R0 member of [O1..On]
 /// ```
-#[derive(Debug, Clone, HashStable, TypeFoldable, Lift)]
+#[derive(Debug, Clone, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct MemberConstraint<'tcx> {
     /// The `DefId` of the opaque type causing this constraint: used for error reporting.
     pub opaque_type_def_id: DefId,
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index 215d8decf2a..4b156de410d 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -2,9 +2,7 @@ use std::cmp;
 
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
-use rustc_errors::{
-    Diagnostic, DiagnosticBuilder, DiagnosticId, EmissionGuarantee, ErrorGuaranteed, MultiSpan,
-};
+use rustc_errors::{Diagnostic, DiagnosticId, LintDiagnosticBuilder, MultiSpan};
 use rustc_hir::HirId;
 use rustc_index::vec::IndexVec;
 use rustc_query_system::ich::StableHashingContext;
@@ -227,28 +225,6 @@ impl LintExpectation {
     }
 }
 
-pub struct LintDiagnosticBuilder<'a, G: EmissionGuarantee>(DiagnosticBuilder<'a, G>);
-
-impl<'a, G: EmissionGuarantee> LintDiagnosticBuilder<'a, G> {
-    /// Return the inner `DiagnosticBuilder`, first setting the primary message to `msg`.
-    pub fn build(mut self, msg: &str) -> DiagnosticBuilder<'a, G> {
-        self.0.set_primary_message(msg);
-        self.0.set_is_lint();
-        self.0
-    }
-
-    /// Create a `LintDiagnosticBuilder` from some existing `DiagnosticBuilder`.
-    pub fn new(err: DiagnosticBuilder<'a, G>) -> LintDiagnosticBuilder<'a, G> {
-        LintDiagnosticBuilder(err)
-    }
-}
-
-impl<'a> LintDiagnosticBuilder<'a, ErrorGuaranteed> {
-    pub fn forget_guarantee(self) -> LintDiagnosticBuilder<'a, ()> {
-        LintDiagnosticBuilder(self.0.forget_guarantee())
-    }
-}
-
 pub fn explain_lint_level_source(
     lint: &'static Lint,
     level: Level,
diff --git a/compiler/rustc_middle/src/macros.rs b/compiler/rustc_middle/src/macros.rs
index 33b4dff977e..0e85c60a363 100644
--- a/compiler/rustc_middle/src/macros.rs
+++ b/compiler/rustc_middle/src/macros.rs
@@ -18,7 +18,7 @@ macro_rules! span_bug {
 }
 
 ///////////////////////////////////////////////////////////////////////////
-// Lift and TypeFoldable macros
+// Lift and TypeFoldable/TypeVisitable macros
 //
 // When possible, use one of these (relatively) convenient macros to write
 // the impls for you.
@@ -48,7 +48,7 @@ macro_rules! CloneLiftImpls {
 /// Used for types that are `Copy` and which **do not care arena
 /// allocated data** (i.e., don't need to be folded).
 #[macro_export]
-macro_rules! TrivialTypeFoldableImpls {
+macro_rules! TrivialTypeTraversalImpls {
     (for <$tcx:lifetime> { $($ty:ty,)+ }) => {
         $(
             impl<$tcx> $crate::ty::fold::TypeFoldable<$tcx> for $ty {
@@ -58,8 +58,10 @@ macro_rules! TrivialTypeFoldableImpls {
                 ) -> ::std::result::Result<$ty, F::Error> {
                     Ok(self)
                 }
+            }
 
-                fn visit_with<F: $crate::ty::fold::TypeVisitor<$tcx>>(
+            impl<$tcx> $crate::ty::visit::TypeVisitable<$tcx> for $ty {
+                fn visit_with<F: $crate::ty::visit::TypeVisitor<$tcx>>(
                     &self,
                     _: &mut F)
                     -> ::std::ops::ControlFlow<F::BreakTy>
@@ -71,7 +73,7 @@ macro_rules! TrivialTypeFoldableImpls {
     };
 
     ($($ty:ty,)+) => {
-        TrivialTypeFoldableImpls! {
+        TrivialTypeTraversalImpls! {
             for <'tcx> {
                 $($ty,)+
             }
@@ -80,15 +82,15 @@ macro_rules! TrivialTypeFoldableImpls {
 }
 
 #[macro_export]
-macro_rules! TrivialTypeFoldableAndLiftImpls {
+macro_rules! TrivialTypeTraversalAndLiftImpls {
     ($($t:tt)*) => {
-        TrivialTypeFoldableImpls! { $($t)* }
+        TrivialTypeTraversalImpls! { $($t)* }
         CloneLiftImpls! { $($t)* }
     }
 }
 
 #[macro_export]
-macro_rules! EnumTypeFoldableImpl {
+macro_rules! EnumTypeTraversalImpl {
     (impl<$($p:tt),*> TypeFoldable<$tcx:tt> for $s:path {
         $($variants:tt)*
     } $(where $($wc:tt)*)*) => {
@@ -99,14 +101,22 @@ macro_rules! EnumTypeFoldableImpl {
                 self,
                 folder: &mut V,
             ) -> ::std::result::Result<Self, V::Error> {
-                EnumTypeFoldableImpl!(@FoldVariants(self, folder) input($($variants)*) output())
+                EnumTypeTraversalImpl!(@FoldVariants(self, folder) input($($variants)*) output())
             }
+        }
+    };
 
-            fn visit_with<V: $crate::ty::fold::TypeVisitor<$tcx>>(
+    (impl<$($p:tt),*> TypeVisitable<$tcx:tt> for $s:path {
+        $($variants:tt)*
+    } $(where $($wc:tt)*)*) => {
+        impl<$($p),*> $crate::ty::visit::TypeVisitable<$tcx> for $s
+            $(where $($wc)*)*
+        {
+            fn visit_with<V: $crate::ty::visit::TypeVisitor<$tcx>>(
                 &self,
                 visitor: &mut V,
             ) -> ::std::ops::ControlFlow<V::BreakTy> {
-                EnumTypeFoldableImpl!(@VisitVariants(self, visitor) input($($variants)*) output())
+                EnumTypeTraversalImpl!(@VisitVariants(self, visitor) input($($variants)*) output())
             }
         }
     };
@@ -120,7 +130,7 @@ macro_rules! EnumTypeFoldableImpl {
     (@FoldVariants($this:expr, $folder:expr)
      input( ($variant:path) ( $($variant_arg:ident),* ) , $($input:tt)*)
      output( $($output:tt)*) ) => {
-        EnumTypeFoldableImpl!(
+        EnumTypeTraversalImpl!(
             @FoldVariants($this, $folder)
                 input($($input)*)
                 output(
@@ -137,7 +147,7 @@ macro_rules! EnumTypeFoldableImpl {
     (@FoldVariants($this:expr, $folder:expr)
      input( ($variant:path) { $($variant_arg:ident),* $(,)? } , $($input:tt)*)
      output( $($output:tt)*) ) => {
-        EnumTypeFoldableImpl!(
+        EnumTypeTraversalImpl!(
             @FoldVariants($this, $folder)
                 input($($input)*)
                 output(
@@ -155,7 +165,7 @@ macro_rules! EnumTypeFoldableImpl {
     (@FoldVariants($this:expr, $folder:expr)
      input( ($variant:path), $($input:tt)*)
      output( $($output:tt)*) ) => {
-        EnumTypeFoldableImpl!(
+        EnumTypeTraversalImpl!(
             @FoldVariants($this, $folder)
                 input($($input)*)
                 output(
@@ -174,12 +184,12 @@ macro_rules! EnumTypeFoldableImpl {
     (@VisitVariants($this:expr, $visitor:expr)
      input( ($variant:path) ( $($variant_arg:ident),* ) , $($input:tt)*)
      output( $($output:tt)*) ) => {
-        EnumTypeFoldableImpl!(
+        EnumTypeTraversalImpl!(
             @VisitVariants($this, $visitor)
                 input($($input)*)
                 output(
                     $variant ( $($variant_arg),* ) => {
-                        $($crate::ty::fold::TypeFoldable::visit_with(
+                        $($crate::ty::visit::TypeVisitable::visit_with(
                             $variant_arg, $visitor
                         )?;)*
                         ::std::ops::ControlFlow::CONTINUE
@@ -192,12 +202,12 @@ macro_rules! EnumTypeFoldableImpl {
     (@VisitVariants($this:expr, $visitor:expr)
      input( ($variant:path) { $($variant_arg:ident),* $(,)? } , $($input:tt)*)
      output( $($output:tt)*) ) => {
-        EnumTypeFoldableImpl!(
+        EnumTypeTraversalImpl!(
             @VisitVariants($this, $visitor)
                 input($($input)*)
                 output(
                     $variant { $($variant_arg),* } => {
-                        $($crate::ty::fold::TypeFoldable::visit_with(
+                        $($crate::ty::visit::TypeVisitable::visit_with(
                             $variant_arg, $visitor
                         )?;)*
                         ::std::ops::ControlFlow::CONTINUE
@@ -210,7 +220,7 @@ macro_rules! EnumTypeFoldableImpl {
     (@VisitVariants($this:expr, $visitor:expr)
      input( ($variant:path), $($input:tt)*)
      output( $($output:tt)*) ) => {
-        EnumTypeFoldableImpl!(
+        EnumTypeTraversalImpl!(
             @VisitVariants($this, $visitor)
                 input($($input)*)
                 output(
diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs
index 2336d460407..efa9464529e 100644
--- a/compiler/rustc_middle/src/mir/coverage.rs
+++ b/compiler/rustc_middle/src/mir/coverage.rs
@@ -100,7 +100,7 @@ impl From<InjectedExpressionId> for ExpressionOperandId {
     }
 }
 
-#[derive(Clone, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
+#[derive(Clone, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
 pub enum CoverageKind {
     Counter {
         function_source_hash: u64,
@@ -148,18 +148,8 @@ impl Debug for CoverageKind {
     }
 }
 
-#[derive(
-    Clone,
-    TyEncodable,
-    TyDecodable,
-    Hash,
-    HashStable,
-    TypeFoldable,
-    PartialEq,
-    Eq,
-    PartialOrd,
-    Ord
-)]
+#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct CodeRegion {
     pub file_name: Symbol,
     pub start_line: u32,
@@ -178,7 +168,8 @@ impl Debug for CodeRegion {
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
+#[derive(Copy, Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum Op {
     Subtract,
     Add,
diff --git a/compiler/rustc_middle/src/mir/graph_cyclic_cache.rs b/compiler/rustc_middle/src/mir/graph_cyclic_cache.rs
index 1279f5aee36..f97bf2883b3 100644
--- a/compiler/rustc_middle/src/mir/graph_cyclic_cache.rs
+++ b/compiler/rustc_middle/src/mir/graph_cyclic_cache.rs
@@ -58,6 +58,6 @@ impl<CTX> HashStable<CTX> for GraphIsCyclicCache {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     GraphIsCyclicCache,
 }
diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs
index 1e2b53040d2..1bbd71c3f1f 100644
--- a/compiler/rustc_middle/src/mir/interpret/allocation.rs
+++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs
@@ -160,12 +160,18 @@ impl AllocError {
 }
 
 /// The information that makes up a memory access: offset and size.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone)]
 pub struct AllocRange {
     pub start: Size,
     pub size: Size,
 }
 
+impl fmt::Debug for AllocRange {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "[{:#x}..{:#x}]", self.start.bytes(), self.end().bytes())
+    }
+}
+
 /// Free-starting constructor for less syntactic overhead.
 #[inline(always)]
 pub fn alloc_range(start: Size, size: Size) -> AllocRange {
diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs
index a33a2921f57..2a1fd6f736e 100644
--- a/compiler/rustc_middle/src/mir/interpret/error.rs
+++ b/compiler/rustc_middle/src/mir/interpret/error.rs
@@ -29,7 +29,7 @@ impl From<ErrorGuaranteed> for ErrorHandled {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     ErrorHandled,
 }
 
@@ -186,7 +186,7 @@ pub enum CheckInAllocMsg {
 
 impl fmt::Display for CheckInAllocMsg {
     /// When this is printed as an error the context looks like this:
-    /// "{msg}0x01 is not a valid pointer".
+    /// "{msg}{pointer} is a dangling pointer".
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(
             f,
@@ -194,9 +194,9 @@ impl fmt::Display for CheckInAllocMsg {
             match *self {
                 CheckInAllocMsg::DerefTest => "dereferencing pointer failed: ",
                 CheckInAllocMsg::MemoryAccessTest => "memory access failed: ",
-                CheckInAllocMsg::PointerArithmeticTest => "pointer arithmetic failed: ",
+                CheckInAllocMsg::PointerArithmeticTest => "out-of-bounds pointer arithmetic: ",
                 CheckInAllocMsg::OffsetFromTest => "out-of-bounds offset_from: ",
-                CheckInAllocMsg::InboundsTest => "",
+                CheckInAllocMsg::InboundsTest => "out-of-bounds pointer use: ",
             }
         )
     }
@@ -334,36 +334,28 @@ impl fmt::Display for UndefinedBehaviorInfo<'_> {
                 p,
             ),
             PointerUseAfterFree(a) => {
-                write!(f, "pointer to {} was dereferenced after this allocation got freed", a)
+                write!(f, "pointer to {a:?} was dereferenced after this allocation got freed")
             }
             PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size: Size::ZERO, msg } => {
                 write!(
                     f,
-                    "{}{alloc_id} has size {alloc_size}, so pointer at offset {ptr_offset} is out-of-bounds",
-                    msg,
-                    alloc_id = alloc_id,
+                    "{msg}{alloc_id:?} has size {alloc_size}, so pointer at offset {ptr_offset} is out-of-bounds",
                     alloc_size = alloc_size.bytes(),
-                    ptr_offset = ptr_offset,
                 )
             }
             PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => write!(
                 f,
-                "{}{alloc_id} has size {alloc_size}, so pointer to {ptr_size} byte{ptr_size_p} starting at offset {ptr_offset} is out-of-bounds",
-                msg,
-                alloc_id = alloc_id,
+                "{msg}{alloc_id:?} has size {alloc_size}, so pointer to {ptr_size} byte{ptr_size_p} starting at offset {ptr_offset} is out-of-bounds",
                 alloc_size = alloc_size.bytes(),
                 ptr_size = ptr_size.bytes(),
                 ptr_size_p = pluralize!(ptr_size.bytes()),
-                ptr_offset = ptr_offset,
             ),
-            DanglingIntPointer(0, CheckInAllocMsg::InboundsTest) => {
-                write!(f, "null pointer is not a valid pointer for this operation")
-            }
-            DanglingIntPointer(0, msg) => {
-                write!(f, "{}null pointer is not a valid pointer", msg)
-            }
             DanglingIntPointer(i, msg) => {
-                write!(f, "{}0x{:x} is not a valid pointer", msg, i)
+                write!(
+                    f,
+                    "{msg}{pointer} is a dangling pointer (it has no provenance)",
+                    pointer = Pointer::<Option<AllocId>>::from_addr(*i),
+                )
             }
             AlignmentCheckFailed { required, has } => write!(
                 f,
@@ -371,8 +363,8 @@ impl fmt::Display for UndefinedBehaviorInfo<'_> {
                 has.bytes(),
                 required.bytes()
             ),
-            WriteToReadOnly(a) => write!(f, "writing to {} which is read-only", a),
-            DerefFunctionPointer(a) => write!(f, "accessing {} which contains a function", a),
+            WriteToReadOnly(a) => write!(f, "writing to {a:?} which is read-only"),
+            DerefFunctionPointer(a) => write!(f, "accessing {a:?} which contains a function"),
             ValidationFailure { path: None, msg } => {
                 write!(f, "constructing invalid value: {}", msg)
             }
diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs
index 8733a85ef3f..698024b2330 100644
--- a/compiler/rustc_middle/src/mir/interpret/mod.rs
+++ b/compiler/rustc_middle/src/mir/interpret/mod.rs
@@ -190,11 +190,7 @@ impl fmt::Debug for AllocId {
     }
 }
 
-impl fmt::Display for AllocId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        fmt::Debug::fmt(self, f)
-    }
-}
+// No "Display" since AllocIds are not usually user-visible.
 
 #[derive(TyDecodable, TyEncodable)]
 enum AllocDiscriminant {
@@ -470,7 +466,7 @@ impl<'tcx> TyCtxt<'tcx> {
             return alloc_id;
         }
         let id = alloc_map.reserve();
-        debug!("creating alloc {:?} with id {}", alloc, id);
+        debug!("creating alloc {alloc:?} with id {id:?}");
         alloc_map.alloc_map.insert(id, alloc.clone());
         alloc_map.dedup.insert(alloc, id);
         id
@@ -538,7 +534,7 @@ impl<'tcx> TyCtxt<'tcx> {
     pub fn global_alloc(self, id: AllocId) -> GlobalAlloc<'tcx> {
         match self.get_global_alloc(id) {
             Some(alloc) => alloc,
-            None => bug!("could not find allocation for {}", id),
+            None => bug!("could not find allocation for {id:?}"),
         }
     }
 
@@ -546,7 +542,7 @@ impl<'tcx> TyCtxt<'tcx> {
     /// call this function twice, even with the same `Allocation` will ICE the compiler.
     pub fn set_alloc_id_memory(self, id: AllocId, mem: ConstAllocation<'tcx>) {
         if let Some(old) = self.alloc_map.lock().alloc_map.insert(id, GlobalAlloc::Memory(mem)) {
-            bug!("tried to set allocation ID {}, but it was already existing as {:#?}", id, old);
+            bug!("tried to set allocation ID {id:?}, but it was already existing as {old:#?}");
         }
     }
 
diff --git a/compiler/rustc_middle/src/mir/interpret/pointer.rs b/compiler/rustc_middle/src/mir/interpret/pointer.rs
index 26da93b9dce..d4cdf45d186 100644
--- a/compiler/rustc_middle/src/mir/interpret/pointer.rs
+++ b/compiler/rustc_middle/src/mir/interpret/pointer.rs
@@ -144,7 +144,7 @@ impl Provenance for AllocId {
         }
         // Print offset only if it is non-zero.
         if ptr.offset.bytes() > 0 {
-            write!(f, "+0x{:x}", ptr.offset.bytes())?;
+            write!(f, "+{:#x}", ptr.offset.bytes())?;
         }
         Ok(())
     }
@@ -181,7 +181,17 @@ impl<Tag: Provenance> fmt::Debug for Pointer<Option<Tag>> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self.provenance {
             Some(tag) => Provenance::fmt(&Pointer::new(tag, self.offset), f),
-            None => write!(f, "0x{:x}", self.offset.bytes()),
+            None => write!(f, "{:#x}[noalloc]", self.offset.bytes()),
+        }
+    }
+}
+
+impl<Tag: Provenance> fmt::Display for Pointer<Option<Tag>> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if self.provenance.is_none() && self.offset.bytes() == 0 {
+            write!(f, "null pointer")
+        } else {
+            fmt::Debug::fmt(self, f)
         }
     }
 }
@@ -227,8 +237,13 @@ impl<Tag> Pointer<Option<Tag>> {
 
 impl<Tag> Pointer<Option<Tag>> {
     #[inline(always)]
+    pub fn from_addr(addr: u64) -> Self {
+        Pointer { provenance: None, offset: Size::from_bytes(addr) }
+    }
+
+    #[inline(always)]
     pub fn null() -> Self {
-        Pointer { provenance: None, offset: Size::ZERO }
+        Pointer::from_addr(0)
     }
 }
 
diff --git a/compiler/rustc_middle/src/mir/interpret/queries.rs b/compiler/rustc_middle/src/mir/interpret/queries.rs
index 575147feebc..786927e2dad 100644
--- a/compiler/rustc_middle/src/mir/interpret/queries.rs
+++ b/compiler/rustc_middle/src/mir/interpret/queries.rs
@@ -1,8 +1,8 @@
 use super::{ErrorHandled, EvalToConstValueResult, EvalToValTreeResult, GlobalId};
 
 use crate::mir;
-use crate::ty::fold::TypeFoldable;
 use crate::ty::subst::InternalSubsts;
+use crate::ty::visit::TypeVisitable;
 use crate::ty::{self, query::TyCtxtAt, query::TyCtxtEnsure, TyCtxt};
 use rustc_hir::def_id::DefId;
 use rustc_span::{Span, DUMMY_SP};
diff --git a/compiler/rustc_middle/src/mir/interpret/value.rs b/compiler/rustc_middle/src/mir/interpret/value.rs
index e80918d5e5d..8ecbb5ab0b3 100644
--- a/compiler/rustc_middle/src/mir/interpret/value.rs
+++ b/compiler/rustc_middle/src/mir/interpret/value.rs
@@ -167,7 +167,7 @@ impl<Tag: Provenance> fmt::LowerHex for Scalar<Tag> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr),
-            Scalar::Int(int) => write!(f, "0x{:x}", int),
+            Scalar::Int(int) => write!(f, "{:#x}", int),
         }
     }
 }
diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index ad33a54e0a2..e7d7317456c 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -8,9 +8,10 @@ use crate::mir::interpret::{
 use crate::mir::traversal::PostorderCache;
 use crate::mir::visit::MirVisitable;
 use crate::ty::codec::{TyDecoder, TyEncoder};
-use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable};
 use crate::ty::print::{FmtPrinter, Printer};
 use crate::ty::subst::{GenericArg, InternalSubsts, Subst, SubstsRef};
+use crate::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use crate::ty::{self, List, Ty, TyCtxt};
 use crate::ty::{AdtDef, InstanceDef, ScalarInt, UserTypeAnnotationIndex};
 
@@ -68,6 +69,7 @@ pub use terminator::*;
 
 pub mod traversal;
 mod type_foldable;
+mod type_visitable;
 pub mod visit;
 
 pub use self::generic_graph::graphviz_safe_def_name;
@@ -136,7 +138,7 @@ impl MirPhase {
 
 /// Where a specific `mir::Body` comes from.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
-#[derive(HashStable, TyEncodable, TyDecodable, TypeFoldable)]
+#[derive(HashStable, TyEncodable, TyDecodable, TypeFoldable, TypeVisitable)]
 pub struct MirSource<'tcx> {
     pub instance: InstanceDef<'tcx>,
 
@@ -166,7 +168,7 @@ impl<'tcx> MirSource<'tcx> {
     }
 }
 
-#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable)]
+#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable, TypeVisitable)]
 pub struct GeneratorInfo<'tcx> {
     /// The yield type of the function, if it is a generator.
     pub yield_ty: Option<Ty<'tcx>>,
@@ -183,7 +185,7 @@ pub struct GeneratorInfo<'tcx> {
 }
 
 /// The lowered representation of a single function.
-#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable)]
+#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable, TypeVisitable)]
 pub struct Body<'tcx> {
     /// A list of basic blocks. References to basic block use a newtyped index type [`BasicBlock`]
     /// that indexes into this vector.
@@ -357,10 +359,7 @@ impl<'tcx> Body<'tcx> {
         //
         // FIXME: Use a finer-grained API for this, so only transformations that alter terminators
         // invalidate the caches.
-        self.predecessor_cache.invalidate();
-        self.switch_source_cache.invalidate();
-        self.is_cyclic.invalidate();
-        self.postorder_cache.invalidate();
+        self.invalidate_cfg_cache();
         &mut self.basic_blocks
     }
 
@@ -368,10 +367,7 @@ impl<'tcx> Body<'tcx> {
     pub fn basic_blocks_and_local_decls_mut(
         &mut self,
     ) -> (&mut IndexVec<BasicBlock, BasicBlockData<'tcx>>, &mut LocalDecls<'tcx>) {
-        self.predecessor_cache.invalidate();
-        self.switch_source_cache.invalidate();
-        self.is_cyclic.invalidate();
-        self.postorder_cache.invalidate();
+        self.invalidate_cfg_cache();
         (&mut self.basic_blocks, &mut self.local_decls)
     }
 
@@ -383,11 +379,43 @@ impl<'tcx> Body<'tcx> {
         &mut LocalDecls<'tcx>,
         &mut Vec<VarDebugInfo<'tcx>>,
     ) {
+        self.invalidate_cfg_cache();
+        (&mut self.basic_blocks, &mut self.local_decls, &mut self.var_debug_info)
+    }
+
+    /// Get mutable access to parts of the Body without invalidating the CFG cache.
+    ///
+    /// By calling this method instead of eg [`Body::basic_blocks_mut`], you promise not to change
+    /// the CFG. This means that
+    ///
+    ///  1) The number of basic blocks remains unchanged
+    ///  2) The set of successors of each terminator remains unchanged.
+    ///  3) For each `TerminatorKind::SwitchInt`, the `targets` remains the same and the terminator
+    ///     kind is not changed.
+    ///
+    /// If any of these conditions cannot be upheld, you should call [`Body::invalidate_cfg_cache`].
+    #[inline]
+    pub fn basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate(
+        &mut self,
+    ) -> (
+        &mut IndexVec<BasicBlock, BasicBlockData<'tcx>>,
+        &mut LocalDecls<'tcx>,
+        &mut Vec<VarDebugInfo<'tcx>>,
+    ) {
+        (&mut self.basic_blocks, &mut self.local_decls, &mut self.var_debug_info)
+    }
+
+    /// Invalidates cached information about the CFG.
+    ///
+    /// You will only ever need this if you have also called
+    /// [`Body::basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate`]. All other methods
+    /// that allow you to mutate the body also call this method themselves, thereby avoiding any
+    /// risk of accidentaly cache invalidation.
+    pub fn invalidate_cfg_cache(&mut self) {
         self.predecessor_cache.invalidate();
         self.switch_source_cache.invalidate();
         self.is_cyclic.invalidate();
         self.postorder_cache.invalidate();
-        (&mut self.basic_blocks, &mut self.local_decls, &mut self.var_debug_info)
     }
 
     /// Returns `true` if a cycle exists in the control-flow graph that is reachable from the
@@ -575,7 +603,7 @@ impl<'tcx> IndexMut<BasicBlock> for Body<'tcx> {
     }
 }
 
-#[derive(Copy, Clone, Debug, HashStable, TypeFoldable)]
+#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable)]
 pub enum ClearCrossCrate<T> {
     Clear,
     Set(T),
@@ -736,7 +764,7 @@ pub enum ImplicitSelfKind {
     None,
 }
 
-TrivialTypeFoldableAndLiftImpls! { BindingForm<'tcx>, }
+TrivialTypeTraversalAndLiftImpls! { BindingForm<'tcx>, }
 
 mod binding_form_impl {
     use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
@@ -781,7 +809,7 @@ pub struct BlockTailInfo {
 ///
 /// This can be a binding declared by the user, a temporary inserted by the compiler, a function
 /// argument, or the return place.
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct LocalDecl<'tcx> {
     /// Whether this is a mutable binding (i.e., `let x` or `let mut x`).
     ///
@@ -916,7 +944,7 @@ static_assert_size!(LocalDecl<'_>, 56);
 ///
 /// Not used for non-StaticRef temporaries, the return place, or anonymous
 /// function parameters.
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub enum LocalInfo<'tcx> {
     /// A user-defined local variable or function parameter
     ///
@@ -1055,7 +1083,7 @@ impl<'tcx> LocalDecl<'tcx> {
     }
 }
 
-#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub enum VarDebugInfoContents<'tcx> {
     /// NOTE(eddyb) There's an unenforced invariant that this `Place` is
     /// based on a `Local`, not a `Static`, and contains no indexing.
@@ -1073,7 +1101,7 @@ impl<'tcx> Debug for VarDebugInfoContents<'tcx> {
 }
 
 /// Debug information pertaining to a user variable.
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct VarDebugInfo<'tcx> {
     pub name: Symbol,
 
@@ -1129,7 +1157,7 @@ impl BasicBlock {
 // BasicBlockData
 
 /// See [`BasicBlock`] for documentation on what basic blocks are at a high level.
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct BasicBlockData<'tcx> {
     /// List of statements in this block.
     pub statements: Vec<Statement<'tcx>>,
@@ -1366,7 +1394,7 @@ impl<O: fmt::Debug> fmt::Debug for AssertKind<O> {
 ///////////////////////////////////////////////////////////////////////////
 // Statements
 
-#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct Statement<'tcx> {
     pub source_info: SourceInfo,
     pub kind: StatementKind<'tcx>,
@@ -1732,7 +1760,7 @@ impl SourceScope {
     }
 }
 
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct SourceScopeData<'tcx> {
     pub span: Span,
     pub parent_scope: Option<SourceScope>,
@@ -2498,7 +2526,7 @@ impl<'tcx> ConstantKind<'tcx> {
 /// The first will lead to the constraint `w: &'1 str` (for some
 /// inferred region `'1`). The second will lead to the constraint `w:
 /// &'static str`.
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct UserTypeProjections {
     pub contents: Vec<(UserTypeProjection, Span)>,
 }
@@ -2615,7 +2643,7 @@ impl UserTypeProjection {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! { ProjectionKind, }
+TrivialTypeTraversalAndLiftImpls! { ProjectionKind, }
 
 impl<'tcx> TypeFoldable<'tcx> for UserTypeProjection {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
@@ -2624,7 +2652,9 @@ impl<'tcx> TypeFoldable<'tcx> for UserTypeProjection {
             projs: self.projs.try_fold_with(folder)?,
         })
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for UserTypeProjection {
     fn visit_with<Vs: TypeVisitor<'tcx>>(&self, visitor: &mut Vs) -> ControlFlow<Vs::BreakTy> {
         self.base.visit_with(visitor)
         // Note: there's nothing in `self.proj` to visit.
diff --git a/compiler/rustc_middle/src/mir/predecessors.rs b/compiler/rustc_middle/src/mir/predecessors.rs
index 620cf7e336b..5f1fadaf3bc 100644
--- a/compiler/rustc_middle/src/mir/predecessors.rs
+++ b/compiler/rustc_middle/src/mir/predecessors.rs
@@ -73,6 +73,6 @@ impl<CTX> HashStable<CTX> for PredecessorCache {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     PredecessorCache,
 }
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index 462c0ada3cf..24c6cd91d0a 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -716,12 +716,12 @@ pub fn write_allocations<'tcx>(
                 }
                 write!(w, "{}", display_allocation(tcx, alloc.inner()))
             };
-        write!(w, "\n{}", id)?;
+        write!(w, "\n{id:?}")?;
         match tcx.get_global_alloc(id) {
             // This can't really happen unless there are bugs, but it doesn't cost us anything to
             // gracefully handle it and allow buggy rustc to be debugged via allocation printing.
             None => write!(w, " (deallocated)")?,
-            Some(GlobalAlloc::Function(inst)) => write!(w, " (fn: {})", inst)?,
+            Some(GlobalAlloc::Function(inst)) => write!(w, " (fn: {inst})")?,
             Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
                 match tcx.eval_static_initializer(did) {
                     Ok(alloc) => {
diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs
index da4793fa039..620f0380d53 100644
--- a/compiler/rustc_middle/src/mir/query.rs
+++ b/compiler/rustc_middle/src/mir/query.rs
@@ -161,7 +161,7 @@ rustc_index::newtype_index! {
 }
 
 /// The layout of generator state.
-#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct GeneratorLayout<'tcx> {
     /// The type of every local stored inside the generator.
     pub field_tys: IndexVec<GeneratorSavedLocal, Ty<'tcx>>,
diff --git a/compiler/rustc_middle/src/mir/switch_sources.rs b/compiler/rustc_middle/src/mir/switch_sources.rs
index 99d13fcfef4..d1f3e6b6fe6 100644
--- a/compiler/rustc_middle/src/mir/switch_sources.rs
+++ b/compiler/rustc_middle/src/mir/switch_sources.rs
@@ -73,6 +73,6 @@ impl<CTX> HashStable<CTX> for SwitchSourceCache {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     SwitchSourceCache,
 }
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index f8ee59f306f..dcc37c565c9 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -179,7 +179,8 @@ pub enum BorrowKind {
 /// Not all of these are allowed at every [`MirPhase`]. Check the documentation there to see which
 /// ones you do not have to worry about. The MIR validator will generally enforce such restrictions,
 /// causing an ICE if they are violated.
-#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum StatementKind<'tcx> {
     /// Assign statements roughly correspond to an assignment in Rust proper (`x = ...`) except
     /// without the possibility of dropping the previous value (that must be done separately, if at
@@ -311,6 +312,7 @@ pub enum StatementKind<'tcx> {
 
 /// Describes what kind of retag is to be performed.
 #[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, Hash, HashStable)]
+#[rustc_pass_by_value]
 pub enum RetagKind {
     /// The initial retag when entering a function.
     FnEntry,
@@ -375,13 +377,15 @@ pub enum FakeReadCause {
     ForIndex,
 }
 
-#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct Coverage {
     pub kind: CoverageKind,
     pub code_region: Option<CodeRegion>,
 }
 
-#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct CopyNonOverlapping<'tcx> {
     pub src: Operand<'tcx>,
     pub dst: Operand<'tcx>,
@@ -392,6 +396,8 @@ pub struct CopyNonOverlapping<'tcx> {
 ///////////////////////////////////////////////////////////////////////////
 // Terminators
 
+/// The various kinds of terminators, representing ways of exiting from a basic block.
+///
 /// A note on unwinding: Panics may occur during the execution of some terminators. Depending on the
 /// `-C panic` flag, this may either cause the program to abort or the call stack to unwind. Such
 /// terminators have a `cleanup: Option<BasicBlock>` field on them. If stack unwinding occurs, then
@@ -671,7 +677,8 @@ pub enum AssertKind<O> {
     ResumedAfterPanic(GeneratorKind),
 }
 
-#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum InlineAsmOperand<'tcx> {
     In {
         reg: InlineAsmRegOrRegClass,
@@ -906,7 +913,7 @@ pub enum Operand<'tcx> {
 static_assert_size!(Operand<'_>, 24);
 
 ///////////////////////////////////////////////////////////////////////////
-/// Rvalues
+// Rvalues
 
 /// The various kinds of rvalues that can appear in MIR.
 ///
@@ -990,11 +997,20 @@ pub enum Rvalue<'tcx> {
     ///   matching types and return a value of that type.
     BinaryOp(BinOp, Box<(Operand<'tcx>, Operand<'tcx>)>),
 
-    /// Same as `BinaryOp`, but yields `(T, bool)` instead of `T`. In addition to performing the
-    /// same computation as the matching `BinaryOp`, checks if the infinite precison result would be
-    /// unequal to the actual result and sets the `bool` if this is the case.
+    /// Same as `BinaryOp`, but yields `(T, bool)` with a `bool` indicating an error condition.
+    ///
+    /// When overflow checking is disabled and we are generating run-time code, the error condition
+    /// is false. Otherwise, and always during CTFE, the error condition is determined as described
+    /// below.
+    ///
+    /// For addition, subtraction, and multiplication on integers the error condition is set when
+    /// the infinite precision result would be unequal to the actual result.
+    ///
+    /// For shift operations on integers the error condition is set when the value of right-hand
+    /// side is greater than or equal to the number of bits in the type of the left-hand side, or
+    /// when the value of right-hand side is negative.
     ///
-    /// This only supports addition, subtraction, multiplication, and shift operations on integers.
+    /// Other combinations of types and operators are unsupported.
     CheckedBinaryOp(BinOp, Box<(Operand<'tcx>, Operand<'tcx>)>),
 
     /// Computes a value as described by the operation.
diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs
index c93b7a95502..c99faf80187 100644
--- a/compiler/rustc_middle/src/mir/tcx.rs
+++ b/compiler/rustc_middle/src/mir/tcx.rs
@@ -9,7 +9,7 @@ use crate::ty::{self, Ty, TyCtxt};
 use rustc_hir as hir;
 use rustc_target::abi::VariantIdx;
 
-#[derive(Copy, Clone, Debug, TypeFoldable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct PlaceTy<'tcx> {
     pub ty: Ty<'tcx>,
     /// Downcast to a particular variant of an enum or a generator, if included.
diff --git a/compiler/rustc_middle/src/mir/traversal.rs b/compiler/rustc_middle/src/mir/traversal.rs
index 7228e3f33b1..30648679dae 100644
--- a/compiler/rustc_middle/src/mir/traversal.rs
+++ b/compiler/rustc_middle/src/mir/traversal.rs
@@ -384,6 +384,6 @@ impl<CTX> HashStable<CTX> for PostorderCache {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     PostorderCache,
 }
diff --git a/compiler/rustc_middle/src/mir/type_foldable.rs b/compiler/rustc_middle/src/mir/type_foldable.rs
index 4201b2d11ce..3c9850a9eb3 100644
--- a/compiler/rustc_middle/src/mir/type_foldable.rs
+++ b/compiler/rustc_middle/src/mir/type_foldable.rs
@@ -4,7 +4,7 @@ use super::*;
 use crate::ty;
 use rustc_data_structures::functor::IdFunctor;
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     BlockTailInfo,
     MirPhase,
     SourceInfo,
@@ -89,65 +89,12 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> {
         };
         Ok(Terminator { source_info: self.source_info, kind })
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        use crate::mir::TerminatorKind::*;
-
-        match self.kind {
-            SwitchInt { ref discr, switch_ty, .. } => {
-                discr.visit_with(visitor)?;
-                switch_ty.visit_with(visitor)
-            }
-            Drop { ref place, .. } => place.visit_with(visitor),
-            DropAndReplace { ref place, ref value, .. } => {
-                place.visit_with(visitor)?;
-                value.visit_with(visitor)
-            }
-            Yield { ref value, .. } => value.visit_with(visitor),
-            Call { ref func, ref args, ref destination, .. } => {
-                destination.visit_with(visitor)?;
-                func.visit_with(visitor)?;
-                args.visit_with(visitor)
-            }
-            Assert { ref cond, ref msg, .. } => {
-                cond.visit_with(visitor)?;
-                use AssertKind::*;
-                match msg {
-                    BoundsCheck { ref len, ref index } => {
-                        len.visit_with(visitor)?;
-                        index.visit_with(visitor)
-                    }
-                    Overflow(_, l, r) => {
-                        l.visit_with(visitor)?;
-                        r.visit_with(visitor)
-                    }
-                    OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => {
-                        op.visit_with(visitor)
-                    }
-                    ResumedAfterReturn(_) | ResumedAfterPanic(_) => ControlFlow::CONTINUE,
-                }
-            }
-            InlineAsm { ref operands, .. } => operands.visit_with(visitor),
-            Goto { .. }
-            | Resume
-            | Abort
-            | Return
-            | GeneratorDrop
-            | Unreachable
-            | FalseEdge { .. }
-            | FalseUnwind { .. } => ControlFlow::CONTINUE,
-        }
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for GeneratorKind {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
-        ControlFlow::CONTINUE
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for Place<'tcx> {
@@ -157,21 +104,12 @@ impl<'tcx> TypeFoldable<'tcx> for Place<'tcx> {
             projection: self.projection.try_fold_with(folder)?,
         })
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        self.local.visit_with(visitor)?;
-        self.projection.visit_with(visitor)
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<PlaceElem<'tcx>> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         ty::util::fold_list(self, folder, |tcx, v| tcx.intern_place_elems(v))
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        self.iter().try_for_each(|t| t.visit_with(visitor))
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for Rvalue<'tcx> {
@@ -224,55 +162,6 @@ impl<'tcx> TypeFoldable<'tcx> for Rvalue<'tcx> {
             }
         })
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        use crate::mir::Rvalue::*;
-        match *self {
-            Use(ref op) => op.visit_with(visitor),
-            Repeat(ref op, _) => op.visit_with(visitor),
-            ThreadLocalRef(did) => did.visit_with(visitor),
-            Ref(region, _, ref place) => {
-                region.visit_with(visitor)?;
-                place.visit_with(visitor)
-            }
-            AddressOf(_, ref place) => place.visit_with(visitor),
-            Len(ref place) => place.visit_with(visitor),
-            Cast(_, ref op, ty) => {
-                op.visit_with(visitor)?;
-                ty.visit_with(visitor)
-            }
-            BinaryOp(_, box (ref rhs, ref lhs)) | CheckedBinaryOp(_, box (ref rhs, ref lhs)) => {
-                rhs.visit_with(visitor)?;
-                lhs.visit_with(visitor)
-            }
-            UnaryOp(_, ref val) => val.visit_with(visitor),
-            Discriminant(ref place) => place.visit_with(visitor),
-            NullaryOp(_, ty) => ty.visit_with(visitor),
-            Aggregate(ref kind, ref fields) => {
-                match **kind {
-                    AggregateKind::Array(ty) => {
-                        ty.visit_with(visitor)?;
-                    }
-                    AggregateKind::Tuple => {}
-                    AggregateKind::Adt(_, _, substs, user_ty, _) => {
-                        substs.visit_with(visitor)?;
-                        user_ty.visit_with(visitor)?;
-                    }
-                    AggregateKind::Closure(_, substs) => {
-                        substs.visit_with(visitor)?;
-                    }
-                    AggregateKind::Generator(_, substs, _) => {
-                        substs.visit_with(visitor)?;
-                    }
-                }
-                fields.visit_with(visitor)
-            }
-            ShallowInitBox(ref op, ty) => {
-                op.visit_with(visitor)?;
-                ty.visit_with(visitor)
-            }
-        }
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for Operand<'tcx> {
@@ -283,13 +172,6 @@ impl<'tcx> TypeFoldable<'tcx> for Operand<'tcx> {
             Operand::Constant(c) => Operand::Constant(c.try_fold_with(folder)?),
         })
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        match *self {
-            Operand::Copy(ref place) | Operand::Move(ref place) => place.visit_with(visitor),
-            Operand::Constant(ref c) => c.visit_with(visitor),
-        }
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for PlaceElem<'tcx> {
@@ -307,43 +189,24 @@ impl<'tcx> TypeFoldable<'tcx> for PlaceElem<'tcx> {
             Subslice { from, to, from_end } => Subslice { from, to, from_end },
         })
     }
-
-    fn visit_with<Vs: TypeVisitor<'tcx>>(&self, visitor: &mut Vs) -> ControlFlow<Vs::BreakTy> {
-        use crate::mir::ProjectionElem::*;
-
-        match self {
-            Field(_, ty) => ty.visit_with(visitor),
-            Index(v) => v.visit_with(visitor),
-            _ => ControlFlow::CONTINUE,
-        }
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for Field {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
-        ControlFlow::CONTINUE
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for GeneratorSavedLocal {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
-        ControlFlow::CONTINUE
-    }
 }
 
 impl<'tcx, R: Idx, C: Idx> TypeFoldable<'tcx> for BitMatrix<R, C> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
-        ControlFlow::CONTINUE
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for Constant<'tcx> {
@@ -354,10 +217,6 @@ impl<'tcx> TypeFoldable<'tcx> for Constant<'tcx> {
             literal: self.literal.try_fold_with(folder)?,
         })
     }
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        self.literal.visit_with(visitor)?;
-        self.user_ty.visit_with(visitor)
-    }
 }
 
 impl<'tcx> TypeFoldable<'tcx> for ConstantKind<'tcx> {
@@ -365,10 +224,6 @@ impl<'tcx> TypeFoldable<'tcx> for ConstantKind<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_mir_const(self)
     }
-
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        visitor.visit_mir_const(*self)
-    }
 }
 
 impl<'tcx> TypeSuperFoldable<'tcx> for ConstantKind<'tcx> {
@@ -381,11 +236,4 @@ impl<'tcx> TypeSuperFoldable<'tcx> for ConstantKind<'tcx> {
             ConstantKind::Val(v, t) => Ok(ConstantKind::Val(v, t.try_fold_with(folder)?)),
         }
     }
-
-    fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
-        match *self {
-            ConstantKind::Ty(c) => c.visit_with(visitor),
-            ConstantKind::Val(_, t) => t.visit_with(visitor),
-        }
-    }
 }
diff --git a/compiler/rustc_middle/src/mir/type_visitable.rs b/compiler/rustc_middle/src/mir/type_visitable.rs
new file mode 100644
index 00000000000..d52ae5fac67
--- /dev/null
+++ b/compiler/rustc_middle/src/mir/type_visitable.rs
@@ -0,0 +1,186 @@
+//! `TypeVisitable` implementations for MIR types
+
+use super::*;
+use crate::ty;
+
+impl<'tcx> TypeVisitable<'tcx> for Terminator<'tcx> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        use crate::mir::TerminatorKind::*;
+
+        match self.kind {
+            SwitchInt { ref discr, switch_ty, .. } => {
+                discr.visit_with(visitor)?;
+                switch_ty.visit_with(visitor)
+            }
+            Drop { ref place, .. } => place.visit_with(visitor),
+            DropAndReplace { ref place, ref value, .. } => {
+                place.visit_with(visitor)?;
+                value.visit_with(visitor)
+            }
+            Yield { ref value, .. } => value.visit_with(visitor),
+            Call { ref func, ref args, ref destination, .. } => {
+                destination.visit_with(visitor)?;
+                func.visit_with(visitor)?;
+                args.visit_with(visitor)
+            }
+            Assert { ref cond, ref msg, .. } => {
+                cond.visit_with(visitor)?;
+                use AssertKind::*;
+                match msg {
+                    BoundsCheck { ref len, ref index } => {
+                        len.visit_with(visitor)?;
+                        index.visit_with(visitor)
+                    }
+                    Overflow(_, l, r) => {
+                        l.visit_with(visitor)?;
+                        r.visit_with(visitor)
+                    }
+                    OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => {
+                        op.visit_with(visitor)
+                    }
+                    ResumedAfterReturn(_) | ResumedAfterPanic(_) => ControlFlow::CONTINUE,
+                }
+            }
+            InlineAsm { ref operands, .. } => operands.visit_with(visitor),
+            Goto { .. }
+            | Resume
+            | Abort
+            | Return
+            | GeneratorDrop
+            | Unreachable
+            | FalseEdge { .. }
+            | FalseUnwind { .. } => ControlFlow::CONTINUE,
+        }
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for GeneratorKind {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
+        ControlFlow::CONTINUE
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for Place<'tcx> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        self.local.visit_with(visitor)?;
+        self.projection.visit_with(visitor)
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for &'tcx ty::List<PlaceElem<'tcx>> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        self.iter().try_for_each(|t| t.visit_with(visitor))
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for Rvalue<'tcx> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        use crate::mir::Rvalue::*;
+        match *self {
+            Use(ref op) => op.visit_with(visitor),
+            Repeat(ref op, _) => op.visit_with(visitor),
+            ThreadLocalRef(did) => did.visit_with(visitor),
+            Ref(region, _, ref place) => {
+                region.visit_with(visitor)?;
+                place.visit_with(visitor)
+            }
+            AddressOf(_, ref place) => place.visit_with(visitor),
+            Len(ref place) => place.visit_with(visitor),
+            Cast(_, ref op, ty) => {
+                op.visit_with(visitor)?;
+                ty.visit_with(visitor)
+            }
+            BinaryOp(_, box (ref rhs, ref lhs)) | CheckedBinaryOp(_, box (ref rhs, ref lhs)) => {
+                rhs.visit_with(visitor)?;
+                lhs.visit_with(visitor)
+            }
+            UnaryOp(_, ref val) => val.visit_with(visitor),
+            Discriminant(ref place) => place.visit_with(visitor),
+            NullaryOp(_, ty) => ty.visit_with(visitor),
+            Aggregate(ref kind, ref fields) => {
+                match **kind {
+                    AggregateKind::Array(ty) => {
+                        ty.visit_with(visitor)?;
+                    }
+                    AggregateKind::Tuple => {}
+                    AggregateKind::Adt(_, _, substs, user_ty, _) => {
+                        substs.visit_with(visitor)?;
+                        user_ty.visit_with(visitor)?;
+                    }
+                    AggregateKind::Closure(_, substs) => {
+                        substs.visit_with(visitor)?;
+                    }
+                    AggregateKind::Generator(_, substs, _) => {
+                        substs.visit_with(visitor)?;
+                    }
+                }
+                fields.visit_with(visitor)
+            }
+            ShallowInitBox(ref op, ty) => {
+                op.visit_with(visitor)?;
+                ty.visit_with(visitor)
+            }
+        }
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for Operand<'tcx> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        match *self {
+            Operand::Copy(ref place) | Operand::Move(ref place) => place.visit_with(visitor),
+            Operand::Constant(ref c) => c.visit_with(visitor),
+        }
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for PlaceElem<'tcx> {
+    fn visit_with<Vs: TypeVisitor<'tcx>>(&self, visitor: &mut Vs) -> ControlFlow<Vs::BreakTy> {
+        use crate::mir::ProjectionElem::*;
+
+        match self {
+            Field(_, ty) => ty.visit_with(visitor),
+            Index(v) => v.visit_with(visitor),
+            _ => ControlFlow::CONTINUE,
+        }
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for Field {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
+        ControlFlow::CONTINUE
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for GeneratorSavedLocal {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
+        ControlFlow::CONTINUE
+    }
+}
+
+impl<'tcx, R: Idx, C: Idx> TypeVisitable<'tcx> for BitMatrix<R, C> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
+        ControlFlow::CONTINUE
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for Constant<'tcx> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        self.literal.visit_with(visitor)?;
+        self.user_ty.visit_with(visitor)
+    }
+}
+
+impl<'tcx> TypeVisitable<'tcx> for ConstantKind<'tcx> {
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        visitor.visit_mir_const(*self)
+    }
+}
+
+impl<'tcx> TypeSuperVisitable<'tcx> for ConstantKind<'tcx> {
+    fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
+        match *self {
+            ConstantKind::Ty(c) => c.visit_with(visitor),
+            ConstantKind::Val(_, t) => t.visit_with(visitor),
+        }
+    }
+}
diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs
index 9285246eb79..d1477f9e2ae 100644
--- a/compiler/rustc_middle/src/mir/visit.rs
+++ b/compiler/rustc_middle/src/mir/visit.rs
@@ -147,7 +147,7 @@ macro_rules! make_mir_visitor {
             fn visit_ascribe_user_ty(
                 &mut self,
                 place: & $($mutability)? Place<'tcx>,
-                variance: & $($mutability)? ty::Variance,
+                variance: $(& $mutability)? ty::Variance,
                 user_ty: & $($mutability)? UserTypeProjection,
                 location: Location,
             ) {
@@ -164,7 +164,7 @@ macro_rules! make_mir_visitor {
 
             fn visit_retag(
                 &mut self,
-                kind: & $($mutability)? RetagKind,
+                kind: $(& $mutability)? RetagKind,
                 place: & $($mutability)? Place<'tcx>,
                 location: Location,
             ) {
@@ -425,7 +425,7 @@ macro_rules! make_mir_visitor {
                 self.visit_source_info(source_info);
                 match kind {
                     StatementKind::Assign(
-                        box(ref $($mutability)? place, ref $($mutability)? rvalue)
+                        box (place, rvalue)
                     ) => {
                         self.visit_assign(place, rvalue, location);
                     }
@@ -465,13 +465,13 @@ macro_rules! make_mir_visitor {
                         );
                     }
                     StatementKind::Retag(kind, place) => {
-                        self.visit_retag(kind, place, location);
+                        self.visit_retag($(& $mutability)? *kind, place, location);
                     }
                     StatementKind::AscribeUserType(
-                        box(ref $($mutability)? place, ref $($mutability)? user_ty),
+                        box (place, user_ty),
                         variance
                     ) => {
-                        self.visit_ascribe_user_ty(place, variance, user_ty, location);
+                        self.visit_ascribe_user_ty(place, $(& $mutability)? *variance, user_ty, location);
                     }
                     StatementKind::Coverage(coverage) => {
                         self.visit_coverage(
@@ -480,9 +480,9 @@ macro_rules! make_mir_visitor {
                         )
                     }
                     StatementKind::CopyNonOverlapping(box crate::mir::CopyNonOverlapping{
-                      ref $($mutability)? src,
-                      ref $($mutability)? dst,
-                      ref $($mutability)? count,
+                        src,
+                        dst,
+                        count,
                     }) => {
                       self.visit_operand(src, location);
                       self.visit_operand(dst, location);
@@ -517,8 +517,7 @@ macro_rules! make_mir_visitor {
                     TerminatorKind::GeneratorDrop |
                     TerminatorKind::Unreachable |
                     TerminatorKind::FalseEdge { .. } |
-                    TerminatorKind::FalseUnwind { .. } => {
-                    }
+                    TerminatorKind::FalseUnwind { .. } => {}
 
                     TerminatorKind::Return => {
                         // `return` logically moves from the return place `_0`. Note that the place
@@ -830,7 +829,7 @@ macro_rules! make_mir_visitor {
 
             fn super_ascribe_user_ty(&mut self,
                                      place: & $($mutability)? Place<'tcx>,
-                                     _variance: & $($mutability)? ty::Variance,
+                                     _variance: $(& $mutability)? ty::Variance,
                                      user_ty: & $($mutability)? UserTypeProjection,
                                      location: Location) {
                 self.visit_place(
@@ -847,7 +846,7 @@ macro_rules! make_mir_visitor {
             }
 
             fn super_retag(&mut self,
-                           _kind: & $($mutability)? RetagKind,
+                           _kind: $(& $mutability)? RetagKind,
                            place: & $($mutability)? Place<'tcx>,
                            location: Location) {
                 self.visit_place(
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 294f56d16b1..57c4f3f3ba3 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1573,7 +1573,7 @@ rustc_queries! {
         Option<&'tcx FxHashMap<ItemLocalId, Region>> {
         desc { "looking up a named region" }
     }
-    query is_late_bound_map(_: LocalDefId) -> Option<&'tcx FxHashSet<LocalDefId>> {
+    query is_late_bound_map(_: LocalDefId) -> Option<&'tcx FxIndexSet<LocalDefId>> {
         desc { "testing if a region is late bound" }
     }
     /// For a given item (like a struct), gets the default lifetimes to be used
@@ -1825,7 +1825,8 @@ rustc_queries! {
         remap_env_constness
     }
 
-    /// Do not call this query directly: invoke `infcx.at().dropck_outlives()` instead.
+    /// Do not call this query directly:
+    /// invoke `DropckOutlives::new(dropped_ty)).fully_perform(typeck.infcx)` instead.
     query dropck_outlives(
         goal: CanonicalTyGoal<'tcx>
     ) -> Result<
diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index 120d09ee353..03c11c2863f 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -191,18 +191,8 @@ pub enum StmtKind<'tcx> {
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
 rustc_data_structures::static_assert_size!(Expr<'_>, 104);
 
-#[derive(
-    Clone,
-    Debug,
-    Copy,
-    PartialEq,
-    Eq,
-    Hash,
-    HashStable,
-    TyEncodable,
-    TyDecodable,
-    TypeFoldable
-)]
+#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, HashStable, TyEncodable, TyDecodable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct LocalVarId(pub hir::HirId);
 
 /// A THIR expression.
diff --git a/compiler/rustc_middle/src/thir/abstract_const.rs b/compiler/rustc_middle/src/thir/abstract_const.rs
index e02ed414574..527dbd1cd09 100644
--- a/compiler/rustc_middle/src/thir/abstract_const.rs
+++ b/compiler/rustc_middle/src/thir/abstract_const.rs
@@ -42,7 +42,7 @@ impl From<ErrorGuaranteed> for NotConstEvaluatable {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     NotConstEvaluatable,
 }
 
diff --git a/compiler/rustc_middle/src/traits/chalk.rs b/compiler/rustc_middle/src/traits/chalk.rs
index 70abdb9ab4c..6d4af8bea62 100644
--- a/compiler/rustc_middle/src/traits/chalk.rs
+++ b/compiler/rustc_middle/src/traits/chalk.rs
@@ -390,7 +390,7 @@ impl<'tcx> chalk_ir::interner::HasInterner for RustInterner<'tcx> {
 }
 
 /// A chalk environment and goal.
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable, TypeFoldable)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable, TypeFoldable, TypeVisitable)]
 pub struct ChalkEnvironmentAndGoal<'tcx> {
     pub environment: &'tcx ty::List<ty::Predicate<'tcx>>,
     pub goal: ty::Predicate<'tcx>,
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index 7f913faf860..eee44df8645 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -523,7 +523,7 @@ pub struct DerivedObligationCause<'tcx> {
     pub parent_code: InternedObligationCauseCode<'tcx>,
 }
 
-#[derive(Clone, Debug, TypeFoldable, Lift)]
+#[derive(Clone, Debug, TypeFoldable, TypeVisitable, Lift)]
 pub enum SelectionError<'tcx> {
     /// The trait is not implemented.
     Unimplemented,
@@ -592,7 +592,8 @@ pub type SelectionResult<'tcx, T> = Result<Option<T>, SelectionError<'tcx>>;
 /// ### The type parameter `N`
 ///
 /// See explanation on `ImplSourceUserDefinedData`.
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum ImplSource<'tcx, N> {
     /// ImplSource identifying a particular impl.
     UserDefined(ImplSourceUserDefinedData<'tcx, N>),
@@ -753,14 +754,16 @@ impl<'tcx, N> ImplSource<'tcx, N> {
 /// is `Obligation`, as one might expect. During codegen, however, this
 /// is `()`, because codegen only requires a shallow resolution of an
 /// impl, and nested obligations are satisfied later.
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceUserDefinedData<'tcx, N> {
     pub impl_def_id: DefId,
     pub substs: SubstsRef<'tcx>,
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceGeneratorData<'tcx, N> {
     pub generator_def_id: DefId,
     pub substs: SubstsRef<'tcx>,
@@ -769,7 +772,8 @@ pub struct ImplSourceGeneratorData<'tcx, N> {
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceClosureData<'tcx, N> {
     pub closure_def_id: DefId,
     pub substs: SubstsRef<'tcx>,
@@ -778,13 +782,15 @@ pub struct ImplSourceClosureData<'tcx, N> {
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceAutoImplData<N> {
     pub trait_def_id: DefId,
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceTraitUpcastingData<'tcx, N> {
     /// `Foo` upcast to the obligation trait. This will be some supertrait of `Foo`.
     pub upcast_trait_ref: ty::PolyTraitRef<'tcx>,
@@ -798,12 +804,14 @@ pub struct ImplSourceTraitUpcastingData<'tcx, N> {
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceBuiltinData<N> {
     pub nested: Vec<N>,
 }
 
-#[derive(PartialEq, Eq, Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(PartialEq, Eq, Clone, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceObjectData<'tcx, N> {
     /// `Foo` upcast to the obligation trait. This will be some supertrait of `Foo`.
     pub upcast_trait_ref: ty::PolyTraitRef<'tcx>,
@@ -817,7 +825,8 @@ pub struct ImplSourceObjectData<'tcx, N> {
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceFnPointerData<'tcx, N> {
     pub fn_ty: Ty<'tcx>,
     pub nested: Vec<N>,
@@ -830,12 +839,14 @@ pub struct ImplSourceDiscriminantKindData;
 #[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, HashStable)]
 pub struct ImplSourcePointeeData;
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceConstDestructData<N> {
     pub nested: Vec<N>,
 }
 
-#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ImplSourceTraitAliasData<'tcx, N> {
     pub alias_def_id: DefId,
     pub substs: SubstsRef<'tcx>,
diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs
index d43492c903c..937b166d484 100644
--- a/compiler/rustc_middle/src/traits/query.rs
+++ b/compiler/rustc_middle/src/traits/query.rs
@@ -24,7 +24,8 @@ pub mod type_op {
     use rustc_hir::def_id::DefId;
     use std::fmt;
 
-    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, Lift)]
+    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, Lift)]
+    #[derive(TypeFoldable, TypeVisitable)]
     pub struct AscribeUserType<'tcx> {
         pub mir_ty: Ty<'tcx>,
         pub def_id: DefId,
@@ -37,19 +38,22 @@ pub mod type_op {
         }
     }
 
-    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, Lift)]
+    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, Lift)]
+    #[derive(TypeFoldable, TypeVisitable)]
     pub struct Eq<'tcx> {
         pub a: Ty<'tcx>,
         pub b: Ty<'tcx>,
     }
 
-    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, Lift)]
+    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, Lift)]
+    #[derive(TypeFoldable, TypeVisitable)]
     pub struct Subtype<'tcx> {
         pub sub: Ty<'tcx>,
         pub sup: Ty<'tcx>,
     }
 
-    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, Lift)]
+    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, Lift)]
+    #[derive(TypeFoldable, TypeVisitable)]
     pub struct ProvePredicate<'tcx> {
         pub predicate: Predicate<'tcx>,
     }
@@ -60,7 +64,8 @@ pub mod type_op {
         }
     }
 
-    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, Lift)]
+    #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, Lift)]
+    #[derive(TypeFoldable, TypeVisitable)]
     pub struct Normalize<T> {
         pub value: T,
     }
@@ -107,7 +112,7 @@ impl<'tcx> From<TypeError<'tcx>> for NoSolution {
     }
 }
 
-#[derive(Clone, Debug, Default, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, Debug, Default, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct DropckOutlivesResult<'tcx> {
     pub kinds: Vec<GenericArg<'tcx>>,
     pub overflows: Vec<Ty<'tcx>>,
@@ -208,7 +213,7 @@ pub struct MethodAutoderefBadTy<'tcx> {
 }
 
 /// Result from the `normalize_projection_ty` query.
-#[derive(Clone, Debug, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct NormalizationResult<'tcx> {
     /// Result of normalization.
     pub normalized_ty: Ty<'tcx>,
@@ -221,7 +226,7 @@ pub struct NormalizationResult<'tcx> {
 /// case they are called implied bounds). They are fed to the
 /// `OutlivesEnv` which in turn is supplied to the region checker and
 /// other parts of the inference system.
-#[derive(Clone, Debug, TypeFoldable, Lift)]
+#[derive(Clone, Debug, TypeFoldable, TypeVisitable, Lift)]
 pub enum OutlivesBound<'tcx> {
     RegionSubRegion(ty::Region<'tcx>, ty::Region<'tcx>),
     RegionSubParam(ty::Region<'tcx>, ty::ParamTy),
diff --git a/compiler/rustc_middle/src/traits/select.rs b/compiler/rustc_middle/src/traits/select.rs
index ffa70cddbd5..34703b62820 100644
--- a/compiler/rustc_middle/src/traits/select.rs
+++ b/compiler/rustc_middle/src/traits/select.rs
@@ -103,7 +103,7 @@ pub type EvaluationCache<'tcx> = Cache<
 /// required for associated types to work in default impls, as the bounds
 /// are visible both as projection bounds and as where-clauses from the
 /// parameter environment.
-#[derive(PartialEq, Eq, Debug, Clone, TypeFoldable)]
+#[derive(PartialEq, Eq, Debug, Clone, TypeFoldable, TypeVisitable)]
 pub enum SelectionCandidate<'tcx> {
     BuiltinCandidate {
         /// `false` if there are no *further* obligations.
@@ -283,7 +283,7 @@ impl From<ErrorGuaranteed> for OverflowError {
     }
 }
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     OverflowError,
 }
 
diff --git a/compiler/rustc_middle/src/traits/specialization_graph.rs b/compiler/rustc_middle/src/traits/specialization_graph.rs
index ce13825b016..3c1d0061ae1 100644
--- a/compiler/rustc_middle/src/traits/specialization_graph.rs
+++ b/compiler/rustc_middle/src/traits/specialization_graph.rs
@@ -1,5 +1,5 @@
 use crate::ty::fast_reject::SimplifiedType;
-use crate::ty::fold::TypeFoldable;
+use crate::ty::visit::TypeVisitable;
 use crate::ty::{self, TyCtxt};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::ErrorGuaranteed;
diff --git a/compiler/rustc_middle/src/traits/structural_impls.rs b/compiler/rustc_middle/src/traits/structural_impls.rs
index ea706053231..8f1a1564fc8 100644
--- a/compiler/rustc_middle/src/traits/structural_impls.rs
+++ b/compiler/rustc_middle/src/traits/structural_impls.rs
@@ -129,7 +129,7 @@ impl<N: fmt::Debug> fmt::Debug for traits::ImplSourceConstDestructData<N> {
 ///////////////////////////////////////////////////////////////////////////
 // Lift implementations
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     super::IfExpressionCause,
     super::ImplSourceDiscriminantKindData,
     super::ImplSourcePointeeData,
diff --git a/compiler/rustc_middle/src/ty/adjustment.rs b/compiler/rustc_middle/src/ty/adjustment.rs
index d9332f6896a..d36cf2fe3f8 100644
--- a/compiler/rustc_middle/src/ty/adjustment.rs
+++ b/compiler/rustc_middle/src/ty/adjustment.rs
@@ -77,7 +77,7 @@ pub enum PointerCast {
 ///    At some point, of course, `Box` should move out of the compiler, in which
 ///    case this is analogous to transforming a struct. E.g., `Box<[i32; 4]>` ->
 ///    `Box<[i32]>` is an `Adjust::Unsize` with the target `Box<[i32]>`.
-#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub struct Adjustment<'tcx> {
     pub kind: Adjust<'tcx>,
     pub target: Ty<'tcx>,
@@ -89,7 +89,7 @@ impl<'tcx> Adjustment<'tcx> {
     }
 }
 
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub enum Adjust<'tcx> {
     /// Go from ! to any type.
     NeverToAny,
@@ -107,7 +107,8 @@ pub enum Adjust<'tcx> {
 /// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`.
 /// The target type is `U` in both cases, with the region and mutability
 /// being those shared by both the receiver and the returned reference.
-#[derive(Copy, Clone, PartialEq, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Copy, Clone, PartialEq, Debug, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct OverloadedDeref<'tcx> {
     pub region: ty::Region<'tcx>,
     pub mutbl: hir::Mutability,
@@ -165,7 +166,8 @@ impl From<AutoBorrowMutability> for hir::Mutability {
     }
 }
 
-#[derive(Copy, Clone, PartialEq, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(Copy, Clone, PartialEq, Debug, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum AutoBorrow<'tcx> {
     /// Converts from T to &T.
     Ref(ty::Region<'tcx>, AutoBorrowMutability),
diff --git a/compiler/rustc_middle/src/ty/binding.rs b/compiler/rustc_middle/src/ty/binding.rs
index 7ab192daf4b..3d65429f2e5 100644
--- a/compiler/rustc_middle/src/ty/binding.rs
+++ b/compiler/rustc_middle/src/ty/binding.rs
@@ -8,7 +8,7 @@ pub enum BindingMode {
     BindByValue(Mutability),
 }
 
-TrivialTypeFoldableAndLiftImpls! { BindingMode, }
+TrivialTypeTraversalAndLiftImpls! { BindingMode, }
 
 impl BindingMode {
     pub fn convert(ba: BindingAnnotation) -> BindingMode {
diff --git a/compiler/rustc_middle/src/ty/cast.rs b/compiler/rustc_middle/src/ty/cast.rs
index 20a6af5f6c1..c4b743dd467 100644
--- a/compiler/rustc_middle/src/ty/cast.rs
+++ b/compiler/rustc_middle/src/ty/cast.rs
@@ -15,6 +15,12 @@ pub enum IntTy {
     Char,
 }
 
+impl IntTy {
+    pub fn is_signed(self) -> bool {
+        matches!(self, Self::I)
+    }
+}
+
 // Valid types for the result of a non-coercion cast
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum CastTy<'tcx> {
diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs
index c88cac30a19..f5ce43f3afb 100644
--- a/compiler/rustc_middle/src/ty/closure.rs
+++ b/compiler/rustc_middle/src/ty/closure.rs
@@ -18,18 +18,8 @@ use self::BorrowKind::*;
 // This represents accessing self in the closure structure
 pub const CAPTURE_STRUCT_LOCAL: mir::Local = mir::Local::from_u32(1);
 
-#[derive(
-    Clone,
-    Copy,
-    Debug,
-    PartialEq,
-    Eq,
-    Hash,
-    TyEncodable,
-    TyDecodable,
-    TypeFoldable,
-    HashStable
-)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct UpvarPath {
     pub hir_id: hir::HirId,
 }
@@ -37,7 +27,8 @@ pub struct UpvarPath {
 /// Upvars do not get their own `NodeId`. Instead, we use the pair of
 /// the original var ID (that is, the root variable that is referenced
 /// by the upvar) and the ID of the closure expression.
-#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
+#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct UpvarId {
     pub var_path: UpvarPath,
     pub closure_expr_id: LocalDefId,
@@ -51,7 +42,8 @@ impl UpvarId {
 
 /// Information describing the capture of an upvar. This is computed
 /// during `typeck`, specifically by `regionck`.
-#[derive(PartialEq, Clone, Debug, Copy, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
+#[derive(PartialEq, Clone, Debug, Copy, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum UpvarCapture {
     /// Upvar is captured by value. This is always true when the
     /// closure is labeled `move`, but can also be true in other cases
@@ -139,7 +131,8 @@ impl<'tcx> ClosureKind {
 }
 
 /// A composite describing a `Place` that is captured by a closure.
-#[derive(PartialEq, Clone, Debug, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
+#[derive(PartialEq, Clone, Debug, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct CapturedPlace<'tcx> {
     /// The `Place` that is captured.
     pub place: HirPlace<'tcx>,
@@ -284,7 +277,8 @@ pub fn is_ancestor_or_same_capture(
 /// Part of `MinCaptureInformationMap`; describes the capture kind (&, &mut, move)
 /// for a particular capture as well as identifying the part of the source code
 /// that triggered this capture to occur.
-#[derive(PartialEq, Clone, Debug, Copy, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
+#[derive(PartialEq, Clone, Debug, Copy, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct CaptureInfo {
     /// Expr Id pointing to use that resulted in selecting the current capture kind
     ///
@@ -362,7 +356,8 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc
     curr_string
 }
 
-#[derive(Clone, PartialEq, Debug, TyEncodable, TyDecodable, TypeFoldable, Copy, HashStable)]
+#[derive(Clone, PartialEq, Debug, TyEncodable, TyDecodable, Copy, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum BorrowKind {
     /// Data must be immutable and is aliasable.
     ImmBorrow,
diff --git a/compiler/rustc_middle/src/ty/consts.rs b/compiler/rustc_middle/src/ty/consts.rs
index bc52259b151..a4e7a12bba3 100644
--- a/compiler/rustc_middle/src/ty/consts.rs
+++ b/compiler/rustc_middle/src/ty/consts.rs
@@ -2,7 +2,7 @@ use crate::mir::interpret::LitToConstInput;
 use crate::mir::ConstantKind;
 use crate::ty::{
     self, InlineConstSubsts, InlineConstSubstsParts, InternalSubsts, ParamEnv, ParamEnvAnd, Ty,
-    TyCtxt, TypeFoldable,
+    TyCtxt, TypeVisitable,
 };
 use rustc_data_structures::intern::Interned;
 use rustc_errors::ErrorGuaranteed;
diff --git a/compiler/rustc_middle/src/ty/consts/int.rs b/compiler/rustc_middle/src/ty/consts/int.rs
index 51e51a63fd0..c7c2692281e 100644
--- a/compiler/rustc_middle/src/ty/consts/int.rs
+++ b/compiler/rustc_middle/src/ty/consts/int.rs
@@ -452,6 +452,10 @@ impl fmt::Debug for ScalarInt {
 impl fmt::LowerHex for ScalarInt {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         self.check_data();
+        if f.alternate() {
+            // Like regular ints, alternate flag adds leading `0x`.
+            write!(f, "0x")?;
+        }
         // Format as hex number wide enough to fit any value of the given `size`.
         // So data=20, size=1 will be "0x14", but with size=4 it'll be "0x00000014".
         // Using a block `{self.data}` here to force a copy instead of using `self.data`
diff --git a/compiler/rustc_middle/src/ty/consts/kind.rs b/compiler/rustc_middle/src/ty/consts/kind.rs
index 10d03065c79..cb0137d2e5e 100644
--- a/compiler/rustc_middle/src/ty/consts/kind.rs
+++ b/compiler/rustc_middle/src/ty/consts/kind.rs
@@ -4,7 +4,7 @@ use crate::mir::interpret::{AllocId, ConstValue, Scalar};
 use crate::mir::Promoted;
 use crate::ty::subst::{InternalSubsts, SubstsRef};
 use crate::ty::ParamEnv;
-use crate::ty::{self, TyCtxt, TypeFoldable};
+use crate::ty::{self, TyCtxt, TypeVisitable};
 use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def_id::DefId;
 use rustc_macros::HashStable;
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index c3df9a66fe7..af071b4e939 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -4,7 +4,7 @@ use crate::arena::Arena;
 use crate::dep_graph::{DepGraph, DepKind, DepKindStruct};
 use crate::hir::place::Place as HirPlace;
 use crate::infer::canonical::{Canonical, CanonicalVarInfo, CanonicalVarInfos};
-use crate::lint::{struct_lint_level, LintDiagnosticBuilder, LintLevelSource};
+use crate::lint::{struct_lint_level, LintLevelSource};
 use crate::middle::codegen_fn_attrs::CodegenFnAttrs;
 use crate::middle::resolve_lifetime;
 use crate::middle::stability;
@@ -34,7 +34,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_data_structures::steal::Steal;
 use rustc_data_structures::sync::{self, Lock, Lrc, WorkerLocal};
 use rustc_data_structures::vec_map::VecMap;
-use rustc_errors::{ErrorGuaranteed, MultiSpan};
+use rustc_errors::{DecorateLint, ErrorGuaranteed, LintDiagnosticBuilder, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
@@ -390,7 +390,7 @@ impl<'a, V> LocalTableInContextMut<'a, V> {
 /// Here, we would store the type `T`, the span of the value `x`, the "scope-span" for
 /// the scope that contains `x`, the expr `T` evaluated from, and the span of `foo.await`.
 #[derive(TyEncodable, TyDecodable, Clone, Debug, Eq, Hash, PartialEq, HashStable)]
-#[derive(TypeFoldable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct GeneratorInteriorTypeCause<'tcx> {
     /// Type of the captured binding.
     pub ty: Ty<'tcx>,
@@ -871,7 +871,7 @@ rustc_index::newtype_index! {
 pub type CanonicalUserTypeAnnotations<'tcx> =
     IndexVec<UserTypeAnnotationIndex, CanonicalUserTypeAnnotation<'tcx>>;
 
-#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, Lift)]
+#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct CanonicalUserTypeAnnotation<'tcx> {
     pub user_ty: CanonicalUserType<'tcx>,
     pub span: Span,
@@ -931,7 +931,7 @@ impl<'tcx> CanonicalUserType<'tcx> {
 /// from constants that are named via paths, like `Foo::<A>::new` and
 /// so forth.
 #[derive(Copy, Clone, Debug, PartialEq, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable, Lift)]
+#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub enum UserType<'tcx> {
     Ty(Ty<'tcx>),
 
@@ -2787,6 +2787,18 @@ impl<'tcx> TyCtxt<'tcx> {
         }
     }
 
+    /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`,
+    /// typically generated by `#[derive(LintDiagnostic)]`).
+    pub fn emit_spanned_lint(
+        self,
+        lint: &'static Lint,
+        hir_id: HirId,
+        span: impl Into<MultiSpan>,
+        decorator: impl for<'a> DecorateLint<'a, ()>,
+    ) {
+        self.struct_span_lint_hir(lint, hir_id, span, |diag| decorator.decorate_lint(diag))
+    }
+
     pub fn struct_span_lint_hir(
         self,
         lint: &'static Lint,
@@ -2798,6 +2810,17 @@ impl<'tcx> TyCtxt<'tcx> {
         struct_lint_level(self.sess, lint, level, src, Some(span.into()), decorate);
     }
 
+    /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically
+    /// generated by `#[derive(LintDiagnostic)]`).
+    pub fn emit_lint(
+        self,
+        lint: &'static Lint,
+        id: HirId,
+        decorator: impl for<'a> DecorateLint<'a, ()>,
+    ) {
+        self.struct_lint_node(lint, id, |diag| decorator.decorate_lint(diag))
+    }
+
     pub fn struct_lint_node(
         self,
         lint: &'static Lint,
diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs
index a84b3c9373b..25bc6dc6167 100644
--- a/compiler/rustc_middle/src/ty/diagnostics.rs
+++ b/compiler/rustc_middle/src/ty/diagnostics.rs
@@ -3,8 +3,8 @@
 use std::ops::ControlFlow;
 
 use crate::ty::{
-    fold::TypeFoldable, Const, ConstKind, DefIdTree, ExistentialPredicate, InferTy,
-    PolyTraitPredicate, Ty, TyCtxt, TypeSuperFoldable, TypeVisitor,
+    visit::TypeVisitable, Const, ConstKind, DefIdTree, ExistentialPredicate, InferTy,
+    PolyTraitPredicate, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor,
 };
 
 use rustc_data_structures::fx::FxHashMap;
@@ -87,7 +87,7 @@ pub trait IsSuggestable<'tcx> {
 
 impl<'tcx, T> IsSuggestable<'tcx> for T
 where
-    T: TypeFoldable<'tcx>,
+    T: TypeVisitable<'tcx>,
 {
     fn is_suggestable(self, tcx: TyCtxt<'tcx>) -> bool {
         self.visit_with(&mut IsSuggestableVisitor { tcx }).is_continue()
diff --git a/compiler/rustc_middle/src/ty/erase_regions.rs b/compiler/rustc_middle/src/ty/erase_regions.rs
index 6d7e60ecc31..f39fa363a16 100644
--- a/compiler/rustc_middle/src/ty/erase_regions.rs
+++ b/compiler/rustc_middle/src/ty/erase_regions.rs
@@ -1,5 +1,6 @@
 use crate::mir;
 use crate::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
+use crate::ty::visit::TypeVisitable;
 use crate::ty::{self, Ty, TyCtxt, TypeFlags};
 
 pub(super) fn provide(providers: &mut ty::query::Providers) {
diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs
index 812dd2adc2e..49a518b101d 100644
--- a/compiler/rustc_middle/src/ty/error.rs
+++ b/compiler/rustc_middle/src/ty/error.rs
@@ -13,7 +13,7 @@ use rustc_target::spec::abi;
 use std::borrow::Cow;
 use std::fmt;
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq, TypeFoldable)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, TypeFoldable, TypeVisitable)]
 pub struct ExpectedFound<T> {
     pub expected: T,
     pub found: T,
@@ -30,7 +30,7 @@ impl<T> ExpectedFound<T> {
 }
 
 // Data structures used in type unification
-#[derive(Clone, Debug, TypeFoldable)]
+#[derive(Clone, Debug, TypeFoldable, TypeVisitable)]
 pub enum TypeError<'tcx> {
     Mismatch,
     ConstnessMismatch(ExpectedFound<ty::BoundConstness>),
@@ -795,7 +795,7 @@ fn foo(&self) -> Self::T { String::new() }
                         if item_def_id == proj_ty_item_def_id =>
                     {
                         Some((
-                            self.sess.source_map().guess_head_span(self.def_span(item.def_id)),
+                            self.def_span(item.def_id),
                             format!("consider calling `{}`", self.def_path_str(item.def_id)),
                         ))
                     }
diff --git a/compiler/rustc_middle/src/ty/fast_reject.rs b/compiler/rustc_middle/src/ty/fast_reject.rs
index a8145e6820c..8d019a3bad8 100644
--- a/compiler/rustc_middle/src/ty/fast_reject.rs
+++ b/compiler/rustc_middle/src/ty/fast_reject.rs
@@ -1,6 +1,6 @@
 use crate::mir::Mutability;
 use crate::ty::subst::GenericArgKind;
-use crate::ty::{self, Ty, TyCtxt, TypeFoldable};
+use crate::ty::{self, Ty, TyCtxt, TypeVisitable};
 use rustc_hir::def_id::DefId;
 use std::fmt::Debug;
 use std::hash::Hash;
diff --git a/compiler/rustc_middle/src/ty/fold.rs b/compiler/rustc_middle/src/ty/fold.rs
index a6310ae5e66..f8893ae29f5 100644
--- a/compiler/rustc_middle/src/ty/fold.rs
+++ b/compiler/rustc_middle/src/ty/fold.rs
@@ -1,76 +1,60 @@
-//! A generalized traversal mechanism for complex data structures that contain
-//! type information.
+//! A folding traversal mechanism for complex data structures that contain type
+//! information.
 //!
-//! There are two types of traversal.
-//! - Folding. This is a modifying traversal. It consumes the data structure,
-//!   producing a (possibly) modified version of it. Both fallible and
-//!   infallible versions are available. The name is potentially
-//!   confusing, because this traversal is more like `Iterator::map` than
-//!   `Iterator::fold`.
-//! - Visiting. This is a read-only traversal of the data structure.
+//! This is a modifying traversal. It consumes the data structure, producing a
+//! (possibly) modified version of it. Both fallible and infallible versions are
+//! available. The name is potentially confusing, because this traversal is more
+//! like `Iterator::map` than `Iterator::fold`.
 //!
-//! These traversals have limited flexibility. Only a small number of "types of
+//! This traversal has limited flexibility. Only a small number of "types of
 //! interest" within the complex data structures can receive custom
-//! modification (when folding) or custom visitation (when visiting). These are
-//! the ones containing the most important type-related information, such as
-//! `Ty`, `Predicate`, `Region`, and `Const`.
+//! modification. These are the ones containing the most important type-related
+//! information, such as `Ty`, `Predicate`, `Region`, and `Const`.
 //!
-//! There are three traits involved in each traversal type.
-//! - `TypeFoldable`. This is implemented once for many types. This includes
-//!   both:
+//! There are three groups of traits involved in each traversal.
+//! - `TypeFoldable`. This is implemented once for many types, including:
 //!   - Types of interest, for which the the methods delegate to the
-//!     folder/visitor.
+//!     folder.
 //!   - All other types, including generic containers like `Vec` and `Option`.
-//!     It defines a "skeleton" of how they should be traversed, for both
-//!     folding and visiting.
+//!     It defines a "skeleton" of how they should be folded.
 //! - `TypeSuperFoldable`. This is implemented only for each type of interest,
-//!   and defines the traversal "skeleton" for these types.
-//! - `TypeFolder`/`FallibleTypeFolder` (for infallible/fallible folding
-//!   traversals) or `TypeVisitor` (for visiting traversals). One of these is
-//!   implemented for each folder/visitor. This defines how types of interest
-//!   are folded/visited.
+//!   and defines the folding "skeleton" for these types.
+//! - `TypeFolder`/`FallibleTypeFolder. One of these is implemented for each
+//!   folder. This defines how types of interest are folded.
 //!
-//! This means each traversal is a mixture of (a) generic traversal operations,
-//! and (b) custom fold/visit operations that are specific to the
-//! folder/visitor.
+//! This means each fold is a mixture of (a) generic folding operations, and (b)
+//! custom fold operations that are specific to the folder.
 //! - The `TypeFoldable` impls handle most of the traversal, and call into
-//!   `TypeFolder`/`FallibleTypeFolder`/`TypeVisitor` when they encounter a
-//!   type of interest.
-//! - A `TypeFolder`/`FallibleTypeFolder`/`TypeVisitor` may call into another
-//!   `TypeFoldable` impl, because some of the types of interest are recursive
-//!   and can contain other types of interest.
-//! - A `TypeFolder`/`FallibleTypeFolder`/`TypeVisitor` may also call into
-//!   a `TypeSuperFoldable` impl, because each folder/visitor might provide
-//!   custom handling only for some types of interest, or only for some
-//!   variants of each type of interest, and then use default traversal for the
-//!   remaining cases.
+//!   `TypeFolder`/`FallibleTypeFolder` when they encounter a type of interest.
+//! - A `TypeFolder`/`FallibleTypeFolder` may call into another `TypeFoldable`
+//!   impl, because some of the types of interest are recursive and can contain
+//!   other types of interest.
+//! - A `TypeFolder`/`FallibleTypeFolder` may also call into a `TypeSuperFoldable`
+//!   impl, because each folder might provide custom handling only for some types
+//!   of interest, or only for some variants of each type of interest, and then
+//!   use default traversal for the remaining cases.
 //!
 //! For example, if you have `struct S(Ty, U)` where `S: TypeFoldable` and `U:
-//! TypeFoldable`, and an instance `s = S(ty, u)`, it would be visited like so:
+//! TypeFoldable`, and an instance `s = S(ty, u)`, it would be folded like so:
 //! ```text
-//! s.visit_with(visitor) calls
-//! - ty.visit_with(visitor) calls
-//!   - visitor.visit_ty(ty) may call
-//!     - ty.super_visit_with(visitor)
-//! - u.visit_with(visitor)
+//! s.fold_with(folder) calls
+//! - ty.fold_with(folder) calls
+//!   - folder.fold_ty(ty) may call
+//!     - ty.super_fold_with(folder)
+//! - u.fold_with(folder)
 //! ```
 use crate::mir;
-use crate::ty::{self, flags::FlagComputation, Binder, Ty, TyCtxt, TypeFlags};
-use rustc_errors::ErrorGuaranteed;
+use crate::ty::{self, Binder, Ty, TyCtxt, TypeVisitable};
 use rustc_hir::def_id::DefId;
 
-use rustc_data_structures::fx::FxHashSet;
-use rustc_data_structures::sso::SsoHashSet;
 use std::collections::BTreeMap;
-use std::fmt;
-use std::ops::ControlFlow;
 
-/// This trait is implemented for every type that can be folded/visited,
+/// This trait is implemented for every type that can be folded,
 /// providing the skeleton of the traversal.
 ///
 /// To implement this conveniently, use the derive macro located in
 /// `rustc_macros`.
-pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
+pub trait TypeFoldable<'tcx>: TypeVisitable<'tcx> {
     /// The entry point for folding. To fold a value `t` with a folder `f`
     /// call: `t.try_fold_with(f)`.
     ///
@@ -89,115 +73,6 @@ pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
     fn fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self {
         self.try_fold_with(folder).into_ok()
     }
-
-    /// The entry point for visiting. To visit a value `t` with a visitor `v`
-    /// call: `t.visit_with(v)`.
-    ///
-    /// For most types, this just traverses the value, calling `visit_with` on
-    /// each field/element.
-    ///
-    /// For types of interest (such as `Ty`), the implementation of this method
-    /// that calls a visitor method specifically for that type (such as
-    /// `V::visit_ty`). This is where control transfers from `TypeFoldable` to
-    /// `TypeVisitor`.
-    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
-
-    /// Returns `true` if `self` has any late-bound regions that are either
-    /// bound by `binder` or bound by some binder outside of `binder`.
-    /// If `binder` is `ty::INNERMOST`, this indicates whether
-    /// there are any late-bound regions that appear free.
-    fn has_vars_bound_at_or_above(&self, binder: ty::DebruijnIndex) -> bool {
-        self.visit_with(&mut HasEscapingVarsVisitor { outer_index: binder }).is_break()
-    }
-
-    /// Returns `true` if this `self` has any regions that escape `binder` (and
-    /// hence are not bound by it).
-    fn has_vars_bound_above(&self, binder: ty::DebruijnIndex) -> bool {
-        self.has_vars_bound_at_or_above(binder.shifted_in(1))
-    }
-
-    fn has_escaping_bound_vars(&self) -> bool {
-        self.has_vars_bound_at_or_above(ty::INNERMOST)
-    }
-
-    #[instrument(level = "trace")]
-    fn has_type_flags(&self, flags: TypeFlags) -> bool {
-        self.visit_with(&mut HasTypeFlagsVisitor { flags }).break_value() == Some(FoundFlags)
-    }
-    fn has_projections(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_PROJECTION)
-    }
-    fn has_opaque_types(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_TY_OPAQUE)
-    }
-    fn references_error(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_ERROR)
-    }
-    fn error_reported(&self) -> Option<ErrorGuaranteed> {
-        if self.references_error() {
-            Some(ErrorGuaranteed::unchecked_claim_error_was_emitted())
-        } else {
-            None
-        }
-    }
-    fn has_param_types_or_consts(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_CT_PARAM)
-    }
-    fn has_infer_regions(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_RE_INFER)
-    }
-    fn has_infer_types(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_TY_INFER)
-    }
-    fn has_infer_types_or_consts(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_TY_INFER | TypeFlags::HAS_CT_INFER)
-    }
-    fn needs_infer(&self) -> bool {
-        self.has_type_flags(TypeFlags::NEEDS_INFER)
-    }
-    fn has_placeholders(&self) -> bool {
-        self.has_type_flags(
-            TypeFlags::HAS_RE_PLACEHOLDER
-                | TypeFlags::HAS_TY_PLACEHOLDER
-                | TypeFlags::HAS_CT_PLACEHOLDER,
-        )
-    }
-    fn needs_subst(&self) -> bool {
-        self.has_type_flags(TypeFlags::NEEDS_SUBST)
-    }
-    /// "Free" regions in this context means that it has any region
-    /// that is not (a) erased or (b) late-bound.
-    fn has_free_regions(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_FREE_REGIONS)
-    }
-
-    fn has_erased_regions(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_RE_ERASED)
-    }
-
-    /// True if there are any un-erased free regions.
-    fn has_erasable_regions(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_FREE_REGIONS)
-    }
-
-    /// Indicates whether this value references only 'global'
-    /// generic parameters that are the same regardless of what fn we are
-    /// in. This is used for caching.
-    fn is_global(&self) -> bool {
-        !self.has_type_flags(TypeFlags::HAS_FREE_LOCAL_NAMES)
-    }
-
-    /// True if there are any late-bound regions
-    fn has_late_bound_regions(&self) -> bool {
-        self.has_type_flags(TypeFlags::HAS_RE_LATE_BOUND)
-    }
-
-    /// Indicates whether this value still has parameters/placeholders/inference variables
-    /// which could be replaced later, in a way that would change the results of `impl`
-    /// specialization.
-    fn still_further_specializable(&self) -> bool {
-        self.has_type_flags(TypeFlags::STILL_FURTHER_SPECIALIZABLE)
-    }
 }
 
 // This trait is implemented for types of interest.
@@ -219,14 +94,6 @@ pub trait TypeSuperFoldable<'tcx>: TypeFoldable<'tcx> {
     fn super_fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self {
         self.try_super_fold_with(folder).into_ok()
     }
-
-    /// Provides a default visit for a type of interest. This should only be
-    /// called within `TypeVisitor` methods, when a non-custom traversal is
-    /// desired for the value of the type of interest passed to that method.
-    /// For example, in `MyVisitor::visit_ty(ty)`, it is valid to call
-    /// `ty.super_visit_with(self)`, but any other visiting should be done
-    /// with `xyz.visit_with(self)`.
-    fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
 }
 
 /// This trait is implemented for every infallible folding traversal. There is
@@ -376,44 +243,6 @@ where
     }
 }
 
-/// This trait is implemented for every visiting traversal. There is a visit
-/// method defined for every type of interest. Each such method has a default
-/// that recurses into the type's fields in a non-custom fashion.
-pub trait TypeVisitor<'tcx>: Sized {
-    type BreakTy = !;
-
-    fn visit_binder<T: TypeFoldable<'tcx>>(
-        &mut self,
-        t: &Binder<'tcx, T>,
-    ) -> ControlFlow<Self::BreakTy> {
-        t.super_visit_with(self)
-    }
-
-    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        t.super_visit_with(self)
-    }
-
-    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-        r.super_visit_with(self)
-    }
-
-    fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        c.super_visit_with(self)
-    }
-
-    fn visit_unevaluated(&mut self, uv: ty::Unevaluated<'tcx>) -> ControlFlow<Self::BreakTy> {
-        uv.super_visit_with(self)
-    }
-
-    fn visit_predicate(&mut self, p: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
-        p.super_visit_with(self)
-    }
-
-    fn visit_mir_const(&mut self, c: mir::ConstantKind<'tcx>) -> ControlFlow<Self::BreakTy> {
-        c.super_visit_with(self)
-    }
-}
-
 ///////////////////////////////////////////////////////////////////////////
 // Some sample folders
 
@@ -472,99 +301,6 @@ impl<'tcx> TyCtxt<'tcx> {
     {
         value.fold_with(&mut RegionFolder::new(self, &mut f))
     }
-
-    /// Invoke `callback` on every region appearing free in `value`.
-    pub fn for_each_free_region(
-        self,
-        value: &impl TypeFoldable<'tcx>,
-        mut callback: impl FnMut(ty::Region<'tcx>),
-    ) {
-        self.any_free_region_meets(value, |r| {
-            callback(r);
-            false
-        });
-    }
-
-    /// Returns `true` if `callback` returns true for every region appearing free in `value`.
-    pub fn all_free_regions_meet(
-        self,
-        value: &impl TypeFoldable<'tcx>,
-        mut callback: impl FnMut(ty::Region<'tcx>) -> bool,
-    ) -> bool {
-        !self.any_free_region_meets(value, |r| !callback(r))
-    }
-
-    /// Returns `true` if `callback` returns true for some region appearing free in `value`.
-    pub fn any_free_region_meets(
-        self,
-        value: &impl TypeFoldable<'tcx>,
-        callback: impl FnMut(ty::Region<'tcx>) -> bool,
-    ) -> bool {
-        struct RegionVisitor<F> {
-            /// The index of a binder *just outside* the things we have
-            /// traversed. If we encounter a bound region bound by this
-            /// binder or one outer to it, it appears free. Example:
-            ///
-            /// ```ignore (illustrative)
-            ///       for<'a> fn(for<'b> fn(), T)
-            /// // ^          ^          ^     ^
-            /// // |          |          |     | here, would be shifted in 1
-            /// // |          |          | here, would be shifted in 2
-            /// // |          | here, would be `INNERMOST` shifted in by 1
-            /// // | here, initially, binder would be `INNERMOST`
-            /// ```
-            ///
-            /// You see that, initially, *any* bound value is free,
-            /// because we've not traversed any binders. As we pass
-            /// through a binder, we shift the `outer_index` by 1 to
-            /// account for the new binder that encloses us.
-            outer_index: ty::DebruijnIndex,
-            callback: F,
-        }
-
-        impl<'tcx, F> TypeVisitor<'tcx> for RegionVisitor<F>
-        where
-            F: FnMut(ty::Region<'tcx>) -> bool,
-        {
-            type BreakTy = ();
-
-            fn visit_binder<T: TypeFoldable<'tcx>>(
-                &mut self,
-                t: &Binder<'tcx, T>,
-            ) -> ControlFlow<Self::BreakTy> {
-                self.outer_index.shift_in(1);
-                let result = t.super_visit_with(self);
-                self.outer_index.shift_out(1);
-                result
-            }
-
-            fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-                match *r {
-                    ty::ReLateBound(debruijn, _) if debruijn < self.outer_index => {
-                        ControlFlow::CONTINUE
-                    }
-                    _ => {
-                        if (self.callback)(r) {
-                            ControlFlow::BREAK
-                        } else {
-                            ControlFlow::CONTINUE
-                        }
-                    }
-                }
-            }
-
-            fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-                // We're only interested in types involving regions
-                if ty.flags().intersects(TypeFlags::HAS_FREE_REGIONS) {
-                    ty.super_visit_with(self)
-                } else {
-                    ControlFlow::CONTINUE
-                }
-            }
-        }
-
-        value.visit_with(&mut RegionVisitor { outer_index: ty::INNERMOST, callback }).is_break()
-    }
 }
 
 /// Folds over the substructure of a type, visiting its component
@@ -861,45 +597,6 @@ impl<'tcx> TyCtxt<'tcx> {
         )
     }
 
-    /// Returns a set of all late-bound regions that are constrained
-    /// by `value`, meaning that if we instantiate those LBR with
-    /// variables and equate `value` with something else, those
-    /// variables will also be equated.
-    pub fn collect_constrained_late_bound_regions<T>(
-        self,
-        value: &Binder<'tcx, T>,
-    ) -> FxHashSet<ty::BoundRegionKind>
-    where
-        T: TypeFoldable<'tcx>,
-    {
-        self.collect_late_bound_regions(value, true)
-    }
-
-    /// Returns a set of all late-bound regions that appear in `value` anywhere.
-    pub fn collect_referenced_late_bound_regions<T>(
-        self,
-        value: &Binder<'tcx, T>,
-    ) -> FxHashSet<ty::BoundRegionKind>
-    where
-        T: TypeFoldable<'tcx>,
-    {
-        self.collect_late_bound_regions(value, false)
-    }
-
-    fn collect_late_bound_regions<T>(
-        self,
-        value: &Binder<'tcx, T>,
-        just_constraint: bool,
-    ) -> FxHashSet<ty::BoundRegionKind>
-    where
-        T: TypeFoldable<'tcx>,
-    {
-        let mut collector = LateBoundRegionsCollector::new(just_constraint);
-        let result = value.as_ref().skip_binder().visit_with(&mut collector);
-        assert!(result.is_continue()); // should never have stopped early
-        collector.regions
-    }
-
     /// Replaces any late-bound regions bound in `value` with `'erased`. Useful in codegen but also
     /// method lookup and a few other places where precise region relationships are not required.
     pub fn erase_late_bound_regions<T>(self, value: Binder<'tcx, T>) -> T
@@ -940,103 +637,6 @@ impl<'tcx> TyCtxt<'tcx> {
     }
 }
 
-pub struct ValidateBoundVars<'tcx> {
-    bound_vars: &'tcx ty::List<ty::BoundVariableKind>,
-    binder_index: ty::DebruijnIndex,
-    // We may encounter the same variable at different levels of binding, so
-    // this can't just be `Ty`
-    visited: SsoHashSet<(ty::DebruijnIndex, Ty<'tcx>)>,
-}
-
-impl<'tcx> ValidateBoundVars<'tcx> {
-    pub fn new(bound_vars: &'tcx ty::List<ty::BoundVariableKind>) -> Self {
-        ValidateBoundVars {
-            bound_vars,
-            binder_index: ty::INNERMOST,
-            visited: SsoHashSet::default(),
-        }
-    }
-}
-
-impl<'tcx> TypeVisitor<'tcx> for ValidateBoundVars<'tcx> {
-    type BreakTy = ();
-
-    fn visit_binder<T: TypeFoldable<'tcx>>(
-        &mut self,
-        t: &Binder<'tcx, T>,
-    ) -> ControlFlow<Self::BreakTy> {
-        self.binder_index.shift_in(1);
-        let result = t.super_visit_with(self);
-        self.binder_index.shift_out(1);
-        result
-    }
-
-    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        if t.outer_exclusive_binder() < self.binder_index
-            || !self.visited.insert((self.binder_index, t))
-        {
-            return ControlFlow::BREAK;
-        }
-        match *t.kind() {
-            ty::Bound(debruijn, bound_ty) if debruijn == self.binder_index => {
-                if self.bound_vars.len() <= bound_ty.var.as_usize() {
-                    bug!("Not enough bound vars: {:?} not found in {:?}", t, self.bound_vars);
-                }
-                let list_var = self.bound_vars[bound_ty.var.as_usize()];
-                match list_var {
-                    ty::BoundVariableKind::Ty(kind) => {
-                        if kind != bound_ty.kind {
-                            bug!(
-                                "Mismatched type kinds: {:?} doesn't var in list {:?}",
-                                bound_ty.kind,
-                                list_var
-                            );
-                        }
-                    }
-                    _ => {
-                        bug!("Mismatched bound variable kinds! Expected type, found {:?}", list_var)
-                    }
-                }
-            }
-
-            _ => (),
-        };
-
-        t.super_visit_with(self)
-    }
-
-    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-        match *r {
-            ty::ReLateBound(index, br) if index == self.binder_index => {
-                if self.bound_vars.len() <= br.var.as_usize() {
-                    bug!("Not enough bound vars: {:?} not found in {:?}", br, self.bound_vars);
-                }
-                let list_var = self.bound_vars[br.var.as_usize()];
-                match list_var {
-                    ty::BoundVariableKind::Region(kind) => {
-                        if kind != br.kind {
-                            bug!(
-                                "Mismatched region kinds: {:?} doesn't match var ({:?}) in list ({:?})",
-                                br.kind,
-                                list_var,
-                                self.bound_vars
-                            );
-                        }
-                    }
-                    _ => bug!(
-                        "Mismatched bound variable kinds! Expected region, found {:?}",
-                        list_var
-                    ),
-                }
-            }
-
-            _ => (),
-        };
-
-        r.super_visit_with(self)
-    }
-}
-
 ///////////////////////////////////////////////////////////////////////////
 // Shifter
 //
@@ -1141,301 +741,3 @@ where
 
     value.fold_with(&mut Shifter::new(tcx, amount))
 }
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-struct FoundEscapingVars;
-
-/// An "escaping var" is a bound var whose binder is not part of `t`. A bound var can be a
-/// bound region or a bound type.
-///
-/// So, for example, consider a type like the following, which has two binders:
-///
-///    for<'a> fn(x: for<'b> fn(&'a isize, &'b isize))
-///    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ outer scope
-///                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~  inner scope
-///
-/// This type has *bound regions* (`'a`, `'b`), but it does not have escaping regions, because the
-/// binders of both `'a` and `'b` are part of the type itself. However, if we consider the *inner
-/// fn type*, that type has an escaping region: `'a`.
-///
-/// Note that what I'm calling an "escaping var" is often just called a "free var". However,
-/// we already use the term "free var". It refers to the regions or types that we use to represent
-/// bound regions or type params on a fn definition while we are type checking its body.
-///
-/// To clarify, conceptually there is no particular difference between
-/// an "escaping" var and a "free" var. However, there is a big
-/// difference in practice. Basically, when "entering" a binding
-/// level, one is generally required to do some sort of processing to
-/// a bound var, such as replacing it with a fresh/placeholder
-/// var, or making an entry in the environment to represent the
-/// scope to which it is attached, etc. An escaping var represents
-/// a bound var for which this processing has not yet been done.
-struct HasEscapingVarsVisitor {
-    /// Anything bound by `outer_index` or "above" is escaping.
-    outer_index: ty::DebruijnIndex,
-}
-
-impl<'tcx> TypeVisitor<'tcx> for HasEscapingVarsVisitor {
-    type BreakTy = FoundEscapingVars;
-
-    fn visit_binder<T: TypeFoldable<'tcx>>(
-        &mut self,
-        t: &Binder<'tcx, T>,
-    ) -> ControlFlow<Self::BreakTy> {
-        self.outer_index.shift_in(1);
-        let result = t.super_visit_with(self);
-        self.outer_index.shift_out(1);
-        result
-    }
-
-    #[inline]
-    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        // If the outer-exclusive-binder is *strictly greater* than
-        // `outer_index`, that means that `t` contains some content
-        // bound at `outer_index` or above (because
-        // `outer_exclusive_binder` is always 1 higher than the
-        // content in `t`). Therefore, `t` has some escaping vars.
-        if t.outer_exclusive_binder() > self.outer_index {
-            ControlFlow::Break(FoundEscapingVars)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-
-    #[inline]
-    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-        // If the region is bound by `outer_index` or anything outside
-        // of outer index, then it escapes the binders we have
-        // visited.
-        if r.bound_at_or_above_binder(self.outer_index) {
-            ControlFlow::Break(FoundEscapingVars)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-
-    fn visit_const(&mut self, ct: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        // we don't have a `visit_infer_const` callback, so we have to
-        // hook in here to catch this case (annoying...), but
-        // otherwise we do want to remember to visit the rest of the
-        // const, as it has types/regions embedded in a lot of other
-        // places.
-        match ct.kind() {
-            ty::ConstKind::Bound(debruijn, _) if debruijn >= self.outer_index => {
-                ControlFlow::Break(FoundEscapingVars)
-            }
-            _ => ct.super_visit_with(self),
-        }
-    }
-
-    #[inline]
-    fn visit_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
-        if predicate.outer_exclusive_binder() > self.outer_index {
-            ControlFlow::Break(FoundEscapingVars)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-struct FoundFlags;
-
-// FIXME: Optimize for checking for infer flags
-struct HasTypeFlagsVisitor {
-    flags: ty::TypeFlags,
-}
-
-impl std::fmt::Debug for HasTypeFlagsVisitor {
-    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.flags.fmt(fmt)
-    }
-}
-
-impl<'tcx> TypeVisitor<'tcx> for HasTypeFlagsVisitor {
-    type BreakTy = FoundFlags;
-
-    #[inline]
-    #[instrument(skip(self), level = "trace")]
-    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        let flags = t.flags();
-        trace!(t.flags=?t.flags());
-        if flags.intersects(self.flags) {
-            ControlFlow::Break(FoundFlags)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-
-    #[inline]
-    #[instrument(skip(self), level = "trace")]
-    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-        let flags = r.type_flags();
-        trace!(r.flags=?flags);
-        if flags.intersects(self.flags) {
-            ControlFlow::Break(FoundFlags)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-
-    #[inline]
-    #[instrument(level = "trace")]
-    fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        let flags = FlagComputation::for_const(c);
-        trace!(r.flags=?flags);
-        if flags.intersects(self.flags) {
-            ControlFlow::Break(FoundFlags)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-
-    #[inline]
-    #[instrument(level = "trace")]
-    fn visit_unevaluated(&mut self, uv: ty::Unevaluated<'tcx>) -> ControlFlow<Self::BreakTy> {
-        let flags = FlagComputation::for_unevaluated_const(uv);
-        trace!(r.flags=?flags);
-        if flags.intersects(self.flags) {
-            ControlFlow::Break(FoundFlags)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-
-    #[inline]
-    #[instrument(level = "trace")]
-    fn visit_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
-        debug!(
-            "HasTypeFlagsVisitor: predicate={:?} predicate.flags={:?} self.flags={:?}",
-            predicate,
-            predicate.flags(),
-            self.flags
-        );
-        if predicate.flags().intersects(self.flags) {
-            ControlFlow::Break(FoundFlags)
-        } else {
-            ControlFlow::CONTINUE
-        }
-    }
-}
-
-/// Collects all the late-bound regions at the innermost binding level
-/// into a hash set.
-struct LateBoundRegionsCollector {
-    current_index: ty::DebruijnIndex,
-    regions: FxHashSet<ty::BoundRegionKind>,
-
-    /// `true` if we only want regions that are known to be
-    /// "constrained" when you equate this type with another type. In
-    /// particular, if you have e.g., `&'a u32` and `&'b u32`, equating
-    /// them constraints `'a == 'b`. But if you have `<&'a u32 as
-    /// Trait>::Foo` and `<&'b u32 as Trait>::Foo`, normalizing those
-    /// types may mean that `'a` and `'b` don't appear in the results,
-    /// so they are not considered *constrained*.
-    just_constrained: bool,
-}
-
-impl LateBoundRegionsCollector {
-    fn new(just_constrained: bool) -> Self {
-        LateBoundRegionsCollector {
-            current_index: ty::INNERMOST,
-            regions: Default::default(),
-            just_constrained,
-        }
-    }
-}
-
-impl<'tcx> TypeVisitor<'tcx> for LateBoundRegionsCollector {
-    fn visit_binder<T: TypeFoldable<'tcx>>(
-        &mut self,
-        t: &Binder<'tcx, T>,
-    ) -> ControlFlow<Self::BreakTy> {
-        self.current_index.shift_in(1);
-        let result = t.super_visit_with(self);
-        self.current_index.shift_out(1);
-        result
-    }
-
-    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        // if we are only looking for "constrained" region, we have to
-        // ignore the inputs to a projection, as they may not appear
-        // in the normalized form
-        if self.just_constrained {
-            if let ty::Projection(..) = t.kind() {
-                return ControlFlow::CONTINUE;
-            }
-        }
-
-        t.super_visit_with(self)
-    }
-
-    fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        // if we are only looking for "constrained" region, we have to
-        // ignore the inputs of an unevaluated const, as they may not appear
-        // in the normalized form
-        if self.just_constrained {
-            if let ty::ConstKind::Unevaluated(..) = c.kind() {
-                return ControlFlow::CONTINUE;
-            }
-        }
-
-        c.super_visit_with(self)
-    }
-
-    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-        if let ty::ReLateBound(debruijn, br) = *r {
-            if debruijn == self.current_index {
-                self.regions.insert(br.kind);
-            }
-        }
-        ControlFlow::CONTINUE
-    }
-}
-
-/// Finds the max universe present
-pub struct MaxUniverse {
-    max_universe: ty::UniverseIndex,
-}
-
-impl MaxUniverse {
-    pub fn new() -> Self {
-        MaxUniverse { max_universe: ty::UniverseIndex::ROOT }
-    }
-
-    pub fn max_universe(self) -> ty::UniverseIndex {
-        self.max_universe
-    }
-}
-
-impl<'tcx> TypeVisitor<'tcx> for MaxUniverse {
-    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-        if let ty::Placeholder(placeholder) = t.kind() {
-            self.max_universe = ty::UniverseIndex::from_u32(
-                self.max_universe.as_u32().max(placeholder.universe.as_u32()),
-            );
-        }
-
-        t.super_visit_with(self)
-    }
-
-    fn visit_const(&mut self, c: ty::consts::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
-        if let ty::ConstKind::Placeholder(placeholder) = c.kind() {
-            self.max_universe = ty::UniverseIndex::from_u32(
-                self.max_universe.as_u32().max(placeholder.universe.as_u32()),
-            );
-        }
-
-        c.super_visit_with(self)
-    }
-
-    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
-        if let ty::RePlaceholder(placeholder) = *r {
-            self.max_universe = ty::UniverseIndex::from_u32(
-                self.max_universe.as_u32().max(placeholder.universe.as_u32()),
-            );
-        }
-
-        ControlFlow::CONTINUE
-    }
-}
diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs
index 0e10fe25c10..391abdbe84c 100644
--- a/compiler/rustc_middle/src/ty/instance.rs
+++ b/compiler/rustc_middle/src/ty/instance.rs
@@ -1,7 +1,9 @@
 use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use crate::ty::print::{FmtPrinter, Printer};
 use crate::ty::subst::{InternalSubsts, Subst};
-use crate::ty::{self, EarlyBinder, SubstsRef, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable};
+use crate::ty::{
+    self, EarlyBinder, SubstsRef, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitable,
+};
 use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def::Namespace;
 use rustc_hir::def_id::{CrateNum, DefId};
@@ -25,7 +27,7 @@ pub struct Instance<'tcx> {
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
-#[derive(TyEncodable, TyDecodable, HashStable, TypeFoldable)]
+#[derive(TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
 pub enum InstanceDef<'tcx> {
     /// A user-defined callable item.
     ///
diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs
index 3b05e42a53e..f87b6e4212d 100644
--- a/compiler/rustc_middle/src/ty/layout.rs
+++ b/compiler/rustc_middle/src/ty/layout.rs
@@ -2,7 +2,7 @@ use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use crate::mir::{GeneratorLayout, GeneratorSavedLocal};
 use crate::ty::normalize_erasing_regions::NormalizationError;
 use crate::ty::subst::Subst;
-use crate::ty::{self, subst::SubstsRef, EarlyBinder, ReprOptions, Ty, TyCtxt, TypeFoldable};
+use crate::ty::{self, subst::SubstsRef, EarlyBinder, ReprOptions, Ty, TyCtxt, TypeVisitable};
 use rustc_ast as ast;
 use rustc_attr as attr;
 use rustc_hir as hir;
@@ -235,9 +235,8 @@ fn sanity_check_layout<'tcx>(
     if cfg!(debug_assertions) {
         fn check_layout_abi<'tcx>(tcx: TyCtxt<'tcx>, layout: Layout<'tcx>) {
             match layout.abi() {
-                Abi::Scalar(_scalar) => {
+                Abi::Scalar(scalar) => {
                     // No padding in scalars.
-                    /* FIXME(#96185):
                     assert_eq!(
                         layout.align().abi,
                         scalar.align(&tcx).abi,
@@ -247,7 +246,7 @@ fn sanity_check_layout<'tcx>(
                         layout.size(),
                         scalar.size(&tcx),
                         "size mismatch between ABI and layout in {layout:#?}"
-                    );*/
+                    );
                 }
                 Abi::Vector { count, element } => {
                     // No padding in vectors. Alignment can be strengthened, though.
@@ -1418,9 +1417,9 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> {
 
                 if layout_variants.iter().all(|v| v.abi.is_uninhabited()) {
                     abi = Abi::Uninhabited;
-                } else if tag.size(dl) == size || variants.iter().all(|layout| layout.is_empty()) {
-                    // Without latter check aligned enums with custom discriminant values
-                    // Would result in ICE see the issue #92464 for more info
+                } else if tag.size(dl) == size {
+                    // Make sure we only use scalar layout when the enum is entirely its
+                    // own tag (i.e. it has no padding nor any non-ZST variant fields).
                     abi = Abi::Scalar(tag);
                 } else {
                     // Try to use a ScalarPair for all tagged enums.
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index f73eca5bf61..3a795af2121 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -9,9 +9,8 @@
 //!
 //! ["The `ty` module: representing types"]: https://rustc-dev-guide.rust-lang.org/ty.html
 
-pub use self::fold::{
-    FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitor,
-};
+pub use self::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable};
+pub use self::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor};
 pub use self::AssocItemContainer::*;
 pub use self::BorrowKind::*;
 pub use self::IntVarValue::*;
@@ -110,6 +109,7 @@ pub mod relate;
 pub mod subst;
 pub mod trait_def;
 pub mod util;
+pub mod visit;
 pub mod vtable;
 pub mod walk;
 
@@ -206,7 +206,7 @@ impl MainDefinition {
 /// The "header" of an impl is everything outside the body: a Self type, a trait
 /// ref (in the case of a trait impl), and a set of predicates (from the
 /// bounds / where-clauses).
-#[derive(Clone, Debug, TypeFoldable)]
+#[derive(Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct ImplHeader<'tcx> {
     pub impl_def_id: DefId,
     pub self_ty: Ty<'tcx>,
@@ -214,24 +214,14 @@ pub struct ImplHeader<'tcx> {
     pub predicates: Vec<Predicate<'tcx>>,
 }
 
-#[derive(Copy, Clone, Debug, TypeFoldable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable)]
 pub enum ImplSubject<'tcx> {
     Trait(TraitRef<'tcx>),
     Inherent(Ty<'tcx>),
 }
 
-#[derive(
-    Copy,
-    Clone,
-    PartialEq,
-    Eq,
-    Hash,
-    TyEncodable,
-    TyDecodable,
-    HashStable,
-    Debug,
-    TypeFoldable
-)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable, Debug)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub enum ImplPolarity {
     /// `impl Trait for Type`
     Positive,
@@ -307,18 +297,8 @@ impl fmt::Display for BoundConstness {
     }
 }
 
-#[derive(
-    Clone,
-    Debug,
-    PartialEq,
-    Eq,
-    Copy,
-    Hash,
-    TyEncodable,
-    TyDecodable,
-    HashStable,
-    TypeFoldable
-)]
+#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, TyEncodable, TyDecodable, HashStable)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct ClosureSizeProfileData<'tcx> {
     /// Tuple containing the types of closure captures before the feature `capture_disjoint_fields`
     pub before_feature_tys: Ty<'tcx>,
@@ -611,8 +591,14 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for Predicate<'tcx> {
     }
 }
 
+impl rustc_errors::IntoDiagnosticArg for Predicate<'_> {
+    fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
+        rustc_errors::DiagnosticArgValue::Str(std::borrow::Cow::Owned(self.to_string()))
+    }
+}
+
 #[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub enum PredicateKind<'tcx> {
     /// Corresponds to `where Foo: Bar<A, B, C>`. `Foo` here would be
     /// the `Self` type of the trait reference and `A`, `B`, and `C`
@@ -784,7 +770,7 @@ impl<'tcx> Predicate<'tcx> {
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct TraitPredicate<'tcx> {
     pub trait_ref: TraitRef<'tcx>,
 
@@ -863,7 +849,7 @@ impl<'tcx> PolyTraitPredicate<'tcx> {
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct OutlivesPredicate<A, B>(pub A, pub B); // `A: B`
 pub type RegionOutlivesPredicate<'tcx> = OutlivesPredicate<ty::Region<'tcx>, ty::Region<'tcx>>;
 pub type TypeOutlivesPredicate<'tcx> = OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>;
@@ -874,7 +860,7 @@ pub type PolyTypeOutlivesPredicate<'tcx> = ty::Binder<'tcx, TypeOutlivesPredicat
 /// whether the `a` type is the type that we should label as "expected" when
 /// presenting user diagnostics.
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct SubtypePredicate<'tcx> {
     pub a_is_expected: bool,
     pub a: Ty<'tcx>,
@@ -884,7 +870,7 @@ pub type PolySubtypePredicate<'tcx> = ty::Binder<'tcx, SubtypePredicate<'tcx>>;
 
 /// Encodes that we have to coerce *from* the `a` type to the `b` type.
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct CoercePredicate<'tcx> {
     pub a: Ty<'tcx>,
     pub b: Ty<'tcx>,
@@ -892,7 +878,7 @@ pub struct CoercePredicate<'tcx> {
 pub type PolyCoercePredicate<'tcx> = ty::Binder<'tcx, CoercePredicate<'tcx>>;
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub enum Term<'tcx> {
     Ty(Ty<'tcx>),
     Const(Const<'tcx>),
@@ -940,7 +926,7 @@ impl<'tcx> Term<'tcx> {
 /// Form #2 eventually yields one of these `ProjectionPredicate`
 /// instances to normalize the LHS.
 #[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct ProjectionPredicate<'tcx> {
     pub projection_ty: ProjectionTy<'tcx>,
     pub term: Term<'tcx>,
@@ -1084,7 +1070,7 @@ impl<'tcx> Predicate<'tcx> {
 /// `[[], [U:Bar<T>]]`. Now if there were some particular reference
 /// like `Foo<isize,usize>`, then the `InstantiatedPredicates` would be `[[],
 /// [usize:Bar<isize>]]`.
-#[derive(Clone, Debug, TypeFoldable)]
+#[derive(Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct InstantiatedPredicates<'tcx> {
     pub predicates: Vec<Predicate<'tcx>>,
     pub spans: Vec<Span>,
@@ -1100,24 +1086,14 @@ impl<'tcx> InstantiatedPredicates<'tcx> {
     }
 }
 
-#[derive(
-    Copy,
-    Clone,
-    Debug,
-    PartialEq,
-    Eq,
-    HashStable,
-    TyEncodable,
-    TyDecodable,
-    TypeFoldable,
-    Lift
-)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, HashStable, TyEncodable, TyDecodable, Lift)]
+#[derive(TypeFoldable, TypeVisitable)]
 pub struct OpaqueTypeKey<'tcx> {
     pub def_id: DefId,
     pub substs: SubstsRef<'tcx>,
 }
 
-#[derive(Copy, Clone, Debug, TypeFoldable, HashStable, TyEncodable, TyDecodable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, HashStable, TyEncodable, TyDecodable)]
 pub struct OpaqueHiddenType<'tcx> {
     /// The span of this particular definition of the opaque type. So
     /// for example:
@@ -1253,7 +1229,7 @@ pub type PlaceholderConst<'tcx> = Placeholder<BoundConst<'tcx>>;
 /// except that instead of a `Ty` we bundle the `DefId` of the const parameter.
 /// Meaning that we need to use `type_of(const_param_did)` if `const_param_did` is `Some`
 /// to get the type of `did`.
-#[derive(Copy, Clone, Debug, TypeFoldable, Lift, TyEncodable, TyDecodable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, Lift, TyEncodable, TyDecodable)]
 #[derive(PartialEq, Eq, PartialOrd, Ord)]
 #[derive(Hash, HashStable)]
 pub struct WithOptConstParam<T> {
@@ -1409,7 +1385,9 @@ impl<'tcx> TypeFoldable<'tcx> for ParamEnv<'tcx> {
             self.constness().try_fold_with(folder)?,
         ))
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ParamEnv<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.caller_bounds().visit_with(visitor)?;
         self.reveal().visit_with(visitor)?;
@@ -1536,7 +1514,7 @@ impl<'tcx> ParamEnv<'tcx> {
     /// `where Box<u32>: Copy`, which are clearly never
     /// satisfiable. We generally want to behave as if they were true,
     /// although the surrounding function is never reachable.
-    pub fn and<T: TypeFoldable<'tcx>>(self, value: T) -> ParamEnvAnd<'tcx, T> {
+    pub fn and<T: TypeVisitable<'tcx>>(self, value: T) -> ParamEnvAnd<'tcx, T> {
         match self.reveal() {
             Reveal::UserFacing => ParamEnvAnd { param_env: self, value },
 
@@ -1569,7 +1547,7 @@ impl<'tcx> PolyTraitRef<'tcx> {
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TypeFoldable)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TypeFoldable, TypeVisitable)]
 pub struct ParamEnvAnd<'tcx, T> {
     pub param_env: ParamEnv<'tcx>,
     pub value: T,
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 7f3b0fdccc6..887236c484b 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -2,7 +2,7 @@ use crate::mir::interpret::{AllocRange, GlobalAlloc, Pointer, Provenance, Scalar
 use crate::ty::subst::{GenericArg, GenericArgKind, Subst};
 use crate::ty::{
     self, ConstInt, DefIdTree, ParamConst, ScalarInt, Term, Ty, TyCtxt, TypeFoldable,
-    TypeSuperFoldable,
+    TypeSuperFoldable, TypeSuperVisitable, TypeVisitable,
 };
 use rustc_apfloat::ieee::{Double, Single};
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
@@ -2277,14 +2277,14 @@ impl<'tcx> FmtPrinter<'_, 'tcx> {
 
     fn prepare_late_bound_region_info<T>(&mut self, value: &ty::Binder<'tcx, T>)
     where
-        T: TypeFoldable<'tcx>,
+        T: TypeVisitable<'tcx>,
     {
         struct LateBoundRegionNameCollector<'a, 'tcx> {
             used_region_names: &'a mut FxHashSet<Symbol>,
             type_collector: SsoHashSet<Ty<'tcx>>,
         }
 
-        impl<'tcx> ty::fold::TypeVisitor<'tcx> for LateBoundRegionNameCollector<'_, 'tcx> {
+        impl<'tcx> ty::visit::TypeVisitor<'tcx> for LateBoundRegionNameCollector<'_, 'tcx> {
             type BreakTy = ();
 
             fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
@@ -2388,7 +2388,7 @@ macro_rules! define_print_and_forward_display {
 /// Wrapper type for `ty::TraitRef` which opts-in to pretty printing only
 /// the trait path. That is, it will print `Trait<U>` instead of
 /// `<T as Trait<U>>`.
-#[derive(Copy, Clone, TypeFoldable, Lift)]
+#[derive(Copy, Clone, TypeFoldable, TypeVisitable, Lift)]
 pub struct TraitRefPrintOnlyTraitPath<'tcx>(ty::TraitRef<'tcx>);
 
 impl<'tcx> fmt::Debug for TraitRefPrintOnlyTraitPath<'tcx> {
@@ -2400,7 +2400,7 @@ impl<'tcx> fmt::Debug for TraitRefPrintOnlyTraitPath<'tcx> {
 /// Wrapper type for `ty::TraitRef` which opts-in to pretty printing only
 /// the trait name. That is, it will print `Trait` instead of
 /// `<T as Trait<U>>`.
-#[derive(Copy, Clone, TypeFoldable, Lift)]
+#[derive(Copy, Clone, TypeFoldable, TypeVisitable, Lift)]
 pub struct TraitRefPrintOnlyTraitName<'tcx>(ty::TraitRef<'tcx>);
 
 impl<'tcx> fmt::Debug for TraitRefPrintOnlyTraitName<'tcx> {
@@ -2425,7 +2425,7 @@ impl<'tcx> ty::Binder<'tcx, ty::TraitRef<'tcx>> {
     }
 }
 
-#[derive(Copy, Clone, TypeFoldable, Lift)]
+#[derive(Copy, Clone, TypeFoldable, TypeVisitable, Lift)]
 pub struct TraitPredPrintModifiersAndPath<'tcx>(ty::TraitPredicate<'tcx>);
 
 impl<'tcx> fmt::Debug for TraitPredPrintModifiersAndPath<'tcx> {
diff --git a/compiler/rustc_middle/src/ty/relate.rs b/compiler/rustc_middle/src/ty/relate.rs
index 51980acd38f..818affa7113 100644
--- a/compiler/rustc_middle/src/ty/relate.rs
+++ b/compiler/rustc_middle/src/ty/relate.rs
@@ -345,7 +345,7 @@ impl<'tcx> Relate<'tcx> for ty::ExistentialTraitRef<'tcx> {
     }
 }
 
-#[derive(Copy, Debug, Clone, TypeFoldable)]
+#[derive(Copy, Debug, Clone, TypeFoldable, TypeVisitable)]
 struct GeneratorWitness<'tcx>(&'tcx ty::List<Ty<'tcx>>);
 
 impl<'tcx> Relate<'tcx> for GeneratorWitness<'tcx> {
diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs
index 8ba5b882fdd..1b4008019fb 100644
--- a/compiler/rustc_middle/src/ty/structural_impls.rs
+++ b/compiler/rustc_middle/src/ty/structural_impls.rs
@@ -4,8 +4,9 @@
 
 use crate::mir::interpret;
 use crate::mir::ProjectionKind;
-use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable};
 use crate::ty::print::{with_no_trimmed_paths, FmtPrinter, Printer};
+use crate::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use crate::ty::{self, InferConst, Lift, Term, Ty, TyCtxt};
 use rustc_data_structures::functor::IdFunctor;
 use rustc_hir as hir;
@@ -183,7 +184,7 @@ impl<'tcx> fmt::Debug for ty::PredicateKind<'tcx> {
 // For things that don't carry any arena-allocated data (and are
 // copy...), just add them to this list.
 
-TrivialTypeFoldableAndLiftImpls! {
+TrivialTypeTraversalAndLiftImpls! {
     (),
     bool,
     usize,
@@ -452,7 +453,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::PredicateKind<'a> {
 
 impl<'a, 'tcx, T: Lift<'tcx>> Lift<'tcx> for ty::Binder<'a, T>
 where
-    <T as Lift<'tcx>>::Lifted: TypeFoldable<'tcx>,
+    <T as Lift<'tcx>>::Lifted: TypeVisitable<'tcx>,
 {
     type Lifted = ty::Binder<'tcx, T::Lifted>;
     fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option<Self::Lifted> {
@@ -651,7 +652,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::AdtDef<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _folder: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::AdtDef<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, _visitor: &mut V) -> ControlFlow<V::BreakTy> {
         ControlFlow::CONTINUE
     }
@@ -664,7 +667,9 @@ impl<'tcx, T: TypeFoldable<'tcx>, U: TypeFoldable<'tcx>> TypeFoldable<'tcx> for
     ) -> Result<(T, U), F::Error> {
         Ok((self.0.try_fold_with(folder)?, self.1.try_fold_with(folder)?))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>, U: TypeVisitable<'tcx>> TypeVisitable<'tcx> for (T, U) {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.0.visit_with(visitor)?;
         self.1.visit_with(visitor)
@@ -684,7 +689,11 @@ impl<'tcx, A: TypeFoldable<'tcx>, B: TypeFoldable<'tcx>, C: TypeFoldable<'tcx>>
             self.2.try_fold_with(folder)?,
         ))
     }
+}
 
+impl<'tcx, A: TypeVisitable<'tcx>, B: TypeVisitable<'tcx>, C: TypeVisitable<'tcx>>
+    TypeVisitable<'tcx> for (A, B, C)
+{
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.0.visit_with(visitor)?;
         self.1.visit_with(visitor)?;
@@ -692,19 +701,31 @@ impl<'tcx, A: TypeFoldable<'tcx>, B: TypeFoldable<'tcx>, C: TypeFoldable<'tcx>>
     }
 }
 
-EnumTypeFoldableImpl! {
+EnumTypeTraversalImpl! {
     impl<'tcx, T> TypeFoldable<'tcx> for Option<T> {
         (Some)(a),
         (None),
     } where T: TypeFoldable<'tcx>
 }
+EnumTypeTraversalImpl! {
+    impl<'tcx, T> TypeVisitable<'tcx> for Option<T> {
+        (Some)(a),
+        (None),
+    } where T: TypeVisitable<'tcx>
+}
 
-EnumTypeFoldableImpl! {
+EnumTypeTraversalImpl! {
     impl<'tcx, T, E> TypeFoldable<'tcx> for Result<T, E> {
         (Ok)(a),
         (Err)(a),
     } where T: TypeFoldable<'tcx>, E: TypeFoldable<'tcx>,
 }
+EnumTypeTraversalImpl! {
+    impl<'tcx, T, E> TypeVisitable<'tcx> for Result<T, E> {
+        (Ok)(a),
+        (Err)(a),
+    } where T: TypeVisitable<'tcx>, E: TypeVisitable<'tcx>,
+}
 
 impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Rc<T> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(
@@ -744,7 +765,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Rc<T> {
             Ok(Rc::from_raw(Rc::into_raw(unique).cast()))
         }
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Rc<T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         (**self).visit_with(visitor)
     }
@@ -788,7 +811,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Arc<T> {
             Ok(Arc::from_raw(Arc::into_raw(unique).cast()))
         }
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Arc<T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         (**self).visit_with(visitor)
     }
@@ -798,7 +823,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Box<T> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         self.try_map_id(|value| value.try_fold_with(folder))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Box<T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         (**self).visit_with(visitor)
     }
@@ -808,7 +835,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Vec<T> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         self.try_map_id(|t| t.try_fold_with(folder))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Vec<T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|t| t.visit_with(visitor))
     }
@@ -818,7 +847,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Box<[T]> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         self.try_map_id(|t| t.try_fold_with(folder))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Box<[T]> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|t| t.visit_with(visitor))
     }
@@ -828,7 +859,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for ty::EarlyBinder<T> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         self.try_map_bound(|ty| ty.try_fold_with(folder))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for ty::EarlyBinder<T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.as_ref().0.visit_with(visitor)
     }
@@ -838,7 +871,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for ty::Binder<'tcx, T> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_binder(self)
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for ty::Binder<'tcx, T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         visitor.visit_binder(self)
     }
@@ -851,7 +886,9 @@ impl<'tcx, T: TypeFoldable<'tcx>> TypeSuperFoldable<'tcx> for ty::Binder<'tcx, T
     ) -> Result<Self, F::Error> {
         self.try_map_bound(|ty| ty.try_fold_with(folder))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>> TypeSuperVisitable<'tcx> for ty::Binder<'tcx, T> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.as_ref().skip_binder().visit_with(visitor)
     }
@@ -861,7 +898,11 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ty::Binder<'tcx, ty::Existentia
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         ty::util::fold_list(self, folder, |tcx, v| tcx.intern_poly_existential_predicates(v))
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx>
+    for &'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>
+{
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|p| p.visit_with(visitor))
     }
@@ -871,7 +912,9 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ProjectionKind> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         ty::util::fold_list(self, folder, |tcx, v| tcx.intern_projs(v))
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for &'tcx ty::List<ProjectionKind> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|t| t.visit_with(visitor))
     }
@@ -903,7 +946,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::instance::Instance<'tcx> {
             },
         })
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::instance::Instance<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         use crate::ty::InstanceDef::*;
         self.substs.visit_with(visitor)?;
@@ -929,7 +974,9 @@ impl<'tcx> TypeFoldable<'tcx> for interpret::GlobalId<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         Ok(Self { instance: self.instance.try_fold_with(folder)?, promoted: self.promoted })
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for interpret::GlobalId<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.instance.visit_with(visitor)
     }
@@ -939,7 +986,9 @@ impl<'tcx> TypeFoldable<'tcx> for Ty<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_ty(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for Ty<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         visitor.visit_ty(*self)
     }
@@ -989,7 +1038,9 @@ impl<'tcx> TypeSuperFoldable<'tcx> for Ty<'tcx> {
 
         Ok(if *self.kind() == kind { self } else { folder.tcx().mk_ty(kind) })
     }
+}
 
+impl<'tcx> TypeSuperVisitable<'tcx> for Ty<'tcx> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         match self.kind() {
             ty::RawPtr(ref tm) => tm.visit_with(visitor),
@@ -1037,7 +1088,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Region<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_region(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::Region<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         visitor.visit_region(*self)
     }
@@ -1050,7 +1103,9 @@ impl<'tcx> TypeSuperFoldable<'tcx> for ty::Region<'tcx> {
     ) -> Result<Self, F::Error> {
         Ok(self)
     }
+}
 
+impl<'tcx> TypeSuperVisitable<'tcx> for ty::Region<'tcx> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, _visitor: &mut V) -> ControlFlow<V::BreakTy> {
         ControlFlow::CONTINUE
     }
@@ -1060,7 +1115,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Predicate<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_predicate(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::Predicate<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         visitor.visit_predicate(*self)
     }
@@ -1082,7 +1139,9 @@ impl<'tcx> TypeSuperFoldable<'tcx> for ty::Predicate<'tcx> {
         let new = self.kind().try_fold_with(folder)?;
         Ok(folder.tcx().reuse_or_mk_predicate(self, new))
     }
+}
 
+impl<'tcx> TypeSuperVisitable<'tcx> for ty::Predicate<'tcx> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.kind().visit_with(visitor)
     }
@@ -1092,7 +1151,9 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ty::Predicate<'tcx>> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         ty::util::fold_list(self, folder, |tcx, v| tcx.intern_predicates(v))
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for &'tcx ty::List<ty::Predicate<'tcx>> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|p| p.visit_with(visitor))
     }
@@ -1102,7 +1163,9 @@ impl<'tcx, T: TypeFoldable<'tcx>, I: Idx> TypeFoldable<'tcx> for IndexVec<I, T>
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         self.try_map_id(|x| x.try_fold_with(folder))
     }
+}
 
+impl<'tcx, T: TypeVisitable<'tcx>, I: Idx> TypeVisitable<'tcx> for IndexVec<I, T> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|t| t.visit_with(visitor))
     }
@@ -1112,7 +1175,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Const<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_const(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::Const<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         visitor.visit_const(*self)
     }
@@ -1131,7 +1196,9 @@ impl<'tcx> TypeSuperFoldable<'tcx> for ty::Const<'tcx> {
             Ok(self)
         }
     }
+}
 
+impl<'tcx> TypeSuperVisitable<'tcx> for ty::Const<'tcx> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.ty().visit_with(visitor)?;
         self.kind().visit_with(visitor)
@@ -1150,7 +1217,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::ConstKind<'tcx> {
             | ty::ConstKind::Error(_) => self,
         })
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::ConstKind<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         match *self {
             ty::ConstKind::Infer(ic) => ic.visit_with(visitor),
@@ -1168,7 +1237,9 @@ impl<'tcx> TypeFoldable<'tcx> for InferConst<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _folder: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for InferConst<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, _visitor: &mut V) -> ControlFlow<V::BreakTy> {
         ControlFlow::CONTINUE
     }
@@ -1178,7 +1249,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Unevaluated<'tcx> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         folder.try_fold_unevaluated(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::Unevaluated<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         visitor.visit_unevaluated(*self)
     }
@@ -1195,7 +1268,9 @@ impl<'tcx> TypeSuperFoldable<'tcx> for ty::Unevaluated<'tcx> {
             promoted: self.promoted,
         })
     }
+}
 
+impl<'tcx> TypeSuperVisitable<'tcx> for ty::Unevaluated<'tcx> {
     fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.substs.visit_with(visitor)
     }
@@ -1205,7 +1280,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Unevaluated<'tcx, ()> {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
         Ok(self.expand().try_fold_with(folder)?.shrink())
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for ty::Unevaluated<'tcx, ()> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.expand().visit_with(visitor)
     }
@@ -1215,7 +1292,9 @@ impl<'tcx> TypeFoldable<'tcx> for hir::Constness {
     fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
         Ok(self)
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for hir::Constness {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
         ControlFlow::CONTINUE
     }
diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs
index 296442e2436..815e39aab57 100644
--- a/compiler/rustc_middle/src/ty/sty.rs
+++ b/compiler/rustc_middle/src/ty/sty.rs
@@ -3,11 +3,11 @@
 #![allow(rustc::usage_of_ty_tykind)]
 
 use crate::infer::canonical::Canonical;
-use crate::ty::fold::ValidateBoundVars;
 use crate::ty::subst::{GenericArg, InternalSubsts, Subst, SubstsRef};
+use crate::ty::visit::ValidateBoundVars;
 use crate::ty::InferTy::*;
 use crate::ty::{
-    self, AdtDef, DefIdTree, Discr, Term, Ty, TyCtxt, TypeFlags, TypeFoldable, TypeSuperFoldable,
+    self, AdtDef, DefIdTree, Discr, Term, Ty, TyCtxt, TypeFlags, TypeSuperVisitable, TypeVisitable,
     TypeVisitor,
 };
 use crate::ty::{List, ParamEnv};
@@ -38,7 +38,7 @@ pub type TyKind<'tcx> = IrTyKind<TyCtxt<'tcx>>;
 pub type RegionKind<'tcx> = IrRegionKind<TyCtxt<'tcx>>;
 
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable, Lift)]
+#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct TypeAndMut<'tcx> {
     pub ty: Ty<'tcx>,
     pub mutbl: hir::Mutability,
@@ -201,7 +201,7 @@ static_assert_size!(TyKind<'_>, 32);
 /// * `GR`: The "return type", which is the type of value returned upon
 ///   completion of the generator.
 /// * `GW`: The "generator witness".
-#[derive(Copy, Clone, Debug, TypeFoldable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct ClosureSubsts<'tcx> {
     /// Lifetime and type parameters from the enclosing function,
     /// concatenated with a tuple containing the types of the upvars.
@@ -328,7 +328,7 @@ impl<'tcx> ClosureSubsts<'tcx> {
 }
 
 /// Similar to `ClosureSubsts`; see the above documentation for more.
-#[derive(Copy, Clone, Debug, TypeFoldable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct GeneratorSubsts<'tcx> {
     pub substs: SubstsRef<'tcx>,
 }
@@ -608,7 +608,7 @@ impl<'tcx> UpvarSubsts<'tcx> {
 /// type of the constant. The reason that `R` is represented as an extra type parameter
 /// is the same reason that [`ClosureSubsts`] have `CS` and `U` as type parameters:
 /// inline const can reference lifetimes that are internal to the creating function.
-#[derive(Copy, Clone, Debug, TypeFoldable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct InlineConstSubsts<'tcx> {
     /// Generic parameters from the enclosing item,
     /// concatenated with the inferred type of the constant.
@@ -655,7 +655,7 @@ impl<'tcx> InlineConstSubsts<'tcx> {
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub enum ExistentialPredicate<'tcx> {
     /// E.g., `Iterator`.
     Trait(ExistentialTraitRef<'tcx>),
@@ -781,7 +781,7 @@ impl<'tcx> List<ty::Binder<'tcx, ExistentialPredicate<'tcx>>> {
 /// Trait references also appear in object types like `Foo<U>`, but in
 /// that case the `Self` parameter is absent from the substitutions.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct TraitRef<'tcx> {
     pub def_id: DefId,
     pub substs: SubstsRef<'tcx>,
@@ -853,7 +853,7 @@ impl<'tcx> PolyTraitRef<'tcx> {
 /// The substitutions don't include the erased `Self`, only trait
 /// type and lifetime parameters (`[X, Y]` and `['a, 'b]` above).
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct ExistentialTraitRef<'tcx> {
     pub def_id: DefId,
     pub substs: SubstsRef<'tcx>,
@@ -986,7 +986,7 @@ pub struct Binder<'tcx, T>(T, &'tcx List<BoundVariableKind>);
 
 impl<'tcx, T> Binder<'tcx, T>
 where
-    T: TypeFoldable<'tcx>,
+    T: TypeVisitable<'tcx>,
 {
     /// Wraps `value` in a binder, asserting that `value` does not
     /// contain any bound vars that would be bound by the
@@ -1050,14 +1050,14 @@ impl<'tcx, T> Binder<'tcx, T> {
         Binder(value, self.1)
     }
 
-    pub fn map_bound_ref<F, U: TypeFoldable<'tcx>>(&self, f: F) -> Binder<'tcx, U>
+    pub fn map_bound_ref<F, U: TypeVisitable<'tcx>>(&self, f: F) -> Binder<'tcx, U>
     where
         F: FnOnce(&T) -> U,
     {
         self.as_ref().map_bound(f)
     }
 
-    pub fn map_bound<F, U: TypeFoldable<'tcx>>(self, f: F) -> Binder<'tcx, U>
+    pub fn map_bound<F, U: TypeVisitable<'tcx>>(self, f: F) -> Binder<'tcx, U>
     where
         F: FnOnce(T) -> U,
     {
@@ -1069,7 +1069,7 @@ impl<'tcx, T> Binder<'tcx, T> {
         Binder(value, self.1)
     }
 
-    pub fn try_map_bound<F, U: TypeFoldable<'tcx>, E>(self, f: F) -> Result<Binder<'tcx, U>, E>
+    pub fn try_map_bound<F, U: TypeVisitable<'tcx>, E>(self, f: F) -> Result<Binder<'tcx, U>, E>
     where
         F: FnOnce(T) -> Result<U, E>,
     {
@@ -1092,7 +1092,7 @@ impl<'tcx, T> Binder<'tcx, T> {
     /// in `bind`. This may be (debug) asserted in the future.
     pub fn rebind<U>(&self, value: U) -> Binder<'tcx, U>
     where
-        U: TypeFoldable<'tcx>,
+        U: TypeVisitable<'tcx>,
     {
         if cfg!(debug_assertions) {
             let mut validator = ValidateBoundVars::new(self.bound_vars());
@@ -1113,7 +1113,7 @@ impl<'tcx, T> Binder<'tcx, T> {
     /// would not be that useful.)
     pub fn no_bound_vars(self) -> Option<T>
     where
-        T: TypeFoldable<'tcx>,
+        T: TypeVisitable<'tcx>,
     {
         if self.0.has_escaping_bound_vars() { None } else { Some(self.skip_binder()) }
     }
@@ -1143,7 +1143,7 @@ impl<'tcx, T> Binder<'tcx, Option<T>> {
 /// Represents the projection of an associated type. In explicit UFCS
 /// form this would be written `<T as Trait<..>>::N`.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct ProjectionTy<'tcx> {
     /// The parameters of the associated item.
     pub substs: SubstsRef<'tcx>,
@@ -1192,7 +1192,7 @@ impl<'tcx> ProjectionTy<'tcx> {
     }
 }
 
-#[derive(Copy, Clone, Debug, TypeFoldable)]
+#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable)]
 pub struct GenSig<'tcx> {
     pub resume_ty: Ty<'tcx>,
     pub yield_ty: Ty<'tcx>,
@@ -1208,7 +1208,7 @@ pub type PolyGenSig<'tcx> = Binder<'tcx, GenSig<'tcx>>;
 /// - `output`: is the return type.
 /// - `c_variadic`: indicates whether this is a C-variadic function.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct FnSig<'tcx> {
     pub inputs_and_output: &'tcx List<Ty<'tcx>>,
     pub c_variadic: bool,
@@ -1385,7 +1385,7 @@ impl From<BoundVar> for BoundTy {
 
 /// A `ProjectionPredicate` for an `ExistentialTraitRef`.
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable)]
+#[derive(HashStable, TypeFoldable, TypeVisitable)]
 pub struct ExistentialProjection<'tcx> {
     pub item_def_id: DefId,
     pub substs: SubstsRef<'tcx>,
diff --git a/compiler/rustc_middle/src/ty/subst.rs b/compiler/rustc_middle/src/ty/subst.rs
index 1417c8a511c..3a524d7b0f3 100644
--- a/compiler/rustc_middle/src/ty/subst.rs
+++ b/compiler/rustc_middle/src/ty/subst.rs
@@ -2,10 +2,9 @@
 
 use crate::mir;
 use crate::ty::codec::{TyDecoder, TyEncoder};
-use crate::ty::fold::{
-    FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitor,
-};
+use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable};
 use crate::ty::sty::{ClosureSubsts, GeneratorSubsts, InlineConstSubsts};
+use crate::ty::visit::{TypeVisitable, TypeVisitor};
 use crate::ty::{self, Lift, List, ParamConst, Ty, TyCtxt};
 
 use rustc_data_structures::intern::{Interned, WithStableHash};
@@ -205,7 +204,9 @@ impl<'tcx> TypeFoldable<'tcx> for GenericArg<'tcx> {
             GenericArgKind::Const(ct) => ct.try_fold_with(folder).map(Into::into),
         }
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for GenericArg<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         match self.unpack() {
             GenericArgKind::Lifetime(lt) => lt.visit_with(visitor),
@@ -449,7 +450,9 @@ impl<'tcx> TypeFoldable<'tcx> for SubstsRef<'tcx> {
             _ => ty::util::fold_list(self, folder, |tcx, v| tcx.intern_substs(v)),
         }
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for SubstsRef<'tcx> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|t| t.visit_with(visitor))
     }
@@ -485,7 +488,9 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<Ty<'tcx>> {
             _ => ty::util::fold_list(self, folder, |tcx, v| tcx.intern_type_list(v)),
         }
     }
+}
 
+impl<'tcx> TypeVisitable<'tcx> for &'tcx ty::List<Ty<'tcx>> {
     fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
         self.iter().try_for_each(|t| t.visit_with(visitor))
     }
@@ -722,7 +727,7 @@ impl<'a, 'tcx> SubstFolder<'a, 'tcx> {
 /// Stores the user-given substs to reach some fully qualified path
 /// (e.g., `<T>::Item` or `<T as Trait>::Item`).
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable, Lift)]
+#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct UserSubsts<'tcx> {
     /// The substitutions for the item as given by the user.
     pub substs: SubstsRef<'tcx>,
@@ -749,7 +754,7 @@ pub struct UserSubsts<'tcx> {
 /// the self type, giving `Foo<?A>`. Finally, we unify that with
 /// the self type here, which contains `?A` to be `&'static u32`
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable)]
-#[derive(HashStable, TypeFoldable, Lift)]
+#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct UserSelfTy<'tcx> {
     pub impl_def_id: DefId,
     pub self_ty: Ty<'tcx>,
diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs
index cb34b64660d..826c16dda4a 100644
--- a/compiler/rustc_middle/src/ty/trait_def.rs
+++ b/compiler/rustc_middle/src/ty/trait_def.rs
@@ -1,6 +1,6 @@
 use crate::traits::specialization_graph;
 use crate::ty::fast_reject::{self, SimplifiedType, TreatParams};
-use crate::ty::fold::TypeFoldable;
+use crate::ty::visit::TypeVisitable;
 use crate::ty::{Ident, Ty, TyCtxt};
 use hir::def_id::LOCAL_CRATE;
 use rustc_hir as hir;
diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs
index 38846031bad..3a876df84c2 100644
--- a/compiler/rustc_middle/src/ty/util.rs
+++ b/compiler/rustc_middle/src/ty/util.rs
@@ -6,6 +6,7 @@ use crate::ty::query::TyCtxtAt;
 use crate::ty::subst::{GenericArgKind, Subst, SubstsRef};
 use crate::ty::{
     self, DefIdTree, FallibleTypeFolder, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
+    TypeVisitable,
 };
 use rustc_apfloat::Float as _;
 use rustc_ast as ast;
diff --git a/compiler/rustc_middle/src/ty/visit.rs b/compiler/rustc_middle/src/ty/visit.rs
new file mode 100644
index 00000000000..5365067209a
--- /dev/null
+++ b/compiler/rustc_middle/src/ty/visit.rs
@@ -0,0 +1,745 @@
+//! A visiting traversal mechanism for complex data structures that contain type
+//! information.
+//!
+//! This is a read-only traversal of the data structure.
+//!
+//! This traversal has limited flexibility. Only a small number of "types of
+//! interest" within the complex data structures can receive custom
+//! visitation. These are the ones containing the most important type-related
+//! information, such as `Ty`, `Predicate`, `Region`, and `Const`.
+//!
+//! There are three groups of traits involved in each traversal.
+//! - `TypeVisitable`. This is implemented once for many types, including:
+//!   - Types of interest, for which the the methods delegate to the
+//!     visitor.
+//!   - All other types, including generic containers like `Vec` and `Option`.
+//!     It defines a "skeleton" of how they should be visited.
+//! - `TypeSuperVisitable`. This is implemented only for each type of interest,
+//!   and defines the visiting "skeleton" for these types.
+//! - `TypeVisitor`. This is implemented for each visitor. This defines how
+//!   types of interest are visited.
+//!
+//! This means each visit is a mixture of (a) generic visiting operations, and (b)
+//! custom visit operations that are specific to the visitor.
+//! - The `TypeVisitable` impls handle most of the traversal, and call into
+//!   `TypeVisitor` when they encounter a type of interest.
+//! - A `TypeVisitor` may call into another `TypeVisitable` impl, because some of
+//!   the types of interest are recursive and can contain other types of interest.
+//! - A `TypeVisitor` may also call into a `TypeSuperVisitable` impl, because each
+//!   visitor might provide custom handling only for some types of interest, or
+//!   only for some variants of each type of interest, and then use default
+//!   traversal for the remaining cases.
+//!
+//! For example, if you have `struct S(Ty, U)` where `S: TypeVisitable` and `U:
+//! TypeVisitable`, and an instance `s = S(ty, u)`, it would be visited like so:
+//! ```text
+//! s.visit_with(visitor) calls
+//! - ty.visit_with(visitor) calls
+//!   - visitor.visit_ty(ty) may call
+//!     - ty.super_visit_with(visitor)
+//! - u.visit_with(visitor)
+//! ```
+use crate::mir;
+use crate::ty::{self, flags::FlagComputation, Binder, Ty, TyCtxt, TypeFlags};
+use rustc_errors::ErrorGuaranteed;
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sso::SsoHashSet;
+use std::fmt;
+use std::ops::ControlFlow;
+
+/// This trait is implemented for every type that can be visited,
+/// providing the skeleton of the traversal.
+///
+/// To implement this conveniently, use the derive macro located in
+/// `rustc_macros`.
+pub trait TypeVisitable<'tcx>: fmt::Debug + Clone {
+    /// The entry point for visiting. To visit a value `t` with a visitor `v`
+    /// call: `t.visit_with(v)`.
+    ///
+    /// For most types, this just traverses the value, calling `visit_with` on
+    /// each field/element.
+    ///
+    /// For types of interest (such as `Ty`), the implementation of this method
+    /// that calls a visitor method specifically for that type (such as
+    /// `V::visit_ty`). This is where control transfers from `TypeFoldable` to
+    /// `TypeVisitor`.
+    fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
+
+    /// Returns `true` if `self` has any late-bound regions that are either
+    /// bound by `binder` or bound by some binder outside of `binder`.
+    /// If `binder` is `ty::INNERMOST`, this indicates whether
+    /// there are any late-bound regions that appear free.
+    fn has_vars_bound_at_or_above(&self, binder: ty::DebruijnIndex) -> bool {
+        self.visit_with(&mut HasEscapingVarsVisitor { outer_index: binder }).is_break()
+    }
+
+    /// Returns `true` if this `self` has any regions that escape `binder` (and
+    /// hence are not bound by it).
+    fn has_vars_bound_above(&self, binder: ty::DebruijnIndex) -> bool {
+        self.has_vars_bound_at_or_above(binder.shifted_in(1))
+    }
+
+    fn has_escaping_bound_vars(&self) -> bool {
+        self.has_vars_bound_at_or_above(ty::INNERMOST)
+    }
+
+    #[instrument(level = "trace")]
+    fn has_type_flags(&self, flags: TypeFlags) -> bool {
+        self.visit_with(&mut HasTypeFlagsVisitor { flags }).break_value() == Some(FoundFlags)
+    }
+    fn has_projections(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_PROJECTION)
+    }
+    fn has_opaque_types(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_TY_OPAQUE)
+    }
+    fn references_error(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_ERROR)
+    }
+    fn error_reported(&self) -> Option<ErrorGuaranteed> {
+        if self.references_error() {
+            Some(ErrorGuaranteed::unchecked_claim_error_was_emitted())
+        } else {
+            None
+        }
+    }
+    fn has_param_types_or_consts(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_CT_PARAM)
+    }
+    fn has_infer_regions(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_RE_INFER)
+    }
+    fn has_infer_types(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_TY_INFER)
+    }
+    fn has_infer_types_or_consts(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_TY_INFER | TypeFlags::HAS_CT_INFER)
+    }
+    fn needs_infer(&self) -> bool {
+        self.has_type_flags(TypeFlags::NEEDS_INFER)
+    }
+    fn has_placeholders(&self) -> bool {
+        self.has_type_flags(
+            TypeFlags::HAS_RE_PLACEHOLDER
+                | TypeFlags::HAS_TY_PLACEHOLDER
+                | TypeFlags::HAS_CT_PLACEHOLDER,
+        )
+    }
+    fn needs_subst(&self) -> bool {
+        self.has_type_flags(TypeFlags::NEEDS_SUBST)
+    }
+    /// "Free" regions in this context means that it has any region
+    /// that is not (a) erased or (b) late-bound.
+    fn has_free_regions(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_FREE_REGIONS)
+    }
+
+    fn has_erased_regions(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_RE_ERASED)
+    }
+
+    /// True if there are any un-erased free regions.
+    fn has_erasable_regions(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_FREE_REGIONS)
+    }
+
+    /// Indicates whether this value references only 'global'
+    /// generic parameters that are the same regardless of what fn we are
+    /// in. This is used for caching.
+    fn is_global(&self) -> bool {
+        !self.has_type_flags(TypeFlags::HAS_FREE_LOCAL_NAMES)
+    }
+
+    /// True if there are any late-bound regions
+    fn has_late_bound_regions(&self) -> bool {
+        self.has_type_flags(TypeFlags::HAS_RE_LATE_BOUND)
+    }
+
+    /// Indicates whether this value still has parameters/placeholders/inference variables
+    /// which could be replaced later, in a way that would change the results of `impl`
+    /// specialization.
+    fn still_further_specializable(&self) -> bool {
+        self.has_type_flags(TypeFlags::STILL_FURTHER_SPECIALIZABLE)
+    }
+}
+
+pub trait TypeSuperVisitable<'tcx>: TypeVisitable<'tcx> {
+    /// Provides a default visit for a type of interest. This should only be
+    /// called within `TypeVisitor` methods, when a non-custom traversal is
+    /// desired for the value of the type of interest passed to that method.
+    /// For example, in `MyVisitor::visit_ty(ty)`, it is valid to call
+    /// `ty.super_visit_with(self)`, but any other visiting should be done
+    /// with `xyz.visit_with(self)`.
+    fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
+}
+
+/// This trait is implemented for every visiting traversal. There is a visit
+/// method defined for every type of interest. Each such method has a default
+/// that recurses into the type's fields in a non-custom fashion.
+pub trait TypeVisitor<'tcx>: Sized {
+    type BreakTy = !;
+
+    fn visit_binder<T: TypeVisitable<'tcx>>(
+        &mut self,
+        t: &Binder<'tcx, T>,
+    ) -> ControlFlow<Self::BreakTy> {
+        t.super_visit_with(self)
+    }
+
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        t.super_visit_with(self)
+    }
+
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+        r.super_visit_with(self)
+    }
+
+    fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+        c.super_visit_with(self)
+    }
+
+    fn visit_unevaluated(&mut self, uv: ty::Unevaluated<'tcx>) -> ControlFlow<Self::BreakTy> {
+        uv.super_visit_with(self)
+    }
+
+    fn visit_predicate(&mut self, p: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
+        p.super_visit_with(self)
+    }
+
+    fn visit_mir_const(&mut self, c: mir::ConstantKind<'tcx>) -> ControlFlow<Self::BreakTy> {
+        c.super_visit_with(self)
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Region folder
+
+impl<'tcx> TyCtxt<'tcx> {
+    /// Invoke `callback` on every region appearing free in `value`.
+    pub fn for_each_free_region(
+        self,
+        value: &impl TypeVisitable<'tcx>,
+        mut callback: impl FnMut(ty::Region<'tcx>),
+    ) {
+        self.any_free_region_meets(value, |r| {
+            callback(r);
+            false
+        });
+    }
+
+    /// Returns `true` if `callback` returns true for every region appearing free in `value`.
+    pub fn all_free_regions_meet(
+        self,
+        value: &impl TypeVisitable<'tcx>,
+        mut callback: impl FnMut(ty::Region<'tcx>) -> bool,
+    ) -> bool {
+        !self.any_free_region_meets(value, |r| !callback(r))
+    }
+
+    /// Returns `true` if `callback` returns true for some region appearing free in `value`.
+    pub fn any_free_region_meets(
+        self,
+        value: &impl TypeVisitable<'tcx>,
+        callback: impl FnMut(ty::Region<'tcx>) -> bool,
+    ) -> bool {
+        struct RegionVisitor<F> {
+            /// The index of a binder *just outside* the things we have
+            /// traversed. If we encounter a bound region bound by this
+            /// binder or one outer to it, it appears free. Example:
+            ///
+            /// ```ignore (illustrative)
+            ///       for<'a> fn(for<'b> fn(), T)
+            /// // ^          ^          ^     ^
+            /// // |          |          |     | here, would be shifted in 1
+            /// // |          |          | here, would be shifted in 2
+            /// // |          | here, would be `INNERMOST` shifted in by 1
+            /// // | here, initially, binder would be `INNERMOST`
+            /// ```
+            ///
+            /// You see that, initially, *any* bound value is free,
+            /// because we've not traversed any binders. As we pass
+            /// through a binder, we shift the `outer_index` by 1 to
+            /// account for the new binder that encloses us.
+            outer_index: ty::DebruijnIndex,
+            callback: F,
+        }
+
+        impl<'tcx, F> TypeVisitor<'tcx> for RegionVisitor<F>
+        where
+            F: FnMut(ty::Region<'tcx>) -> bool,
+        {
+            type BreakTy = ();
+
+            fn visit_binder<T: TypeVisitable<'tcx>>(
+                &mut self,
+                t: &Binder<'tcx, T>,
+            ) -> ControlFlow<Self::BreakTy> {
+                self.outer_index.shift_in(1);
+                let result = t.super_visit_with(self);
+                self.outer_index.shift_out(1);
+                result
+            }
+
+            fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+                match *r {
+                    ty::ReLateBound(debruijn, _) if debruijn < self.outer_index => {
+                        ControlFlow::CONTINUE
+                    }
+                    _ => {
+                        if (self.callback)(r) {
+                            ControlFlow::BREAK
+                        } else {
+                            ControlFlow::CONTINUE
+                        }
+                    }
+                }
+            }
+
+            fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+                // We're only interested in types involving regions
+                if ty.flags().intersects(TypeFlags::HAS_FREE_REGIONS) {
+                    ty.super_visit_with(self)
+                } else {
+                    ControlFlow::CONTINUE
+                }
+            }
+        }
+
+        value.visit_with(&mut RegionVisitor { outer_index: ty::INNERMOST, callback }).is_break()
+    }
+
+    /// Returns a set of all late-bound regions that are constrained
+    /// by `value`, meaning that if we instantiate those LBR with
+    /// variables and equate `value` with something else, those
+    /// variables will also be equated.
+    pub fn collect_constrained_late_bound_regions<T>(
+        self,
+        value: &Binder<'tcx, T>,
+    ) -> FxHashSet<ty::BoundRegionKind>
+    where
+        T: TypeVisitable<'tcx>,
+    {
+        self.collect_late_bound_regions(value, true)
+    }
+
+    /// Returns a set of all late-bound regions that appear in `value` anywhere.
+    pub fn collect_referenced_late_bound_regions<T>(
+        self,
+        value: &Binder<'tcx, T>,
+    ) -> FxHashSet<ty::BoundRegionKind>
+    where
+        T: TypeVisitable<'tcx>,
+    {
+        self.collect_late_bound_regions(value, false)
+    }
+
+    fn collect_late_bound_regions<T>(
+        self,
+        value: &Binder<'tcx, T>,
+        just_constraint: bool,
+    ) -> FxHashSet<ty::BoundRegionKind>
+    where
+        T: TypeVisitable<'tcx>,
+    {
+        let mut collector = LateBoundRegionsCollector::new(just_constraint);
+        let result = value.as_ref().skip_binder().visit_with(&mut collector);
+        assert!(result.is_continue()); // should never have stopped early
+        collector.regions
+    }
+}
+
+pub struct ValidateBoundVars<'tcx> {
+    bound_vars: &'tcx ty::List<ty::BoundVariableKind>,
+    binder_index: ty::DebruijnIndex,
+    // We may encounter the same variable at different levels of binding, so
+    // this can't just be `Ty`
+    visited: SsoHashSet<(ty::DebruijnIndex, Ty<'tcx>)>,
+}
+
+impl<'tcx> ValidateBoundVars<'tcx> {
+    pub fn new(bound_vars: &'tcx ty::List<ty::BoundVariableKind>) -> Self {
+        ValidateBoundVars {
+            bound_vars,
+            binder_index: ty::INNERMOST,
+            visited: SsoHashSet::default(),
+        }
+    }
+}
+
+impl<'tcx> TypeVisitor<'tcx> for ValidateBoundVars<'tcx> {
+    type BreakTy = ();
+
+    fn visit_binder<T: TypeVisitable<'tcx>>(
+        &mut self,
+        t: &Binder<'tcx, T>,
+    ) -> ControlFlow<Self::BreakTy> {
+        self.binder_index.shift_in(1);
+        let result = t.super_visit_with(self);
+        self.binder_index.shift_out(1);
+        result
+    }
+
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        if t.outer_exclusive_binder() < self.binder_index
+            || !self.visited.insert((self.binder_index, t))
+        {
+            return ControlFlow::BREAK;
+        }
+        match *t.kind() {
+            ty::Bound(debruijn, bound_ty) if debruijn == self.binder_index => {
+                if self.bound_vars.len() <= bound_ty.var.as_usize() {
+                    bug!("Not enough bound vars: {:?} not found in {:?}", t, self.bound_vars);
+                }
+                let list_var = self.bound_vars[bound_ty.var.as_usize()];
+                match list_var {
+                    ty::BoundVariableKind::Ty(kind) => {
+                        if kind != bound_ty.kind {
+                            bug!(
+                                "Mismatched type kinds: {:?} doesn't var in list {:?}",
+                                bound_ty.kind,
+                                list_var
+                            );
+                        }
+                    }
+                    _ => {
+                        bug!("Mismatched bound variable kinds! Expected type, found {:?}", list_var)
+                    }
+                }
+            }
+
+            _ => (),
+        };
+
+        t.super_visit_with(self)
+    }
+
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+        match *r {
+            ty::ReLateBound(index, br) if index == self.binder_index => {
+                if self.bound_vars.len() <= br.var.as_usize() {
+                    bug!("Not enough bound vars: {:?} not found in {:?}", br, self.bound_vars);
+                }
+                let list_var = self.bound_vars[br.var.as_usize()];
+                match list_var {
+                    ty::BoundVariableKind::Region(kind) => {
+                        if kind != br.kind {
+                            bug!(
+                                "Mismatched region kinds: {:?} doesn't match var ({:?}) in list ({:?})",
+                                br.kind,
+                                list_var,
+                                self.bound_vars
+                            );
+                        }
+                    }
+                    _ => bug!(
+                        "Mismatched bound variable kinds! Expected region, found {:?}",
+                        list_var
+                    ),
+                }
+            }
+
+            _ => (),
+        };
+
+        r.super_visit_with(self)
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+struct FoundEscapingVars;
+
+/// An "escaping var" is a bound var whose binder is not part of `t`. A bound var can be a
+/// bound region or a bound type.
+///
+/// So, for example, consider a type like the following, which has two binders:
+///
+///    for<'a> fn(x: for<'b> fn(&'a isize, &'b isize))
+///    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ outer scope
+///                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~  inner scope
+///
+/// This type has *bound regions* (`'a`, `'b`), but it does not have escaping regions, because the
+/// binders of both `'a` and `'b` are part of the type itself. However, if we consider the *inner
+/// fn type*, that type has an escaping region: `'a`.
+///
+/// Note that what I'm calling an "escaping var" is often just called a "free var". However,
+/// we already use the term "free var". It refers to the regions or types that we use to represent
+/// bound regions or type params on a fn definition while we are type checking its body.
+///
+/// To clarify, conceptually there is no particular difference between
+/// an "escaping" var and a "free" var. However, there is a big
+/// difference in practice. Basically, when "entering" a binding
+/// level, one is generally required to do some sort of processing to
+/// a bound var, such as replacing it with a fresh/placeholder
+/// var, or making an entry in the environment to represent the
+/// scope to which it is attached, etc. An escaping var represents
+/// a bound var for which this processing has not yet been done.
+struct HasEscapingVarsVisitor {
+    /// Anything bound by `outer_index` or "above" is escaping.
+    outer_index: ty::DebruijnIndex,
+}
+
+impl<'tcx> TypeVisitor<'tcx> for HasEscapingVarsVisitor {
+    type BreakTy = FoundEscapingVars;
+
+    fn visit_binder<T: TypeVisitable<'tcx>>(
+        &mut self,
+        t: &Binder<'tcx, T>,
+    ) -> ControlFlow<Self::BreakTy> {
+        self.outer_index.shift_in(1);
+        let result = t.super_visit_with(self);
+        self.outer_index.shift_out(1);
+        result
+    }
+
+    #[inline]
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        // If the outer-exclusive-binder is *strictly greater* than
+        // `outer_index`, that means that `t` contains some content
+        // bound at `outer_index` or above (because
+        // `outer_exclusive_binder` is always 1 higher than the
+        // content in `t`). Therefore, `t` has some escaping vars.
+        if t.outer_exclusive_binder() > self.outer_index {
+            ControlFlow::Break(FoundEscapingVars)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+
+    #[inline]
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+        // If the region is bound by `outer_index` or anything outside
+        // of outer index, then it escapes the binders we have
+        // visited.
+        if r.bound_at_or_above_binder(self.outer_index) {
+            ControlFlow::Break(FoundEscapingVars)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+
+    fn visit_const(&mut self, ct: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+        // we don't have a `visit_infer_const` callback, so we have to
+        // hook in here to catch this case (annoying...), but
+        // otherwise we do want to remember to visit the rest of the
+        // const, as it has types/regions embedded in a lot of other
+        // places.
+        match ct.kind() {
+            ty::ConstKind::Bound(debruijn, _) if debruijn >= self.outer_index => {
+                ControlFlow::Break(FoundEscapingVars)
+            }
+            _ => ct.super_visit_with(self),
+        }
+    }
+
+    #[inline]
+    fn visit_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
+        if predicate.outer_exclusive_binder() > self.outer_index {
+            ControlFlow::Break(FoundEscapingVars)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+struct FoundFlags;
+
+// FIXME: Optimize for checking for infer flags
+struct HasTypeFlagsVisitor {
+    flags: ty::TypeFlags,
+}
+
+impl std::fmt::Debug for HasTypeFlagsVisitor {
+    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.flags.fmt(fmt)
+    }
+}
+
+impl<'tcx> TypeVisitor<'tcx> for HasTypeFlagsVisitor {
+    type BreakTy = FoundFlags;
+
+    #[inline]
+    #[instrument(skip(self), level = "trace")]
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        let flags = t.flags();
+        trace!(t.flags=?t.flags());
+        if flags.intersects(self.flags) {
+            ControlFlow::Break(FoundFlags)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+
+    #[inline]
+    #[instrument(skip(self), level = "trace")]
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+        let flags = r.type_flags();
+        trace!(r.flags=?flags);
+        if flags.intersects(self.flags) {
+            ControlFlow::Break(FoundFlags)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+
+    #[inline]
+    #[instrument(level = "trace")]
+    fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+        let flags = FlagComputation::for_const(c);
+        trace!(r.flags=?flags);
+        if flags.intersects(self.flags) {
+            ControlFlow::Break(FoundFlags)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+
+    #[inline]
+    #[instrument(level = "trace")]
+    fn visit_unevaluated(&mut self, uv: ty::Unevaluated<'tcx>) -> ControlFlow<Self::BreakTy> {
+        let flags = FlagComputation::for_unevaluated_const(uv);
+        trace!(r.flags=?flags);
+        if flags.intersects(self.flags) {
+            ControlFlow::Break(FoundFlags)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+
+    #[inline]
+    #[instrument(level = "trace")]
+    fn visit_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
+        debug!(
+            "HasTypeFlagsVisitor: predicate={:?} predicate.flags={:?} self.flags={:?}",
+            predicate,
+            predicate.flags(),
+            self.flags
+        );
+        if predicate.flags().intersects(self.flags) {
+            ControlFlow::Break(FoundFlags)
+        } else {
+            ControlFlow::CONTINUE
+        }
+    }
+}
+
+/// Collects all the late-bound regions at the innermost binding level
+/// into a hash set.
+struct LateBoundRegionsCollector {
+    current_index: ty::DebruijnIndex,
+    regions: FxHashSet<ty::BoundRegionKind>,
+
+    /// `true` if we only want regions that are known to be
+    /// "constrained" when you equate this type with another type. In
+    /// particular, if you have e.g., `&'a u32` and `&'b u32`, equating
+    /// them constraints `'a == 'b`. But if you have `<&'a u32 as
+    /// Trait>::Foo` and `<&'b u32 as Trait>::Foo`, normalizing those
+    /// types may mean that `'a` and `'b` don't appear in the results,
+    /// so they are not considered *constrained*.
+    just_constrained: bool,
+}
+
+impl LateBoundRegionsCollector {
+    fn new(just_constrained: bool) -> Self {
+        LateBoundRegionsCollector {
+            current_index: ty::INNERMOST,
+            regions: Default::default(),
+            just_constrained,
+        }
+    }
+}
+
+impl<'tcx> TypeVisitor<'tcx> for LateBoundRegionsCollector {
+    fn visit_binder<T: TypeVisitable<'tcx>>(
+        &mut self,
+        t: &Binder<'tcx, T>,
+    ) -> ControlFlow<Self::BreakTy> {
+        self.current_index.shift_in(1);
+        let result = t.super_visit_with(self);
+        self.current_index.shift_out(1);
+        result
+    }
+
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        // if we are only looking for "constrained" region, we have to
+        // ignore the inputs to a projection, as they may not appear
+        // in the normalized form
+        if self.just_constrained {
+            if let ty::Projection(..) = t.kind() {
+                return ControlFlow::CONTINUE;
+            }
+        }
+
+        t.super_visit_with(self)
+    }
+
+    fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+        // if we are only looking for "constrained" region, we have to
+        // ignore the inputs of an unevaluated const, as they may not appear
+        // in the normalized form
+        if self.just_constrained {
+            if let ty::ConstKind::Unevaluated(..) = c.kind() {
+                return ControlFlow::CONTINUE;
+            }
+        }
+
+        c.super_visit_with(self)
+    }
+
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+        if let ty::ReLateBound(debruijn, br) = *r {
+            if debruijn == self.current_index {
+                self.regions.insert(br.kind);
+            }
+        }
+        ControlFlow::CONTINUE
+    }
+}
+
+/// Finds the max universe present
+pub struct MaxUniverse {
+    max_universe: ty::UniverseIndex,
+}
+
+impl MaxUniverse {
+    pub fn new() -> Self {
+        MaxUniverse { max_universe: ty::UniverseIndex::ROOT }
+    }
+
+    pub fn max_universe(self) -> ty::UniverseIndex {
+        self.max_universe
+    }
+}
+
+impl<'tcx> TypeVisitor<'tcx> for MaxUniverse {
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+        if let ty::Placeholder(placeholder) = t.kind() {
+            self.max_universe = ty::UniverseIndex::from_u32(
+                self.max_universe.as_u32().max(placeholder.universe.as_u32()),
+            );
+        }
+
+        t.super_visit_with(self)
+    }
+
+    fn visit_const(&mut self, c: ty::consts::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+        if let ty::ConstKind::Placeholder(placeholder) = c.kind() {
+            self.max_universe = ty::UniverseIndex::from_u32(
+                self.max_universe.as_u32().max(placeholder.universe.as_u32()),
+            );
+        }
+
+        c.super_visit_with(self)
+    }
+
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+        if let ty::RePlaceholder(placeholder) = *r {
+            self.max_universe = ty::UniverseIndex::from_u32(
+                self.max_universe.as_u32().max(placeholder.universe.as_u32()),
+            );
+        }
+
+        ControlFlow::CONTINUE
+    }
+}
diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
index 8e87ecd27d2..e3a383f86a7 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
@@ -1,6 +1,7 @@
 //! See docs in `build/expr/mod.rs`.
 
 use rustc_index::vec::Idx;
+use rustc_middle::ty::util::IntTypeExt;
 
 use crate::build::expr::as_place::PlaceBase;
 use crate::build::expr::category::{Category, RvalueFunc};
@@ -190,7 +191,30 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             }
             ExprKind::Cast { source } => {
                 let source = &this.thir[source];
-                let from_ty = CastTy::from_ty(source.ty);
+
+                // Casting an enum to an integer is equivalent to computing the discriminant and casting the
+                // discriminant. Previously every backend had to repeat the logic for this operation. Now we
+                // create all the steps directly in MIR with operations all backends need to support anyway.
+                let (source, ty) = if let ty::Adt(adt_def, ..) = source.ty.kind() && adt_def.is_enum() {
+                    let discr_ty = adt_def.repr().discr_type().to_ty(this.tcx);
+                    let place = unpack!(block = this.as_place(block, source));
+                    let discr = this.temp(discr_ty, source.span);
+                    this.cfg.push_assign(
+                        block,
+                        source_info,
+                        discr,
+                        Rvalue::Discriminant(place),
+                    );
+
+                    (Operand::Move(discr), discr_ty)
+                } else {
+                    let ty = source.ty;
+                    let source = unpack!(
+                        block = this.as_operand(block, scope, source, None, NeedsTemporary::No)
+                    );
+                    (source, ty)
+                };
+                let from_ty = CastTy::from_ty(ty);
                 let cast_ty = CastTy::from_ty(expr.ty);
                 let cast_kind = match (from_ty, cast_ty) {
                     (Some(CastTy::Ptr(_) | CastTy::FnPtr), Some(CastTy::Int(_))) => {
@@ -201,9 +225,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     }
                     (_, _) => CastKind::Misc,
                 };
-                let source = unpack!(
-                    block = this.as_operand(block, scope, source, None, NeedsTemporary::No)
-                );
                 block.and(Rvalue::Cast(cast_kind, source, expr.ty))
             }
             ExprKind::Pointer { cast, source } => {
diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs
index e27761381f6..e6d42af9817 100644
--- a/compiler/rustc_mir_build/src/build/mod.rs
+++ b/compiler/rustc_mir_build/src/build/mod.rs
@@ -20,7 +20,7 @@ use rustc_middle::mir::interpret::Scalar;
 use rustc_middle::mir::*;
 use rustc_middle::thir::{BindingMode, Expr, ExprId, LintLevel, LocalVarId, PatKind, Thir};
 use rustc_middle::ty::subst::Subst;
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable, TypeckResults};
 use rustc_span::symbol::sym;
 use rustc_span::Span;
 use rustc_span::Symbol;
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index 08b66d0abc7..4bc3d216a40 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -32,13 +32,14 @@ impl<'tcx> Cx<'tcx> {
         exprs.iter().map(|expr| self.mirror_expr_inner(expr)).collect()
     }
 
+    #[instrument(level = "trace", skip(self, hir_expr))]
     pub(super) fn mirror_expr_inner(&mut self, hir_expr: &'tcx hir::Expr<'tcx>) -> ExprId {
         let temp_lifetime =
             self.rvalue_scopes.temporary_scope(self.region_scope_tree, hir_expr.hir_id.local_id);
         let expr_scope =
             region::Scope { id: hir_expr.hir_id.local_id, data: region::ScopeData::Node };
 
-        debug!("Expr::make_mirror(): id={}, span={:?}", hir_expr.hir_id, hir_expr.span);
+        trace!(?hir_expr.hir_id, ?hir_expr.span);
 
         let mut expr = self.make_mirror_unadjusted(hir_expr);
 
@@ -49,7 +50,7 @@ impl<'tcx> Cx<'tcx> {
 
         // Now apply adjustments, if any.
         for adjustment in self.typeck_results.expr_adjustments(hir_expr) {
-            debug!("make_mirror: expr={:?} applying adjustment={:?}", expr, adjustment);
+            trace!(?expr, ?adjustment);
             let span = expr.span;
             expr =
                 self.apply_adjustment(hir_expr, expr, adjustment, adjustment_span.unwrap_or(span));
diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
index 845be2ab264..f22f3f61a01 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
@@ -550,7 +550,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
                         id,
                         span,
                         |lint| {
-                            lint.build(&msg).emit();
+                            lint.build(msg).emit();
                         },
                     );
                 }
diff --git a/compiler/rustc_mir_dataflow/src/framework/lattice.rs b/compiler/rustc_mir_dataflow/src/framework/lattice.rs
index 9a462f6e1a4..d6b89eb8227 100644
--- a/compiler/rustc_mir_dataflow/src/framework/lattice.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/lattice.rs
@@ -199,14 +199,16 @@ impl<T: JoinSemiLattice> MeetSemiLattice for Dual<T> {
 }
 
 /// Extends a type `T` with top and bottom elements to make it a partially ordered set in which no
-/// value of `T` is comparable with any other. A flat set has the following [Hasse diagram]:
+/// value of `T` is comparable with any other.
+///
+/// A flat set has the following [Hasse diagram]:
 ///
 /// ```text
-///         top
-///       / /  \ \
+///          top
+///  / ... / /  \ \ ... \
 /// all possible values of `T`
-///       \ \  / /
-///        bottom
+///  \ ... \ \  / / ... /
+///         bottom
 /// ```
 ///
 /// [Hasse diagram]: https://en.wikipedia.org/wiki/Hasse_diagram
diff --git a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs
index 097a6186cd5..8838b14c53a 100644
--- a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs
+++ b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs
@@ -1,5 +1,4 @@
-use rustc_errors::DiagnosticBuilder;
-use rustc_middle::lint::LintDiagnosticBuilder;
+use rustc_errors::{DiagnosticBuilder, LintDiagnosticBuilder};
 use rustc_middle::mir::visit::Visitor;
 use rustc_middle::mir::*;
 use rustc_middle::ty::TyCtxt;
diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs
index 36844d5f6cf..01eda979f9e 100644
--- a/compiler/rustc_mir_transform/src/const_prop.rs
+++ b/compiler/rustc_mir_transform/src/const_prop.rs
@@ -19,7 +19,7 @@ use rustc_middle::mir::{
 use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
 use rustc_middle::ty::subst::{InternalSubsts, Subst};
 use rustc_middle::ty::{
-    self, ConstKind, EarlyBinder, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable,
+    self, ConstKind, EarlyBinder, Instance, ParamEnv, Ty, TyCtxt, TypeVisitable,
 };
 use rustc_span::{def_id::DefId, Span};
 use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
@@ -29,9 +29,8 @@ use rustc_trait_selection::traits;
 use crate::MirPass;
 use rustc_const_eval::interpret::{
     self, compile_time_machine, AllocId, ConstAllocation, ConstValue, CtfeValidationMode, Frame,
-    ImmTy, Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemPlace, MemoryKind, OpTy,
-    Operand as InterpOperand, PlaceTy, Pointer, Scalar, ScalarMaybeUninit, StackPopCleanup,
-    StackPopUnwind,
+    ImmTy, Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, PlaceTy,
+    Pointer, Scalar, ScalarMaybeUninit, StackPopCleanup, StackPopUnwind,
 };
 
 /// The maximum number of bytes that we'll allocate space for a local or the return value.
@@ -237,15 +236,19 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
         throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
     }
 
-    fn access_local(
-        _ecx: &InterpCx<'mir, 'tcx, Self>,
-        frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
+    fn access_local<'a>(
+        frame: &'a Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
         local: Local,
-    ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
+    ) -> InterpResult<'tcx, &'a interpret::Operand<Self::PointerTag>> {
         let l = &frame.locals[local];
 
-        if l.value == LocalValue::Unallocated {
-            throw_machine_stop_str!("tried to access an unallocated local")
+        if matches!(
+            l.value,
+            LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit))
+        ) {
+            // For us "uninit" means "we don't know its value, might be initiailized or not".
+            // So stop here.
+            throw_machine_stop_str!("tried to access alocal with unknown value ")
         }
 
         l.access()
@@ -255,8 +258,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
         frame: usize,
         local: Local,
-    ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
-    {
+    ) -> InterpResult<'tcx, &'a mut interpret::Operand<Self::PointerTag>> {
         if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
             throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
         }
@@ -391,7 +393,11 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
             .layout_of(EarlyBinder(body.return_ty()).subst(tcx, substs))
             .ok()
             // Don't bother allocating memory for large values.
-            .filter(|ret_layout| ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT))
+            // I don't know how return types can seem to be unsized but this happens in the
+            // `type/type-unsatisfiable.rs` test.
+            .filter(|ret_layout| {
+                !ret_layout.is_unsized() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
+            })
             .unwrap_or_else(|| ecx.layout_of(tcx.types.unit).unwrap());
 
         let ret = ecx
@@ -436,8 +442,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
     /// Remove `local` from the pool of `Locals`. Allows writing to them,
     /// but not reading from them anymore.
     fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
-        ecx.frame_mut().locals[local] =
-            LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
+        ecx.frame_mut().locals[local] = LocalState {
+            value: LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)),
+            layout: Cell::new(None),
+        };
     }
 
     fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
@@ -1042,7 +1050,9 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
                     let frame = self.ecx.frame_mut();
                     frame.locals[local].value =
                         if let StatementKind::StorageLive(_) = statement.kind {
-                            LocalValue::Unallocated
+                            LocalValue::Live(interpret::Operand::Immediate(
+                                interpret::Immediate::Uninit,
+                            ))
                         } else {
                             LocalValue::Dead
                         };
diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs
index dc3cb282c73..280ed17f03c 100644
--- a/compiler/rustc_mir_transform/src/const_prop_lint.rs
+++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs
@@ -18,7 +18,8 @@ use rustc_middle::mir::{
 use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
 use rustc_middle::ty::subst::{InternalSubsts, Subst};
 use rustc_middle::ty::{
-    self, ConstInt, ConstKind, EarlyBinder, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeFoldable,
+    self, ConstInt, ConstKind, EarlyBinder, Instance, ParamEnv, ScalarInt, Ty, TyCtxt,
+    TypeVisitable,
 };
 use rustc_session::lint;
 use rustc_span::{def_id::DefId, Span};
@@ -30,8 +31,8 @@ use crate::MirLint;
 use rustc_const_eval::const_eval::ConstEvalErr;
 use rustc_const_eval::interpret::{
     self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
-    LocalState, LocalValue, MemPlace, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Pointer,
-    Scalar, ScalarMaybeUninit, StackPopCleanup, StackPopUnwind,
+    LocalState, LocalValue, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, ScalarMaybeUninit,
+    StackPopCleanup, StackPopUnwind,
 };
 
 /// The maximum number of bytes that we'll allocate space for a local or the return value.
@@ -228,15 +229,19 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
         throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
     }
 
-    fn access_local(
-        _ecx: &InterpCx<'mir, 'tcx, Self>,
-        frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
+    fn access_local<'a>(
+        frame: &'a Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
         local: Local,
-    ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
+    ) -> InterpResult<'tcx, &'a interpret::Operand<Self::PointerTag>> {
         let l = &frame.locals[local];
 
-        if l.value == LocalValue::Unallocated {
-            throw_machine_stop_str!("tried to access an uninitialized local")
+        if matches!(
+            l.value,
+            LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit))
+        ) {
+            // For us "uninit" means "we don't know its value, might be initiailized or not".
+            // So stop here.
+            throw_machine_stop_str!("tried to access a local with unknown value")
         }
 
         l.access()
@@ -246,8 +251,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
         frame: usize,
         local: Local,
-    ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
-    {
+    ) -> InterpResult<'tcx, &'a mut interpret::Operand<Self::PointerTag>> {
         if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
             throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
         }
@@ -383,7 +387,11 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
             .layout_of(EarlyBinder(body.return_ty()).subst(tcx, substs))
             .ok()
             // Don't bother allocating memory for large values.
-            .filter(|ret_layout| ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT))
+            // I don't know how return types can seem to be unsized but this happens in the
+            // `type/type-unsatisfiable.rs` test.
+            .filter(|ret_layout| {
+                !ret_layout.is_unsized() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
+            })
             .unwrap_or_else(|| ecx.layout_of(tcx.types.unit).unwrap());
 
         let ret = ecx
@@ -429,8 +437,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
     /// Remove `local` from the pool of `Locals`. Allows writing to them,
     /// but not reading from them anymore.
     fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
-        ecx.frame_mut().locals[local] =
-            LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
+        ecx.frame_mut().locals[local] = LocalState {
+            value: LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)),
+            layout: Cell::new(None),
+        };
     }
 
     fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
@@ -914,7 +924,9 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
                     let frame = self.ecx.frame_mut();
                     frame.locals[local].value =
                         if let StatementKind::StorageLive(_) = statement.kind {
-                            LocalValue::Unallocated
+                            LocalValue::Live(interpret::Operand::Immediate(
+                                interpret::Immediate::Uninit,
+                            ))
                         } else {
                             LocalValue::Dead
                         };
diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs
index 28f3790914b..c96497abf8f 100644
--- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs
+++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs
@@ -66,7 +66,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
         return;
     }
 
-    let bbs = body.basic_blocks_mut();
+    let bbs = body.basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate().0;
     for Location { block, statement_index } in patch {
         bbs[block].statements[statement_index].make_nop();
     }
diff --git a/compiler/rustc_mir_transform/src/deaggregator.rs b/compiler/rustc_mir_transform/src/deaggregator.rs
index 616ba819982..01f490e23bf 100644
--- a/compiler/rustc_mir_transform/src/deaggregator.rs
+++ b/compiler/rustc_mir_transform/src/deaggregator.rs
@@ -11,7 +11,8 @@ impl<'tcx> MirPass<'tcx> for Deaggregator {
     }
 
     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
-        let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
+        let (basic_blocks, local_decls, _) =
+            body.basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate();
         let local_decls = &*local_decls;
         for bb in basic_blocks {
             bb.expand_statements(|stmt| {
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index ce387cb4453..12f5764152e 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -180,16 +180,20 @@ impl<'tcx> Inliner<'tcx> {
             return Err("failed to normalize return type");
         }
         if callsite.fn_sig.abi() == Abi::RustCall {
-            let mut args = args.into_iter();
-            let _ = args.next(); // Skip `self` argument.
-            let arg_tuple_ty = args.next().unwrap().ty(&caller_body.local_decls, self.tcx);
-            assert!(args.next().is_none());
+            let (arg_tuple, skipped_args) = match &args[..] {
+                [arg_tuple] => (arg_tuple, 0),
+                [_, arg_tuple] => (arg_tuple, 1),
+                _ => bug!("Expected `rust-call` to have 1 or 2 args"),
+            };
 
+            let arg_tuple_ty = arg_tuple.ty(&caller_body.local_decls, self.tcx);
             let ty::Tuple(arg_tuple_tys) = arg_tuple_ty.kind() else {
                 bug!("Closure arguments are not passed as a tuple");
             };
 
-            for (arg_ty, input) in arg_tuple_tys.iter().zip(callee_body.args_iter().skip(1)) {
+            for (arg_ty, input) in
+                arg_tuple_tys.iter().zip(callee_body.args_iter().skip(skipped_args))
+            {
                 let input_type = callee_body.local_decls[input].ty;
                 if !equal_up_to_regions(self.tcx, self.param_env, arg_ty, input_type) {
                     trace!(?arg_ty, ?input_type);
diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs
index ee4a6bfba0e..a3a35f95071 100644
--- a/compiler/rustc_mir_transform/src/inline/cycle.rs
+++ b/compiler/rustc_mir_transform/src/inline/cycle.rs
@@ -2,7 +2,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::mir::TerminatorKind;
-use rustc_middle::ty::TypeFoldable;
+use rustc_middle::ty::TypeVisitable;
 use rustc_middle::ty::{self, subst::SubstsRef, InstanceDef, TyCtxt};
 use rustc_session::Limit;
 
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 12fcb299ce3..9776cd0ab8b 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -28,7 +28,7 @@ use rustc_index::vec::IndexVec;
 use rustc_middle::mir::visit::Visitor as _;
 use rustc_middle::mir::{traversal, Body, ConstQualifs, MirPass, MirPhase, Promoted};
 use rustc_middle::ty::query::Providers;
-use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
 use rustc_span::{Span, Symbol};
 
 #[macro_use]
diff --git a/compiler/rustc_mir_transform/src/lower_slice_len.rs b/compiler/rustc_mir_transform/src/lower_slice_len.rs
index 07163cfe575..813ab4001a7 100644
--- a/compiler/rustc_mir_transform/src/lower_slice_len.rs
+++ b/compiler/rustc_mir_transform/src/lower_slice_len.rs
@@ -26,7 +26,9 @@ pub fn lower_slice_len_calls<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
         return;
     };
 
-    let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
+    // The one successor remains unchanged, so no need to invalidate
+    let (basic_blocks, local_decls, _) =
+        body.basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate();
 
     for block in basic_blocks {
         // lower `<[_]>::len` calls
diff --git a/compiler/rustc_mir_transform/src/normalize_array_len.rs b/compiler/rustc_mir_transform/src/normalize_array_len.rs
index 0f45711baa3..3396a446df2 100644
--- a/compiler/rustc_mir_transform/src/normalize_array_len.rs
+++ b/compiler/rustc_mir_transform/src/normalize_array_len.rs
@@ -32,7 +32,9 @@ impl<'tcx> MirPass<'tcx> for NormalizeArrayLen {
 }
 
 pub fn normalize_array_len_calls<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
-    let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
+    // We don't ever touch terminators, so no need to invalidate the CFG cache
+    let (basic_blocks, local_decls, _) =
+        body.basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate();
 
     // do a preliminary analysis to see if we ever have locals of type `[T;N]` or `&[T;N]`
     let mut interesting_locals = BitSet::new_empty(local_decls.len());
diff --git a/compiler/rustc_mir_transform/src/remove_storage_markers.rs b/compiler/rustc_mir_transform/src/remove_storage_markers.rs
index c9b6e1459d3..5bb4f8bb9b3 100644
--- a/compiler/rustc_mir_transform/src/remove_storage_markers.rs
+++ b/compiler/rustc_mir_transform/src/remove_storage_markers.rs
@@ -17,7 +17,7 @@ impl<'tcx> MirPass<'tcx> for RemoveStorageMarkers {
         }
 
         trace!("Running RemoveStorageMarkers on {:?}", body.source);
-        for data in body.basic_blocks_mut() {
+        for data in body.basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate().0 {
             data.statements.retain(|statement| match statement.kind {
                 StatementKind::StorageLive(..)
                 | StatementKind::StorageDead(..)
diff --git a/compiler/rustc_mir_transform/src/remove_zsts.rs b/compiler/rustc_mir_transform/src/remove_zsts.rs
index aaee6f491cd..34941c1907d 100644
--- a/compiler/rustc_mir_transform/src/remove_zsts.rs
+++ b/compiler/rustc_mir_transform/src/remove_zsts.rs
@@ -18,7 +18,8 @@ impl<'tcx> MirPass<'tcx> for RemoveZsts {
             return;
         }
         let param_env = tcx.param_env(body.source.def_id());
-        let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
+        let (basic_blocks, local_decls, _) =
+            body.basic_blocks_local_decls_mut_and_var_debug_info_no_invalidate();
         for block in basic_blocks.iter_mut() {
             for statement in block.statements.iter_mut() {
                 if let StatementKind::Assign(box (place, _)) | StatementKind::Deinit(box place) =
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index b9936e35b77..b3ac0f4cbea 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -194,7 +194,9 @@ use rustc_middle::mir::{self, Local, Location};
 use rustc_middle::ty::adjustment::{CustomCoerceUnsized, PointerCast};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts};
-use rustc_middle::ty::{self, GenericParamDefKind, Instance, Ty, TyCtxt, TypeFoldable, VtblEntry};
+use rustc_middle::ty::{
+    self, GenericParamDefKind, Instance, Ty, TyCtxt, TypeFoldable, TypeVisitable, VtblEntry,
+};
 use rustc_middle::{middle::codegen_fn_attrs::CodegenFnAttrFlags, mir::visit::TyContext};
 use rustc_session::config::EntryFnType;
 use rustc_session::lint::builtin::LARGE_ASSIGNMENTS;
diff --git a/compiler/rustc_monomorphize/src/partitioning/default.rs b/compiler/rustc_monomorphize/src/partitioning/default.rs
index 320765e7af3..9190e5fe4eb 100644
--- a/compiler/rustc_monomorphize/src/partitioning/default.rs
+++ b/compiler/rustc_monomorphize/src/partitioning/default.rs
@@ -9,7 +9,7 @@ use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel
 use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, Linkage, Visibility};
 use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
 use rustc_middle::ty::print::characteristic_def_id_of_type;
-use rustc_middle::ty::{self, fold::TypeFoldable, DefIdTree, InstanceDef, TyCtxt};
+use rustc_middle::ty::{self, visit::TypeVisitable, DefIdTree, InstanceDef, TyCtxt};
 use rustc_span::symbol::Symbol;
 
 use super::PartitioningCx;
diff --git a/compiler/rustc_monomorphize/src/polymorphize.rs b/compiler/rustc_monomorphize/src/polymorphize.rs
index f29143b4480..aaa924d7fa0 100644
--- a/compiler/rustc_monomorphize/src/polymorphize.rs
+++ b/compiler/rustc_monomorphize/src/polymorphize.rs
@@ -13,9 +13,9 @@ use rustc_middle::mir::{
 };
 use rustc_middle::ty::{
     self,
-    fold::{TypeFoldable, TypeSuperFoldable, TypeVisitor},
     query::Providers,
     subst::SubstsRef,
+    visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
     Const, Ty, TyCtxt,
 };
 use rustc_span::symbol::sym;
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 0869ed65ad2..63055c56c5c 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -357,7 +357,7 @@ impl<'a> DerefMut for SnapshotParser<'a> {
 }
 
 impl<'a> Parser<'a> {
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub(super) fn span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -366,7 +366,7 @@ impl<'a> Parser<'a> {
         err.span_err(sp, self.diagnostic())
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 00002f6f59b..67e6402c0ae 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -1353,7 +1353,16 @@ impl<'a> Parser<'a> {
 
     /// Parses `extern string_literal?`.
     fn parse_extern(&mut self) -> Extern {
-        if self.eat_keyword(kw::Extern) { Extern::from_abi(self.parse_abi()) } else { Extern::None }
+        if self.eat_keyword(kw::Extern) {
+            let mut extern_span = self.prev_token.span;
+            let abi = self.parse_abi();
+            if let Some(abi) = abi {
+                extern_span = extern_span.to(abi.span);
+            }
+            Extern::from_abi(abi, extern_span)
+        } else {
+            Extern::None
+        }
     }
 
     /// Parses a string literal as an ABI spec.
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 40545b19b24..8c123c052e5 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -1163,7 +1163,7 @@ impl CheckAttrVisitor<'_> {
                         hir_id,
                         meta.span(),
                         |lint| {
-                            lint.build(&"invalid `doc` attribute").emit();
+                            lint.build("invalid `doc` attribute").emit();
                         },
                     );
                     is_valid = false;
diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs
index f9e67310452..1add91fc9c5 100644
--- a/compiler/rustc_passes/src/entry.rs
+++ b/compiler/rustc_passes/src/entry.rs
@@ -1,4 +1,4 @@
-use rustc_ast::entry::EntryPointType;
+use rustc_ast::{entry::EntryPointType, Attribute};
 use rustc_errors::struct_span_err;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
@@ -7,9 +7,8 @@ use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{DefIdTree, TyCtxt};
 use rustc_session::config::{CrateType, EntryFnType};
 use rustc_session::parse::feature_err;
-use rustc_session::Session;
 use rustc_span::symbol::sym;
-use rustc_span::{Span, DUMMY_SP};
+use rustc_span::{Span, Symbol, DUMMY_SP};
 
 struct EntryContext<'tcx> {
     tcx: TyCtxt<'tcx>,
@@ -72,9 +71,16 @@ fn entry_point_type(ctxt: &EntryContext<'_>, id: ItemId, at_root: bool) -> Entry
     }
 }
 
-fn throw_attr_err(sess: &Session, span: Span, attr: &str) {
-    sess.struct_span_err(span, &format!("`{}` attribute can only be used on functions", attr))
-        .emit();
+fn err_if_attr_found(ctxt: &EntryContext<'_>, attrs: &[Attribute], sym: Symbol) {
+    if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym) {
+        ctxt.tcx
+            .sess
+            .struct_span_err(
+                attr.span,
+                &format!("`{}` attribute can only be used on functions", sym.as_str()),
+            )
+            .emit();
+    }
 }
 
 fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
@@ -84,12 +90,8 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
         EntryPointType::None => (),
         _ if !matches!(ctxt.tcx.def_kind(id.def_id), DefKind::Fn) => {
             let attrs = ctxt.tcx.hir().attrs(id.hir_id());
-            if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym::start) {
-                throw_attr_err(&ctxt.tcx.sess, attr.span, "start");
-            }
-            if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym::rustc_main) {
-                throw_attr_err(&ctxt.tcx.sess, attr.span, "rustc_main");
-            }
+            err_if_attr_found(ctxt, attrs, sym::start);
+            err_if_attr_found(ctxt, attrs, sym::rustc_main);
         }
         EntryPointType::MainNamed => (),
         EntryPointType::OtherMain => {
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index f5e323e2bc4..5560d44aa0d 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -27,7 +27,7 @@ use rustc_middle::thir::abstract_const::Node as ACNode;
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::subst::InternalSubsts;
 use rustc_middle::ty::{self, Const, DefIdTree, GenericParamDefKind};
-use rustc_middle::ty::{TraitRef, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use rustc_middle::ty::{TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use rustc_session::lint;
 use rustc_span::hygiene::Transparency;
 use rustc_span::symbol::{kw, Ident};
@@ -80,7 +80,7 @@ trait DefIdVisitor<'tcx> {
             dummy: Default::default(),
         }
     }
-    fn visit(&mut self, ty_fragment: impl TypeFoldable<'tcx>) -> ControlFlow<Self::BreakTy> {
+    fn visit(&mut self, ty_fragment: impl TypeVisitable<'tcx>) -> ControlFlow<Self::BreakTy> {
         ty_fragment.visit_with(&mut self.skeleton())
     }
     fn visit_trait(&mut self, trait_ref: TraitRef<'tcx>) -> ControlFlow<Self::BreakTy> {
@@ -467,7 +467,7 @@ impl<'tcx> EmbargoVisitor<'tcx> {
         }
 
         let macro_module_def_id = self.tcx.local_parent(local_def_id);
-        if self.tcx.hir().opt_def_kind(macro_module_def_id) != Some(DefKind::Mod) {
+        if self.tcx.opt_def_kind(macro_module_def_id) != Some(DefKind::Mod) {
             // The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252).
             return;
         }
diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs
index 307ad4e844b..3ed6632ba66 100644
--- a/compiler/rustc_query_impl/src/plumbing.rs
+++ b/compiler/rustc_query_impl/src/plumbing.rs
@@ -282,11 +282,14 @@ macro_rules! define_queries {
                 } else {
                     Some(key.default_span(*tcx))
                 };
-                // Use `tcx.hir().opt_def_kind()` to reduce the chance of
-                // accidentally triggering an infinite query loop.
-                let def_kind = key.key_as_def_id()
-                    .and_then(|def_id| def_id.as_local())
-                    .and_then(|def_id| tcx.hir().opt_def_kind(def_id));
+                let def_kind = if kind == dep_graph::DepKind::opt_def_kind {
+                    // Try to avoid infinite recursion.
+                    None
+                } else {
+                    key.key_as_def_id()
+                        .and_then(|def_id| def_id.as_local())
+                        .and_then(|def_id| tcx.opt_def_kind(def_id))
+                };
                 let hash = || {
                     let mut hcx = tcx.create_stable_hashing_context();
                     let mut hasher = StableHasher::new();
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index e8b7cee5734..0f58206eee9 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -1495,6 +1495,15 @@ impl<'a> Resolver<'a> {
             err.help("have you added the `#[macro_use]` on the module/import?");
             return;
         }
+        if ident.name == kw::Default
+            && let ModuleKind::Def(DefKind::Enum, def_id, _) = parent_scope.module.kind
+            && let Some(span) = self.opt_span(def_id)
+        {
+            err.span_help(
+                self.session.source_map().guess_head_span(span),
+                "consider adding `#[derive(Default)]` to this enum",
+            );
+        }
         for ns in [Namespace::MacroNS, Namespace::TypeNS, Namespace::ValueNS] {
             if let Ok(binding) = self.early_resolve_ident_in_lexical_scope(
                 ident,
@@ -2580,8 +2589,10 @@ fn show_candidates(
             } else {
                 "item".to_string()
             };
+            let plural_descr =
+                if descr.ends_with("s") { format!("{}es", descr) } else { format!("{}s", descr) };
 
-            let mut msg = format!("{}these {}s exist but are inaccessible", prefix, descr);
+            let mut msg = format!("{}these {} exist but are inaccessible", prefix, plural_descr);
             let mut has_colon = false;
 
             let mut spans = Vec::new();
diff --git a/compiler/rustc_resolve/src/late/lifetimes.rs b/compiler/rustc_resolve/src/late/lifetimes.rs
index ed5d1165c7d..557dbecfabe 100644
--- a/compiler/rustc_resolve/src/late/lifetimes.rs
+++ b/compiler/rustc_resolve/src/late/lifetimes.rs
@@ -2539,12 +2539,12 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
 /// "Constrained" basically means that it appears in any type but
 /// not amongst the inputs to a projection. In other words, `<&'a
 /// T as Trait<''b>>::Foo` does not constrain `'a` or `'b`.
-fn is_late_bound_map(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<&FxHashSet<LocalDefId>> {
+fn is_late_bound_map(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<&FxIndexSet<LocalDefId>> {
     let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
     let decl = tcx.hir().fn_decl_by_hir_id(hir_id)?;
     let generics = tcx.hir().get_generics(def_id)?;
 
-    let mut late_bound = FxHashSet::default();
+    let mut late_bound = FxIndexSet::default();
 
     let mut constrained_by_input = ConstrainedCollector::default();
     for arg_ty in decl.inputs {
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 57384877da5..28ef384f2c5 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -13,7 +13,6 @@
 #![feature(let_chains)]
 #![feature(let_else)]
 #![feature(never_type)]
-#![cfg_attr(bootstrap, feature(nll))]
 #![recursion_limit = "256"]
 #![allow(rustdoc::private_intra_doc_links)]
 #![allow(rustc::potential_query_instability)]
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 14ad1a42a7d..e7717f1367c 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1102,49 +1102,6 @@ impl CrateCheckConfig {
             .extend(atomic_values);
 
         // Target specific values
-        #[cfg(bootstrap)]
-        {
-            for target in TARGETS
-                .iter()
-                .map(|target| Target::expect_builtin(&TargetTriple::from_triple(target)))
-            {
-                self.values_valid
-                    .entry(sym::target_os)
-                    .or_default()
-                    .insert(Symbol::intern(&target.options.os));
-                self.values_valid
-                    .entry(sym::target_family)
-                    .or_default()
-                    .extend(target.options.families.iter().map(|family| Symbol::intern(family)));
-                self.values_valid
-                    .entry(sym::target_arch)
-                    .or_default()
-                    .insert(Symbol::intern(&target.arch));
-                self.values_valid
-                    .entry(sym::target_endian)
-                    .or_default()
-                    .insert(Symbol::intern(&target.options.endian.as_str()));
-                self.values_valid
-                    .entry(sym::target_env)
-                    .or_default()
-                    .insert(Symbol::intern(&target.options.env));
-                self.values_valid
-                    .entry(sym::target_abi)
-                    .or_default()
-                    .insert(Symbol::intern(&target.options.abi));
-                self.values_valid
-                    .entry(sym::target_vendor)
-                    .or_default()
-                    .insert(Symbol::intern(&target.options.vendor));
-                self.values_valid
-                    .entry(sym::target_pointer_width)
-                    .or_default()
-                    .insert(sym::integer(target.pointer_width));
-            }
-        }
-
-        // Target specific values
-        #[cfg(not(bootstrap))]
         {
             const VALUES: [&Symbol; 8] = [
                 &sym::target_os,
@@ -2767,8 +2724,8 @@ pub(crate) mod dep_tracking {
     use super::{
         BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType,
         InstrumentCoverage, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, OomStrategy, OptLevel,
-        OutputType, OutputTypes, Passes, SourceFileHashAlgorithm, SwitchWithOptPath,
-        SymbolManglingVersion, TrimmedDefPaths,
+        OutputType, OutputTypes, Passes, SourceFileHashAlgorithm, SplitDwarfKind,
+        SwitchWithOptPath, SymbolManglingVersion, TrimmedDefPaths,
     };
     use crate::lint;
     use crate::options::WasiExecModel;
@@ -2855,6 +2812,7 @@ pub(crate) mod dep_tracking {
         Edition,
         LinkerPluginLto,
         SplitDebuginfo,
+        SplitDwarfKind,
         StackProtector,
         SwitchWithOptPath,
         SymbolManglingVersion,
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
index 35b55981e37..7353c1ca0e2 100644
--- a/compiler/rustc_session/src/lib.rs
+++ b/compiler/rustc_session/src/lib.rs
@@ -6,7 +6,7 @@
 #![feature(once_cell)]
 #![feature(option_get_or_insert_default)]
 #![feature(rustc_attrs)]
-#![cfg_attr(not(bootstrap), feature(map_many_mut))]
+#![feature(map_many_mut)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 441e1f9f6a2..be70ea5d5e4 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -1496,7 +1496,7 @@ options! {
         "control if mem::uninitialized and mem::zeroed panic on more UB"),
     strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
         "tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
-    split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [UNTRACKED],
+    split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [TRACKED],
         "split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform)
         (default: `split`)
 
@@ -1504,7 +1504,7 @@ options! {
                  file which is ignored by the linker
         `single`: sections which do not require relocation are written into object file but ignored
                   by the linker"),
-    split_dwarf_inlining: bool = (true, parse_bool, [UNTRACKED],
+    split_dwarf_inlining: bool = (true, parse_bool, [TRACKED],
         "provide minimal debug info in the object/executable to facilitate online \
          symbolication/stack traces in the absence of .dwo/.dwp files when using Split DWARF"),
     symbol_mangling_version: Option<SymbolManglingVersion> = (None,
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index a5ccae047fc..f31d52147b4 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -311,7 +311,7 @@ impl ParseSess {
         self.create_warning(warning).emit()
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_err(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -319,7 +319,7 @@ impl ParseSess {
         self.span_diagnostic.struct_err(msg)
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         self.span_diagnostic.struct_warn(msg)
     }
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index b5058fd699a..2a5ddd4e9e4 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -280,7 +280,7 @@ impl Session {
         self.crate_types.set(crate_types).expect("`crate_types` was initialized twice")
     }
 
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_warn<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -288,7 +288,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn(sp, msg)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_warn_with_expectation<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -297,7 +297,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn_with_expectation(sp, msg, id)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_warn_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -306,11 +306,11 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn_with_code(sp, msg, code)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[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)]
+    #[rustc_lint_diagnostics]
     pub fn struct_warn_with_expectation(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -318,7 +318,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_warn_with_expectation(msg, id)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_allow<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -326,11 +326,11 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_allow(sp, msg)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[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)]
+    #[rustc_lint_diagnostics]
     pub fn struct_expect(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -338,7 +338,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_expect(msg, id)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -346,7 +346,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_span_err(sp, msg)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -356,14 +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)]
+    #[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)]
+    #[rustc_lint_diagnostics]
     pub fn struct_err_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -371,7 +371,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_err_with_code(msg, code)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_warn_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -379,7 +379,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_warn_with_code(msg, code)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_fatal<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -387,7 +387,7 @@ impl Session {
     ) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_span_fatal(sp, msg)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn struct_span_fatal_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -396,16 +396,16 @@ impl Session {
     ) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_span_fatal_with_code(sp, msg, code)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[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)]
+    #[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)]
+    #[rustc_lint_diagnostics]
     pub fn span_fatal_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -414,11 +414,11 @@ impl Session {
     ) -> ! {
         self.diagnostic().span_fatal_with_code(sp, msg, code)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> ! {
         self.diagnostic().fatal(msg).raise()
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_err_or_warn<S: Into<MultiSpan>>(
         &self,
         is_warning: bool,
@@ -431,7 +431,7 @@ impl Session {
             self.span_err(sp, msg);
         }
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -439,7 +439,7 @@ impl Session {
     ) -> ErrorGuaranteed {
         self.diagnostic().span_err(sp, msg)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -448,7 +448,7 @@ impl Session {
     ) {
         self.diagnostic().span_err_with_code(sp, msg, code)
     }
-    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
+    #[rustc_lint_diagnostics]
     pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.diagnostic().err(msg)
     }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 4e28d2b6001..65a2a18e02f 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -173,6 +173,7 @@ symbols! {
         DebugTuple,
         Decodable,
         Decoder,
+        DecorateLint,
         Default,
         Deref,
         DiagnosticMessage,
diff --git a/compiler/rustc_symbol_mangling/src/legacy.rs b/compiler/rustc_symbol_mangling/src/legacy.rs
index f67b87a6a52..470438471cb 100644
--- a/compiler/rustc_symbol_mangling/src/legacy.rs
+++ b/compiler/rustc_symbol_mangling/src/legacy.rs
@@ -3,7 +3,7 @@ use rustc_hir::def_id::CrateNum;
 use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
 use rustc_middle::ty::print::{PrettyPrinter, Print, Printer};
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
-use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitable};
 use rustc_middle::util::common::record_time;
 
 use tracing::debug;
diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs
index 1036c5d941b..13229a3995c 100644
--- a/compiler/rustc_symbol_mangling/src/v0.rs
+++ b/compiler/rustc_symbol_mangling/src/v0.rs
@@ -9,7 +9,7 @@ use rustc_middle::ty::layout::IntegerExt;
 use rustc_middle::ty::print::{Print, Printer};
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
 use rustc_middle::ty::{
-    self, EarlyBinder, FloatTy, Instance, IntTy, Ty, TyCtxt, TypeFoldable, UintTy,
+    self, EarlyBinder, FloatTy, Instance, IntTy, Ty, TyCtxt, TypeVisitable, UintTy,
 };
 use rustc_span::symbol::kw;
 use rustc_target::abi::call::FnAbi;
@@ -240,7 +240,7 @@ impl<'tcx> SymbolMangler<'tcx> {
         print_value: impl FnOnce(&'a mut Self, &T) -> Result<&'a mut Self, !>,
     ) -> Result<&'a mut Self, !>
     where
-        T: TypeFoldable<'tcx>,
+        T: TypeVisitable<'tcx>,
     {
         let regions = if value.has_late_bound_regions() {
             self.tcx.collect_referenced_late_bound_regions(value)
diff --git a/compiler/rustc_trait_selection/src/autoderef.rs b/compiler/rustc_trait_selection/src/autoderef.rs
index 5b88cff03d7..8b7e8984a8a 100644
--- a/compiler/rustc_trait_selection/src/autoderef.rs
+++ b/compiler/rustc_trait_selection/src/autoderef.rs
@@ -4,7 +4,7 @@ use rustc_errors::struct_span_err;
 use rustc_hir as hir;
 use rustc_infer::infer::InferCtxt;
 use rustc_middle::ty::{self, TraitRef, Ty, TyCtxt};
-use rustc_middle::ty::{ToPredicate, TypeFoldable};
+use rustc_middle::ty::{ToPredicate, TypeVisitable};
 use rustc_session::Limit;
 use rustc_span::def_id::LOCAL_CRATE;
 use rustc_span::Span;
diff --git a/compiler/rustc_trait_selection/src/infer.rs b/compiler/rustc_trait_selection/src/infer.rs
index f135f0c1b13..9d30374f8b8 100644
--- a/compiler/rustc_trait_selection/src/infer.rs
+++ b/compiler/rustc_trait_selection/src/infer.rs
@@ -9,7 +9,7 @@ use rustc_middle::infer::canonical::{Canonical, CanonicalizedQueryResponse, Quer
 use rustc_middle::traits::query::Fallible;
 use rustc_middle::ty::subst::SubstsRef;
 use rustc_middle::ty::ToPredicate;
-use rustc_middle::ty::{self, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TypeFoldable, TypeVisitable};
 use rustc_span::{Span, DUMMY_SP};
 
 use std::fmt::Debug;
diff --git a/compiler/rustc_trait_selection/src/opaque_types.rs b/compiler/rustc_trait_selection/src/opaque_types.rs
index 9dd8588cece..d290f7b074c 100644
--- a/compiler/rustc_trait_selection/src/opaque_types.rs
+++ b/compiler/rustc_trait_selection/src/opaque_types.rs
@@ -9,6 +9,7 @@ use rustc_infer::infer::{InferCtxt, TyCtxtInferExt as _};
 use rustc_infer::traits::{Obligation, ObligationCause, TraitEngine};
 use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, InternalSubsts};
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, OpaqueHiddenType, OpaqueTypeKey, ToPredicate, Ty, TyCtxt};
 use rustc_span::Span;
 
diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
index 90ff07cba02..65ff9ceb67e 100644
--- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs
+++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
@@ -8,6 +8,7 @@ use crate::infer::InferCtxt;
 use crate::traits::project::ProjectAndUnifyResult;
 use rustc_middle::mir::interpret::ErrorHandled;
 use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{Region, RegionVid, Term};
 
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -212,15 +213,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
                 panic!("Unable to fulfill trait {:?} for '{:?}': {:?}", trait_did, ty, errors);
             }
 
-            let body_id_map: FxHashMap<_, _> = infcx
-                .inner
-                .borrow()
-                .region_obligations()
-                .iter()
-                .map(|&(id, _)| (id, vec![]))
-                .collect();
-
-            infcx.process_registered_region_obligations(&body_id_map, full_env);
+            infcx.process_registered_region_obligations(&Default::default(), full_env);
 
             let region_data = infcx
                 .inner
diff --git a/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs b/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs
index 592b0ab477a..9ef7ac9a8e0 100644
--- a/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs
+++ b/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs
@@ -8,7 +8,7 @@ use crate::traits::{
     PredicateObligation, SelectionError, TraitEngine,
 };
 use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
-use rustc_middle::ty::{self, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TypeVisitable};
 
 pub struct FulfillmentContext<'tcx> {
     obligations: FxIndexSet<PredicateObligation<'tcx>>,
diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs
index 2b26b916d32..f933f1c3c94 100644
--- a/compiler/rustc_trait_selection/src/traits/coherence.rs
+++ b/compiler/rustc_trait_selection/src/traits/coherence.rs
@@ -16,13 +16,12 @@ use crate::traits::{
 //use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Diagnostic;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE};
-use rustc_hir::CRATE_HIR_ID;
 use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
 use rustc_infer::traits::{util, TraitEngine};
 use rustc_middle::traits::specialization_graph::OverlapMode;
 use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, ImplSubject, Ty, TyCtxt};
 use rustc_span::symbol::sym;
 use rustc_span::DUMMY_SP;
@@ -317,14 +316,13 @@ fn negative_impl<'cx, 'tcx>(
         let (subject2, obligations) =
             impl_subject_and_oblig(selcx, impl_env, impl2_def_id, impl2_substs);
 
-        !equate(&infcx, impl_env, impl1_def_id, subject1, subject2, obligations)
+        !equate(&infcx, impl_env, subject1, subject2, obligations)
     })
 }
 
 fn equate<'cx, 'tcx>(
     infcx: &InferCtxt<'cx, 'tcx>,
     impl_env: ty::ParamEnv<'tcx>,
-    impl1_def_id: DefId,
     subject1: ImplSubject<'tcx>,
     subject2: ImplSubject<'tcx>,
     obligations: impl Iterator<Item = PredicateObligation<'tcx>>,
@@ -341,7 +339,7 @@ fn equate<'cx, 'tcx>(
     let opt_failing_obligation = obligations
         .into_iter()
         .chain(more_obligations)
-        .find(|o| negative_impl_exists(selcx, impl_env, impl1_def_id, o));
+        .find(|o| negative_impl_exists(selcx, impl_env, o));
 
     if let Some(failing_obligation) = opt_failing_obligation {
         debug!("overlap: obligation unsatisfiable {:?}", failing_obligation);
@@ -356,18 +354,17 @@ fn equate<'cx, 'tcx>(
 fn negative_impl_exists<'cx, 'tcx>(
     selcx: &SelectionContext<'cx, 'tcx>,
     param_env: ty::ParamEnv<'tcx>,
-    region_context: DefId,
     o: &PredicateObligation<'tcx>,
 ) -> bool {
     let infcx = &selcx.infcx().fork();
 
-    if resolve_negative_obligation(infcx, param_env, region_context, o) {
+    if resolve_negative_obligation(infcx, param_env, o) {
         return true;
     }
 
     // Try to prove a negative obligation exists for super predicates
     for o in util::elaborate_predicates(infcx.tcx, iter::once(o.predicate)) {
-        if resolve_negative_obligation(infcx, param_env, region_context, &o) {
+        if resolve_negative_obligation(infcx, param_env, &o) {
             return true;
         }
     }
@@ -379,7 +376,6 @@ fn negative_impl_exists<'cx, 'tcx>(
 fn resolve_negative_obligation<'cx, 'tcx>(
     infcx: &InferCtxt<'cx, 'tcx>,
     param_env: ty::ParamEnv<'tcx>,
-    region_context: DefId,
     o: &PredicateObligation<'tcx>,
 ) -> bool {
     let tcx = infcx.tcx;
@@ -397,19 +393,11 @@ fn resolve_negative_obligation<'cx, 'tcx>(
         return false;
     }
 
-    let mut outlives_env = OutlivesEnvironment::new(param_env);
-    // FIXME -- add "assumed to be well formed" types into the `outlives_env`
-
-    // "Save" the accumulated implied bounds into the outlives environment
-    // (due to the FIXME above, there aren't any, but this step is still needed).
-    // The "body id" is given as `CRATE_HIR_ID`, which is the same body-id used
-    // by the "dummy" causes elsewhere (body-id is only relevant when checking
-    // function bodies with closures).
-    outlives_env.save_implied_bounds(CRATE_HIR_ID);
-
-    infcx.process_registered_region_obligations(outlives_env.region_bound_pairs_map(), param_env);
+    // FIXME -- also add "assumed to be well formed" types into the `outlives_env`
+    let outlives_env = OutlivesEnvironment::new(param_env);
+    infcx.process_registered_region_obligations(outlives_env.region_bound_pairs(), param_env);
 
-    let errors = infcx.resolve_regions(region_context, &outlives_env);
+    let errors = infcx.resolve_regions(&outlives_env);
 
     if !errors.is_empty() {
         return false;
diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
index 424eff2f621..f9b4a1583cc 100644
--- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
+++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
@@ -17,7 +17,7 @@ use rustc_middle::mir::interpret::{ErrorHandled, LitToConstError, LitToConstInpu
 use rustc_middle::thir;
 use rustc_middle::thir::abstract_const::{self, Node, NodeId, NotConstEvaluatable};
 use rustc_middle::ty::subst::{Subst, SubstsRef};
-use rustc_middle::ty::{self, DelaySpanBugEmitted, EarlyBinder, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, DelaySpanBugEmitted, EarlyBinder, TyCtxt, TypeVisitable};
 use rustc_session::lint;
 use rustc_span::def_id::LocalDefId;
 use rustc_span::Span;
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 fa56219b409..8efefd476ab 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -30,6 +30,7 @@ use rustc_middle::ty::error::ExpectedFound;
 use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable};
 use rustc_middle::ty::{
     self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable,
+    TypeVisitable,
 };
 use rustc_span::symbol::{kw, sym};
 use rustc_span::{ExpnKind, Span, DUMMY_SP};
@@ -1111,18 +1112,12 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     })
                     .collect::<Option<Vec<ArgKind>>>()?,
             ),
-            Node::Item(&hir::Item { span, kind: hir::ItemKind::Fn(ref sig, ..), .. })
-            | Node::ImplItem(&hir::ImplItem {
-                span,
-                kind: hir::ImplItemKind::Fn(ref sig, _),
-                ..
-            })
+            Node::Item(&hir::Item { kind: hir::ItemKind::Fn(ref sig, ..), .. })
+            | Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(ref sig, _), .. })
             | Node::TraitItem(&hir::TraitItem {
-                span,
-                kind: hir::TraitItemKind::Fn(ref sig, _),
-                ..
+                kind: hir::TraitItemKind::Fn(ref sig, _), ..
             }) => (
-                sm.guess_head_span(span),
+                sig.span,
                 sig.decl
                     .inputs
                     .iter()
@@ -1137,7 +1132,6 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
             ),
             Node::Ctor(ref variant_data) => {
                 let span = variant_data.ctor_hir_id().map_or(DUMMY_SP, |id| hir.span(id));
-                let span = sm.guess_head_span(span);
                 (span, vec![ArgKind::empty(); variant_data.fields().len()])
             }
             _ => panic!("non-FnLike node found: {:?}", node),
@@ -1980,7 +1974,6 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
                             body_id,
                             span,
                             trait_ref.self_ty().skip_binder().into(),
-                            vec![],
                             ErrorCode::E0282,
                             false,
                         )
@@ -2005,19 +1998,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
                 let subst = data.trait_ref.substs.iter().find(|s| s.has_infer_types_or_consts());
 
                 let mut err = if let Some(subst) = subst {
-                    let impl_candidates = self
-                        .find_similar_impl_candidates(trait_ref)
-                        .into_iter()
-                        .map(|candidate| candidate.trait_ref)
-                        .collect();
-                    self.emit_inference_failure_err(
-                        body_id,
-                        span,
-                        subst,
-                        impl_candidates,
-                        ErrorCode::E0283,
-                        true,
-                    )
+                    self.emit_inference_failure_err(body_id, span, subst, ErrorCode::E0283, true)
                 } else {
                     struct_span_err!(
                         self.tcx.sess,
@@ -2117,7 +2098,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
                     return;
                 }
 
-                self.emit_inference_failure_err(body_id, span, arg, vec![], ErrorCode::E0282, false)
+                self.emit_inference_failure_err(body_id, span, arg, ErrorCode::E0282, false)
             }
 
             ty::PredicateKind::Subtype(data) => {
@@ -2131,14 +2112,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
                 let SubtypePredicate { a_is_expected: _, a, b } = data;
                 // both must be type variables, or the other would've been instantiated
                 assert!(a.is_ty_var() && b.is_ty_var());
-                self.emit_inference_failure_err(
-                    body_id,
-                    span,
-                    a.into(),
-                    vec![],
-                    ErrorCode::E0282,
-                    true,
-                )
+                self.emit_inference_failure_err(body_id, span, a.into(), ErrorCode::E0282, true)
             }
             ty::PredicateKind::Projection(data) => {
                 if predicate.references_error() || self.is_tainted_by_errors() {
@@ -2155,7 +2129,6 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
                         body_id,
                         span,
                         subst,
-                        vec![],
                         ErrorCode::E0284,
                         true,
                     );
@@ -2205,7 +2178,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
         let mut post = vec![];
         for def_id in impls {
             match self.tcx.span_of_impl(*def_id) {
-                Ok(span) => spans.push(self.tcx.sess.source_map().guess_head_span(span)),
+                Ok(span) => spans.push(span),
                 Err(name) => {
                     crates.push(name);
                     if let Some(header) = to_pretty_impl_header(self.tcx, *def_id) {
@@ -2552,8 +2525,7 @@ pub fn recursive_type_with_infinite_size_error<'tcx>(
     spans: Vec<(Span, Option<hir::HirId>)>,
 ) {
     assert!(type_def_id.is_local());
-    let span = tcx.hir().span_if_local(type_def_id).unwrap();
-    let span = tcx.sess.source_map().guess_head_span(span);
+    let span = tcx.def_span(type_def_id);
     let path = tcx.def_path_str(type_def_id);
     let mut err =
         struct_span_err!(tcx.sess, span, E0072, "recursive type `{}` has infinite size", path);
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 a8be6f74c99..31d54b5b403 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -24,7 +24,7 @@ use rustc_middle::hir::map;
 use rustc_middle::ty::{
     self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
     GeneratorDiagnosticData, GeneratorInteriorTypeCause, Infer, InferTy, IsSuggestable,
-    ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
+    ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitable,
 };
 use rustc_middle::ty::{TypeAndMut, TypeckResults};
 use rustc_session::Limit;
diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs
index d61166437d7..78600652254 100644
--- a/compiler/rustc_trait_selection/src/traits/fulfill.rs
+++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs
@@ -10,7 +10,7 @@ use rustc_middle::thir::abstract_const::NotConstEvaluatable;
 use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::subst::SubstsRef;
 use rustc_middle::ty::ToPredicate;
-use rustc_middle::ty::{self, Binder, Const, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Binder, Const, Ty, TypeVisitable};
 use std::marker::PhantomData;
 
 use super::const_evaluatable;
@@ -131,8 +131,6 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
         let span = debug_span!("select", obligation_forest_size = ?self.predicates.len());
         let _enter = span.enter();
 
-        let mut errors = Vec::new();
-
         // Process pending obligations.
         let outcome: Outcome<_, _> = self.predicates.process_obligations(&mut FulfillProcessor {
             selcx,
@@ -142,7 +140,8 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
         // FIXME: if we kept the original cache key, we could mark projection
         // obligations as complete for the projection cache here.
 
-        errors.extend(outcome.errors.into_iter().map(to_fulfillment_error));
+        let errors: Vec<FulfillmentError<'tcx>> =
+            outcome.errors.into_iter().map(to_fulfillment_error).collect();
 
         debug!(
             "select({} predicates remaining, {} errors) done",
@@ -728,7 +727,7 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
                 }
                 return ProcessResult::Changed(vec![]);
             } else {
-                tracing::debug!("Does NOT hold: {:?}", obligation);
+                debug!("Does NOT hold: {:?}", obligation);
             }
         }
 
diff --git a/compiler/rustc_trait_selection/src/traits/misc.rs b/compiler/rustc_trait_selection/src/traits/misc.rs
index f04f527ccb7..dd2769c7186 100644
--- a/compiler/rustc_trait_selection/src/traits/misc.rs
+++ b/compiler/rustc_trait_selection/src/traits/misc.rs
@@ -5,7 +5,7 @@ use crate::traits::{self, ObligationCause};
 
 use rustc_hir as hir;
 use rustc_infer::infer::TyCtxtInferExt;
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
 
 use crate::traits::error_reporting::InferCtxtExt;
 
diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs
index 34b0f431b8e..74d2eb17b6b 100644
--- a/compiler/rustc_trait_selection/src/traits/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/mod.rs
@@ -32,6 +32,7 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
 use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, GenericParamDefKind, ToPredicate, Ty, TyCtxt, VtblEntry};
 use rustc_span::{sym, Span};
 use smallvec::SmallVec;
@@ -198,17 +199,13 @@ pub fn type_known_to_meet_bound_modulo_regions<'a, 'tcx>(
     }
 }
 
+#[instrument(level = "debug", skip(tcx, elaborated_env))]
 fn do_normalize_predicates<'tcx>(
     tcx: TyCtxt<'tcx>,
-    region_context: DefId,
     cause: ObligationCause<'tcx>,
     elaborated_env: ty::ParamEnv<'tcx>,
     predicates: Vec<ty::Predicate<'tcx>>,
 ) -> Result<Vec<ty::Predicate<'tcx>>, ErrorGuaranteed> {
-    debug!(
-        "do_normalize_predicates(predicates={:?}, region_context={:?}, cause={:?})",
-        predicates, region_context, cause,
-    );
     let span = cause.span;
     tcx.infer_ctxt().enter(|infcx| {
         // FIXME. We should really... do something with these region
@@ -240,7 +237,7 @@ fn do_normalize_predicates<'tcx>(
         // cares about declarations like `'a: 'b`.
         let outlives_env = OutlivesEnvironment::new(elaborated_env);
 
-        infcx.resolve_regions_and_report_errors(region_context, &outlives_env);
+        infcx.resolve_regions_and_report_errors(&outlives_env);
 
         let predicates = match infcx.fully_resolve(predicates) {
             Ok(predicates) => predicates,
@@ -269,9 +266,9 @@ fn do_normalize_predicates<'tcx>(
 
 // FIXME: this is gonna need to be removed ...
 /// Normalizes the parameter environment, reporting errors if they occur.
+#[instrument(level = "debug", skip(tcx))]
 pub fn normalize_param_env_or_error<'tcx>(
     tcx: TyCtxt<'tcx>,
-    region_context: DefId,
     unnormalized_env: ty::ParamEnv<'tcx>,
     cause: ObligationCause<'tcx>,
 ) -> ty::ParamEnv<'tcx> {
@@ -289,12 +286,6 @@ pub fn normalize_param_env_or_error<'tcx>(
     // parameter environments once for every fn as it goes,
     // and errors will get reported then; so outside of type inference we
     // can be sure that no errors should occur.
-
-    debug!(
-        "normalize_param_env_or_error(region_context={:?}, unnormalized_env={:?}, cause={:?})",
-        region_context, unnormalized_env, cause
-    );
-
     let mut predicates: Vec<_> =
         util::elaborate_predicates(tcx, unnormalized_env.caller_bounds().into_iter())
             .map(|obligation| obligation.predicate)
@@ -338,7 +329,6 @@ pub fn normalize_param_env_or_error<'tcx>(
     );
     let Ok(non_outlives_predicates) = do_normalize_predicates(
         tcx,
-        region_context,
         cause.clone(),
         elaborated_env,
         predicates,
@@ -362,7 +352,6 @@ pub fn normalize_param_env_or_error<'tcx>(
     );
     let Ok(outlives_predicates) = do_normalize_predicates(
         tcx,
-        region_context,
         cause,
         outlives_env,
         outlives_predicates,
diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs
index 8d344591915..ac1811244ca 100644
--- a/compiler/rustc_trait_selection/src/traits/object_safety.rs
+++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs
@@ -19,7 +19,7 @@ use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_middle::ty::subst::{GenericArg, InternalSubsts, Subst};
 use rustc_middle::ty::{
-    self, EarlyBinder, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor,
+    self, EarlyBinder, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 use rustc_middle::ty::{Predicate, ToPredicate};
 use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY;
@@ -731,7 +731,7 @@ fn receiver_is_dispatchable<'tcx>(
     })
 }
 
-fn contains_illegal_self_type_reference<'tcx, T: TypeFoldable<'tcx>>(
+fn contains_illegal_self_type_reference<'tcx, T: TypeVisitable<'tcx>>(
     tcx: TyCtxt<'tcx>,
     trait_def_id: DefId,
     value: T,
diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs
index aba4f144d4b..b3e7fbb3578 100644
--- a/compiler/rustc_trait_selection/src/traits/project.rs
+++ b/compiler/rustc_trait_selection/src/traits/project.rs
@@ -29,8 +29,9 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
 use rustc_infer::infer::resolve::OpportunisticRegionResolver;
 use rustc_middle::traits::select::OverflowError;
-use rustc_middle::ty::fold::{MaxUniverse, TypeFoldable, TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
 use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::visit::{MaxUniverse, TypeVisitable};
 use rustc_middle::ty::{self, EarlyBinder, Term, ToPredicate, Ty, TyCtxt};
 use rustc_span::symbol::sym;
 
@@ -359,7 +360,7 @@ where
     result
 }
 
-pub(crate) fn needs_normalization<'tcx, T: TypeFoldable<'tcx>>(value: &T, reveal: Reveal) -> bool {
+pub(crate) fn needs_normalization<'tcx, T: TypeVisitable<'tcx>>(value: &T, reveal: Reveal) -> bool {
     match reveal {
         Reveal::UserFacing => value
             .has_type_flags(ty::TypeFlags::HAS_TY_PROJECTION | ty::TypeFlags::HAS_CT_PROJECTION),
diff --git a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs
index 25cc6e9f9f2..aad3c37f84e 100644
--- a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs
@@ -1,73 +1,7 @@
-use crate::infer::at::At;
-use crate::infer::canonical::OriginalQueryValues;
-use crate::infer::InferOk;
-
-use rustc_middle::ty::subst::GenericArg;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 
 pub use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
 
-pub trait AtExt<'tcx> {
-    fn dropck_outlives(&self, ty: Ty<'tcx>) -> InferOk<'tcx, Vec<GenericArg<'tcx>>>;
-}
-
-impl<'cx, 'tcx> AtExt<'tcx> for At<'cx, 'tcx> {
-    /// Given a type `ty` of some value being dropped, computes a set
-    /// of "kinds" (types, regions) that must be outlive the execution
-    /// of the destructor. These basically correspond to data that the
-    /// destructor might access. This is used during regionck to
-    /// impose "outlives" constraints on any lifetimes referenced
-    /// within.
-    ///
-    /// The rules here are given by the "dropck" RFCs, notably [#1238]
-    /// and [#1327]. This is a fixed-point computation, where we
-    /// explore all the data that will be dropped (transitively) when
-    /// a value of type `ty` is dropped. For each type T that will be
-    /// dropped and which has a destructor, we must assume that all
-    /// the types/regions of T are live during the destructor, unless
-    /// they are marked with a special attribute (`#[may_dangle]`).
-    ///
-    /// [#1238]: https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md
-    /// [#1327]: https://github.com/rust-lang/rfcs/blob/master/text/1327-dropck-param-eyepatch.md
-    fn dropck_outlives(&self, ty: Ty<'tcx>) -> InferOk<'tcx, Vec<GenericArg<'tcx>>> {
-        debug!("dropck_outlives(ty={:?}, param_env={:?})", ty, self.param_env,);
-
-        // Quick check: there are a number of cases that we know do not require
-        // any destructor.
-        let tcx = self.infcx.tcx;
-        if trivial_dropck_outlives(tcx, ty) {
-            return InferOk { value: vec![], obligations: vec![] };
-        }
-
-        let mut orig_values = OriginalQueryValues::default();
-        let c_ty = self.infcx.canonicalize_query(self.param_env.and(ty), &mut orig_values);
-        let span = self.cause.span;
-        debug!("c_ty = {:?}", c_ty);
-        if let Ok(result) = tcx.dropck_outlives(c_ty)
-            && result.is_proven()
-            && let Ok(InferOk { value, obligations }) =
-                self.infcx.instantiate_query_response_and_region_obligations(
-                    self.cause,
-                    self.param_env,
-                    &orig_values,
-                    result,
-                )
-        {
-            let ty = self.infcx.resolve_vars_if_possible(ty);
-            let kinds = value.into_kinds_reporting_overflows(tcx, span, ty);
-            return InferOk { value: kinds, obligations };
-        }
-
-        // Errors and ambiguity in dropck occur in two cases:
-        // - unresolved inference variables at the end of typeck
-        // - non well-formed types where projections cannot be resolved
-        // Either of these should have created an error before.
-        tcx.sess.delay_span_bug(span, "dtorck encountered internal error");
-
-        InferOk { value: vec![], obligations: vec![] }
-    }
-}
-
 /// This returns true if the type `ty` is "trivial" for
 /// dropck-outlives -- that is, if it doesn't require any types to
 /// outlive. This is similar but not *quite* the same as the
@@ -79,6 +13,8 @@ impl<'cx, 'tcx> AtExt<'tcx> for At<'cx, 'tcx> {
 ///
 /// Note also that `needs_drop` requires a "global" type (i.e., one
 /// with erased regions), but this function does not.
+///
+// FIXME(@lcnr): remove this module and move this function somewhere else.
 pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
     match ty.kind() {
         // None of these types have a destructor and hence they do not
@@ -105,7 +41,7 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
         ty::Array(ty, _) | ty::Slice(ty) => trivial_dropck_outlives(tcx, *ty),
 
         // (T1..Tn) and closures have same properties as T1..Tn --
-        // check if *any* of those are trivial.
+        // check if *all* of them are trivial.
         ty::Tuple(tys) => tys.iter().all(|t| trivial_dropck_outlives(tcx, t)),
         ty::Closure(_, ref substs) => {
             trivial_dropck_outlives(tcx, substs.as_closure().tupled_upvars_ty())
diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
index 7f15b683fda..eccfb3477b9 100644
--- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
@@ -14,6 +14,7 @@ use rustc_infer::traits::Normalized;
 use rustc_middle::mir;
 use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable};
 use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable};
 use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitor};
 
 use std::ops::ControlFlow;
@@ -108,7 +109,7 @@ struct MaxEscapingBoundVarVisitor {
 }
 
 impl<'tcx> TypeVisitor<'tcx> for MaxEscapingBoundVarVisitor {
-    fn visit_binder<T: TypeFoldable<'tcx>>(
+    fn visit_binder<T: TypeVisitable<'tcx>>(
         &mut self,
         t: &ty::Binder<'tcx, T>,
     ) -> ControlFlow<Self::BreakTy> {
diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs
index 605c9ace5ed..c9d46b2810d 100644
--- a/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs
@@ -95,7 +95,7 @@ pub fn scrape_region_constraints<'tcx, Op: super::TypeOp<'tcx, Output = R>, R>(
         infcx.tcx,
         region_obligations
             .iter()
-            .map(|(_, r_o)| (r_o.sup_type, r_o.sub_region))
+            .map(|r_o| (r_o.sup_type, r_o.sub_region))
             .map(|(ty, r)| (infcx.resolve_vars_if_possible(ty), r)),
         &region_constraint_data,
     );
diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs
index 46d6e973d4c..2a3319f0f26 100644
--- a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs
@@ -3,7 +3,7 @@ use crate::traits::query::Fallible;
 use rustc_infer::traits::query::OutlivesBound;
 use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt};
 
-#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, Lift)]
+#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct ImpliedOutlivesBounds<'tcx> {
     pub ty: Ty<'tcx>,
 }
diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs
index 82f147f8143..b63382429d0 100644
--- a/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs
@@ -3,7 +3,7 @@ use crate::traits::query::dropck_outlives::{trivial_dropck_outlives, DropckOutli
 use crate::traits::query::Fallible;
 use rustc_middle::ty::{ParamEnvAnd, Ty, TyCtxt};
 
-#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, Lift)]
+#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct DropckOutlives<'tcx> {
     dropped_ty: Ty<'tcx>,
 }
diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
index cfd50c1afb9..0f7af41cfe3 100644
--- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
@@ -12,7 +12,7 @@ use rustc_infer::traits::TraitEngine;
 use rustc_infer::traits::{Obligation, SelectionError, TraitObligation};
 use rustc_lint_defs::builtin::DEREF_INTO_DYN_SUPERTRAIT;
 use rustc_middle::ty::print::with_no_trimmed_paths;
-use rustc_middle::ty::{self, ToPredicate, Ty, TypeFoldable};
+use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable};
 use rustc_target::spec::abi::Abi;
 
 use crate::traits;
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 4223cdb5dbb..596ee435622 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -39,7 +39,7 @@ use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::relate::TypeRelation;
 use rustc_middle::ty::subst::{Subst, SubstsRef};
 use rustc_middle::ty::{self, EarlyBinder, PolyProjectionPredicate, ToPolyTraitRef, ToPredicate};
-use rustc_middle::ty::{Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{Ty, TyCtxt, TypeFoldable, TypeVisitable};
 use rustc_span::symbol::sym;
 
 use std::cell::{Cell, RefCell};
diff --git a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
index a1861529b59..a80354897d6 100644
--- a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
@@ -16,9 +16,8 @@ use crate::infer::{InferCtxt, InferOk, TyCtxtInferExt};
 use crate::traits::select::IntercrateAmbiguityCause;
 use crate::traits::{self, coherence, FutureCompatOverlapErrorKind, ObligationCause, TraitEngine};
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{struct_span_err, EmissionGuarantee};
+use rustc_errors::{struct_span_err, EmissionGuarantee, LintDiagnosticBuilder};
 use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_middle::lint::LintDiagnosticBuilder;
 use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef};
 use rustc_middle::ty::{self, ImplSubject, TyCtxt};
 use rustc_session::lint::builtin::COHERENCE_LEAK_CHECK;
@@ -341,10 +340,7 @@ fn report_negative_positive_conflict(
     positive_impl_def_id: DefId,
     sg: &mut specialization_graph::Graph,
 ) {
-    let impl_span = tcx
-        .sess
-        .source_map()
-        .guess_head_span(tcx.span_of_impl(local_impl_def_id.to_def_id()).unwrap());
+    let impl_span = tcx.def_span(local_impl_def_id);
 
     let mut err = struct_span_err!(
         tcx.sess,
@@ -357,10 +353,7 @@ fn report_negative_positive_conflict(
 
     match tcx.span_of_impl(negative_impl_def_id) {
         Ok(span) => {
-            err.span_label(
-                tcx.sess.source_map().guess_head_span(span),
-                "negative implementation here".to_string(),
-            );
+            err.span_label(span, "negative implementation here");
         }
         Err(cname) => {
             err.note(&format!("negative implementation in crate `{}`", cname));
@@ -369,10 +362,7 @@ fn report_negative_positive_conflict(
 
     match tcx.span_of_impl(positive_impl_def_id) {
         Ok(span) => {
-            err.span_label(
-                tcx.sess.source_map().guess_head_span(span),
-                "positive implementation here".to_string(),
-            );
+            err.span_label(span, "positive implementation here");
         }
         Err(cname) => {
             err.note(&format!("positive implementation in crate `{}`", cname));
@@ -389,8 +379,7 @@ fn report_conflicting_impls(
     used_to_be_allowed: Option<FutureCompatOverlapErrorKind>,
     sg: &mut specialization_graph::Graph,
 ) {
-    let impl_span =
-        tcx.sess.source_map().guess_head_span(tcx.span_of_impl(impl_def_id.to_def_id()).unwrap());
+    let impl_span = tcx.def_span(impl_def_id);
 
     // Work to be done after we've built the DiagnosticBuilder. We have to define it
     // now because the struct_lint methods don't return back the DiagnosticBuilder
@@ -417,10 +406,7 @@ fn report_conflicting_impls(
         let mut err = err.build(&msg);
         match tcx.span_of_impl(overlap.with_impl) {
             Ok(span) => {
-                err.span_label(
-                    tcx.sess.source_map().guess_head_span(span),
-                    "first implementation here".to_string(),
-                );
+                err.span_label(span, "first implementation here".to_string());
 
                 err.span_label(
                     impl_span,
diff --git a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs
index 930c80e0abb..fcb73b43fa8 100644
--- a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs
+++ b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs
@@ -4,7 +4,7 @@ use crate::traits;
 use rustc_hir::def_id::DefId;
 use rustc_middle::ty::fast_reject::{self, SimplifiedType, TreatParams};
 use rustc_middle::ty::print::with_no_trimmed_paths;
-use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
 
 pub use rustc_middle::traits::specialization_graph::*;
 
diff --git a/compiler/rustc_trait_selection/src/traits/structural_match.rs b/compiler/rustc_trait_selection/src/traits/structural_match.rs
index bc2ce31df6d..f3ae34ce30a 100644
--- a/compiler/rustc_trait_selection/src/traits/structural_match.rs
+++ b/compiler/rustc_trait_selection/src/traits/structural_match.rs
@@ -6,7 +6,7 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
 use rustc_hir::lang_items::LangItem;
 use rustc_middle::ty::query::Providers;
-use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use rustc_span::Span;
 use std::ops::ControlFlow;
 
diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs
index 3a00c41d90a..3170b29ee69 100644
--- a/compiler/rustc_trait_selection/src/traits/util.rs
+++ b/compiler/rustc_trait_selection/src/traits/util.rs
@@ -6,7 +6,7 @@ use smallvec::SmallVec;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def_id::DefId;
 use rustc_middle::ty::subst::{GenericArg, Subst, SubstsRef};
-use rustc_middle::ty::{self, EarlyBinder, ImplSubject, ToPredicate, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, EarlyBinder, ImplSubject, ToPredicate, Ty, TyCtxt, TypeVisitable};
 
 use super::{Normalized, Obligation, ObligationCause, PredicateObligation, SelectionContext};
 pub use rustc_infer::traits::{self, util::*};
diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs
index 8d666046ad9..d43b3c9091f 100644
--- a/compiler/rustc_trait_selection/src/traits/wf.rs
+++ b/compiler/rustc_trait_selection/src/traits/wf.rs
@@ -5,7 +5,7 @@ use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
-use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeVisitable};
 use rustc_span::Span;
 
 use std::iter;
diff --git a/compiler/rustc_traits/src/chalk/lowering.rs b/compiler/rustc_traits/src/chalk/lowering.rs
index 2453d3a692a..c7c604e14e3 100644
--- a/compiler/rustc_traits/src/chalk/lowering.rs
+++ b/compiler/rustc_traits/src/chalk/lowering.rs
@@ -35,7 +35,8 @@ use rustc_ast::ast;
 use rustc_middle::traits::{ChalkEnvironmentAndGoal, ChalkRustInterner as RustInterner};
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
 use rustc_middle::ty::{
-    self, Binder, Region, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitor,
+    self, Binder, Region, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
+    TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 use rustc_span::def_id::DefId;
 
@@ -896,7 +897,7 @@ impl<'tcx> BoundVarsCollector<'tcx> {
 }
 
 impl<'tcx> TypeVisitor<'tcx> for BoundVarsCollector<'tcx> {
-    fn visit_binder<T: TypeFoldable<'tcx>>(
+    fn visit_binder<T: TypeVisitable<'tcx>>(
         &mut self,
         t: &Binder<'tcx, T>,
     ) -> ControlFlow<Self::BreakTy> {
diff --git a/compiler/rustc_traits/src/chalk/mod.rs b/compiler/rustc_traits/src/chalk/mod.rs
index 59cf37fee9c..db7ea4253e3 100644
--- a/compiler/rustc_traits/src/chalk/mod.rs
+++ b/compiler/rustc_traits/src/chalk/mod.rs
@@ -14,7 +14,7 @@ use rustc_middle::infer::canonical::{CanonicalTyVarKind, CanonicalVarKind};
 use rustc_middle::traits::ChalkRustInterner;
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::subst::GenericArg;
-use rustc_middle::ty::{self, BoundVar, ParamTy, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, BoundVar, ParamTy, TyCtxt, TypeFoldable, TypeVisitable};
 
 use rustc_infer::infer::canonical::{
     Canonical, CanonicalVarValues, Certainty, QueryRegionConstraints, QueryResponse,
diff --git a/compiler/rustc_traits/src/implied_outlives_bounds.rs b/compiler/rustc_traits/src/implied_outlives_bounds.rs
index ae48211d52d..c7cac8fca89 100644
--- a/compiler/rustc_traits/src/implied_outlives_bounds.rs
+++ b/compiler/rustc_traits/src/implied_outlives_bounds.rs
@@ -9,7 +9,7 @@ use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
 use rustc_infer::traits::query::OutlivesBound;
 use rustc_infer::traits::TraitEngineExt as _;
 use rustc_middle::ty::query::Providers;
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
 use rustc_span::source_map::DUMMY_SP;
 use rustc_trait_selection::infer::InferCtxtBuilderExt;
 use rustc_trait_selection::traits::query::{CanonicalTyGoal, Fallible, NoSolution};
diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs
index d08fe6dada1..5e58f237982 100644
--- a/compiler/rustc_ty_utils/src/instance.rs
+++ b/compiler/rustc_ty_utils/src/instance.rs
@@ -4,7 +4,7 @@ use rustc_infer::infer::TyCtxtInferExt;
 use rustc_middle::traits::CodegenObligationError;
 use rustc_middle::ty::subst::SubstsRef;
 use rustc_middle::ty::{
-    self, Binder, Instance, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor,
+    self, Binder, Instance, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 use rustc_span::{sym, DUMMY_SP};
 use rustc_trait_selection::traits;
@@ -56,7 +56,7 @@ impl<'tcx> BoundVarsCollector<'tcx> {
 impl<'tcx> TypeVisitor<'tcx> for BoundVarsCollector<'tcx> {
     type BreakTy = ();
 
-    fn visit_binder<T: TypeFoldable<'tcx>>(
+    fn visit_binder<T: TypeVisitable<'tcx>>(
         &mut self,
         t: &Binder<'tcx, T>,
     ) -> ControlFlow<Self::BreakTy> {
diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs
index 38ae6a25b18..1d345caf699 100644
--- a/compiler/rustc_ty_utils/src/ty.rs
+++ b/compiler/rustc_ty_utils/src/ty.rs
@@ -211,7 +211,7 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> {
         tcx.hir().maybe_body_owned_by(id).map_or(id, |body| body.hir_id)
     });
     let cause = traits::ObligationCause::misc(tcx.def_span(def_id), body_id);
-    traits::normalize_param_env_or_error(tcx, def_id, unnormalized_env, cause)
+    traits::normalize_param_env_or_error(tcx, unnormalized_env, cause)
 }
 
 /// Elaborate the environment.
diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs
index f9708d6d919..6744536338c 100644
--- a/compiler/rustc_type_ir/src/lib.rs
+++ b/compiler/rustc_type_ir/src/lib.rs
@@ -599,6 +599,7 @@ impl UnifyKey for FloatVid {
 }
 
 #[derive(Copy, Clone, PartialEq, Decodable, Encodable, Hash)]
+#[rustc_pass_by_value]
 pub enum Variance {
     Covariant,     // T<A> <: T<B> iff A <: B -- e.g., function return type
     Invariant,     // T<A> <: T<B> iff B == A -- e.g., type of mutable cell
diff --git a/compiler/rustc_typeck/src/astconv/mod.rs b/compiler/rustc_typeck/src/astconv/mod.rs
index 37958cc0f40..0a2b54eec47 100644
--- a/compiler/rustc_typeck/src/astconv/mod.rs
+++ b/compiler/rustc_typeck/src/astconv/mod.rs
@@ -28,7 +28,7 @@ use rustc_middle::middle::stability::AllowUnstable;
 use rustc_middle::ty::subst::{self, GenericArgKind, InternalSubsts, Subst, SubstsRef};
 use rustc_middle::ty::GenericParamDefKind;
 use rustc_middle::ty::{
-    self, Const, DefIdTree, EarlyBinder, IsSuggestable, Ty, TyCtxt, TypeFoldable,
+    self, Const, DefIdTree, EarlyBinder, IsSuggestable, Ty, TyCtxt, TypeVisitable,
 };
 use rustc_session::lint::builtin::{AMBIGUOUS_ASSOCIATED_ITEMS, BARE_TRAIT_OBJECTS};
 use rustc_span::edition::Edition;
@@ -1958,9 +1958,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                         );
                     }
 
-                    if let Some(sp) = tcx.hir().span_if_local(adt_def.did()) {
-                        let sp = tcx.sess.source_map().guess_head_span(sp);
-                        err.span_label(sp, format!("variant `{}` not found here", assoc_ident));
+                    if adt_def.did().is_local() {
+                        err.span_label(
+                            tcx.def_span(adt_def.did()),
+                            format!("variant `{assoc_ident}` not found for this enum"),
+                        );
                     }
 
                     err.emit()
@@ -2450,7 +2452,6 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
 
                     let msg = format!("`Self` is of type `{ty}`");
                     if let (Ok(i_sp), Some(t_sp)) = (span_of_impl, span_of_ty) {
-                        let i_sp = tcx.sess.source_map().guess_head_span(i_sp);
                         let mut span: MultiSpan = vec![t_sp].into();
                         span.push_span_label(
                             i_sp,
diff --git a/compiler/rustc_typeck/src/check/_match.rs b/compiler/rustc_typeck/src/check/_match.rs
index 035571c881c..deaadf0e5c8 100644
--- a/compiler/rustc_typeck/src/check/_match.rs
+++ b/compiler/rustc_typeck/src/check/_match.rs
@@ -4,7 +4,7 @@ use rustc_errors::{Applicability, Diagnostic, MultiSpan};
 use rustc_hir::{self as hir, ExprKind};
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::traits::Obligation;
-use rustc_middle::ty::{self, ToPredicate, Ty, TypeFoldable};
+use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable};
 use rustc_span::Span;
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
 use rustc_trait_selection::traits::{
diff --git a/compiler/rustc_typeck/src/check/callee.rs b/compiler/rustc_typeck/src/check/callee.rs
index 83a8c5ea021..2c8be88ef6c 100644
--- a/compiler/rustc_typeck/src/check/callee.rs
+++ b/compiler/rustc_typeck/src/check/callee.rs
@@ -18,7 +18,7 @@ use rustc_middle::ty::adjustment::{
     Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
 };
 use rustc_middle::ty::subst::{Subst, SubstsRef};
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
 use rustc_target::spec::abi;
@@ -280,15 +280,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         callee_node: &hir::ExprKind<'_>,
         callee_span: Span,
     ) {
-        let hir_id = self.tcx.hir().get_parent_node(hir_id);
-        let parent_node = self.tcx.hir().get(hir_id);
+        let hir = self.tcx.hir();
+        let parent_hir_id = hir.get_parent_node(hir_id);
+        let parent_node = hir.get(parent_hir_id);
         if let (
             hir::Node::Expr(hir::Expr {
-                kind: hir::ExprKind::Closure { fn_decl_span, .. }, ..
+                kind: hir::ExprKind::Closure { fn_decl_span, body, .. },
+                ..
             }),
             hir::ExprKind::Block(..),
         ) = (parent_node, callee_node)
         {
+            let fn_decl_span = if hir.body(*body).generator_kind
+                == Some(hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Closure))
+            {
+                // Actually need to unwrap a few more layers of HIR to get to
+                // the _real_ closure...
+                let async_closure = hir.get_parent_node(hir.get_parent_node(parent_hir_id));
+                if let hir::Node::Expr(hir::Expr {
+                    kind: hir::ExprKind::Closure { fn_decl_span, .. },
+                    ..
+                }) = hir.get(async_closure)
+                {
+                    *fn_decl_span
+                } else {
+                    return;
+                }
+            } else {
+                *fn_decl_span
+            };
+
             let start = fn_decl_span.shrink_to_lo();
             let end = callee_span.shrink_to_hi();
             err.multipart_suggestion(
diff --git a/compiler/rustc_typeck/src/check/cast.rs b/compiler/rustc_typeck/src/check/cast.rs
index 7d5d6849b3b..66dd5582490 100644
--- a/compiler/rustc_typeck/src/check/cast.rs
+++ b/compiler/rustc_typeck/src/check/cast.rs
@@ -40,7 +40,7 @@ use rustc_middle::ty::adjustment::AllowTwoPhase;
 use rustc_middle::ty::cast::{CastKind, CastTy};
 use rustc_middle::ty::error::TypeError;
 use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::{self, Ty, TypeAndMut, TypeFoldable};
+use rustc_middle::ty::{self, Ty, TypeAndMut, TypeVisitable};
 use rustc_session::lint;
 use rustc_session::Session;
 use rustc_span::symbol::sym;
diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs
index 6222dbb4074..e9709b64d93 100644
--- a/compiler/rustc_typeck/src/check/check.rs
+++ b/compiler/rustc_typeck/src/check/check.rs
@@ -13,6 +13,7 @@ use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::intravisit::Visitor;
 use rustc_hir::lang_items::LangItem;
 use rustc_hir::{ItemKind, Node, PathSegment};
+use rustc_infer::infer::outlives::env::OutlivesEnvironment;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
 use rustc_infer::traits::Obligation;
@@ -20,7 +21,9 @@ use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES};
 use rustc_middle::ty::subst::GenericArgKind;
 use rustc_middle::ty::util::{Discr, IntTypeExt};
-use rustc_middle::ty::{self, ParamEnv, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable};
+use rustc_middle::ty::{
+    self, ParamEnv, ToPredicate, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
+};
 use rustc_session::lint::builtin::{UNINHABITED_STATIC, UNSUPPORTED_CALLING_CONVENTIONS};
 use rustc_span::symbol::sym;
 use rustc_span::{self, Span};
@@ -285,11 +288,9 @@ fn check_panic_info_fn(
         tcx.sess.span_err(decl.output.span(), "return type should be `!`");
     }
 
-    let span = tcx.def_span(fn_id);
     let inputs = fn_sig.inputs();
     if inputs.len() != 1 {
-        let span = tcx.sess.source_map().guess_head_span(span);
-        tcx.sess.span_err(span, "function should have one argument");
+        tcx.sess.span_err(tcx.def_span(fn_id), "function should have one argument");
         return;
     }
 
@@ -342,9 +343,7 @@ fn check_alloc_error_fn(
 
     let inputs = fn_sig.inputs();
     if inputs.len() != 1 {
-        let span = tcx.def_span(fn_id);
-        let span = tcx.sess.source_map().guess_head_span(span);
-        tcx.sess.span_err(span, "function should have one argument");
+        tcx.sess.span_err(tcx.def_span(fn_id), "function should have one argument");
         return;
     }
 
@@ -521,7 +520,7 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
 
     struct FoundParentLifetime;
     struct FindParentLifetimeVisitor<'tcx>(&'tcx ty::Generics);
-    impl<'tcx> ty::fold::TypeVisitor<'tcx> for FindParentLifetimeVisitor<'tcx> {
+    impl<'tcx> ty::visit::TypeVisitor<'tcx> for FindParentLifetimeVisitor<'tcx> {
         type BreakTy = FoundParentLifetime;
 
         fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
@@ -555,7 +554,7 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
         selftys: Vec<(Span, Option<String>)>,
     }
 
-    impl<'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueVisitor<'tcx> {
+    impl<'tcx> ty::visit::TypeVisitor<'tcx> for ProhibitOpaqueVisitor<'tcx> {
         type BreakTy = Ty<'tcx>;
 
         fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
@@ -736,10 +735,8 @@ fn check_opaque_meets_bounds<'tcx>(
             hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) => {}
             // Can have different predicates to their defining use
             hir::OpaqueTyOrigin::TyAlias => {
-                // Finally, resolve all regions. This catches wily misuses of
-                // lifetime parameters.
-                let fcx = FnCtxt::new(&inh, param_env, hir_id);
-                fcx.regionck_item(hir_id, span, FxHashSet::default());
+                let outlives_environment = OutlivesEnvironment::new(param_env);
+                infcx.check_region_obligations_and_report_errors(&outlives_environment);
             }
         }
 
@@ -1033,7 +1030,6 @@ fn check_impl_items_against_trait<'tcx>(
                 compare_impl_method(
                     tcx,
                     &ty_impl_item,
-                    impl_item.span,
                     &ty_trait_item,
                     impl_trait_ref,
                     opt_trait_span,
@@ -1093,17 +1089,20 @@ fn check_impl_items_against_trait<'tcx>(
         }
 
         if !missing_items.is_empty() {
-            let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span);
-            missing_items_err(tcx, impl_span, &missing_items, full_impl_span);
+            missing_items_err(tcx, tcx.def_span(impl_id), &missing_items, full_impl_span);
         }
 
         if let Some(missing_items) = must_implement_one_of {
-            let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span);
             let attr_span = tcx
                 .get_attr(impl_trait_ref.def_id, sym::rustc_must_implement_one_of)
                 .map(|attr| attr.span);
 
-            missing_items_must_implement_one_of_err(tcx, impl_span, missing_items, attr_span);
+            missing_items_must_implement_one_of_err(
+                tcx,
+                tcx.def_span(impl_id),
+                missing_items,
+                attr_span,
+            );
         }
     }
 }
@@ -1596,7 +1595,7 @@ fn opaque_type_cycle_error(tcx: TyCtxt<'_>, def_id: LocalDefId, span: Span) -> E
                 .filter(|(_, ty)| !matches!(ty.kind(), ty::Never))
             {
                 struct OpaqueTypeCollector(Vec<DefId>);
-                impl<'tcx> ty::fold::TypeVisitor<'tcx> for OpaqueTypeCollector {
+                impl<'tcx> ty::visit::TypeVisitor<'tcx> for OpaqueTypeCollector {
                     fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
                         match *t.kind() {
                             ty::Opaque(def, _) => {
diff --git a/compiler/rustc_typeck/src/check/closure.rs b/compiler/rustc_typeck/src/check/closure.rs
index 131e594ed94..1681e6af812 100644
--- a/compiler/rustc_typeck/src/check/closure.rs
+++ b/compiler/rustc_typeck/src/check/closure.rs
@@ -10,8 +10,8 @@ use rustc_hir::lang_items::LangItem;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::infer::LateBoundRegionConversionTime;
 use rustc_infer::infer::{InferOk, InferResult};
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::InternalSubsts;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, Ty};
 use rustc_span::source_map::Span;
 use rustc_target::spec::abi::Abi;
diff --git a/compiler/rustc_typeck/src/check/coercion.rs b/compiler/rustc_typeck/src/check/coercion.rs
index 43fc49c6801..acd7e4a92dc 100644
--- a/compiler/rustc_typeck/src/check/coercion.rs
+++ b/compiler/rustc_typeck/src/check/coercion.rs
@@ -50,9 +50,9 @@ use rustc_middle::ty::adjustment::{
     Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast,
 };
 use rustc_middle::ty::error::TypeError;
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::relate::RelateResult;
 use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, ToPredicate, Ty, TypeAndMut};
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::sym;
diff --git a/compiler/rustc_typeck/src/check/compare_method.rs b/compiler/rustc_typeck/src/check/compare_method.rs
index 95c82a7d2c3..2bfb9343877 100644
--- a/compiler/rustc_typeck/src/check/compare_method.rs
+++ b/compiler/rustc_typeck/src/check/compare_method.rs
@@ -1,3 +1,4 @@
+use crate::check::regionck::OutlivesEnvironmentExt;
 use crate::errors::LifetimesOrBoundsMismatchOnTrait;
 use rustc_data_structures::stable_set::FxHashSet;
 use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticId, ErrorGuaranteed};
@@ -5,6 +6,7 @@ use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::intravisit;
 use rustc_hir::{GenericParamKind, ImplItemKind, TraitItemKind};
+use rustc_infer::infer::outlives::env::OutlivesEnvironment;
 use rustc_infer::infer::{self, InferOk, TyCtxtInferExt};
 use rustc_infer::traits::util;
 use rustc_middle::ty::error::{ExpectedFound, TypeError};
@@ -31,14 +33,13 @@ use super::{potentially_plural_count, FnCtxt, Inherited};
 pub(crate) fn compare_impl_method<'tcx>(
     tcx: TyCtxt<'tcx>,
     impl_m: &ty::AssocItem,
-    impl_m_span: Span,
     trait_m: &ty::AssocItem,
     impl_trait_ref: ty::TraitRef<'tcx>,
     trait_item_span: Option<Span>,
 ) {
     debug!("compare_impl_method(impl_trait_ref={:?})", impl_trait_ref);
 
-    let impl_m_span = tcx.sess.source_map().guess_head_span(impl_m_span);
+    let impl_m_span = tcx.def_span(impl_m.def_id);
 
     if let Err(_) = compare_self_type(tcx, impl_m, impl_m_span, trait_m, impl_trait_ref) {
         return;
@@ -78,10 +79,11 @@ fn compare_predicate_entailment<'tcx>(
     let trait_to_impl_substs = impl_trait_ref.substs;
 
     // This node-id should be used for the `body_id` field on each
-    // `ObligationCause` (and the `FnCtxt`). This is what
-    // `regionck_item` expects.
+    // `ObligationCause` (and the `FnCtxt`).
+    //
+    // FIXME(@lcnr): remove that after removing `cause.body_id` from
+    // obligations.
     let impl_m_hir_id = tcx.hir().local_def_id_to_hir_id(impl_m.def_id.expect_local());
-
     // We sometimes modify the span further down.
     let mut cause = ObligationCause::new(
         impl_m_span,
@@ -208,8 +210,7 @@ fn compare_predicate_entailment<'tcx>(
         Reveal::UserFacing,
         hir::Constness::NotConst,
     );
-    let param_env =
-        traits::normalize_param_env_or_error(tcx, impl_m.def_id, param_env, normalize_cause);
+    let param_env = traits::normalize_param_env_or_error(tcx, param_env, normalize_cause);
 
     tcx.infer_ctxt().enter(|infcx| {
         let inh = Inherited::new(infcx, impl_m.def_id.expect_local());
@@ -399,8 +400,9 @@ fn compare_predicate_entailment<'tcx>(
 
         // Finally, resolve all regions. This catches wily misuses of
         // lifetime parameters.
-        let fcx = FnCtxt::new(&inh, param_env, impl_m_hir_id);
-        fcx.regionck_item(impl_m_hir_id, impl_m_span, wf_tys);
+        let mut outlives_environment = OutlivesEnvironment::new(param_env);
+        outlives_environment.add_implied_bounds(infcx, wf_tys, impl_m_hir_id);
+        infcx.check_region_obligations_and_report_errors(&outlives_environment);
 
         Ok(())
     })
@@ -441,13 +443,9 @@ fn check_region_bounds_on_impl_item<'tcx>(
             .as_local()
             .and_then(|did| tcx.hir().get_generics(did))
             .map_or(def_span, |g| g.span);
-        let generics_span = tcx.hir().span_if_local(trait_m.def_id).map(|sp| {
-            let def_sp = tcx.sess.source_map().guess_head_span(sp);
-            trait_m
-                .def_id
-                .as_local()
-                .and_then(|did| tcx.hir().get_generics(did))
-                .map_or(def_sp, |g| g.span)
+        let generics_span = trait_m.def_id.as_local().map(|did| {
+            let def_sp = tcx.def_span(did);
+            tcx.hir().get_generics(did).map_or(def_sp, |g| g.span)
         });
 
         let reported = tcx.sess.emit_err(LifetimesOrBoundsMismatchOnTrait {
@@ -1041,8 +1039,7 @@ fn compare_generic_param_kinds<'tcx>(
             err.span_label(trait_header_span, "");
             err.span_label(param_trait_span, make_param_message("expected", param_trait));
 
-            let impl_header_span =
-                tcx.sess.source_map().guess_head_span(tcx.def_span(tcx.parent(impl_item.def_id)));
+            let impl_header_span = tcx.def_span(tcx.parent(impl_item.def_id));
             err.span_label(impl_header_span, "");
             err.span_label(param_impl_span, make_param_message("found", param_impl));
 
@@ -1155,8 +1152,8 @@ pub(crate) fn compare_const_impl<'tcx>(
             return;
         }
 
-        let fcx = FnCtxt::new(&inh, param_env, impl_c_hir_id);
-        fcx.regionck_item(impl_c_hir_id, impl_c_span, FxHashSet::default());
+        let outlives_environment = OutlivesEnvironment::new(param_env);
+        infcx.resolve_regions_and_report_errors(&outlives_environment);
     });
 }
 
@@ -1247,12 +1244,7 @@ fn compare_type_predicate_entailment<'tcx>(
         Reveal::UserFacing,
         hir::Constness::NotConst,
     );
-    let param_env = traits::normalize_param_env_or_error(
-        tcx,
-        impl_ty.def_id,
-        param_env,
-        normalize_cause.clone(),
-    );
+    let param_env = traits::normalize_param_env_or_error(tcx, param_env, normalize_cause.clone());
     tcx.infer_ctxt().enter(|infcx| {
         let inh = Inherited::new(infcx, impl_ty.def_id.expect_local());
         let infcx = &inh.infcx;
@@ -1279,8 +1271,8 @@ fn compare_type_predicate_entailment<'tcx>(
 
         // Finally, resolve all regions. This catches wily misuses of
         // lifetime parameters.
-        let fcx = FnCtxt::new(&inh, param_env, impl_ty_hir_id);
-        fcx.regionck_item(impl_ty_hir_id, impl_ty_span, FxHashSet::default());
+        let outlives_environment = OutlivesEnvironment::new(param_env);
+        infcx.check_region_obligations_and_report_errors(&outlives_environment);
 
         Ok(())
     })
@@ -1504,12 +1496,16 @@ pub fn check_type_bounds<'tcx>(
 
         // Finally, resolve all regions. This catches wily misuses of
         // lifetime parameters.
+        //
+        // FIXME: Remove that `FnCtxt`.
         let fcx = FnCtxt::new(&inh, param_env, impl_ty_hir_id);
         let implied_bounds = match impl_ty.container {
             ty::TraitContainer(_) => FxHashSet::default(),
             ty::ImplContainer(def_id) => fcx.impl_implied_bounds(def_id, impl_ty_span),
         };
-        fcx.regionck_item(impl_ty_hir_id, impl_ty_span, implied_bounds);
+        let mut outlives_environment = OutlivesEnvironment::new(param_env);
+        outlives_environment.add_implied_bounds(infcx, implied_bounds, impl_ty_hir_id);
+        infcx.check_region_obligations_and_report_errors(&outlives_environment);
 
         Ok(())
     })
diff --git a/compiler/rustc_typeck/src/check/dropck.rs b/compiler/rustc_typeck/src/check/dropck.rs
index ed3b9f2db1f..72095c40807 100644
--- a/compiler/rustc_typeck/src/check/dropck.rs
+++ b/compiler/rustc_typeck/src/check/dropck.rs
@@ -1,5 +1,6 @@
-use crate::check::regionck::RegionCtxt;
-use crate::hir;
+// FIXME(@lcnr): Move this module out of `rustc_typeck`.
+//
+// We don't do any drop checking during hir typeck.
 use crate::hir::def_id::{DefId, LocalDefId};
 use rustc_errors::{struct_span_err, ErrorGuaranteed};
 use rustc_middle::ty::error::TypeError;
@@ -7,9 +8,6 @@ use rustc_middle::ty::relate::{Relate, RelateResult, TypeRelation};
 use rustc_middle::ty::subst::SubstsRef;
 use rustc_middle::ty::util::IgnoreRegions;
 use rustc_middle::ty::{self, Predicate, Ty, TyCtxt};
-use rustc_span::Span;
-use rustc_trait_selection::traits::query::dropck_outlives::AtExt;
-use rustc_trait_selection::traits::ObligationCause;
 
 /// This function confirms that the `Drop` implementation identified by
 /// `drop_impl_did` is not any more specialized than the type it is
@@ -231,23 +229,6 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>(
     result
 }
 
-/// This function is not only checking that the dropck obligations are met for
-/// the given type, but it's also currently preventing non-regular recursion in
-/// types from causing stack overflows (dropck_no_diverge_on_nonregular_*.rs).
-pub(crate) fn check_drop_obligations<'a, 'tcx>(
-    rcx: &mut RegionCtxt<'a, 'tcx>,
-    ty: Ty<'tcx>,
-    span: Span,
-    body_id: hir::HirId,
-) {
-    debug!("check_drop_obligations typ: {:?}", ty);
-
-    let cause = &ObligationCause::misc(span, body_id);
-    let infer_ok = rcx.infcx.at(cause, rcx.fcx.param_env).dropck_outlives(ty);
-    debug!("dropck_outlives = {:#?}", infer_ok);
-    rcx.fcx.register_infer_ok_obligations(infer_ok);
-}
-
 // This is an implementation of the TypeRelation trait with the
 // aim of simply comparing for equality (without side-effects).
 // It is not intended to be used anywhere else other than here.
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index b4476d5c59b..45ea04f2342 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -44,7 +44,7 @@ use rustc_middle::middle::stability;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
 use rustc_middle::ty::error::TypeError::FieldMisMatch;
 use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TypeFoldable};
+use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TypeVisitable};
 use rustc_session::parse::feature_err;
 use rustc_span::hygiene::DesugaringKind;
 use rustc_span::lev_distance::find_best_match_for_name;
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
index bce2e85de84..f3341e72e73 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
@@ -23,6 +23,7 @@ use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::{
     self, GenericArgKind, InternalSubsts, Subst, SubstsRef, UserSelfTy, UserSubsts,
 };
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{
     self, AdtKind, CanonicalUserType, DefIdTree, EarlyBinder, GenericParamDefKind, ToPolyTraitRef,
     ToPredicate, Ty, UserType,
@@ -557,7 +558,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     // sufficiently enforced with erased regions. =)
     fn can_contain_user_lifetime_bounds<T>(t: T) -> bool
     where
-        T: TypeFoldable<'tcx>,
+        T: TypeVisitable<'tcx>,
     {
         t.has_free_regions() || t.has_projections() || t.has_infer_types()
     }
@@ -1538,15 +1539,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             ty
         } else {
             if !self.is_tainted_by_errors() {
-                self.emit_inference_failure_err(
-                    (**self).body_id,
-                    sp,
-                    ty.into(),
-                    vec![],
-                    E0282,
-                    true,
-                )
-                .emit();
+                self.emit_inference_failure_err((**self).body_id, sp, ty.into(), E0282, true)
+                    .emit();
             }
             let err = self.tcx.ty_error();
             self.demand_suptype(sp, err, ty);
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
index d2e16021827..1794446e92a 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
@@ -24,7 +24,7 @@ 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::fold::TypeFoldable;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt};
 use rustc_session::Session;
 use rustc_span::symbol::Ident;
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/mod.rs b/compiler/rustc_typeck/src/check/fn_ctxt/mod.rs
index 8b680a3d042..05bcc710e16 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/mod.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/mod.rs
@@ -15,8 +15,8 @@ use rustc_hir::def_id::DefId;
 use rustc_infer::infer;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
-use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, Const, Ty, TyCtxt};
 use rustc_session::Session;
 use rustc_span::symbol::Ident;
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
index 7195da863db..edb0ec027a0 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
@@ -184,9 +184,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         } else if let (ty::FnDef(def_id, ..), true) =
             (&found.kind(), self.suggest_fn_call(err, expr, expected, found))
         {
-            if let Some(sp) = self.tcx.hir().span_if_local(*def_id) {
-                let sp = self.sess().source_map().guess_head_span(sp);
-                err.span_label(sp, &format!("{} defined here", found));
+            if def_id.is_local() {
+                err.span_label(self.tcx.def_span(def_id), &format!("{} defined here", found));
             }
         } else if !self.check_for_cast(err, expr, found, expected, expected_ty_expr) {
             let is_struct_pat_shorthand_field =
diff --git a/compiler/rustc_typeck/src/check/inherited.rs b/compiler/rustc_typeck/src/check/inherited.rs
index 08a64d8e673..2ce258cf69c 100644
--- a/compiler/rustc_typeck/src/check/inherited.rs
+++ b/compiler/rustc_typeck/src/check/inherited.rs
@@ -8,6 +8,7 @@ use rustc_hir::HirIdMap;
 use rustc_infer::infer;
 use rustc_infer::infer::{InferCtxt, InferOk, TyCtxtInferExt};
 use rustc_middle::ty::fold::TypeFoldable;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_span::{self, Span};
 use rustc_trait_selection::infer::InferCtxtExt as _;
diff --git a/compiler/rustc_typeck/src/check/intrinsicck.rs b/compiler/rustc_typeck/src/check/intrinsicck.rs
index 469e6118575..cc91f2431e0 100644
--- a/compiler/rustc_typeck/src/check/intrinsicck.rs
+++ b/compiler/rustc_typeck/src/check/intrinsicck.rs
@@ -4,7 +4,7 @@ use rustc_errors::struct_span_err;
 use rustc_hir as hir;
 use rustc_index::vec::Idx;
 use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
-use rustc_middle::ty::{self, Article, FloatTy, InferTy, IntTy, Ty, TyCtxt, TypeFoldable, UintTy};
+use rustc_middle::ty::{self, Article, FloatTy, InferTy, IntTy, Ty, TyCtxt, TypeVisitable, UintTy};
 use rustc_session::lint;
 use rustc_span::{Span, Symbol, DUMMY_SP};
 use rustc_target::abi::{Pointer, VariantIdx};
diff --git a/compiler/rustc_typeck/src/check/method/mod.rs b/compiler/rustc_typeck/src/check/method/mod.rs
index 5ca82218355..e29f0275bf4 100644
--- a/compiler/rustc_typeck/src/check/method/mod.rs
+++ b/compiler/rustc_typeck/src/check/method/mod.rs
@@ -21,7 +21,7 @@ use rustc_infer::infer::{self, InferOk};
 use rustc_middle::ty::subst::Subst;
 use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
 use rustc_middle::ty::GenericParamDefKind;
-use rustc_middle::ty::{self, ToPredicate, Ty, TypeFoldable};
+use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable};
 use rustc_span::symbol::Ident;
 use rustc_span::Span;
 use rustc_trait_selection::traits;
diff --git a/compiler/rustc_typeck/src/check/method/probe.rs b/compiler/rustc_typeck/src/check/method/probe.rs
index 87254b211d6..e9b91414a07 100644
--- a/compiler/rustc_typeck/src/check/method/probe.rs
+++ b/compiler/rustc_typeck/src/check/method/probe.rs
@@ -21,7 +21,9 @@ use rustc_middle::middle::stability;
 use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
 use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef};
 use rustc_middle::ty::GenericParamDefKind;
-use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, ToPredicate, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{
+    self, EarlyBinder, ParamEnvAnd, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeVisitable,
+};
 use rustc_session::lint;
 use rustc_span::def_id::LocalDefId;
 use rustc_span::lev_distance::{
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index fa5f0eff223..4e1a105fc71 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -17,7 +17,7 @@ use rustc_middle::traits::util::supertraits;
 use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
 use rustc_middle::ty::print::with_crate_prefix;
 use rustc_middle::ty::ToPolyTraitRef;
-use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeVisitable};
 use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::{lev_distance, source_map, ExpnKind, FileName, MacroKind, Span};
 use rustc_trait_selection::traits::error_reporting::on_unimplemented::InferCtxtExt as _;
@@ -121,11 +121,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         }) else {
                             continue;
                         };
-                        let note_span = self
-                            .tcx
-                            .hir()
-                            .span_if_local(item.def_id)
-                            .or_else(|| self.tcx.hir().span_if_local(impl_did));
+
+                        let note_span = if item.def_id.is_local() {
+                            Some(self.tcx.def_span(item.def_id))
+                        } else if impl_did.is_local() {
+                            Some(self.tcx.def_span(impl_did))
+                        } else {
+                            None
+                        };
 
                         let impl_ty = self.tcx.at(span).type_of(impl_did);
 
@@ -158,10 +161,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         };
                         if let Some(note_span) = note_span {
                             // We have a span pointing to the method. Show note with snippet.
-                            err.span_note(
-                                self.tcx.sess.source_map().guess_head_span(note_span),
-                                &note_str,
-                            );
+                            err.span_note(note_span, &note_str);
                         } else {
                             err.note(&note_str);
                         }
@@ -197,11 +197,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     }
                     CandidateSource::Trait(trait_did) => {
                         let Some(item) = self.associated_value(trait_did, item_name) else { continue };
-                        let item_span = self
-                            .tcx
-                            .sess
-                            .source_map()
-                            .guess_head_span(self.tcx.def_span(item.def_id));
+                        let item_span = self.tcx.def_span(item.def_id);
                         let idx = if sources.len() > 1 {
                             let msg = &format!(
                                 "candidate #{} is defined in the trait `{}`",
@@ -471,9 +467,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         err.note(&format!("`count` is defined on `{iterator_trait}`, which `{actual}` does not implement"));
                     }
                 } else if !unsatisfied_predicates.is_empty() {
-                    let def_span = |def_id| {
-                        self.tcx.sess.source_map().guess_head_span(self.tcx.def_span(def_id))
-                    };
                     let mut type_params = FxHashMap::default();
 
                     // Pick out the list of unimplemented traits on the receiver.
@@ -564,22 +557,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         );
                         match &self_ty.kind() {
                             // Point at the type that couldn't satisfy the bound.
-                            ty::Adt(def, _) => bound_spans.push((def_span(def.did()), msg)),
+                            ty::Adt(def, _) => {
+                                bound_spans.push((self.tcx.def_span(def.did()), msg))
+                            }
                             // Point at the trait object that couldn't satisfy the bound.
                             ty::Dynamic(preds, _) => {
                                 for pred in preds.iter() {
                                     match pred.skip_binder() {
-                                        ty::ExistentialPredicate::Trait(tr) => {
-                                            bound_spans.push((def_span(tr.def_id), msg.clone()))
-                                        }
+                                        ty::ExistentialPredicate::Trait(tr) => bound_spans
+                                            .push((self.tcx.def_span(tr.def_id), msg.clone())),
                                         ty::ExistentialPredicate::Projection(_)
                                         | ty::ExistentialPredicate::AutoTrait(_) => {}
                                     }
                                 }
                             }
                             // Point at the closure that couldn't satisfy the bound.
-                            ty::Closure(def_id, _) => bound_spans
-                                .push((def_span(*def_id), format!("doesn't satisfy `{}`", quiet))),
+                            ty::Closure(def_id, _) => bound_spans.push((
+                                tcx.def_span(*def_id),
+                                format!("doesn't satisfy `{}`", quiet),
+                            )),
                             _ => {}
                         }
                     };
@@ -1469,21 +1465,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 _ => None,
             })
             .collect::<FxHashSet<_>>();
-        let sm = self.tcx.sess.source_map();
         let mut spans: MultiSpan = def_ids
             .iter()
             .filter_map(|def_id| {
                 let span = self.tcx.def_span(*def_id);
-                if span.is_dummy() { None } else { Some(sm.guess_head_span(span)) }
+                if span.is_dummy() { None } else { Some(span) }
             })
             .collect::<Vec<_>>()
             .into();
 
         for pred in &preds {
             match pred.self_ty().kind() {
-                ty::Adt(def, _) => {
+                ty::Adt(def, _) if def.did().is_local() => {
                     spans.push_span_label(
-                        sm.guess_head_span(self.tcx.def_span(def.did())),
+                        self.tcx.def_span(def.did()),
                         format!("must implement `{}`", pred.trait_ref.print_only_trait_path()),
                     );
                 }
@@ -2090,9 +2085,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             match &potential_candidates[..] {
                 [] => {}
                 [trait_info] if trait_info.def_id.is_local() => {
-                    let span = self.tcx.hir().span_if_local(trait_info.def_id).unwrap();
                     err.span_note(
-                        self.tcx.sess.source_map().guess_head_span(span),
+                        self.tcx.def_span(trait_info.def_id),
                         &format!(
                             "`{}` defines an item `{}`, perhaps you need to {} it",
                             self.tcx.def_path_str(trait_info.def_id),
diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs
index 0ede9ef7756..dee58791cec 100644
--- a/compiler/rustc_typeck/src/check/mod.rs
+++ b/compiler/rustc_typeck/src/check/mod.rs
@@ -368,7 +368,7 @@ fn typeck_with_fallback<'tcx>(
 
     let typeck_results = Inherited::build(tcx, def_id).enter(|inh| {
         let param_env = tcx.param_env(def_id);
-        let (fcx, wf_tys) = if let Some(hir::FnSig { header, decl, .. }) = fn_sig {
+        let fcx = if let Some(hir::FnSig { header, decl, .. }) = fn_sig {
             let fn_sig = if crate::collect::get_infer_ret_ty(&decl.output).is_some() {
                 let fcx = FnCtxt::new(&inh, param_env, body.value.hir_id);
                 <dyn AstConv<'_>>::ty_of_fn(&fcx, id, header.unsafety, header.abi, decl, None, None)
@@ -378,13 +378,7 @@ fn typeck_with_fallback<'tcx>(
 
             check_abi(tcx, id, span, fn_sig.abi());
 
-            // When normalizing the function signature, we assume all types are
-            // well-formed. So, we don't need to worry about the obligations
-            // from normalization. We could just discard these, but to align with
-            // compare_method and elsewhere, we just add implied bounds for
-            // these types.
-            let mut wf_tys = FxHashSet::default();
-            // Compute the fty from point of view of inside the fn.
+            // Compute the function signature from point of view of inside the fn.
             let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig);
             let fn_sig = inh.normalize_associated_types_in(
                 body.value.span,
@@ -392,10 +386,7 @@ fn typeck_with_fallback<'tcx>(
                 param_env,
                 fn_sig,
             );
-            wf_tys.extend(fn_sig.inputs_and_output.iter());
-
-            let fcx = check_fn(&inh, param_env, fn_sig, decl, id, body, None, true).0;
-            (fcx, wf_tys)
+            check_fn(&inh, param_env, fn_sig, decl, id, body, None, true).0
         } else {
             let fcx = FnCtxt::new(&inh, param_env, body.value.hir_id);
             let expected_type = body_ty
@@ -458,7 +449,7 @@ fn typeck_with_fallback<'tcx>(
 
             fcx.write_ty(id, expected_type);
 
-            (fcx, FxHashSet::default())
+            fcx
         };
 
         let fallback_has_occurred = fcx.type_inference_fallback();
@@ -490,11 +481,7 @@ fn typeck_with_fallback<'tcx>(
 
         fcx.check_asms();
 
-        if fn_sig.is_some() {
-            fcx.regionck_fn(id, body, span, wf_tys);
-        } else {
-            fcx.regionck_expr(body);
-        }
+        fcx.infcx.skip_region_resolution();
 
         fcx.resolve_type_vars_in_body(body)
     });
diff --git a/compiler/rustc_typeck/src/check/op.rs b/compiler/rustc_typeck/src/check/op.rs
index c2f97a5051c..42893789957 100644
--- a/compiler/rustc_typeck/src/check/op.rs
+++ b/compiler/rustc_typeck/src/check/op.rs
@@ -10,7 +10,7 @@ use rustc_middle::ty::adjustment::{
     Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
 };
 use rustc_middle::ty::{
-    self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitor,
+    self, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{sym, Ident};
diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_typeck/src/check/pat.rs
index e1ec9f13cd1..fbfbfba5c2a 100644
--- a/compiler/rustc_typeck/src/check/pat.rs
+++ b/compiler/rustc_typeck/src/check/pat.rs
@@ -13,7 +13,7 @@ use rustc_hir::{HirId, Pat, PatKind};
 use rustc_infer::infer;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_middle::middle::stability::EvalResult;
-use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeFoldable};
+use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeVisitable};
 use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
 use rustc_span::hygiene::DesugaringKind;
 use rustc_span::lev_distance::find_best_match_for_name;
diff --git a/compiler/rustc_typeck/src/check/regionck.rs b/compiler/rustc_typeck/src/check/regionck.rs
index 0ce63922098..1c3c5f999bc 100644
--- a/compiler/rustc_typeck/src/check/regionck.rs
+++ b/compiler/rustc_typeck/src/check/regionck.rs
@@ -1,106 +1,9 @@
-//! The region check is a final pass that runs over the AST after we have
-//! inferred the type constraints but before we have actually finalized
-//! the types. Its purpose is to embed a variety of region constraints.
-//! Inserting these constraints as a separate pass is good because (1) it
-//! localizes the code that has to do with region inference and (2) often
-//! we cannot know what constraints are needed until the basic types have
-//! been inferred.
-//!
-//! ### Interaction with the borrow checker
-//!
-//! In general, the job of the borrowck module (which runs later) is to
-//! check that all soundness criteria are met, given a particular set of
-//! regions. The job of *this* module is to anticipate the needs of the
-//! borrow checker and infer regions that will satisfy its requirements.
-//! It is generally true that the inference doesn't need to be sound,
-//! meaning that if there is a bug and we inferred bad regions, the borrow
-//! checker should catch it. This is not entirely true though; for
-//! example, the borrow checker doesn't check subtyping, and it doesn't
-//! check that region pointers are always live when they are used. It
-//! might be worthwhile to fix this so that borrowck serves as a kind of
-//! verification step -- that would add confidence in the overall
-//! correctness of the compiler, at the cost of duplicating some type
-//! checks and effort.
-//!
-//! ### Inferring the duration of borrows, automatic and otherwise
-//!
-//! Whenever we introduce a borrowed pointer, for example as the result of
-//! a borrow expression `let x = &data`, the lifetime of the pointer `x`
-//! is always specified as a region inference variable. `regionck` has the
-//! job of adding constraints such that this inference variable is as
-//! narrow as possible while still accommodating all uses (that is, every
-//! dereference of the resulting pointer must be within the lifetime).
-//!
-//! #### Reborrows
-//!
-//! Generally speaking, `regionck` does NOT try to ensure that the data
-//! `data` will outlive the pointer `x`. That is the job of borrowck. The
-//! one exception is when "re-borrowing" the contents of another borrowed
-//! pointer. For example, imagine you have a borrowed pointer `b` with
-//! lifetime `L1` and you have an expression `&*b`. The result of this
-//! expression will be another borrowed pointer with lifetime `L2` (which is
-//! an inference variable). The borrow checker is going to enforce the
-//! constraint that `L2 < L1`, because otherwise you are re-borrowing data
-//! for a lifetime larger than the original loan. However, without the
-//! routines in this module, the region inferencer would not know of this
-//! dependency and thus it might infer the lifetime of `L2` to be greater
-//! than `L1` (issue #3148).
-//!
-//! There are a number of troublesome scenarios in the tests
-//! `region-dependent-*.rs`, but here is one example:
-//!
-//!     struct Foo { i: i32 }
-//!     struct Bar { foo: Foo  }
-//!     fn get_i<'a>(x: &'a Bar) -> &'a i32 {
-//!        let foo = &x.foo; // Lifetime L1
-//!        &foo.i            // Lifetime L2
-//!     }
-//!
-//! Note that this comes up either with `&` expressions, `ref`
-//! bindings, and `autorefs`, which are the three ways to introduce
-//! a borrow.
-//!
-//! The key point here is that when you are borrowing a value that
-//! is "guaranteed" by a borrowed pointer, you must link the
-//! lifetime of that borrowed pointer (`L1`, here) to the lifetime of
-//! the borrow itself (`L2`). What do I mean by "guaranteed" by a
-//! borrowed pointer? I mean any data that is reached by first
-//! dereferencing a borrowed pointer and then either traversing
-//! interior offsets or boxes. We say that the guarantor
-//! of such data is the region of the borrowed pointer that was
-//! traversed. This is essentially the same as the ownership
-//! relation, except that a borrowed pointer never owns its
-//! contents.
-
-use crate::check::dropck;
-use crate::check::FnCtxt;
-use crate::mem_categorization as mc;
 use crate::outlives::outlives_bounds::InferCtxtExt as _;
 use rustc_data_structures::stable_set::FxHashSet;
 use rustc_hir as hir;
-use rustc_hir::def_id::LocalDefId;
-use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::PatKind;
 use rustc_infer::infer::outlives::env::OutlivesEnvironment;
-use rustc_infer::infer::{self, InferCtxt, RegionObligation};
-use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId};
-use rustc_middle::ty::adjustment;
-use rustc_middle::ty::{self, Ty};
-use rustc_span::Span;
-use std::ops::Deref;
-
-// a variation on try that just returns unit
-macro_rules! ignore_err {
-    ($e:expr) => {
-        match $e {
-            Ok(e) => e,
-            Err(_) => {
-                debug!("ignoring mem-categorization error!");
-                return ();
-            }
-        }
-    };
-}
+use rustc_infer::infer::InferCtxt;
+use rustc_middle::ty::Ty;
 
 pub(crate) trait OutlivesEnvironmentExt<'tcx> {
     fn add_implied_bounds(
@@ -108,7 +11,6 @@ pub(crate) trait OutlivesEnvironmentExt<'tcx> {
         infcx: &InferCtxt<'_, 'tcx>,
         fn_sig_tys: FxHashSet<Ty<'tcx>>,
         body_id: hir::HirId,
-        span: Span,
     );
 }
 
@@ -135,750 +37,11 @@ impl<'tcx> OutlivesEnvironmentExt<'tcx> for OutlivesEnvironment<'tcx> {
         infcx: &InferCtxt<'a, 'tcx>,
         fn_sig_tys: FxHashSet<Ty<'tcx>>,
         body_id: hir::HirId,
-        span: Span,
     ) {
         for ty in fn_sig_tys {
             let ty = infcx.resolve_vars_if_possible(ty);
-            let implied_bounds = infcx.implied_outlives_bounds(self.param_env, body_id, ty, span);
+            let implied_bounds = infcx.implied_outlives_bounds(self.param_env, body_id, ty);
             self.add_outlives_bounds(Some(infcx), implied_bounds)
         }
     }
 }
-
-///////////////////////////////////////////////////////////////////////////
-// PUBLIC ENTRY POINTS
-
-impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
-    pub fn regionck_expr(&self, body: &'tcx hir::Body<'tcx>) {
-        let subject = self.tcx.hir().body_owner_def_id(body.id());
-        let id = body.value.hir_id;
-        let mut rcx = RegionCtxt::new(self, id, Subject(subject), self.param_env);
-
-        // There are no add'l implied bounds when checking a
-        // standalone expr (e.g., the `E` in a type like `[u32; E]`).
-        rcx.outlives_environment.save_implied_bounds(id);
-
-        if !self.errors_reported_since_creation() {
-            // regionck assumes typeck succeeded
-            rcx.visit_body(body);
-            rcx.visit_region_obligations(id);
-        }
-        // Checked by NLL
-        rcx.fcx.skip_region_resolution();
-    }
-
-    /// Region checking during the WF phase for items. `wf_tys` are the
-    /// types from which we should derive implied bounds, if any.
-    #[instrument(level = "debug", skip(self))]
-    pub fn regionck_item(&self, item_id: hir::HirId, span: Span, wf_tys: FxHashSet<Ty<'tcx>>) {
-        let subject = self.tcx.hir().local_def_id(item_id);
-        let mut rcx = RegionCtxt::new(self, item_id, Subject(subject), self.param_env);
-        rcx.outlives_environment.add_implied_bounds(self, wf_tys, item_id, span);
-        rcx.outlives_environment.save_implied_bounds(item_id);
-        rcx.visit_region_obligations(item_id);
-        rcx.resolve_regions_and_report_errors();
-    }
-
-    /// Region check a function body. Not invoked on closures, but
-    /// only on the "root" fn item (in which closures may be
-    /// embedded). Walks the function body and adds various add'l
-    /// constraints that are needed for region inference. This is
-    /// separated both to isolate "pure" region constraints from the
-    /// rest of type check and because sometimes we need type
-    /// inference to have completed before we can determine which
-    /// constraints to add.
-    pub(crate) fn regionck_fn(
-        &self,
-        fn_id: hir::HirId,
-        body: &'tcx hir::Body<'tcx>,
-        span: Span,
-        wf_tys: FxHashSet<Ty<'tcx>>,
-    ) {
-        debug!("regionck_fn(id={})", fn_id);
-        let subject = self.tcx.hir().body_owner_def_id(body.id());
-        let hir_id = body.value.hir_id;
-        let mut rcx = RegionCtxt::new(self, hir_id, Subject(subject), self.param_env);
-        // We need to add the implied bounds from the function signature
-        rcx.outlives_environment.add_implied_bounds(self, wf_tys, fn_id, span);
-        rcx.outlives_environment.save_implied_bounds(fn_id);
-
-        if !self.errors_reported_since_creation() {
-            // regionck assumes typeck succeeded
-            rcx.visit_fn_body(fn_id, body, self.tcx.hir().span(fn_id));
-        }
-
-        // Checked by NLL
-        rcx.fcx.skip_region_resolution();
-    }
-}
-
-///////////////////////////////////////////////////////////////////////////
-// INTERNALS
-
-pub struct RegionCtxt<'a, 'tcx> {
-    pub fcx: &'a FnCtxt<'a, 'tcx>,
-
-    outlives_environment: OutlivesEnvironment<'tcx>,
-
-    // id of innermost fn body id
-    body_id: hir::HirId,
-    body_owner: LocalDefId,
-
-    // id of AST node being analyzed (the subject of the analysis).
-    subject_def_id: LocalDefId,
-}
-
-impl<'a, 'tcx> Deref for RegionCtxt<'a, 'tcx> {
-    type Target = FnCtxt<'a, 'tcx>;
-    fn deref(&self) -> &Self::Target {
-        self.fcx
-    }
-}
-
-pub struct Subject(LocalDefId);
-
-impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
-    pub fn new(
-        fcx: &'a FnCtxt<'a, 'tcx>,
-        initial_body_id: hir::HirId,
-        Subject(subject): Subject,
-        param_env: ty::ParamEnv<'tcx>,
-    ) -> RegionCtxt<'a, 'tcx> {
-        let outlives_environment = OutlivesEnvironment::new(param_env);
-        RegionCtxt {
-            fcx,
-            body_id: initial_body_id,
-            body_owner: subject,
-            subject_def_id: subject,
-            outlives_environment,
-        }
-    }
-
-    /// Try to resolve the type for the given node, returning `t_err` if an error results. Note that
-    /// we never care about the details of the error, the same error will be detected and reported
-    /// in the writeback phase.
-    ///
-    /// Note one important point: we do not attempt to resolve *region variables* here. This is
-    /// because regionck is essentially adding constraints to those region variables and so may yet
-    /// influence how they are resolved.
-    ///
-    /// Consider this silly example:
-    ///
-    /// ```ignore UNSOLVED (does replacing @i32 with Box<i32> preserve the desired semantics for the example?)
-    /// fn borrow(x: &i32) -> &i32 {x}
-    /// fn foo(x: @i32) -> i32 {  // block: B
-    ///     let b = borrow(x);    // region: <R0>
-    ///     *b
-    /// }
-    /// ```
-    ///
-    /// Here, the region of `b` will be `<R0>`. `<R0>` is constrained to be some subregion of the
-    /// block B and some superregion of the call. If we forced it now, we'd choose the smaller
-    /// region (the call). But that would make the *b illegal. Since we don't resolve, the type
-    /// of b will be `&<R0>.i32` and then `*b` will require that `<R0>` be bigger than the let and
-    /// the `*b` expression, so we will effectively resolve `<R0>` to be the block B.
-    pub fn resolve_type(&self, unresolved_ty: Ty<'tcx>) -> Ty<'tcx> {
-        self.resolve_vars_if_possible(unresolved_ty)
-    }
-
-    /// Try to resolve the type for the given node.
-    fn resolve_node_type(&self, id: hir::HirId) -> Ty<'tcx> {
-        let t = self.node_ty(id);
-        self.resolve_type(t)
-    }
-
-    /// This is the "main" function when region-checking a function item or a
-    /// closure within a function item. It begins by updating various fields
-    /// (e.g., `outlives_environment`) to be appropriate to the function and
-    /// then adds constraints derived from the function body.
-    ///
-    /// Note that it does **not** restore the state of the fields that
-    /// it updates! This is intentional, since -- for the main
-    /// function -- we wish to be able to read the final
-    /// `outlives_environment` and other fields from the caller. For
-    /// closures, however, we save and restore any "scoped state"
-    /// before we invoke this function. (See `visit_fn` in the
-    /// `intravisit::Visitor` impl below.)
-    fn visit_fn_body(
-        &mut self,
-        id: hir::HirId, // the id of the fn itself
-        body: &'tcx hir::Body<'tcx>,
-        span: Span,
-    ) {
-        // When we enter a function, we can derive
-        debug!("visit_fn_body(id={:?})", id);
-
-        let body_id = body.id();
-        self.body_id = body_id.hir_id;
-        self.body_owner = self.tcx.hir().body_owner_def_id(body_id);
-
-        let Some(fn_sig) = self.typeck_results.borrow().liberated_fn_sigs().get(id) else {
-            bug!("No fn-sig entry for id={:?}", id);
-        };
-
-        // Collect the types from which we create inferred bounds.
-        // For the return type, if diverging, substitute `bool` just
-        // because it will have no effect.
-        //
-        // FIXME(#27579) return types should not be implied bounds
-        let fn_sig_tys: FxHashSet<_> =
-            fn_sig.inputs().iter().cloned().chain(Some(fn_sig.output())).collect();
-
-        self.outlives_environment.add_implied_bounds(self.fcx, fn_sig_tys, body_id.hir_id, span);
-        self.outlives_environment.save_implied_bounds(body_id.hir_id);
-        self.link_fn_params(body.params);
-        self.visit_body(body);
-        self.visit_region_obligations(body_id.hir_id);
-    }
-
-    fn visit_inline_const(&mut self, id: hir::HirId, body: &'tcx hir::Body<'tcx>) {
-        debug!("visit_inline_const(id={:?})", id);
-
-        // Save state of current function. We will restore afterwards.
-        let old_body_id = self.body_id;
-        let old_body_owner = self.body_owner;
-        let env_snapshot = self.outlives_environment.push_snapshot_pre_typeck_child();
-
-        let body_id = body.id();
-        self.body_id = body_id.hir_id;
-        self.body_owner = self.tcx.hir().body_owner_def_id(body_id);
-
-        self.outlives_environment.save_implied_bounds(body_id.hir_id);
-
-        self.visit_body(body);
-        self.visit_region_obligations(body_id.hir_id);
-
-        // Restore state from previous function.
-        self.outlives_environment.pop_snapshot_post_typeck_child(env_snapshot);
-        self.body_id = old_body_id;
-        self.body_owner = old_body_owner;
-    }
-
-    fn visit_region_obligations(&mut self, hir_id: hir::HirId) {
-        debug!("visit_region_obligations: hir_id={:?}", hir_id);
-
-        // region checking can introduce new pending obligations
-        // which, when processed, might generate new region
-        // obligations. So make sure we process those.
-        self.select_all_obligations_or_error();
-    }
-
-    fn resolve_regions_and_report_errors(&self) {
-        self.infcx.process_registered_region_obligations(
-            self.outlives_environment.region_bound_pairs_map(),
-            self.param_env,
-        );
-
-        self.fcx.resolve_regions_and_report_errors(
-            self.subject_def_id.to_def_id(),
-            &self.outlives_environment,
-        );
-    }
-
-    fn constrain_bindings_in_pat(&mut self, pat: &hir::Pat<'_>) {
-        debug!("regionck::visit_pat(pat={:?})", pat);
-        pat.each_binding(|_, hir_id, span, _| {
-            let typ = self.resolve_node_type(hir_id);
-            let body_id = self.body_id;
-            dropck::check_drop_obligations(self, typ, span, body_id);
-        })
-    }
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for RegionCtxt<'a, 'tcx> {
-    // (..) FIXME(#3238) should use visit_pat, not visit_arm/visit_local,
-    // However, right now we run into an issue whereby some free
-    // regions are not properly related if they appear within the
-    // types of arguments that must be inferred. This could be
-    // addressed by deferring the construction of the region
-    // hierarchy, and in particular the relationships between free
-    // regions, until regionck, as described in #3238.
-
-    fn visit_fn(
-        &mut self,
-        fk: intravisit::FnKind<'tcx>,
-        _: &'tcx hir::FnDecl<'tcx>,
-        body_id: hir::BodyId,
-        span: Span,
-        hir_id: hir::HirId,
-    ) {
-        assert!(
-            matches!(fk, intravisit::FnKind::Closure),
-            "visit_fn invoked for something other than a closure"
-        );
-
-        // Save state of current function before invoking
-        // `visit_fn_body`.  We will restore afterwards.
-        let old_body_id = self.body_id;
-        let old_body_owner = self.body_owner;
-        let env_snapshot = self.outlives_environment.push_snapshot_pre_typeck_child();
-
-        let body = self.tcx.hir().body(body_id);
-        self.visit_fn_body(hir_id, body, span);
-
-        // Restore state from previous function.
-        self.outlives_environment.pop_snapshot_post_typeck_child(env_snapshot);
-        self.body_id = old_body_id;
-        self.body_owner = old_body_owner;
-    }
-
-    //visit_pat: visit_pat, // (..) see above
-
-    fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
-        // see above
-        self.constrain_bindings_in_pat(arm.pat);
-        intravisit::walk_arm(self, arm);
-    }
-
-    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
-        // see above
-        self.constrain_bindings_in_pat(l.pat);
-        self.link_local(l);
-        intravisit::walk_local(self, l);
-    }
-
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
-        // Check any autoderefs or autorefs that appear.
-        let cmt_result = self.constrain_adjustments(expr);
-
-        // If necessary, constrain destructors in this expression. This will be
-        // the adjusted form if there is an adjustment.
-        match cmt_result {
-            Ok(head_cmt) => {
-                self.check_safety_of_rvalue_destructor_if_necessary(&head_cmt, expr.span);
-            }
-            Err(..) => {
-                self.tcx.sess.delay_span_bug(expr.span, "cat_expr Errd");
-            }
-        }
-
-        match expr.kind {
-            hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref base) => {
-                self.link_addr_of(expr, m, base);
-
-                intravisit::walk_expr(self, expr);
-            }
-
-            hir::ExprKind::Match(ref discr, arms, _) => {
-                self.link_match(discr, arms);
-
-                intravisit::walk_expr(self, expr);
-            }
-
-            hir::ExprKind::ConstBlock(anon_const) => {
-                let body = self.tcx.hir().body(anon_const.body);
-                self.visit_inline_const(anon_const.hir_id, body);
-            }
-
-            _ => intravisit::walk_expr(self, expr),
-        }
-    }
-}
-
-impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
-    /// Creates a temporary `MemCategorizationContext` and pass it to the closure.
-    fn with_mc<F, R>(&self, f: F) -> R
-    where
-        F: for<'b> FnOnce(mc::MemCategorizationContext<'b, 'tcx>) -> R,
-    {
-        f(mc::MemCategorizationContext::new(
-            &self.infcx,
-            self.outlives_environment.param_env,
-            self.body_owner,
-            &self.typeck_results.borrow(),
-        ))
-    }
-
-    /// Invoked on any adjustments that occur. Checks that if this is a region pointer being
-    /// dereferenced, the lifetime of the pointer includes the deref expr.
-    fn constrain_adjustments(
-        &mut self,
-        expr: &hir::Expr<'_>,
-    ) -> mc::McResult<PlaceWithHirId<'tcx>> {
-        debug!("constrain_adjustments(expr={:?})", expr);
-
-        let mut place = self.with_mc(|mc| mc.cat_expr_unadjusted(expr))?;
-
-        let typeck_results = self.typeck_results.borrow();
-        let adjustments = typeck_results.expr_adjustments(expr);
-        if adjustments.is_empty() {
-            return Ok(place);
-        }
-
-        debug!("constrain_adjustments: adjustments={:?}", adjustments);
-
-        // If necessary, constrain destructors in the unadjusted form of this
-        // expression.
-        self.check_safety_of_rvalue_destructor_if_necessary(&place, expr.span);
-
-        for adjustment in adjustments {
-            debug!("constrain_adjustments: adjustment={:?}, place={:?}", adjustment, place);
-
-            if let adjustment::Adjust::Deref(Some(deref)) = adjustment.kind {
-                self.link_region(
-                    expr.span,
-                    deref.region,
-                    ty::BorrowKind::from_mutbl(deref.mutbl),
-                    &place,
-                );
-            }
-
-            if let adjustment::Adjust::Borrow(ref autoref) = adjustment.kind {
-                self.link_autoref(expr, &place, autoref);
-            }
-
-            place = self.with_mc(|mc| mc.cat_expr_adjusted(expr, place, adjustment))?;
-        }
-
-        Ok(place)
-    }
-
-    fn check_safety_of_rvalue_destructor_if_necessary(
-        &mut self,
-        place_with_id: &PlaceWithHirId<'tcx>,
-        span: Span,
-    ) {
-        if let PlaceBase::Rvalue = place_with_id.place.base {
-            if place_with_id.place.projections.is_empty() {
-                let typ = self.resolve_type(place_with_id.place.ty());
-                let body_id = self.body_id;
-                dropck::check_drop_obligations(self, typ, span, body_id);
-            }
-        }
-    }
-    /// Adds constraints to inference such that `T: 'a` holds (or
-    /// reports an error if it cannot).
-    ///
-    /// # Parameters
-    ///
-    /// - `origin`, the reason we need this constraint
-    /// - `ty`, the type `T`
-    /// - `region`, the region `'a`
-    pub fn type_must_outlive(
-        &self,
-        origin: infer::SubregionOrigin<'tcx>,
-        ty: Ty<'tcx>,
-        region: ty::Region<'tcx>,
-    ) {
-        self.infcx.register_region_obligation(
-            self.body_id,
-            RegionObligation { sub_region: region, sup_type: ty, origin },
-        );
-    }
-
-    /// Computes the guarantor for an expression `&base` and then ensures that the lifetime of the
-    /// resulting pointer is linked to the lifetime of its guarantor (if any).
-    fn link_addr_of(
-        &mut self,
-        expr: &hir::Expr<'_>,
-        mutability: hir::Mutability,
-        base: &hir::Expr<'_>,
-    ) {
-        debug!("link_addr_of(expr={:?}, base={:?})", expr, base);
-
-        let cmt = ignore_err!(self.with_mc(|mc| mc.cat_expr(base)));
-
-        debug!("link_addr_of: cmt={:?}", cmt);
-
-        self.link_region_from_node_type(expr.span, expr.hir_id, mutability, &cmt);
-    }
-
-    /// Computes the guarantors for any ref bindings in a `let` and
-    /// then ensures that the lifetime of the resulting pointer is
-    /// linked to the lifetime of the initialization expression.
-    fn link_local(&self, local: &hir::Local<'_>) {
-        debug!("regionck::for_local()");
-        let init_expr = match local.init {
-            None => {
-                return;
-            }
-            Some(expr) => &*expr,
-        };
-        let discr_cmt = ignore_err!(self.with_mc(|mc| mc.cat_expr(init_expr)));
-        self.link_pattern(discr_cmt, local.pat);
-    }
-
-    /// Computes the guarantors for any ref bindings in a match and
-    /// then ensures that the lifetime of the resulting pointer is
-    /// linked to the lifetime of its guarantor (if any).
-    fn link_match(&self, discr: &hir::Expr<'_>, arms: &[hir::Arm<'_>]) {
-        debug!("regionck::for_match()");
-        let discr_cmt = ignore_err!(self.with_mc(|mc| mc.cat_expr(discr)));
-        debug!("discr_cmt={:?}", discr_cmt);
-        for arm in arms {
-            self.link_pattern(discr_cmt.clone(), arm.pat);
-        }
-    }
-
-    /// Computes the guarantors for any ref bindings in a match and
-    /// then ensures that the lifetime of the resulting pointer is
-    /// linked to the lifetime of its guarantor (if any).
-    fn link_fn_params(&self, params: &[hir::Param<'_>]) {
-        for param in params {
-            let param_ty = self.node_ty(param.hir_id);
-            let param_cmt =
-                self.with_mc(|mc| mc.cat_rvalue(param.hir_id, param.pat.span, param_ty));
-            debug!("param_ty={:?} param_cmt={:?} param={:?}", param_ty, param_cmt, param);
-            self.link_pattern(param_cmt, param.pat);
-        }
-    }
-
-    /// Link lifetimes of any ref bindings in `root_pat` to the pointers found
-    /// in the discriminant, if needed.
-    fn link_pattern(&self, discr_cmt: PlaceWithHirId<'tcx>, root_pat: &hir::Pat<'_>) {
-        debug!("link_pattern(discr_cmt={:?}, root_pat={:?})", discr_cmt, root_pat);
-        ignore_err!(self.with_mc(|mc| {
-            mc.cat_pattern(discr_cmt, root_pat, |sub_cmt, hir::Pat { kind, span, hir_id, .. }| {
-                // `ref x` pattern
-                if let PatKind::Binding(..) = kind
-                    && let Some(ty::BindByReference(mutbl)) = mc.typeck_results.extract_binding_mode(self.tcx.sess, *hir_id, *span) {
-                    self.link_region_from_node_type(*span, *hir_id, mutbl, sub_cmt);
-                }
-            })
-        }));
-    }
-
-    /// Link lifetime of borrowed pointer resulting from autoref to lifetimes in the value being
-    /// autoref'd.
-    fn link_autoref(
-        &self,
-        expr: &hir::Expr<'_>,
-        expr_cmt: &PlaceWithHirId<'tcx>,
-        autoref: &adjustment::AutoBorrow<'tcx>,
-    ) {
-        debug!("link_autoref(autoref={:?}, expr_cmt={:?})", autoref, expr_cmt);
-
-        match *autoref {
-            adjustment::AutoBorrow::Ref(r, m) => {
-                self.link_region(expr.span, r, ty::BorrowKind::from_mutbl(m.into()), expr_cmt);
-            }
-
-            adjustment::AutoBorrow::RawPtr(_) => {}
-        }
-    }
-
-    /// Like `link_region()`, except that the region is extracted from the type of `id`,
-    /// which must be some reference (`&T`, `&str`, etc).
-    fn link_region_from_node_type(
-        &self,
-        span: Span,
-        id: hir::HirId,
-        mutbl: hir::Mutability,
-        cmt_borrowed: &PlaceWithHirId<'tcx>,
-    ) {
-        debug!(
-            "link_region_from_node_type(id={:?}, mutbl={:?}, cmt_borrowed={:?})",
-            id, mutbl, cmt_borrowed
-        );
-
-        let rptr_ty = self.resolve_node_type(id);
-        if let ty::Ref(r, _, _) = rptr_ty.kind() {
-            debug!("rptr_ty={}", rptr_ty);
-            self.link_region(span, *r, ty::BorrowKind::from_mutbl(mutbl), cmt_borrowed);
-        }
-    }
-
-    /// Informs the inference engine that `borrow_cmt` is being borrowed with
-    /// kind `borrow_kind` and lifetime `borrow_region`.
-    /// In order to ensure borrowck is satisfied, this may create constraints
-    /// between regions, as explained in `link_reborrowed_region()`.
-    fn link_region(
-        &self,
-        span: Span,
-        borrow_region: ty::Region<'tcx>,
-        borrow_kind: ty::BorrowKind,
-        borrow_place: &PlaceWithHirId<'tcx>,
-    ) {
-        let origin = infer::DataBorrowed(borrow_place.place.ty(), span);
-        self.type_must_outlive(origin, borrow_place.place.ty(), borrow_region);
-
-        for pointer_ty in borrow_place.place.deref_tys() {
-            debug!(
-                "link_region(borrow_region={:?}, borrow_kind={:?}, pointer_ty={:?})",
-                borrow_region, borrow_kind, borrow_place
-            );
-            match *pointer_ty.kind() {
-                ty::RawPtr(_) => return,
-                ty::Ref(ref_region, _, ref_mutability) => {
-                    if self.link_reborrowed_region(span, borrow_region, ref_region, ref_mutability)
-                    {
-                        return;
-                    }
-                }
-                _ => assert!(pointer_ty.is_box(), "unexpected built-in deref type {}", pointer_ty),
-            }
-        }
-        if let PlaceBase::Upvar(upvar_id) = borrow_place.place.base {
-            self.link_upvar_region(span, borrow_region, upvar_id);
-        }
-    }
-
-    /// This is the most complicated case: the path being borrowed is
-    /// itself the referent of a borrowed pointer. Let me give an
-    /// example fragment of code to make clear(er) the situation:
-    ///
-    /// ```ignore (incomplete Rust code)
-    /// let r: &'a mut T = ...;  // the original reference "r" has lifetime 'a
-    /// ...
-    /// &'z *r                   // the reborrow has lifetime 'z
-    /// ```
-    ///
-    /// Now, in this case, our primary job is to add the inference
-    /// constraint that `'z <= 'a`. Given this setup, let's clarify the
-    /// parameters in (roughly) terms of the example:
-    ///
-    /// ```plain,ignore (pseudo-Rust)
-    /// A borrow of: `& 'z bk * r` where `r` has type `& 'a bk T`
-    /// borrow_region   ^~                 ref_region    ^~
-    /// borrow_kind        ^~               ref_kind        ^~
-    /// ref_cmt                 ^
-    /// ```
-    ///
-    /// Here `bk` stands for some borrow-kind (e.g., `mut`, `uniq`, etc).
-    ///
-    /// There is a complication beyond the simple scenario I just painted: there
-    /// may in fact be more levels of reborrowing. In the example, I said the
-    /// borrow was like `&'z *r`, but it might in fact be a borrow like
-    /// `&'z **q` where `q` has type `&'a &'b mut T`. In that case, we want to
-    /// ensure that `'z <= 'a` and `'z <= 'b`.
-    ///
-    /// The return value of this function indicates whether we *don't* need to
-    /// the recurse to the next reference up.
-    ///
-    /// This is explained more below.
-    fn link_reborrowed_region(
-        &self,
-        span: Span,
-        borrow_region: ty::Region<'tcx>,
-        ref_region: ty::Region<'tcx>,
-        ref_mutability: hir::Mutability,
-    ) -> bool {
-        debug!("link_reborrowed_region: {:?} <= {:?}", borrow_region, ref_region);
-        self.sub_regions(infer::Reborrow(span), borrow_region, ref_region);
-
-        // Decide whether we need to recurse and link any regions within
-        // the `ref_cmt`. This is concerned for the case where the value
-        // being reborrowed is in fact a borrowed pointer found within
-        // another borrowed pointer. For example:
-        //
-        //    let p: &'b &'a mut T = ...;
-        //    ...
-        //    &'z **p
-        //
-        // What makes this case particularly tricky is that, if the data
-        // being borrowed is a `&mut` or `&uniq` borrow, borrowck requires
-        // not only that `'z <= 'a`, (as before) but also `'z <= 'b`
-        // (otherwise the user might mutate through the `&mut T` reference
-        // after `'b` expires and invalidate the borrow we are looking at
-        // now).
-        //
-        // So let's re-examine our parameters in light of this more
-        // complicated (possible) scenario:
-        //
-        //     A borrow of: `& 'z bk * * p` where `p` has type `&'b bk & 'a bk T`
-        //     borrow_region   ^~                 ref_region             ^~
-        //     borrow_kind        ^~               ref_kind                 ^~
-        //     ref_cmt                 ^~~
-        //
-        // (Note that since we have not examined `ref_cmt.cat`, we don't
-        // know whether this scenario has occurred; but I wanted to show
-        // how all the types get adjusted.)
-        match ref_mutability {
-            hir::Mutability::Not => {
-                // The reference being reborrowed is a shareable ref of
-                // type `&'a T`. In this case, it doesn't matter where we
-                // *found* the `&T` pointer, the memory it references will
-                // be valid and immutable for `'a`. So we can stop here.
-                true
-            }
-
-            hir::Mutability::Mut => {
-                // The reference being reborrowed is either an `&mut T`. This is
-                // the case where recursion is needed.
-                false
-            }
-        }
-    }
-
-    /// An upvar may be behind up to 2 references:
-    ///
-    /// * One can come from the reference to a "by-reference" upvar.
-    /// * Another one can come from the reference to the closure itself if it's
-    ///   a `FnMut` or `Fn` closure.
-    ///
-    /// This function links the lifetimes of those references to the lifetime
-    /// of the borrow that's provided. See [RegionCtxt::link_reborrowed_region] for some
-    /// more explanation of this in the general case.
-    ///
-    /// We also supply a *cause*, and in this case we set the cause to
-    /// indicate that the reference being "reborrowed" is itself an upvar. This
-    /// provides a nicer error message should something go wrong.
-    fn link_upvar_region(
-        &self,
-        span: Span,
-        borrow_region: ty::Region<'tcx>,
-        upvar_id: ty::UpvarId,
-    ) {
-        debug!("link_upvar_region(borrorw_region={:?}, upvar_id={:?}", borrow_region, upvar_id);
-        // A by-reference upvar can't be borrowed for longer than the
-        // upvar is borrowed from the environment.
-        let closure_local_def_id = upvar_id.closure_expr_id;
-        let mut all_captures_are_imm_borrow = true;
-        for captured_place in self
-            .typeck_results
-            .borrow()
-            .closure_min_captures
-            .get(&closure_local_def_id.to_def_id())
-            .and_then(|root_var_min_cap| root_var_min_cap.get(&upvar_id.var_path.hir_id))
-            .into_iter()
-            .flatten()
-        {
-            match captured_place.info.capture_kind {
-                ty::UpvarCapture::ByRef(upvar_borrow) => {
-                    self.sub_regions(
-                        infer::ReborrowUpvar(span, upvar_id),
-                        borrow_region,
-                        captured_place.region.unwrap(),
-                    );
-                    if let ty::ImmBorrow = upvar_borrow {
-                        debug!("link_upvar_region: capture by shared ref");
-                    } else {
-                        all_captures_are_imm_borrow = false;
-                    }
-                }
-                ty::UpvarCapture::ByValue => {
-                    all_captures_are_imm_borrow = false;
-                }
-            }
-        }
-        if all_captures_are_imm_borrow {
-            return;
-        }
-        let fn_hir_id = self.tcx.hir().local_def_id_to_hir_id(closure_local_def_id);
-        let ty = self.resolve_node_type(fn_hir_id);
-        debug!("link_upvar_region: ty={:?}", ty);
-
-        // A closure capture can't be borrowed for longer than the
-        // reference to the closure.
-        if let ty::Closure(_, substs) = ty.kind() {
-            match self.infcx.closure_kind(substs) {
-                Some(ty::ClosureKind::Fn | ty::ClosureKind::FnMut) => {
-                    // Region of environment pointer
-                    let env_region = self.tcx.mk_region(ty::ReFree(ty::FreeRegion {
-                        scope: upvar_id.closure_expr_id.to_def_id(),
-                        bound_region: ty::BrEnv,
-                    }));
-                    self.sub_regions(
-                        infer::ReborrowUpvar(span, upvar_id),
-                        borrow_region,
-                        env_region,
-                    );
-                }
-                Some(ty::ClosureKind::FnOnce) => {}
-                None => {
-                    span_bug!(span, "Have not inferred closure kind before regionck");
-                }
-            }
-        }
-    }
-}
diff --git a/compiler/rustc_typeck/src/check/wfcheck.rs b/compiler/rustc_typeck/src/check/wfcheck.rs
index 7fe36781cf4..84a8cc431f4 100644
--- a/compiler/rustc_typeck/src/check/wfcheck.rs
+++ b/compiler/rustc_typeck/src/check/wfcheck.rs
@@ -18,7 +18,7 @@ use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts, Subst};
 use rustc_middle::ty::trait_def::TraitSpecializationKind;
 use rustc_middle::ty::{
     self, AdtKind, DefIdTree, EarlyBinder, GenericParamDefKind, ToPredicate, Ty, TyCtxt,
-    TypeFoldable, TypeSuperFoldable, TypeVisitor,
+    TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::{sym, Ident, Symbol};
@@ -62,7 +62,10 @@ impl<'tcx> CheckWfFcxBuilder<'tcx> {
             }
             let wf_tys = f(&fcx);
             fcx.select_all_obligations_or_error();
-            fcx.regionck_item(id, span, wf_tys);
+
+            let mut outlives_environment = OutlivesEnvironment::new(param_env);
+            outlives_environment.add_implied_bounds(&fcx.infcx, wf_tys, id);
+            fcx.infcx.check_region_obligations_and_report_errors(&outlives_environment);
         });
     }
 }
@@ -655,13 +658,12 @@ fn resolve_regions_with_wf_tys<'tcx>(
     // call individually.
     tcx.infer_ctxt().enter(|infcx| {
         let mut outlives_environment = OutlivesEnvironment::new(param_env);
-        outlives_environment.add_implied_bounds(&infcx, wf_tys.clone(), id, DUMMY_SP);
-        outlives_environment.save_implied_bounds(id);
-        let region_bound_pairs = outlives_environment.region_bound_pairs_map().get(&id).unwrap();
+        outlives_environment.add_implied_bounds(&infcx, wf_tys.clone(), id);
+        let region_bound_pairs = outlives_environment.region_bound_pairs();
 
         add_constraints(&infcx, region_bound_pairs);
 
-        let errors = infcx.resolve_regions(id.expect_owner().to_def_id(), &outlives_environment);
+        let errors = infcx.resolve_regions(&outlives_environment);
 
         debug!(?errors, "errors");
 
@@ -1367,7 +1369,7 @@ fn check_where_clauses<'tcx, 'fcx>(
             struct CountParams {
                 params: FxHashSet<u32>,
             }
-            impl<'tcx> ty::fold::TypeVisitor<'tcx> for CountParams {
+            impl<'tcx> ty::visit::TypeVisitor<'tcx> for CountParams {
                 type BreakTy = ();
 
                 fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
diff --git a/compiler/rustc_typeck/src/check/writeback.rs b/compiler/rustc_typeck/src/check/writeback.rs
index 67160b98b9d..41d241f84ac 100644
--- a/compiler/rustc_typeck/src/check/writeback.rs
+++ b/compiler/rustc_typeck/src/check/writeback.rs
@@ -15,6 +15,7 @@ use rustc_middle::hir::place::Place as HirPlace;
 use rustc_middle::mir::FakeReadCause;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast};
 use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable};
 use rustc_middle::ty::{self, ClosureSizeProfileData, Ty, TyCtxt};
 use rustc_span::symbol::sym;
 use rustc_span::Span;
@@ -692,7 +693,6 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
                     Some(self.body.id()),
                     self.span.to_span(self.tcx),
                     t.into(),
-                    vec![],
                     E0282,
                     false,
                 )
@@ -707,7 +707,6 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
                     Some(self.body.id()),
                     self.span.to_span(self.tcx),
                     c.into(),
-                    vec![],
                     E0282,
                     false,
                 )
diff --git a/compiler/rustc_typeck/src/coherence/builtin.rs b/compiler/rustc_typeck/src/coherence/builtin.rs
index c647c2a4c1b..d532050d050 100644
--- a/compiler/rustc_typeck/src/coherence/builtin.rs
+++ b/compiler/rustc_typeck/src/coherence/builtin.rs
@@ -11,7 +11,7 @@ use rustc_infer::infer;
 use rustc_infer::infer::outlives::env::OutlivesEnvironment;
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_middle::ty::adjustment::CoerceUnsizedInfo;
-use rustc_middle::ty::{self, suggest_constraining_type_params, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, suggest_constraining_type_params, Ty, TyCtxt, TypeVisitable};
 use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
 use rustc_trait_selection::traits::misc::{can_type_implement_copy, CopyImplementationError};
 use rustc_trait_selection::traits::predicate_for_trait_def;
@@ -349,7 +349,7 @@ fn visit_implementation_of_dispatch_from_dyn<'tcx>(tcx: TyCtxt<'tcx>, impl_did:
 
                     // Finally, resolve all regions.
                     let outlives_env = OutlivesEnvironment::new(param_env);
-                    infcx.resolve_regions_and_report_errors(impl_did.to_def_id(), &outlives_env);
+                    infcx.resolve_regions_and_report_errors(&outlives_env);
                 }
             }
             _ => {
@@ -606,7 +606,7 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
 
         // Finally, resolve all regions.
         let outlives_env = OutlivesEnvironment::new(param_env);
-        infcx.resolve_regions_and_report_errors(impl_did.to_def_id(), &outlives_env);
+        infcx.resolve_regions_and_report_errors(&outlives_env);
 
         CoerceUnsizedInfo { custom_kind: kind }
     })
diff --git a/compiler/rustc_typeck/src/coherence/mod.rs b/compiler/rustc_typeck/src/coherence/mod.rs
index 447ec87f302..ae9ebe59091 100644
--- a/compiler/rustc_typeck/src/coherence/mod.rs
+++ b/compiler/rustc_typeck/src/coherence/mod.rs
@@ -8,8 +8,7 @@
 use rustc_errors::struct_span_err;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::ty::query::Providers;
-use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
-use rustc_span::Span;
+use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
 use rustc_trait_selection::traits;
 
 mod builtin;
@@ -18,11 +17,6 @@ mod inherent_impls_overlap;
 mod orphan;
 mod unsafety;
 
-/// Obtains the span of just the impl header of `impl_def_id`.
-fn impl_header_span(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) -> Span {
-    tcx.sess.source_map().guess_head_span(tcx.span_of_impl(impl_def_id.to_def_id()).unwrap())
-}
-
 fn check_impl(tcx: TyCtxt<'_>, impl_def_id: LocalDefId, trait_ref: ty::TraitRef<'_>) {
     debug!(
         "(checking implementation) adding impl for trait '{:?}', item '{}'",
@@ -47,56 +41,53 @@ fn enforce_trait_manually_implementable(
 ) {
     let did = Some(trait_def_id);
     let li = tcx.lang_items();
+    let impl_header_span = tcx.def_span(impl_def_id);
 
     // Disallow *all* explicit impls of `Pointee`, `DiscriminantKind`, `Sized` and `Unsize` for now.
     if did == li.pointee_trait() {
-        let span = impl_header_span(tcx, impl_def_id);
         struct_span_err!(
             tcx.sess,
-            span,
+            impl_header_span,
             E0322,
             "explicit impls for the `Pointee` trait are not permitted"
         )
-        .span_label(span, "impl of `Pointee` not allowed")
+        .span_label(impl_header_span, "impl of `Pointee` not allowed")
         .emit();
         return;
     }
 
     if did == li.discriminant_kind_trait() {
-        let span = impl_header_span(tcx, impl_def_id);
         struct_span_err!(
             tcx.sess,
-            span,
+            impl_header_span,
             E0322,
             "explicit impls for the `DiscriminantKind` trait are not permitted"
         )
-        .span_label(span, "impl of `DiscriminantKind` not allowed")
+        .span_label(impl_header_span, "impl of `DiscriminantKind` not allowed")
         .emit();
         return;
     }
 
     if did == li.sized_trait() {
-        let span = impl_header_span(tcx, impl_def_id);
         struct_span_err!(
             tcx.sess,
-            span,
+            impl_header_span,
             E0322,
             "explicit impls for the `Sized` trait are not permitted"
         )
-        .span_label(span, "impl of `Sized` not allowed")
+        .span_label(impl_header_span, "impl of `Sized` not allowed")
         .emit();
         return;
     }
 
     if did == li.unsize_trait() {
-        let span = impl_header_span(tcx, impl_def_id);
         struct_span_err!(
             tcx.sess,
-            span,
+            impl_header_span,
             E0328,
             "explicit impls for the `Unsize` trait are not permitted"
         )
-        .span_label(span, "impl of `Unsize` not allowed")
+        .span_label(impl_header_span, "impl of `Unsize` not allowed")
         .emit();
         return;
     }
@@ -110,10 +101,9 @@ fn enforce_trait_manually_implementable(
         tcx.trait_def(trait_def_id).specialization_kind
     {
         if !tcx.features().specialization && !tcx.features().min_specialization {
-            let span = impl_header_span(tcx, impl_def_id);
             tcx.sess
                 .struct_span_err(
-                    span,
+                    impl_header_span,
                     "implementing `rustc_specialization_trait` traits is unstable",
                 )
                 .help("add `#![feature(min_specialization)]` to the crate attributes to enable")
@@ -138,8 +128,13 @@ fn enforce_empty_impls_for_marker_traits(
         return;
     }
 
-    let span = impl_header_span(tcx, impl_def_id);
-    struct_span_err!(tcx.sess, span, E0715, "impls for marker traits cannot contain items").emit();
+    struct_span_err!(
+        tcx.sess,
+        tcx.def_span(impl_def_id),
+        E0715,
+        "impls for marker traits cannot contain items"
+    )
+    .emit();
 }
 
 pub fn provide(providers: &mut Providers) {
@@ -217,7 +212,7 @@ fn check_object_overlap<'tcx>(
             } else {
                 let mut supertrait_def_ids = traits::supertrait_def_ids(tcx, component_def_id);
                 if supertrait_def_ids.any(|d| d == trait_def_id) {
-                    let span = impl_header_span(tcx, impl_def_id);
+                    let span = tcx.def_span(impl_def_id);
                     struct_span_err!(
                         tcx.sess,
                         span,
diff --git a/compiler/rustc_typeck/src/coherence/orphan.rs b/compiler/rustc_typeck/src/coherence/orphan.rs
index f3a043a08a3..697ef7bc022 100644
--- a/compiler/rustc_typeck/src/coherence/orphan.rs
+++ b/compiler/rustc_typeck/src/coherence/orphan.rs
@@ -10,7 +10,7 @@ use rustc_middle::ty::subst::GenericArgKind;
 use rustc_middle::ty::subst::InternalSubsts;
 use rustc_middle::ty::util::IgnoreRegions;
 use rustc_middle::ty::{
-    self, ImplPolarity, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor,
+    self, ImplPolarity, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 use rustc_session::lint;
 use rustc_span::def_id::{DefId, LocalDefId};
@@ -47,7 +47,7 @@ fn do_orphan_check_impl<'tcx>(
     let hir::ItemKind::Impl(ref impl_) = item.kind else {
         bug!("{:?} is not an impl: {:?}", def_id, item);
     };
-    let sp = tcx.sess.source_map().guess_head_span(item.span);
+    let sp = tcx.def_span(def_id);
     let tr = impl_.of_trait.as_ref().unwrap();
 
     // Ensure no opaque types are present in this impl header. See issues #76202 and #86411 for examples,
diff --git a/compiler/rustc_typeck/src/collect/type_of.rs b/compiler/rustc_typeck/src/collect/type_of.rs
index 6ee2b544916..f942a4fb53a 100644
--- a/compiler/rustc_typeck/src/collect/type_of.rs
+++ b/compiler/rustc_typeck/src/collect/type_of.rs
@@ -8,7 +8,7 @@ use rustc_hir::{HirId, Node};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::subst::InternalSubsts;
 use rustc_middle::ty::util::IntTypeExt;
-use rustc_middle::ty::{self, DefIdTree, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::{self, DefIdTree, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeVisitable};
 use rustc_span::symbol::Ident;
 use rustc_span::{Span, DUMMY_SP};
 
diff --git a/compiler/rustc_typeck/src/constrained_generic_params.rs b/compiler/rustc_typeck/src/constrained_generic_params.rs
index 858cf63390a..8428e466406 100644
--- a/compiler/rustc_typeck/src/constrained_generic_params.rs
+++ b/compiler/rustc_typeck/src/constrained_generic_params.rs
@@ -1,5 +1,5 @@
 use rustc_data_structures::fx::FxHashSet;
-use rustc_middle::ty::fold::{TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor};
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_span::source_map::Span;
 use std::ops::ControlFlow;
@@ -43,7 +43,7 @@ pub fn parameters_for_impl<'tcx>(
 /// of parameters whose values are needed in order to constrain `ty` - these
 /// differ, with the latter being a superset, in the presence of projections.
 pub fn parameters_for<'tcx>(
-    t: &impl TypeFoldable<'tcx>,
+    t: &impl TypeVisitable<'tcx>,
     include_nonconstraining: bool,
 ) -> Vec<Parameter> {
     let mut collector = ParameterCollector { parameters: vec![], include_nonconstraining };
diff --git a/compiler/rustc_typeck/src/impl_wf_check.rs b/compiler/rustc_typeck/src/impl_wf_check.rs
index 981c35e184b..e7ca70de4ba 100644
--- a/compiler/rustc_typeck/src/impl_wf_check.rs
+++ b/compiler/rustc_typeck/src/impl_wf_check.rs
@@ -16,7 +16,7 @@ use rustc_errors::struct_span_err;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalDefId;
 use rustc_middle::ty::query::Providers;
-use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
 use rustc_span::Span;
 
 use std::collections::hash_map::Entry::{Occupied, Vacant};
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 f07396ce74f..c46b825f457 100644
--- a/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs
+++ b/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs
@@ -75,7 +75,7 @@ use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
 use rustc_infer::traits::specialization_graph::Node;
 use rustc_middle::ty::subst::{GenericArg, InternalSubsts, SubstsRef};
 use rustc_middle::ty::trait_def::TraitSpecializationKind;
-use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
+use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
 use rustc_span::Span;
 use rustc_trait_selection::traits::{self, translate_substs, wf};
 
@@ -150,7 +150,7 @@ fn get_impl_substs<'tcx>(
 
     // Conservatively use an empty `ParamEnv`.
     let outlives_env = OutlivesEnvironment::new(ty::ParamEnv::empty());
-    infcx.resolve_regions_and_report_errors(impl1_def_id.to_def_id(), &outlives_env);
+    infcx.resolve_regions_and_report_errors(&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 });
@@ -279,11 +279,16 @@ fn check_predicates<'tcx>(
     span: Span,
 ) {
     let tcx = infcx.tcx;
-    let impl1_predicates: Vec<_> = traits::elaborate_predicates(
+    let instantiated = tcx.predicates_of(impl1_def_id).instantiate(tcx, impl1_substs);
+    let impl1_predicates: Vec<_> = traits::elaborate_predicates_with_span(
         tcx,
-        tcx.predicates_of(impl1_def_id).instantiate(tcx, impl1_substs).predicates.into_iter(),
+        std::iter::zip(
+            instantiated.predicates,
+            // Don't drop predicates (unsound!) because `spans` is too short
+            instantiated.spans.into_iter().chain(std::iter::repeat(span)),
+        ),
     )
-    .map(|obligation| obligation.predicate)
+    .map(|obligation| (obligation.predicate, obligation.cause.span))
     .collect();
 
     let mut impl2_predicates = if impl2_node.is_from_trait() {
@@ -321,7 +326,7 @@ fn check_predicates<'tcx>(
     // which is sound because we forbid impls like the following
     //
     // impl<D: Debug> AlwaysApplicable for D { }
-    let always_applicable_traits = impl1_predicates.iter().copied().filter(|&predicate| {
+    let always_applicable_traits = impl1_predicates.iter().copied().filter(|&(predicate, _)| {
         matches!(
             trait_predicate_kind(tcx, predicate),
             Some(TraitSpecializationKind::AlwaysApplicable)
@@ -345,11 +350,11 @@ fn check_predicates<'tcx>(
         }
     }
     impl2_predicates.extend(
-        traits::elaborate_predicates(tcx, always_applicable_traits)
+        traits::elaborate_predicates_with_span(tcx, always_applicable_traits)
             .map(|obligation| obligation.predicate),
     );
 
-    for predicate in impl1_predicates {
+    for (predicate, span) in impl1_predicates {
         if !impl2_predicates.contains(&predicate) {
             check_specialization_on(tcx, predicate, span)
         }
@@ -384,9 +389,17 @@ fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc
                     .emit();
             }
         }
+        ty::PredicateKind::Projection(ty::ProjectionPredicate { projection_ty, term }) => {
+            tcx.sess
+                .struct_span_err(
+                    span,
+                    &format!("cannot specialize on associated type `{projection_ty} == {term}`",),
+                )
+                .emit();
+        }
         _ => {
             tcx.sess
-                .struct_span_err(span, &format!("cannot specialize on `{:?}`", predicate))
+                .struct_span_err(span, &format!("cannot specialize on predicate `{}`", predicate))
                 .emit();
         }
     }
diff --git a/compiler/rustc_typeck/src/lib.rs b/compiler/rustc_typeck/src/lib.rs
index b6d4f5fcda6..08c194ec0b6 100644
--- a/compiler/rustc_typeck/src/lib.rs
+++ b/compiler/rustc_typeck/src/lib.rs
@@ -223,15 +223,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
         if !def_id.is_local() {
             return None;
         }
-        let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local());
-        match tcx.hir().find(hir_id) {
-            Some(Node::Item(hir::Item { span: item_span, .. })) => {
-                Some(tcx.sess.source_map().guess_head_span(*item_span))
-            }
-            _ => {
-                span_bug!(tcx.def_span(def_id), "main has a non-function type");
-            }
-        }
+        Some(tcx.def_span(def_id))
     }
 
     fn main_fn_return_type_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Span> {
@@ -416,7 +408,7 @@ fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) {
                         error = true;
                     }
                     if let hir::IsAsync::Async = sig.header.asyncness {
-                        let span = tcx.sess.source_map().guess_head_span(it.span);
+                        let span = tcx.def_span(it.def_id);
                         struct_span_err!(
                             tcx.sess,
                             span,
diff --git a/compiler/rustc_typeck/src/mem_categorization.rs b/compiler/rustc_typeck/src/mem_categorization.rs
index 9acae8d79e7..ced919f66db 100644
--- a/compiler/rustc_typeck/src/mem_categorization.rs
+++ b/compiler/rustc_typeck/src/mem_categorization.rs
@@ -51,6 +51,7 @@
 use rustc_middle::hir::place::*;
 use rustc_middle::ty::adjustment;
 use rustc_middle::ty::fold::TypeFoldable;
+use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 
 use rustc_data_structures::fx::FxIndexMap;
diff --git a/compiler/rustc_typeck/src/outlives/outlives_bounds.rs b/compiler/rustc_typeck/src/outlives/outlives_bounds.rs
index 3bf697e7682..70b8bcd0220 100644
--- a/compiler/rustc_typeck/src/outlives/outlives_bounds.rs
+++ b/compiler/rustc_typeck/src/outlives/outlives_bounds.rs
@@ -1,6 +1,5 @@
 use rustc_hir as hir;
 use rustc_middle::ty::{self, Ty};
-use rustc_span::source_map::Span;
 use rustc_trait_selection::infer::InferCtxt;
 use rustc_trait_selection::traits::query::type_op::{self, TypeOp, TypeOpOutput};
 use rustc_trait_selection::traits::query::NoSolution;
@@ -14,7 +13,6 @@ pub trait InferCtxtExt<'tcx> {
         param_env: ty::ParamEnv<'tcx>,
         body_id: hir::HirId,
         ty: Ty<'tcx>,
-        span: Span,
     ) -> Vec<OutlivesBound<'tcx>>;
 }
 
@@ -38,16 +36,14 @@ impl<'cx, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'cx, 'tcx> {
     ///   Note that this may cause outlives obligations to be injected
     ///   into the inference context with this body-id.
     /// - `ty`, the type that we are supposed to assume is WF.
-    /// - `span`, a span to use when normalizing, hopefully not important,
-    ///   might be useful if a `bug!` occurs.
-    #[instrument(level = "debug", skip(self, param_env, body_id, span))]
+    #[instrument(level = "debug", skip(self, param_env, body_id))]
     fn implied_outlives_bounds(
         &self,
         param_env: ty::ParamEnv<'tcx>,
         body_id: hir::HirId,
         ty: Ty<'tcx>,
-        span: Span,
     ) -> Vec<OutlivesBound<'tcx>> {
+        let span = self.tcx.hir().span(body_id);
         let result = param_env
             .and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty })
             .fully_perform(self);
diff --git a/compiler/rustc_typeck/src/structured_errors/missing_cast_for_variadic_arg.rs b/compiler/rustc_typeck/src/structured_errors/missing_cast_for_variadic_arg.rs
index bafc5a0b918..324df313ef1 100644
--- a/compiler/rustc_typeck/src/structured_errors/missing_cast_for_variadic_arg.rs
+++ b/compiler/rustc_typeck/src/structured_errors/missing_cast_for_variadic_arg.rs
@@ -1,6 +1,6 @@
 use crate::structured_errors::StructuredDiagnostic;
 use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed};
-use rustc_middle::ty::{Ty, TypeFoldable};
+use rustc_middle::ty::{Ty, TypeVisitable};
 use rustc_session::Session;
 use rustc_span::Span;
 
diff --git a/compiler/rustc_typeck/src/structured_errors/sized_unsized_cast.rs b/compiler/rustc_typeck/src/structured_errors/sized_unsized_cast.rs
index afc5c1fe6cc..bb608805488 100644
--- a/compiler/rustc_typeck/src/structured_errors/sized_unsized_cast.rs
+++ b/compiler/rustc_typeck/src/structured_errors/sized_unsized_cast.rs
@@ -1,6 +1,6 @@
 use crate::structured_errors::StructuredDiagnostic;
 use rustc_errors::{DiagnosticBuilder, DiagnosticId, ErrorGuaranteed};
-use rustc_middle::ty::{Ty, TypeFoldable};
+use rustc_middle::ty::{Ty, TypeVisitable};
 use rustc_session::Session;
 use rustc_span::Span;
 
diff --git a/compiler/rustc_typeck/src/variance/constraints.rs b/compiler/rustc_typeck/src/variance/constraints.rs
index a7dcbfff207..d79450e1ae7 100644
--- a/compiler/rustc_typeck/src/variance/constraints.rs
+++ b/compiler/rustc_typeck/src/variance/constraints.rs
@@ -64,74 +64,28 @@ pub fn add_constraints_from_crate<'a, 'tcx>(
 
     let crate_items = tcx.hir_crate_items(());
 
-    for id in crate_items.items() {
-        constraint_cx.check_item(id);
-    }
-
-    for id in crate_items.trait_items() {
-        if let DefKind::AssocFn = tcx.def_kind(id.def_id) {
-            constraint_cx.check_node_helper(id.hir_id());
-        }
-    }
-
-    for id in crate_items.impl_items() {
-        if let DefKind::AssocFn = tcx.def_kind(id.def_id) {
-            constraint_cx.check_node_helper(id.hir_id());
-        }
-    }
-
-    for id in crate_items.foreign_items() {
-        if let DefKind::Fn = tcx.def_kind(id.def_id) {
-            constraint_cx.check_node_helper(id.hir_id());
-        }
-    }
-
-    constraint_cx
-}
-
-impl<'a, 'tcx> ConstraintContext<'a, 'tcx> {
-    fn check_item(&mut self, id: hir::ItemId) {
-        let def_kind = self.tcx().def_kind(id.def_id);
+    for def_id in crate_items.definitions() {
+        let def_kind = tcx.def_kind(def_id);
         match def_kind {
-            DefKind::Struct | DefKind::Union => {
-                let item = self.tcx().hir().item(id);
-
-                if let hir::ItemKind::Struct(ref struct_def, _)
-                | hir::ItemKind::Union(ref struct_def, _) = item.kind
-                {
-                    self.check_node_helper(item.hir_id());
+            DefKind::Struct | DefKind::Union | DefKind::Enum => {
+                constraint_cx.build_constraints_for_item(def_id);
 
-                    if let hir::VariantData::Tuple(..) = *struct_def {
-                        self.check_node_helper(struct_def.ctor_hir_id().unwrap());
+                let adt = tcx.adt_def(def_id);
+                for variant in adt.variants() {
+                    if let Some(ctor) = variant.ctor_def_id {
+                        constraint_cx.build_constraints_for_item(ctor.expect_local());
                     }
                 }
             }
-            DefKind::Enum => {
-                let item = self.tcx().hir().item(id);
-
-                if let hir::ItemKind::Enum(ref enum_def, _) = item.kind {
-                    self.check_node_helper(item.hir_id());
-
-                    for variant in enum_def.variants {
-                        if let hir::VariantData::Tuple(..) = variant.data {
-                            self.check_node_helper(variant.data.ctor_hir_id().unwrap());
-                        }
-                    }
-                }
-            }
-            DefKind::Fn => {
-                self.check_node_helper(id.hir_id());
-            }
+            DefKind::Fn | DefKind::AssocFn => constraint_cx.build_constraints_for_item(def_id),
             _ => {}
         }
     }
 
-    fn check_node_helper(&mut self, id: hir::HirId) {
-        let tcx = self.terms_cx.tcx;
-        let def_id = tcx.hir().local_def_id(id);
-        self.build_constraints_for_item(def_id);
-    }
+    constraint_cx
+}
 
+impl<'a, 'tcx> ConstraintContext<'a, 'tcx> {
     fn tcx(&self) -> TyCtxt<'tcx> {
         self.terms_cx.tcx
     }
@@ -145,8 +99,7 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> {
             return;
         }
 
-        let id = tcx.hir().local_def_id_to_hir_id(def_id);
-        let inferred_start = self.terms_cx.inferred_starts[&id];
+        let inferred_start = self.terms_cx.inferred_starts[&def_id];
         let current_item = &CurrentItem { inferred_start };
         match tcx.type_of(def_id).kind() {
             ty::Adt(def, _) => {
@@ -372,8 +325,7 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> {
         }
 
         let (local, remote) = if let Some(def_id) = def_id.as_local() {
-            let id = self.tcx().hir().local_def_id_to_hir_id(def_id);
-            (Some(self.terms_cx.inferred_starts[&id]), None)
+            (Some(self.terms_cx.inferred_starts[&def_id]), None)
         } else {
             (None, Some(self.tcx().variances_of(def_id)))
         };
diff --git a/compiler/rustc_typeck/src/variance/solve.rs b/compiler/rustc_typeck/src/variance/solve.rs
index 1a4d88ced0e..97aca621aa2 100644
--- a/compiler/rustc_typeck/src/variance/solve.rs
+++ b/compiler/rustc_typeck/src/variance/solve.rs
@@ -96,8 +96,7 @@ impl<'a, 'tcx> SolveContext<'a, 'tcx> {
         self.terms_cx
             .inferred_starts
             .iter()
-            .map(|(&id, &InferredIndex(start))| {
-                let def_id = tcx.hir().local_def_id(id);
+            .map(|(&def_id, &InferredIndex(start))| {
                 let generics = tcx.generics_of(def_id);
                 let count = generics.count();
 
diff --git a/compiler/rustc_typeck/src/variance/terms.rs b/compiler/rustc_typeck/src/variance/terms.rs
index ab64befe5dc..1f763011e06 100644
--- a/compiler/rustc_typeck/src/variance/terms.rs
+++ b/compiler/rustc_typeck/src/variance/terms.rs
@@ -10,9 +10,8 @@
 // a variable.
 
 use rustc_arena::DroplessArena;
-use rustc_hir as hir;
 use rustc_hir::def::DefKind;
-use rustc_hir::HirIdMap;
+use rustc_hir::def_id::{LocalDefId, LocalDefIdMap};
 use rustc_middle::ty::{self, TyCtxt};
 use std::fmt;
 
@@ -52,11 +51,11 @@ pub struct TermsContext<'a, 'tcx> {
     // For marker types, UnsafeCell, and other lang items where
     // variance is hardcoded, records the item-id and the hardcoded
     // variance.
-    pub lang_items: Vec<(hir::HirId, Vec<ty::Variance>)>,
+    pub lang_items: Vec<(LocalDefId, Vec<ty::Variance>)>,
 
     // Maps from the node id of an item to the first inferred index
     // used for its type & region parameters.
-    pub inferred_starts: HirIdMap<InferredIndex>,
+    pub inferred_starts: LocalDefIdMap<InferredIndex>,
 
     // Maps from an InferredIndex to the term for that variable.
     pub inferred_terms: Vec<VarianceTermPtr<'a>>,
@@ -81,32 +80,31 @@ pub fn determine_parameters_to_be_inferred<'a, 'tcx>(
     // - https://rustc-dev-guide.rust-lang.org/variance.html
     let crate_items = tcx.hir_crate_items(());
 
-    for id in crate_items.items() {
-        terms_cx.check_item(id);
-    }
+    for def_id in crate_items.definitions() {
+        debug!("add_inferreds for item {:?}", def_id);
 
-    for id in crate_items.trait_items() {
-        if let DefKind::AssocFn = tcx.def_kind(id.def_id) {
-            terms_cx.add_inferreds_for_item(id.hir_id());
-        }
-    }
+        let def_kind = tcx.def_kind(def_id);
 
-    for id in crate_items.impl_items() {
-        if let DefKind::AssocFn = tcx.def_kind(id.def_id) {
-            terms_cx.add_inferreds_for_item(id.hir_id());
-        }
-    }
+        match def_kind {
+            DefKind::Struct | DefKind::Union | DefKind::Enum => {
+                terms_cx.add_inferreds_for_item(def_id);
 
-    for id in crate_items.foreign_items() {
-        if let DefKind::Fn = tcx.def_kind(id.def_id) {
-            terms_cx.add_inferreds_for_item(id.hir_id());
+                let adt = tcx.adt_def(def_id);
+                for variant in adt.variants() {
+                    if let Some(ctor) = variant.ctor_def_id {
+                        terms_cx.add_inferreds_for_item(ctor.expect_local());
+                    }
+                }
+            }
+            DefKind::Fn | DefKind::AssocFn => terms_cx.add_inferreds_for_item(def_id),
+            _ => {}
         }
     }
 
     terms_cx
 }
 
-fn lang_items(tcx: TyCtxt<'_>) -> Vec<(hir::HirId, Vec<ty::Variance>)> {
+fn lang_items(tcx: TyCtxt<'_>) -> Vec<(LocalDefId, Vec<ty::Variance>)> {
     let lang_items = tcx.lang_items();
     let all = [
         (lang_items.phantom_data(), vec![ty::Covariant]),
@@ -114,18 +112,16 @@ fn lang_items(tcx: TyCtxt<'_>) -> Vec<(hir::HirId, Vec<ty::Variance>)> {
     ];
 
     all.into_iter() // iterating over (Option<DefId>, Variance)
-        .filter(|&(ref d, _)| d.is_some())
-        .map(|(d, v)| (d.unwrap(), v)) // (DefId, Variance)
         .filter_map(|(d, v)| {
-            d.as_local().map(|d| tcx.hir().local_def_id_to_hir_id(d)).map(|n| (n, v))
-        }) // (HirId, Variance)
+            let def_id = d?.as_local()?; // LocalDefId
+            Some((def_id, v))
+        })
         .collect()
 }
 
 impl<'a, 'tcx> TermsContext<'a, 'tcx> {
-    fn add_inferreds_for_item(&mut self, id: hir::HirId) {
+    fn add_inferreds_for_item(&mut self, def_id: LocalDefId) {
         let tcx = self.tcx;
-        let def_id = tcx.hir().local_def_id(id);
         let count = tcx.generics_of(def_id).count();
 
         if count == 0 {
@@ -134,7 +130,7 @@ impl<'a, 'tcx> TermsContext<'a, 'tcx> {
 
         // Record the start of this item's inferreds.
         let start = self.inferred_terms.len();
-        let newly_added = self.inferred_starts.insert(id, InferredIndex(start)).is_none();
+        let newly_added = self.inferred_starts.insert(def_id, InferredIndex(start)).is_none();
         assert!(newly_added);
 
         // N.B., in the code below for writing the results back into the
@@ -146,42 +142,4 @@ impl<'a, 'tcx> TermsContext<'a, 'tcx> {
             (start..(start + count)).map(|i| &*arena.alloc(InferredTerm(InferredIndex(i)))),
         );
     }
-
-    fn check_item(&mut self, id: hir::ItemId) {
-        debug!("add_inferreds for item {}", self.tcx.hir().node_to_string(id.hir_id()));
-
-        let def_kind = self.tcx.def_kind(id.def_id);
-        match def_kind {
-            DefKind::Struct | DefKind::Union => {
-                let item = self.tcx.hir().item(id);
-
-                if let hir::ItemKind::Struct(ref struct_def, _)
-                | hir::ItemKind::Union(ref struct_def, _) = item.kind
-                {
-                    self.add_inferreds_for_item(item.hir_id());
-
-                    if let hir::VariantData::Tuple(..) = *struct_def {
-                        self.add_inferreds_for_item(struct_def.ctor_hir_id().unwrap());
-                    }
-                }
-            }
-            DefKind::Enum => {
-                let item = self.tcx.hir().item(id);
-
-                if let hir::ItemKind::Enum(ref enum_def, _) = item.kind {
-                    self.add_inferreds_for_item(item.hir_id());
-
-                    for variant in enum_def.variants {
-                        if let hir::VariantData::Tuple(..) = variant.data {
-                            self.add_inferreds_for_item(variant.data.ctor_hir_id().unwrap());
-                        }
-                    }
-                }
-            }
-            DefKind::Fn => {
-                self.add_inferreds_for_item(id.hir_id());
-            }
-            _ => {}
-        }
-    }
 }