diff --git a/Cargo.lock b/Cargo.lock
index c72c7b8481b5b..f02ae7ef6cfa3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -721,6 +721,13 @@ version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6"
 
+[[package]]
+name = "coverage_test_macros"
+version = "0.0.0"
+dependencies = [
+ "proc-macro2",
+]
+
 [[package]]
 name = "cpuid-bool"
 version = "0.1.2"
@@ -3922,6 +3929,7 @@ dependencies = [
 name = "rustc_mir"
 version = "0.0.0"
 dependencies = [
+ "coverage_test_macros",
  "either",
  "itertools 0.9.0",
  "polonius-engine",
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index a566200c33896..958219cbebf3f 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -2327,11 +2327,12 @@ impl<'a> State<'a> {
             self.print_path(path, false, depth);
         }
         self.s.word(">");
-        self.s.word("::");
-        let item_segment = path.segments.last().unwrap();
-        self.print_ident(item_segment.ident);
-        if let Some(ref args) = item_segment.args {
-            self.print_generic_args(args, colons_before_params)
+        for item_segment in &path.segments[qself.position..] {
+            self.s.word("::");
+            self.print_ident(item_segment.ident);
+            if let Some(ref args) = item_segment.args {
+                self.print_generic_args(args, colons_before_params)
+            }
         }
     }
 
diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs
index e6acb6860be9e..6237a4c0020eb 100644
--- a/compiler/rustc_codegen_llvm/src/back/write.rs
+++ b/compiler/rustc_codegen_llvm/src/back/write.rs
@@ -925,9 +925,7 @@ unsafe fn embed_bitcode(
         || cgcx.opts.target_triple.triple().starts_with("asmjs")
     {
         // nothing to do here
-    } else if cgcx.opts.target_triple.triple().contains("windows")
-        || cgcx.opts.target_triple.triple().contains("uefi")
-    {
+    } else if cgcx.is_pe_coff {
         let asm = "
             .section .llvmbc,\"n\"
             .section .llvmcmd,\"n\"
diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index b34bee3358b40..7f2bb7b5bcdaf 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -307,6 +307,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {
     pub allocator_module_config: Arc<ModuleConfig>,
     pub tm_factory: TargetMachineFactory<B>,
     pub msvc_imps_needed: bool,
+    pub is_pe_coff: bool,
     pub target_pointer_width: u32,
     pub target_arch: String,
     pub debuginfo: config::DebugInfo,
@@ -1022,6 +1023,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
         tm_factory: TargetMachineFactory(backend.target_machine_factory(tcx.sess, ol)),
         total_cgus,
         msvc_imps_needed: msvc_imps_needed(tcx),
+        is_pe_coff: tcx.sess.target.is_like_windows,
         target_pointer_width: tcx.sess.target.pointer_width,
         target_arch: tcx.sess.target.arch.clone(),
         debuginfo: tcx.sess.opts.debuginfo,
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 0df67b63eba58..a035507924794 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -616,6 +616,9 @@ declare_features! (
     /// Enables `#[cfg(panic = "...")]` config key.
     (active, cfg_panic, "1.49.0", Some(77443), None),
 
+    /// Allows capturing disjoint fields in a closure/generator (RFC 2229).
+    (active, capture_disjoint_fields, "1.49.0", Some(53488), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
@@ -639,6 +642,7 @@ pub const INCOMPLETE_FEATURES: &[Symbol] = &[
     sym::inline_const,
     sym::repr128,
     sym::unsized_locals,
+    sym::capture_disjoint_fields,
 ];
 
 /// Some features are not allowed to be used together at the same time, if
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 5c5cf609ac33c..fa8edba629e92 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -547,6 +547,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     // ==========================================================================
 
     rustc_attr!(TEST, rustc_outlives, Normal, template!(Word)),
+    rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word)),
     rustc_attr!(TEST, rustc_variance, Normal, template!(Word)),
     rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ...")),
     rustc_attr!(TEST, rustc_regions, Normal, template!(Word)),
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 3838e1b006f70..407e07354d174 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -415,6 +415,10 @@ pub struct TypeckResults<'tcx> {
     /// entire variable.
     pub closure_captures: ty::UpvarListMap,
 
+    /// Tracks the minimum captures required for a closure;
+    /// see `MinCaptureInformationMap` for more details.
+    pub closure_min_captures: ty::MinCaptureInformationMap<'tcx>,
+
     /// Stores the type, expression, span and optional scope span of all types
     /// that are live across the yield of this generator (if a generator).
     pub generator_interior_types: Vec<GeneratorInteriorTypeCause<'tcx>>,
@@ -442,6 +446,7 @@ impl<'tcx> TypeckResults<'tcx> {
             tainted_by_errors: None,
             concrete_opaque_types: Default::default(),
             closure_captures: Default::default(),
+            closure_min_captures: Default::default(),
             generator_interior_types: Default::default(),
         }
     }
@@ -676,6 +681,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckResults<'tcx> {
             tainted_by_errors,
             ref concrete_opaque_types,
             ref closure_captures,
+            ref closure_min_captures,
             ref generator_interior_types,
         } = *self;
 
@@ -709,6 +715,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckResults<'tcx> {
             tainted_by_errors.hash_stable(hcx, hasher);
             concrete_opaque_types.hash_stable(hcx, hasher);
             closure_captures.hash_stable(hcx, hasher);
+            closure_min_captures.hash_stable(hcx, hasher);
             generator_interior_types.hash_stable(hcx, hasher);
         })
     }
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 06e69a0009b1f..1eb7c3b56edd2 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -6,6 +6,7 @@ pub use self::IntVarValue::*;
 pub use self::Variance::*;
 
 use crate::hir::exports::ExportMap;
+use crate::hir::place::Place as HirPlace;
 use crate::ich::StableHashingContext;
 use crate::middle::cstore::CrateStoreDyn;
 use crate::middle::resolve_lifetime::ObjectLifetimeDefault;
@@ -611,6 +612,18 @@ pub struct TyS<'tcx> {
     outer_exclusive_binder: ty::DebruijnIndex,
 }
 
+impl<'tcx> TyS<'tcx> {
+    /// A constructor used only for internal testing.
+    #[allow(rustc::usage_of_ty_tykind)]
+    pub fn make_for_test(
+        kind: TyKind<'tcx>,
+        flags: TypeFlags,
+        outer_exclusive_binder: ty::DebruijnIndex,
+    ) -> TyS<'tcx> {
+        TyS { kind, flags, outer_exclusive_binder }
+    }
+}
+
 // `TyS` is used a lot. Make sure it doesn't unintentionally get bigger.
 #[cfg(target_arch = "x86_64")]
 static_assert_size!(TyS<'_>, 32);
@@ -674,6 +687,12 @@ pub struct UpvarId {
     pub closure_expr_id: LocalDefId,
 }
 
+impl UpvarId {
+    pub fn new(var_hir_id: hir::HirId, closure_def_id: LocalDefId) -> UpvarId {
+        UpvarId { var_path: UpvarPath { hir_id: var_hir_id }, closure_expr_id: closure_def_id }
+    }
+}
+
 #[derive(Clone, PartialEq, Debug, TyEncodable, TyDecodable, Copy, HashStable)]
 pub enum BorrowKind {
     /// Data must be immutable and is aliasable.
@@ -756,6 +775,56 @@ pub struct UpvarBorrow<'tcx> {
     pub region: ty::Region<'tcx>,
 }
 
+/// Given the closure DefId this map provides a map of root variables to minimum
+/// set of `CapturedPlace`s that need to be tracked to support all captures of that closure.
+pub type MinCaptureInformationMap<'tcx> = FxHashMap<DefId, RootVariableMinCaptureList<'tcx>>;
+
+/// Part of `MinCaptureInformationMap`; Maps a root variable to the list of `CapturedPlace`.
+/// Used to track the minimum set of `Place`s that need to be captured to support all
+/// Places captured by the closure starting at a given root variable.
+///
+/// This provides a convenient and quick way of checking if a variable being used within
+/// a closure is a capture of a local variable.
+pub type RootVariableMinCaptureList<'tcx> = FxIndexMap<hir::HirId, MinCaptureList<'tcx>>;
+
+/// Part of `MinCaptureInformationMap`; List of `CapturePlace`s.
+pub type MinCaptureList<'tcx> = Vec<CapturedPlace<'tcx>>;
+
+/// A `Place` and the corresponding `CaptureInfo`.
+#[derive(PartialEq, Clone, Debug, TyEncodable, TyDecodable, HashStable)]
+pub struct CapturedPlace<'tcx> {
+    pub place: HirPlace<'tcx>,
+    pub info: CaptureInfo<'tcx>,
+}
+
+/// 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, HashStable)]
+pub struct CaptureInfo<'tcx> {
+    /// Expr Id pointing to use that resulted in selecting the current capture kind
+    ///
+    /// If the user doesn't enable feature `capture_disjoint_fields` (RFC 2229) then, it is
+    /// possible that we don't see the use of a particular place resulting in expr_id being
+    /// None. In such case we fallback on uvpars_mentioned for span.
+    ///
+    /// Eg:
+    /// ```rust
+    /// let x = ...;
+    ///
+    /// let c = || {
+    ///     let _ = x
+    /// }
+    /// ```
+    ///
+    /// In this example, if `capture_disjoint_fields` is **not** set, then x will be captured,
+    /// but we won't see it being used during capture analysis, since it's essentially a discard.
+    pub expr_id: Option<hir::HirId>,
+
+    /// Capture mode that was selected
+    pub capture_kind: UpvarCapture<'tcx>,
+}
+
 pub type UpvarListMap = FxHashMap<DefId, FxIndexMap<hir::HirId, UpvarId>>;
 pub type UpvarCaptureMap<'tcx> = FxHashMap<UpvarId, UpvarCapture<'tcx>>;
 
diff --git a/compiler/rustc_mir/Cargo.toml b/compiler/rustc_mir/Cargo.toml
index 487668cfa1109..9bfd1da039120 100644
--- a/compiler/rustc_mir/Cargo.toml
+++ b/compiler/rustc_mir/Cargo.toml
@@ -31,3 +31,6 @@ rustc_ast = { path = "../rustc_ast" }
 rustc_span = { path = "../rustc_span" }
 rustc_apfloat = { path = "../rustc_apfloat" }
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
+
+[dev-dependencies]
+coverage_test_macros = { path = "src/transform/coverage/test_macros" }
diff --git a/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs b/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs
index 4256f6e39d5e8..41f3edaa41380 100644
--- a/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs
+++ b/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs
@@ -383,16 +383,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
                     self.describe_field_from_ty(&ty, field, variant_index)
                 }
                 ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => {
-                    // `tcx.upvars_mentioned(def_id)` returns an `Option`, which is `None` in case
-                    // the closure comes from another crate. But in that case we wouldn't
-                    // be borrowck'ing it, so we can just unwrap:
-                    let (&var_id, _) = self
-                        .infcx
-                        .tcx
-                        .upvars_mentioned(def_id)
-                        .unwrap()
-                        .get_index(field.index())
-                        .unwrap();
+                    // We won't be borrowck'ing here if the closure came from another crate,
+                    // so it's safe to call `expect_local`.
+                    //
+                    // We know the field exists so it's safe to call operator[] and `unwrap` here.
+                    let (&var_id, _) =
+                        self.infcx.tcx.typeck(def_id.expect_local()).closure_captures[&def_id]
+                            .get_index(field.index())
+                            .unwrap();
 
                     self.infcx.tcx.hir().name(var_id).to_string()
                 }
@@ -967,9 +965,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
         let expr = &self.infcx.tcx.hir().expect_expr(hir_id).kind;
         debug!("closure_span: hir_id={:?} expr={:?}", hir_id, expr);
         if let hir::ExprKind::Closure(.., body_id, args_span, _) = expr {
-            for ((upvar_hir_id, upvar), place) in
-                self.infcx.tcx.upvars_mentioned(def_id)?.iter().zip(places)
+            for (upvar_hir_id, place) in
+                self.infcx.tcx.typeck(def_id.expect_local()).closure_captures[&def_id]
+                    .keys()
+                    .zip(places)
             {
+                let span = self.infcx.tcx.upvars_mentioned(local_did)?[upvar_hir_id].span;
                 match place {
                     Operand::Copy(place) | Operand::Move(place)
                         if target_place == place.as_ref() =>
@@ -991,7 +992,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
                         let usage_span =
                             match self.infcx.tcx.typeck(local_did).upvar_capture(upvar_id) {
                                 ty::UpvarCapture::ByValue(Some(span)) => span,
-                                _ => upvar.span,
+                                _ => span,
                             };
                         return Some((*args_span, generator_kind, usage_span));
                     }
diff --git a/compiler/rustc_mir/src/transform/coverage/counters.rs b/compiler/rustc_mir/src/transform/coverage/counters.rs
index d6c2f7f7aaf1d..20f6a16e0f757 100644
--- a/compiler/rustc_mir/src/transform/coverage/counters.rs
+++ b/compiler/rustc_mir/src/transform/coverage/counters.rs
@@ -14,7 +14,7 @@ use rustc_middle::mir::coverage::*;
 
 /// Manages the counter and expression indexes/IDs to generate `CoverageKind` components for MIR
 /// `Coverage` statements.
-pub(crate) struct CoverageCounters {
+pub(super) struct CoverageCounters {
     function_source_hash: u64,
     next_counter_id: u32,
     num_expressions: u32,
@@ -37,7 +37,7 @@ impl CoverageCounters {
         self.debug_counters.enable();
     }
 
-    /// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlocks` directly or
+    /// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or
     /// indirectly associated with `CoverageSpans`, and returns additional `Expression`s
     /// representing intermediate values.
     pub fn make_bcb_counters(
@@ -120,7 +120,6 @@ struct BcbCounters<'a> {
     basic_coverage_blocks: &'a mut CoverageGraph,
 }
 
-// FIXME(richkadel): Add unit tests for `BcbCounters` functions/algorithms.
 impl<'a> BcbCounters<'a> {
     fn new(
         coverage_counters: &'a mut CoverageCounters,
diff --git a/compiler/rustc_mir/src/transform/coverage/debug.rs b/compiler/rustc_mir/src/transform/coverage/debug.rs
index ffa795134e257..7f1dc3844b21d 100644
--- a/compiler/rustc_mir/src/transform/coverage/debug.rs
+++ b/compiler/rustc_mir/src/transform/coverage/debug.rs
@@ -127,7 +127,7 @@ pub const NESTED_INDENT: &str = "    ";
 
 const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS";
 
-pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
+pub(super) fn debug_options<'a>() -> &'a DebugOptions {
     static DEBUG_OPTIONS: SyncOnceCell<DebugOptions> = SyncOnceCell::new();
 
     &DEBUG_OPTIONS.get_or_init(|| DebugOptions::from_env())
@@ -136,7 +136,7 @@ pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
 /// Parses and maintains coverage-specific debug options captured from the environment variable
 /// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set.
 #[derive(Debug, Clone)]
-pub(crate) struct DebugOptions {
+pub(super) struct DebugOptions {
     pub allow_unused_expressions: bool,
     counter_format: ExpressionFormat,
 }
@@ -250,7 +250,7 @@ impl Default for ExpressionFormat {
 ///
 /// `DebugCounters` supports a recursive rendering of `Expression` counters, so they can be
 /// presented as nested expressions such as `(bcb3 - (bcb0 + bcb1))`.
-pub(crate) struct DebugCounters {
+pub(super) struct DebugCounters {
     some_counters: Option<FxHashMap<ExpressionOperandId, DebugCounter>>,
 }
 
@@ -386,7 +386,7 @@ impl DebugCounter {
 
 /// If enabled, this data structure captures additional debugging information used when generating
 /// a Graphviz (.dot file) representation of the `CoverageGraph`, for debugging purposes.
-pub(crate) struct GraphvizData {
+pub(super) struct GraphvizData {
     some_bcb_to_coverage_spans_with_counters:
         Option<FxHashMap<BasicCoverageBlock, Vec<(CoverageSpan, CoverageKind)>>>,
     some_bcb_to_dependency_counters: Option<FxHashMap<BasicCoverageBlock, Vec<CoverageKind>>>,
@@ -496,7 +496,7 @@ impl GraphvizData {
 /// directly or indirectly, to compute the coverage counts for all `CoverageSpan`s, and any that are
 /// _not_ used are retained in the `unused_expressions` Vec, to be included in debug output (logs
 /// and/or a `CoverageGraph` graphviz output).
-pub(crate) struct UsedExpressions {
+pub(super) struct UsedExpressions {
     some_used_expression_operands:
         Option<FxHashMap<ExpressionOperandId, Vec<InjectedExpressionId>>>,
     some_unused_expressions:
@@ -626,7 +626,7 @@ impl UsedExpressions {
 }
 
 /// Generates the MIR pass `CoverageSpan`-specific spanview dump file.
-pub(crate) fn dump_coverage_spanview(
+pub(super) fn dump_coverage_spanview(
     tcx: TyCtxt<'tcx>,
     mir_body: &mir::Body<'tcx>,
     basic_coverage_blocks: &CoverageGraph,
@@ -666,7 +666,7 @@ fn span_viewables(
 }
 
 /// Generates the MIR pass coverage-specific graphviz dump file.
-pub(crate) fn dump_coverage_graphviz(
+pub(super) fn dump_coverage_graphviz(
     tcx: TyCtxt<'tcx>,
     mir_body: &mir::Body<'tcx>,
     pass_name: &str,
@@ -815,7 +815,7 @@ fn bcb_to_string_sections(
 
 /// Returns a simple string representation of a `TerminatorKind` variant, indenpendent of any
 /// values it might hold.
-pub(crate) fn term_type(kind: &TerminatorKind<'tcx>) -> &'static str {
+pub(super) fn term_type(kind: &TerminatorKind<'tcx>) -> &'static str {
     match kind {
         TerminatorKind::Goto { .. } => "Goto",
         TerminatorKind::SwitchInt { .. } => "SwitchInt",
diff --git a/compiler/rustc_mir/src/transform/coverage/graph.rs b/compiler/rustc_mir/src/transform/coverage/graph.rs
index c2ed2cbb10002..9d375633dcf51 100644
--- a/compiler/rustc_mir/src/transform/coverage/graph.rs
+++ b/compiler/rustc_mir/src/transform/coverage/graph.rs
@@ -17,7 +17,8 @@ const ID_SEPARATOR: &str = ",";
 /// `CoverageKind` counter (to be added by `CoverageCounters::make_bcb_counters`), and an optional
 /// set of additional counters--if needed--to count incoming edges, if there are more than one.
 /// (These "edge counters" are eventually converted into new MIR `BasicBlock`s.)
-pub(crate) struct CoverageGraph {
+#[derive(Debug)]
+pub(super) struct CoverageGraph {
     bcbs: IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
     bb_to_bcb: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
     pub successors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
@@ -275,7 +276,7 @@ impl graph::WithPredecessors for CoverageGraph {
 
 rustc_index::newtype_index! {
     /// A node in the [control-flow graph][CFG] of CoverageGraph.
-    pub(crate) struct BasicCoverageBlock {
+    pub(super) struct BasicCoverageBlock {
         DEBUG_FORMAT = "bcb{}",
     }
 }
@@ -305,7 +306,7 @@ rustc_index::newtype_index! {
 /// queries (`is_dominated_by()`, `predecessors`, `successors`, etc.) have branch (control flow)
 /// significance.
 #[derive(Debug, Clone)]
-pub(crate) struct BasicCoverageBlockData {
+pub(super) struct BasicCoverageBlockData {
     pub basic_blocks: Vec<BasicBlock>,
     pub counter_kind: Option<CoverageKind>,
     edge_from_bcbs: Option<FxHashMap<BasicCoverageBlock, CoverageKind>>,
@@ -431,7 +432,7 @@ impl BasicCoverageBlockData {
 /// the specific branching BCB, representing the edge between the two. The latter case
 /// distinguishes this incoming edge from other incoming edges to the same `target_bcb`.
 #[derive(Clone, Copy, PartialEq, Eq)]
-pub(crate) struct BcbBranch {
+pub(super) struct BcbBranch {
     pub edge_from_bcb: Option<BasicCoverageBlock>,
     pub target_bcb: BasicCoverageBlock,
 }
@@ -498,9 +499,8 @@ fn bcb_filtered_successors<'a, 'tcx>(
 /// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
 /// CoverageGraph outside all loops. This supports traversing the BCB CFG in a way that
 /// ensures a loop is completely traversed before processing Blocks after the end of the loop.
-// FIXME(richkadel): Add unit tests for TraversalContext.
 #[derive(Debug)]
-pub(crate) struct TraversalContext {
+pub(super) struct TraversalContext {
     /// From one or more backedges returning to a loop header.
     pub loop_backedges: Option<(Vec<BasicCoverageBlock>, BasicCoverageBlock)>,
 
@@ -510,7 +510,7 @@ pub(crate) struct TraversalContext {
     pub worklist: Vec<BasicCoverageBlock>,
 }
 
-pub(crate) struct TraverseCoverageGraphWithLoops {
+pub(super) struct TraverseCoverageGraphWithLoops {
     pub backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
     pub context_stack: Vec<TraversalContext>,
     visited: BitSet<BasicCoverageBlock>,
@@ -642,7 +642,7 @@ impl TraverseCoverageGraphWithLoops {
     }
 }
 
-fn find_loop_backedges(
+pub(super) fn find_loop_backedges(
     basic_coverage_blocks: &CoverageGraph,
 ) -> IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>> {
     let num_bcbs = basic_coverage_blocks.num_nodes();
diff --git a/compiler/rustc_mir/src/transform/coverage/mod.rs b/compiler/rustc_mir/src/transform/coverage/mod.rs
index c55349239b034..192bb6680e420 100644
--- a/compiler/rustc_mir/src/transform/coverage/mod.rs
+++ b/compiler/rustc_mir/src/transform/coverage/mod.rs
@@ -5,6 +5,9 @@ mod debug;
 mod graph;
 mod spans;
 
+#[cfg(test)]
+mod tests;
+
 use counters::CoverageCounters;
 use graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
 use spans::{CoverageSpan, CoverageSpans};
@@ -31,7 +34,7 @@ use rustc_span::{CharPos, Pos, SourceFile, Span, Symbol};
 
 /// A simple error message wrapper for `coverage::Error`s.
 #[derive(Debug)]
-pub(crate) struct Error {
+struct Error {
     message: String,
 }
 
diff --git a/compiler/rustc_mir/src/transform/coverage/spans.rs b/compiler/rustc_mir/src/transform/coverage/spans.rs
index cda4fc125442f..95c49922262f6 100644
--- a/compiler/rustc_mir/src/transform/coverage/spans.rs
+++ b/compiler/rustc_mir/src/transform/coverage/spans.rs
@@ -17,7 +17,7 @@ use rustc_span::{BytePos, Span, SyntaxContext};
 use std::cmp::Ordering;
 
 #[derive(Debug, Copy, Clone)]
-pub(crate) enum CoverageStatement {
+pub(super) enum CoverageStatement {
     Statement(BasicBlock, Span, usize),
     Terminator(BasicBlock, Span),
 }
@@ -66,7 +66,7 @@ impl CoverageStatement {
 /// or is subsumed by the `Span` associated with this `CoverageSpan`, and it's `BasicBlock`
 /// `is_dominated_by()` the `BasicBlock`s in this `CoverageSpan`.
 #[derive(Debug, Clone)]
-pub(crate) struct CoverageSpan {
+pub(super) struct CoverageSpan {
     pub span: Span,
     pub bcb: BasicCoverageBlock,
     pub coverage_statements: Vec<CoverageStatement>,
@@ -214,7 +214,7 @@ pub struct CoverageSpans<'a, 'tcx> {
 }
 
 impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
-    pub(crate) fn generate_coverage_spans(
+    pub(super) fn generate_coverage_spans(
         mir_body: &'a mir::Body<'tcx>,
         body_span: Span,
         basic_coverage_blocks: &'a CoverageGraph,
@@ -645,7 +645,10 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
     }
 }
 
-fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> Option<Span> {
+pub(super) fn filtered_statement_span(
+    statement: &'a Statement<'tcx>,
+    body_span: Span,
+) -> Option<Span> {
     match statement.kind {
         // These statements have spans that are often outside the scope of the executed source code
         // for their parent `BasicBlock`.
@@ -686,7 +689,10 @@ fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O
     }
 }
 
-fn filtered_terminator_span(terminator: &'a Terminator<'tcx>, body_span: Span) -> Option<Span> {
+pub(super) fn filtered_terminator_span(
+    terminator: &'a Terminator<'tcx>,
+    body_span: Span,
+) -> Option<Span> {
     match terminator.kind {
         // These terminators have spans that don't positively contribute to computing a reasonable
         // span of actually executed source code. (For example, SwitchInt terminators extracted from
diff --git a/compiler/rustc_mir/src/transform/coverage/test_macros/Cargo.toml b/compiler/rustc_mir/src/transform/coverage/test_macros/Cargo.toml
new file mode 100644
index 0000000000000..a9d6f0c803d2e
--- /dev/null
+++ b/compiler/rustc_mir/src/transform/coverage/test_macros/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+authors = ["The Rust Project Developers"]
+name = "coverage_test_macros"
+version = "0.0.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+doctest = false
+
+[dependencies]
+proc-macro2 = "1"
diff --git a/compiler/rustc_mir/src/transform/coverage/test_macros/src/lib.rs b/compiler/rustc_mir/src/transform/coverage/test_macros/src/lib.rs
new file mode 100644
index 0000000000000..3d6095d2738cb
--- /dev/null
+++ b/compiler/rustc_mir/src/transform/coverage/test_macros/src/lib.rs
@@ -0,0 +1,6 @@
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn let_bcb(item: TokenStream) -> TokenStream {
+    format!("let bcb{} = graph::BasicCoverageBlock::from_usize({});", item, item).parse().unwrap()
+}
diff --git a/compiler/rustc_mir/src/transform/coverage/tests.rs b/compiler/rustc_mir/src/transform/coverage/tests.rs
new file mode 100644
index 0000000000000..d36f1b8e5f670
--- /dev/null
+++ b/compiler/rustc_mir/src/transform/coverage/tests.rs
@@ -0,0 +1,714 @@
+//! This crate hosts a selection of "unit tests" for components of the `InstrumentCoverage` MIR
+//! pass.
+//!
+//! The tests construct a few "mock" objects, as needed, to support the `InstrumentCoverage`
+//! functions and algorithms. Mocked objects include instances of `mir::Body`; including
+//! `Terminator`s of various `kind`s, and `Span` objects. Some functions used by or used on
+//! real, runtime versions of these mocked-up objects have constraints (such as cross-thread
+//! limitations) and deep dependencies on other elements of the full Rust compiler (which is
+//! *not* constructed or mocked for these tests).
+//!
+//! Of particular note, attempting to simply print elements of the `mir::Body` with default
+//! `Debug` formatting can fail because some `Debug` format implementations require the
+//! `TyCtxt`, obtained via a static global variable that is *not* set for these tests.
+//! Initializing the global type context is prohibitively complex for the scope and scale of these
+//! tests (essentially requiring initializing the entire compiler).
+//!
+//! Also note, some basic features of `Span` also rely on the `Span`s own "session globals", which
+//! are unrelated to the `TyCtxt` global. Without initializing the `Span` session globals, some
+//! basic, coverage-specific features would be impossible to test, but thankfully initializing these
+//! globals is comparitively simpler. The easiest way is to wrap the test in a closure argument
+//! to: `rustc_span::with_default_session_globals(|| { test_here(); })`.
+
+use super::counters;
+use super::debug;
+use super::graph;
+use super::spans;
+
+use coverage_test_macros::let_bcb;
+
+use rustc_data_structures::graph::WithNumNodes;
+use rustc_data_structures::graph::WithSuccessors;
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_middle::mir::coverage::CoverageKind;
+use rustc_middle::mir::*;
+use rustc_middle::ty::{self, DebruijnIndex, TyS, TypeFlags};
+use rustc_span::{self, BytePos, Pos, Span, DUMMY_SP};
+
+// All `TEMP_BLOCK` targets should be replaced before calling `to_body() -> mir::Body`.
+const TEMP_BLOCK: BasicBlock = BasicBlock::MAX;
+
+fn dummy_ty() -> &'static TyS<'static> {
+    thread_local! {
+        static DUMMY_TYS: &'static TyS<'static> = Box::leak(box TyS::make_for_test(
+            ty::Bool,
+            TypeFlags::empty(),
+            DebruijnIndex::from_usize(0),
+        ));
+    }
+
+    &DUMMY_TYS.with(|tys| *tys)
+}
+
+struct MockBlocks<'tcx> {
+    blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
+    dummy_place: Place<'tcx>,
+    next_local: usize,
+}
+
+impl<'tcx> MockBlocks<'tcx> {
+    fn new() -> Self {
+        Self {
+            blocks: IndexVec::new(),
+            dummy_place: Place { local: RETURN_PLACE, projection: ty::List::empty() },
+            next_local: 0,
+        }
+    }
+
+    fn new_temp(&mut self) -> Local {
+        let index = self.next_local;
+        self.next_local += 1;
+        Local::new(index)
+    }
+
+    fn push(&mut self, kind: TerminatorKind<'tcx>) -> BasicBlock {
+        let next_lo = if let Some(last) = self.blocks.last() {
+            self.blocks[last].terminator().source_info.span.hi()
+        } else {
+            BytePos(1)
+        };
+        let next_hi = next_lo + BytePos(1);
+        self.blocks.push(BasicBlockData {
+            statements: vec![],
+            terminator: Some(Terminator {
+                source_info: SourceInfo::outermost(Span::with_root_ctxt(next_lo, next_hi)),
+                kind,
+            }),
+            is_cleanup: false,
+        })
+    }
+
+    fn link(&mut self, from_block: BasicBlock, to_block: BasicBlock) {
+        match self.blocks[from_block].terminator_mut().kind {
+            TerminatorKind::Assert { ref mut target, .. }
+            | TerminatorKind::Call { destination: Some((_, ref mut target)), .. }
+            | TerminatorKind::Drop { ref mut target, .. }
+            | TerminatorKind::DropAndReplace { ref mut target, .. }
+            | TerminatorKind::FalseEdge { real_target: ref mut target, .. }
+            | TerminatorKind::FalseUnwind { real_target: ref mut target, .. }
+            | TerminatorKind::Goto { ref mut target }
+            | TerminatorKind::InlineAsm { destination: Some(ref mut target), .. }
+            | TerminatorKind::Yield { resume: ref mut target, .. } => *target = to_block,
+            ref invalid => bug!("Invalid from_block: {:?}", invalid),
+        }
+    }
+
+    fn add_block_from(
+        &mut self,
+        some_from_block: Option<BasicBlock>,
+        to_kind: TerminatorKind<'tcx>,
+    ) -> BasicBlock {
+        let new_block = self.push(to_kind);
+        if let Some(from_block) = some_from_block {
+            self.link(from_block, new_block);
+        }
+        new_block
+    }
+
+    fn set_branch(&mut self, switchint: BasicBlock, branch_index: usize, to_block: BasicBlock) {
+        match self.blocks[switchint].terminator_mut().kind {
+            TerminatorKind::SwitchInt { ref mut targets, .. } => {
+                let mut branches = targets.iter().collect::<Vec<_>>();
+                let otherwise = if branch_index == branches.len() {
+                    to_block
+                } else {
+                    let old_otherwise = targets.otherwise();
+                    if branch_index > branches.len() {
+                        branches.push((branches.len() as u128, old_otherwise));
+                        while branches.len() < branch_index {
+                            branches.push((branches.len() as u128, TEMP_BLOCK));
+                        }
+                        to_block
+                    } else {
+                        branches[branch_index] = (branch_index as u128, to_block);
+                        old_otherwise
+                    }
+                };
+                *targets = SwitchTargets::new(branches.into_iter(), otherwise);
+            }
+            ref invalid => bug!("Invalid BasicBlock kind or no to_block: {:?}", invalid),
+        }
+    }
+
+    fn call(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
+        self.add_block_from(
+            some_from_block,
+            TerminatorKind::Call {
+                func: Operand::Copy(self.dummy_place.clone()),
+                args: vec![],
+                destination: Some((self.dummy_place.clone(), TEMP_BLOCK)),
+                cleanup: None,
+                from_hir_call: false,
+                fn_span: DUMMY_SP,
+            },
+        )
+    }
+
+    fn goto(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
+        self.add_block_from(some_from_block, TerminatorKind::Goto { target: TEMP_BLOCK })
+    }
+
+    fn switchint(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
+        let switchint_kind = TerminatorKind::SwitchInt {
+            discr: Operand::Move(Place::from(self.new_temp())),
+            switch_ty: dummy_ty(),
+            targets: SwitchTargets::static_if(0, TEMP_BLOCK, TEMP_BLOCK),
+        };
+        self.add_block_from(some_from_block, switchint_kind)
+    }
+
+    fn return_(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
+        self.add_block_from(some_from_block, TerminatorKind::Return)
+    }
+
+    fn to_body(self) -> Body<'tcx> {
+        Body::new_cfg_only(self.blocks)
+    }
+}
+
+fn debug_basic_blocks(mir_body: &Body<'tcx>) -> String {
+    format!(
+        "{:?}",
+        mir_body
+            .basic_blocks()
+            .iter_enumerated()
+            .map(|(bb, data)| {
+                let term = &data.terminator();
+                let kind = &term.kind;
+                let span = term.source_info.span;
+                let sp = format!("(span:{},{})", span.lo().to_u32(), span.hi().to_u32());
+                match kind {
+                    TerminatorKind::Assert { target, .. }
+                    | TerminatorKind::Call { destination: Some((_, target)), .. }
+                    | TerminatorKind::Drop { target, .. }
+                    | TerminatorKind::DropAndReplace { target, .. }
+                    | TerminatorKind::FalseEdge { real_target: target, .. }
+                    | TerminatorKind::FalseUnwind { real_target: target, .. }
+                    | TerminatorKind::Goto { target }
+                    | TerminatorKind::InlineAsm { destination: Some(target), .. }
+                    | TerminatorKind::Yield { resume: target, .. } => {
+                        format!("{}{:?}:{} -> {:?}", sp, bb, debug::term_type(kind), target)
+                    }
+                    TerminatorKind::SwitchInt { targets, .. } => {
+                        format!("{}{:?}:{} -> {:?}", sp, bb, debug::term_type(kind), targets)
+                    }
+                    _ => format!("{}{:?}:{}", sp, bb, debug::term_type(kind)),
+                }
+            })
+            .collect::<Vec<_>>()
+    )
+}
+
+static PRINT_GRAPHS: bool = false;
+
+fn print_mir_graphviz(name: &str, mir_body: &Body<'_>) {
+    if PRINT_GRAPHS {
+        println!(
+            "digraph {} {{\n{}\n}}",
+            name,
+            mir_body
+                .basic_blocks()
+                .iter_enumerated()
+                .map(|(bb, data)| {
+                    format!(
+                        "    {:?} [label=\"{:?}: {}\"];\n{}",
+                        bb,
+                        bb,
+                        debug::term_type(&data.terminator().kind),
+                        mir_body
+                            .successors(bb)
+                            .map(|successor| { format!("    {:?} -> {:?};", bb, successor) })
+                            .collect::<Vec<_>>()
+                            .join("\n")
+                    )
+                })
+                .collect::<Vec<_>>()
+                .join("\n")
+        );
+    }
+}
+
+fn print_coverage_graphviz(
+    name: &str,
+    mir_body: &Body<'_>,
+    basic_coverage_blocks: &graph::CoverageGraph,
+) {
+    if PRINT_GRAPHS {
+        println!(
+            "digraph {} {{\n{}\n}}",
+            name,
+            basic_coverage_blocks
+                .iter_enumerated()
+                .map(|(bcb, bcb_data)| {
+                    format!(
+                        "    {:?} [label=\"{:?}: {}\"];\n{}",
+                        bcb,
+                        bcb,
+                        debug::term_type(&bcb_data.terminator(mir_body).kind),
+                        basic_coverage_blocks
+                            .successors(bcb)
+                            .map(|successor| { format!("    {:?} -> {:?};", bcb, successor) })
+                            .collect::<Vec<_>>()
+                            .join("\n")
+                    )
+                })
+                .collect::<Vec<_>>()
+                .join("\n")
+        );
+    }
+}
+
+/// Create a mock `Body` with a simple flow.
+fn goto_switchint() -> Body<'a> {
+    let mut blocks = MockBlocks::new();
+    let start = blocks.call(None);
+    let goto = blocks.goto(Some(start));
+    let switchint = blocks.switchint(Some(goto));
+    let then_call = blocks.call(None);
+    let else_call = blocks.call(None);
+    blocks.set_branch(switchint, 0, then_call);
+    blocks.set_branch(switchint, 1, else_call);
+    blocks.return_(Some(then_call));
+    blocks.return_(Some(else_call));
+
+    let mir_body = blocks.to_body();
+    print_mir_graphviz("mir_goto_switchint", &mir_body);
+    /* Graphviz character plots created using: `graph-easy --as=boxart`:
+                        ┌────────────────┐
+                        │   bb0: Call    │
+                        └────────────────┘
+                          │
+                          │
+                          ▼
+                        ┌────────────────┐
+                        │   bb1: Goto    │
+                        └────────────────┘
+                          │
+                          │
+                          ▼
+    ┌─────────────┐     ┌────────────────┐
+    │  bb4: Call  │ ◀── │ bb2: SwitchInt │
+    └─────────────┘     └────────────────┘
+      │                   │
+      │                   │
+      ▼                   ▼
+    ┌─────────────┐     ┌────────────────┐
+    │ bb6: Return │     │   bb3: Call    │
+    └─────────────┘     └────────────────┘
+                          │
+                          │
+                          ▼
+                        ┌────────────────┐
+                        │  bb5: Return   │
+                        └────────────────┘
+    */
+    mir_body
+}
+
+macro_rules! assert_successors {
+    ($basic_coverage_blocks:ident, $i:ident, [$($successor:ident),*]) => {
+        let mut successors = $basic_coverage_blocks.successors[$i].clone();
+        successors.sort_unstable();
+        assert_eq!(successors, vec![$($successor),*]);
+    }
+}
+
+#[test]
+fn test_covgraph_goto_switchint() {
+    let mir_body = goto_switchint();
+    if false {
+        println!("basic_blocks = {}", debug_basic_blocks(&mir_body));
+    }
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    print_coverage_graphviz("covgraph_goto_switchint ", &mir_body, &basic_coverage_blocks);
+    /*
+    ┌──────────────┐     ┌─────────────────┐
+    │ bcb2: Return │ ◀── │ bcb0: SwitchInt │
+    └──────────────┘     └─────────────────┘
+                           │
+                           │
+                           ▼
+                         ┌─────────────────┐
+                         │  bcb1: Return   │
+                         └─────────────────┘
+    */
+    assert_eq!(
+        basic_coverage_blocks.num_nodes(),
+        3,
+        "basic_coverage_blocks: {:?}",
+        basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
+    );
+
+    let_bcb!(0);
+    let_bcb!(1);
+    let_bcb!(2);
+
+    assert_successors!(basic_coverage_blocks, bcb0, [bcb1, bcb2]);
+    assert_successors!(basic_coverage_blocks, bcb1, []);
+    assert_successors!(basic_coverage_blocks, bcb2, []);
+}
+
+/// Create a mock `Body` with a loop.
+fn switchint_then_loop_else_return() -> Body<'a> {
+    let mut blocks = MockBlocks::new();
+    let start = blocks.call(None);
+    let switchint = blocks.switchint(Some(start));
+    let then_call = blocks.call(None);
+    blocks.set_branch(switchint, 0, then_call);
+    let backedge_goto = blocks.goto(Some(then_call));
+    blocks.link(backedge_goto, switchint);
+    let else_return = blocks.return_(None);
+    blocks.set_branch(switchint, 1, else_return);
+
+    let mir_body = blocks.to_body();
+    print_mir_graphviz("mir_switchint_then_loop_else_return", &mir_body);
+    /*
+                        ┌────────────────┐
+                        │   bb0: Call    │
+                        └────────────────┘
+                          │
+                          │
+                          ▼
+    ┌─────────────┐     ┌────────────────┐
+    │ bb4: Return │ ◀── │ bb1: SwitchInt │ ◀┐
+    └─────────────┘     └────────────────┘  │
+                          │                 │
+                          │                 │
+                          ▼                 │
+                        ┌────────────────┐  │
+                        │   bb2: Call    │  │
+                        └────────────────┘  │
+                          │                 │
+                          │                 │
+                          ▼                 │
+                        ┌────────────────┐  │
+                        │   bb3: Goto    │ ─┘
+                        └────────────────┘
+    */
+    mir_body
+}
+
+#[test]
+fn test_covgraph_switchint_then_loop_else_return() {
+    let mir_body = switchint_then_loop_else_return();
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    print_coverage_graphviz(
+        "covgraph_switchint_then_loop_else_return",
+        &mir_body,
+        &basic_coverage_blocks,
+    );
+    /*
+                       ┌─────────────────┐
+                       │   bcb0: Call    │
+                       └─────────────────┘
+                         │
+                         │
+                         ▼
+    ┌────────────┐     ┌─────────────────┐
+    │ bcb3: Goto │ ◀── │ bcb1: SwitchInt │ ◀┐
+    └────────────┘     └─────────────────┘  │
+      │                  │                  │
+      │                  │                  │
+      │                  ▼                  │
+      │                ┌─────────────────┐  │
+      │                │  bcb2: Return   │  │
+      │                └─────────────────┘  │
+      │                                     │
+      └─────────────────────────────────────┘
+    */
+    assert_eq!(
+        basic_coverage_blocks.num_nodes(),
+        4,
+        "basic_coverage_blocks: {:?}",
+        basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
+    );
+
+    let_bcb!(0);
+    let_bcb!(1);
+    let_bcb!(2);
+    let_bcb!(3);
+
+    assert_successors!(basic_coverage_blocks, bcb0, [bcb1]);
+    assert_successors!(basic_coverage_blocks, bcb1, [bcb2, bcb3]);
+    assert_successors!(basic_coverage_blocks, bcb2, []);
+    assert_successors!(basic_coverage_blocks, bcb3, [bcb1]);
+}
+
+/// Create a mock `Body` with nested loops.
+fn switchint_loop_then_inner_loop_else_break() -> Body<'a> {
+    let mut blocks = MockBlocks::new();
+    let start = blocks.call(None);
+    let switchint = blocks.switchint(Some(start));
+    let then_call = blocks.call(None);
+    blocks.set_branch(switchint, 0, then_call);
+    let else_return = blocks.return_(None);
+    blocks.set_branch(switchint, 1, else_return);
+
+    let inner_start = blocks.call(Some(then_call));
+    let inner_switchint = blocks.switchint(Some(inner_start));
+    let inner_then_call = blocks.call(None);
+    blocks.set_branch(inner_switchint, 0, inner_then_call);
+    let inner_backedge_goto = blocks.goto(Some(inner_then_call));
+    blocks.link(inner_backedge_goto, inner_switchint);
+    let inner_else_break_goto = blocks.goto(None);
+    blocks.set_branch(inner_switchint, 1, inner_else_break_goto);
+
+    let backedge_goto = blocks.goto(Some(inner_else_break_goto));
+    blocks.link(backedge_goto, switchint);
+
+    let mir_body = blocks.to_body();
+    print_mir_graphviz("mir_switchint_loop_then_inner_loop_else_break", &mir_body);
+    /*
+                        ┌────────────────┐
+                        │   bb0: Call    │
+                        └────────────────┘
+                          │
+                          │
+                          ▼
+    ┌─────────────┐     ┌────────────────┐
+    │ bb3: Return │ ◀── │ bb1: SwitchInt │ ◀─────┐
+    └─────────────┘     └────────────────┘       │
+                          │                      │
+                          │                      │
+                          ▼                      │
+                        ┌────────────────┐       │
+                        │   bb2: Call    │       │
+                        └────────────────┘       │
+                          │                      │
+                          │                      │
+                          ▼                      │
+                        ┌────────────────┐       │
+                        │   bb4: Call    │       │
+                        └────────────────┘       │
+                          │                      │
+                          │                      │
+                          ▼                      │
+    ┌─────────────┐     ┌────────────────┐       │
+    │  bb8: Goto  │ ◀── │ bb5: SwitchInt │ ◀┐    │
+    └─────────────┘     └────────────────┘  │    │
+      │                   │                 │    │
+      │                   │                 │    │
+      ▼                   ▼                 │    │
+    ┌─────────────┐     ┌────────────────┐  │    │
+    │  bb9: Goto  │ ─┐  │   bb6: Call    │  │    │
+    └─────────────┘  │  └────────────────┘  │    │
+                     │    │                 │    │
+                     │    │                 │    │
+                     │    ▼                 │    │
+                     │  ┌────────────────┐  │    │
+                     │  │   bb7: Goto    │ ─┘    │
+                     │  └────────────────┘       │
+                     │                           │
+                     └───────────────────────────┘
+    */
+    mir_body
+}
+
+#[test]
+fn test_covgraph_switchint_loop_then_inner_loop_else_break() {
+    let mir_body = switchint_loop_then_inner_loop_else_break();
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    print_coverage_graphviz(
+        "covgraph_switchint_loop_then_inner_loop_else_break",
+        &mir_body,
+        &basic_coverage_blocks,
+    );
+    /*
+                         ┌─────────────────┐
+                         │   bcb0: Call    │
+                         └─────────────────┘
+                           │
+                           │
+                           ▼
+    ┌──────────────┐     ┌─────────────────┐
+    │ bcb2: Return │ ◀── │ bcb1: SwitchInt │ ◀┐
+    └──────────────┘     └─────────────────┘  │
+                           │                  │
+                           │                  │
+                           ▼                  │
+                         ┌─────────────────┐  │
+                         │   bcb3: Call    │  │
+                         └─────────────────┘  │
+                           │                  │
+                           │                  │
+                           ▼                  │
+    ┌──────────────┐     ┌─────────────────┐  │
+    │  bcb6: Goto  │ ◀── │ bcb4: SwitchInt │ ◀┼────┐
+    └──────────────┘     └─────────────────┘  │    │
+      │                    │                  │    │
+      │                    │                  │    │
+      │                    ▼                  │    │
+      │                  ┌─────────────────┐  │    │
+      │                  │   bcb5: Goto    │ ─┘    │
+      │                  └─────────────────┘       │
+      │                                            │
+      └────────────────────────────────────────────┘
+    */
+    assert_eq!(
+        basic_coverage_blocks.num_nodes(),
+        7,
+        "basic_coverage_blocks: {:?}",
+        basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
+    );
+
+    let_bcb!(0);
+    let_bcb!(1);
+    let_bcb!(2);
+    let_bcb!(3);
+    let_bcb!(4);
+    let_bcb!(5);
+    let_bcb!(6);
+
+    assert_successors!(basic_coverage_blocks, bcb0, [bcb1]);
+    assert_successors!(basic_coverage_blocks, bcb1, [bcb2, bcb3]);
+    assert_successors!(basic_coverage_blocks, bcb2, []);
+    assert_successors!(basic_coverage_blocks, bcb3, [bcb4]);
+    assert_successors!(basic_coverage_blocks, bcb4, [bcb5, bcb6]);
+    assert_successors!(basic_coverage_blocks, bcb5, [bcb1]);
+    assert_successors!(basic_coverage_blocks, bcb6, [bcb4]);
+}
+
+#[test]
+fn test_find_loop_backedges_none() {
+    let mir_body = goto_switchint();
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    if false {
+        println!(
+            "basic_coverage_blocks = {:?}",
+            basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
+        );
+        println!("successors = {:?}", basic_coverage_blocks.successors);
+    }
+    let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
+    assert_eq!(
+        backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
+        0,
+        "backedges: {:?}",
+        backedges
+    );
+}
+
+#[test]
+fn test_find_loop_backedges_one() {
+    let mir_body = switchint_then_loop_else_return();
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
+    assert_eq!(
+        backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
+        1,
+        "backedges: {:?}",
+        backedges
+    );
+
+    let_bcb!(1);
+    let_bcb!(3);
+
+    assert_eq!(backedges[bcb1], vec![bcb3]);
+}
+
+#[test]
+fn test_find_loop_backedges_two() {
+    let mir_body = switchint_loop_then_inner_loop_else_break();
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
+    assert_eq!(
+        backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
+        2,
+        "backedges: {:?}",
+        backedges
+    );
+
+    let_bcb!(1);
+    let_bcb!(4);
+    let_bcb!(5);
+    let_bcb!(6);
+
+    assert_eq!(backedges[bcb1], vec![bcb5]);
+    assert_eq!(backedges[bcb4], vec![bcb6]);
+}
+
+#[test]
+fn test_traverse_coverage_with_loops() {
+    let mir_body = switchint_loop_then_inner_loop_else_break();
+    let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+    let mut traversed_in_order = Vec::new();
+    let mut traversal = graph::TraverseCoverageGraphWithLoops::new(&basic_coverage_blocks);
+    while let Some(bcb) = traversal.next(&basic_coverage_blocks) {
+        traversed_in_order.push(bcb);
+    }
+
+    let_bcb!(6);
+
+    // bcb0 is visited first. Then bcb1 starts the first loop, and all remaining nodes, *except*
+    // bcb6 are inside the first loop.
+    assert_eq!(
+        *traversed_in_order.last().expect("should have elements"),
+        bcb6,
+        "bcb6 should not be visited until all nodes inside the first loop have been visited"
+    );
+}
+
+fn synthesize_body_span_from_terminators(mir_body: &Body<'_>) -> Span {
+    let mut some_span: Option<Span> = None;
+    for (_, data) in mir_body.basic_blocks().iter_enumerated() {
+        let term_span = data.terminator().source_info.span;
+        if let Some(span) = some_span.as_mut() {
+            *span = span.to(term_span);
+        } else {
+            some_span = Some(term_span)
+        }
+    }
+    some_span.expect("body must have at least one BasicBlock")
+}
+
+#[test]
+fn test_make_bcb_counters() {
+    rustc_span::with_default_session_globals(|| {
+        let mir_body = goto_switchint();
+        let body_span = synthesize_body_span_from_terminators(&mir_body);
+        let mut basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
+        let mut coverage_spans = Vec::new();
+        for (bcb, data) in basic_coverage_blocks.iter_enumerated() {
+            if let Some(span) =
+                spans::filtered_terminator_span(data.terminator(&mir_body), body_span)
+            {
+                coverage_spans.push(spans::CoverageSpan::for_terminator(span, bcb, data.last_bb()));
+            }
+        }
+        let mut coverage_counters = counters::CoverageCounters::new(0);
+        let intermediate_expressions = coverage_counters
+            .make_bcb_counters(&mut basic_coverage_blocks, &coverage_spans)
+            .expect("should be Ok");
+        assert_eq!(intermediate_expressions.len(), 0);
+
+        let_bcb!(1);
+        assert_eq!(
+            1, // coincidentally, bcb1 has a `Counter` with id = 1
+            match basic_coverage_blocks[bcb1].counter().expect("should have a counter") {
+                CoverageKind::Counter { id, .. } => id,
+                _ => panic!("expected a Counter"),
+            }
+            .as_u32()
+        );
+
+        let_bcb!(2);
+        assert_eq!(
+            2, // coincidentally, bcb2 has a `Counter` with id = 2
+            match basic_coverage_blocks[bcb2].counter().expect("should have a counter") {
+                CoverageKind::Counter { id, .. } => id,
+                _ => panic!("expected a Counter"),
+            }
+            .as_u32()
+        );
+    });
+}
diff --git a/compiler/rustc_mir/src/transform/inline.rs b/compiler/rustc_mir/src/transform/inline.rs
index 7737672dbde66..aae98f5b6d8d6 100644
--- a/compiler/rustc_mir/src/transform/inline.rs
+++ b/compiler/rustc_mir/src/transform/inline.rs
@@ -7,6 +7,7 @@ use rustc_index::vec::Idx;
 use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
 use rustc_middle::mir::visit::*;
 use rustc_middle::mir::*;
+use rustc_middle::ty::subst::Subst;
 use rustc_middle::ty::{self, ConstKind, Instance, InstanceDef, ParamEnv, Ty, TyCtxt};
 use rustc_span::{hygiene::ExpnKind, ExpnData, Span};
 use rustc_target::spec::abi::Abi;
@@ -28,6 +29,7 @@ pub struct Inline;
 #[derive(Copy, Clone, Debug)]
 struct CallSite<'tcx> {
     callee: Instance<'tcx>,
+    fn_sig: ty::PolyFnSig<'tcx>,
     block: BasicBlock,
     target: Option<BasicBlock>,
     source_info: SourceInfo,
@@ -173,22 +175,23 @@ impl Inliner<'tcx> {
 
         // Only consider direct calls to functions
         let terminator = bb_data.terminator();
-        if let TerminatorKind::Call { func: ref op, ref destination, .. } = terminator.kind {
-            if let ty::FnDef(callee_def_id, substs) = *op.ty(caller_body, self.tcx).kind() {
-                // To resolve an instance its substs have to be fully normalized, so
-                // we do this here.
-                let normalized_substs = self.tcx.normalize_erasing_regions(self.param_env, substs);
+        if let TerminatorKind::Call { ref func, ref destination, .. } = terminator.kind {
+            let func_ty = func.ty(caller_body, self.tcx);
+            if let ty::FnDef(def_id, substs) = *func_ty.kind() {
+                // To resolve an instance its substs have to be fully normalized.
+                let substs = self.tcx.normalize_erasing_regions(self.param_env, substs);
                 let callee =
-                    Instance::resolve(self.tcx, self.param_env, callee_def_id, normalized_substs)
-                        .ok()
-                        .flatten()?;
+                    Instance::resolve(self.tcx, self.param_env, def_id, substs).ok().flatten()?;
 
                 if let InstanceDef::Virtual(..) | InstanceDef::Intrinsic(_) = callee.def {
                     return None;
                 }
 
+                let fn_sig = self.tcx.fn_sig(def_id).subst(self.tcx, substs);
+
                 return Some(CallSite {
                     callee,
+                    fn_sig,
                     block: bb,
                     target: destination.map(|(_, target)| target),
                     source_info: terminator.source_info,
@@ -203,9 +206,8 @@ impl Inliner<'tcx> {
         debug!("should_inline({:?})", callsite);
         let tcx = self.tcx;
 
-        // Cannot inline generators which haven't been transformed yet
-        if callee_body.yield_ty.is_some() {
-            debug!("    yield ty present - not inlining");
+        if callsite.fn_sig.c_variadic() {
+            debug!("callee is variadic - not inlining");
             return false;
         }
 
@@ -218,11 +220,7 @@ impl Inliner<'tcx> {
             return false;
         }
 
-        let self_no_sanitize =
-            self.codegen_fn_attrs.no_sanitize & self.tcx.sess.opts.debugging_opts.sanitizer;
-        let callee_no_sanitize =
-            codegen_fn_attrs.no_sanitize & self.tcx.sess.opts.debugging_opts.sanitizer;
-        if self_no_sanitize != callee_no_sanitize {
+        if self.codegen_fn_attrs.no_sanitize != codegen_fn_attrs.no_sanitize {
             debug!("`callee has incompatible no_sanitize attribute - not inlining");
             return false;
         }
@@ -256,9 +254,9 @@ impl Inliner<'tcx> {
             self.tcx.sess.opts.debugging_opts.inline_mir_threshold
         };
 
-        // Significantly lower the threshold for inlining cold functions
         if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
-            threshold /= 5;
+            debug!("#[cold] present - not inlining");
+            return false;
         }
 
         // Give a bonus functions with a small number of blocks,
@@ -447,7 +445,7 @@ impl Inliner<'tcx> {
                 };
 
                 // Copy the arguments if needed.
-                let args: Vec<_> = self.make_call_args(args, &callsite, caller_body);
+                let args: Vec<_> = self.make_call_args(args, &callsite, caller_body, &callee_body);
 
                 let mut integrator = Integrator {
                     args: &args,
@@ -528,6 +526,7 @@ impl Inliner<'tcx> {
         args: Vec<Operand<'tcx>>,
         callsite: &CallSite<'tcx>,
         caller_body: &mut Body<'tcx>,
+        callee_body: &Body<'tcx>,
     ) -> Vec<Local> {
         let tcx = self.tcx;
 
@@ -554,9 +553,7 @@ impl Inliner<'tcx> {
         //     tmp2 = tuple_tmp.2
         //
         // and the vector is `[closure_ref, tmp0, tmp1, tmp2]`.
-        // FIXME(eddyb) make this check for `"rust-call"` ABI combined with
-        // `callee_body.spread_arg == None`, instead of special-casing closures.
-        if tcx.is_closure(callsite.callee.def_id()) {
+        if callsite.fn_sig.abi() == Abi::RustCall && callee_body.spread_arg.is_none() {
             let mut args = args.into_iter();
             let self_ = self.create_temp_if_necessary(args.next().unwrap(), callsite, caller_body);
             let tuple = self.create_temp_if_necessary(args.next().unwrap(), callsite, caller_body);
diff --git a/compiler/rustc_mir/src/transform/validate.rs b/compiler/rustc_mir/src/transform/validate.rs
index e1e6e71acb5a8..876ecee80c6a1 100644
--- a/compiler/rustc_mir/src/transform/validate.rs
+++ b/compiler/rustc_mir/src/transform/validate.rs
@@ -38,7 +38,9 @@ pub struct Validator {
 impl<'tcx> MirPass<'tcx> for Validator {
     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
         let def_id = body.source.def_id();
-        let param_env = tcx.param_env(def_id);
+        // We need to param_env_reveal_all_normalized, as some optimizations
+        // change types in ways that require unfolding opaque types.
+        let param_env = tcx.param_env_reveal_all_normalized(def_id);
         let mir_phase = self.mir_phase;
 
         let always_live_locals = AlwaysLiveLocals::new(body);
@@ -79,7 +81,6 @@ pub fn equal_up_to_regions(
     }
 
     // Normalize lifetimes away on both sides, then compare.
-    let param_env = param_env.with_reveal_all_normalized(tcx);
     let normalize = |ty: Ty<'tcx>| {
         tcx.normalize_erasing_regions(
             param_env,
@@ -167,17 +168,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             return true;
         }
         // Normalize projections and things like that.
-        // FIXME: We need to reveal_all, as some optimizations change types in ways
-        // that require unfolding opaque types.
-        let param_env = self.param_env.with_reveal_all_normalized(self.tcx);
-        let src = self.tcx.normalize_erasing_regions(param_env, src);
-        let dest = self.tcx.normalize_erasing_regions(param_env, dest);
+        let src = self.tcx.normalize_erasing_regions(self.param_env, src);
+        let dest = self.tcx.normalize_erasing_regions(self.param_env, dest);
 
         // Type-changing assignments can happen when subtyping is used. While
         // all normal lifetimes are erased, higher-ranked types with their
         // late-bound lifetimes are still around and can lead to type
         // differences. So we compare ignoring lifetimes.
-        equal_up_to_regions(self.tcx, param_env, src, dest)
+        equal_up_to_regions(self.tcx, self.param_env, src, dest)
     }
 }
 
@@ -358,6 +356,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
             }
             TerminatorKind::Call { func, args, destination, cleanup, .. } => {
                 let func_ty = func.ty(&self.body.local_decls, self.tcx);
+                let func_ty = self.tcx.normalize_erasing_regions(self.param_env, func_ty);
                 match func_ty.kind() {
                     ty::FnPtr(..) | ty::FnDef(..) => {}
                     _ => self.fail(
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index 6ed7ed575fcb5..47c0400533bd8 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -387,8 +387,9 @@ fn make_mirror_unadjusted<'a, 'tcx>(
                 }
             };
             let upvars = cx
-                .tcx
-                .upvars_mentioned(def_id)
+                .typeck_results()
+                .closure_captures
+                .get(&def_id)
                 .iter()
                 .flat_map(|upvars| upvars.iter())
                 .zip(substs.upvar_tys())
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index da1c54e88b5e2..40aa2db58c720 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -1180,8 +1180,7 @@ impl<'a> Parser<'a> {
     /// Records all tokens consumed by the provided callback,
     /// including the current token. These tokens are collected
     /// into a `LazyTokenStream`, and returned along with the result
-    /// of the callback. The returned `LazyTokenStream` will be `None`
-    /// if not tokens were captured.
+    /// of the callback.
     ///
     /// Note: If your callback consumes an opening delimiter
     /// (including the case where you call `collect_tokens`
@@ -1203,17 +1202,14 @@ impl<'a> Parser<'a> {
 
         let ret = f(self)?;
 
-        // We didn't capture any tokens
-        let num_calls = self.token_cursor.num_next_calls - cursor_snapshot.num_next_calls;
-        if num_calls == 0 {
-            return Ok((ret, None));
-        }
-
         // Produces a `TokenStream` on-demand. Using `cursor_snapshot`
         // and `num_calls`, we can reconstruct the `TokenStream` seen
         // by the callback. This allows us to avoid producing a `TokenStream`
         // if it is never needed - for example, a captured `macro_rules!`
         // argument that is never passed to a proc macro.
+        // In practice token stream creation happens rarely compared to
+        // calls to `collect_tokens` (see some statistics in #78736),
+        // so we are doing as little up-front work as possible.
         //
         // This also makes `Parser` very cheap to clone, since
         // there is no intermediate collection buffer to clone.
@@ -1247,8 +1243,8 @@ impl<'a> Parser<'a> {
 
         let lazy_impl = LazyTokenStreamImpl {
             start_token,
+            num_calls: self.token_cursor.num_next_calls - cursor_snapshot.num_next_calls,
             cursor_snapshot,
-            num_calls,
             desugar_doc_comments: self.desugar_doc_comments,
         };
         Ok((ret, Some(LazyTokenStream::new(lazy_impl))))
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index 7288015e17029..debb873beb93b 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -317,10 +317,11 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
         // swap in a new set of IR maps for this body
         let mut maps = IrMaps::new(self.tcx);
         let hir_id = maps.tcx.hir().body_owner(body.id());
-        let def_id = maps.tcx.hir().local_def_id(hir_id);
+        let local_def_id = maps.tcx.hir().local_def_id(hir_id);
+        let def_id = local_def_id.to_def_id();
 
         // Don't run unused pass for #[derive()]
-        if let Some(parent) = self.tcx.parent(def_id.to_def_id()) {
+        if let Some(parent) = self.tcx.parent(def_id) {
             if let DefKind::Impl = self.tcx.def_kind(parent.expect_local()) {
                 if self.tcx.has_attr(parent, sym::automatically_derived) {
                     return;
@@ -328,8 +329,8 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
             }
         }
 
-        if let Some(upvars) = maps.tcx.upvars_mentioned(def_id) {
-            for (&var_hir_id, _upvar) in upvars {
+        if let Some(captures) = maps.tcx.typeck(local_def_id).closure_captures.get(&def_id) {
+            for &var_hir_id in captures.keys() {
                 let var_name = maps.tcx.hir().name(var_hir_id);
                 maps.add_variable(Upvar(var_hir_id, var_name));
             }
@@ -340,7 +341,7 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
         intravisit::walk_body(&mut maps, body);
 
         // compute liveness
-        let mut lsets = Liveness::new(&mut maps, def_id);
+        let mut lsets = Liveness::new(&mut maps, local_def_id);
         let entry_ln = lsets.compute(&body, hir_id);
         lsets.log_liveness(entry_ln, body.id().hir_id);
 
@@ -397,10 +398,18 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
                 // construction site.
                 let mut call_caps = Vec::new();
                 let closure_def_id = self.tcx.hir().local_def_id(expr.hir_id);
-                if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
-                    call_caps.extend(upvars.iter().map(|(&var_id, upvar)| {
+                if let Some(captures) = self
+                    .tcx
+                    .typeck(closure_def_id)
+                    .closure_captures
+                    .get(&closure_def_id.to_def_id())
+                {
+                    // If closure captures is Some, upvars_mentioned must also be Some
+                    let upvars = self.tcx.upvars_mentioned(closure_def_id).unwrap();
+                    call_caps.extend(captures.keys().map(|var_id| {
+                        let upvar = upvars[var_id];
                         let upvar_ln = self.add_live_node(UpvarNode(upvar.span));
-                        CaptureInfo { ln: upvar_ln, var_hid: var_id }
+                        CaptureInfo { ln: upvar_ln, var_hid: *var_id }
                     }));
                 }
                 self.set_captures(expr.hir_id, call_caps);
@@ -564,6 +573,7 @@ struct Liveness<'a, 'tcx> {
     typeck_results: &'a ty::TypeckResults<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     upvars: Option<&'tcx FxIndexMap<hir::HirId, hir::Upvar>>,
+    closure_captures: Option<&'tcx FxIndexMap<hir::HirId, ty::UpvarId>>,
     successors: IndexVec<LiveNode, Option<LiveNode>>,
     rwu_table: RWUTable,
 
@@ -587,6 +597,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
         let typeck_results = ir.tcx.typeck(body_owner);
         let param_env = ir.tcx.param_env(body_owner);
         let upvars = ir.tcx.upvars_mentioned(body_owner);
+        let closure_captures = typeck_results.closure_captures.get(&body_owner.to_def_id());
 
         let closure_ln = ir.add_live_node(ClosureNode);
         let exit_ln = ir.add_live_node(ExitNode);
@@ -600,6 +611,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
             typeck_results,
             param_env,
             upvars,
+            closure_captures,
             successors: IndexVec::from_elem_n(None, num_live_nodes),
             rwu_table: RWUTable::new(num_live_nodes * num_vars),
             closure_ln,
@@ -850,14 +862,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
         // if they are live on the entry to the closure, since only the closure
         // itself can access them on subsequent calls.
 
-        if let Some(upvars) = self.upvars {
+        if let Some(closure_captures) = self.closure_captures {
             // Mark upvars captured by reference as used after closure exits.
-            for (&var_hir_id, upvar) in upvars.iter().rev() {
-                let upvar_id = ty::UpvarId {
-                    var_path: ty::UpvarPath { hir_id: var_hir_id },
-                    closure_expr_id: self.body_owner,
-                };
-                match self.typeck_results.upvar_capture(upvar_id) {
+            // Since closure_captures is Some, upvars must exists too.
+            let upvars = self.upvars.unwrap();
+            for (&var_hir_id, upvar_id) in closure_captures {
+                let upvar = upvars[&var_hir_id];
+                match self.typeck_results.upvar_capture(*upvar_id) {
                     ty::UpvarCapture::ByRef(_) => {
                         let var = self.variable(var_hir_id, upvar.span);
                         self.acc(self.exit_ln, var, ACC_READ | ACC_USE);
@@ -869,7 +880,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
 
         let succ = self.propagate_through_expr(&body.value, self.exit_ln);
 
-        if self.upvars.is_none() {
+        if self.closure_captures.is_none() {
             // Either not a closure, or closure without any captured variables.
             // No need to determine liveness of captured variables, since there
             // are none.
@@ -1341,7 +1352,21 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
         acc: u32,
     ) -> LiveNode {
         match path.res {
-            Res::Local(hid) => self.access_var(hir_id, hid, succ, acc, path.span),
+            Res::Local(hid) => {
+                let in_upvars = self.upvars.map_or(false, |u| u.contains_key(&hid));
+                let in_captures = self.closure_captures.map_or(false, |c| c.contains_key(&hid));
+
+                match (in_upvars, in_captures) {
+                    (false, _) | (true, true) => self.access_var(hir_id, hid, succ, acc, path.span),
+                    (true, false) => {
+                        // This case is possible when with RFC-2229, a wild pattern
+                        // is used within a closure.
+                        // eg: `let _ = x`. The closure doesn't capture x here,
+                        // even though it's mentioned in the closure.
+                        succ
+                    }
+                }
+            }
             _ => succ,
         }
     }
@@ -1531,11 +1556,15 @@ impl<'tcx> Liveness<'_, 'tcx> {
     }
 
     fn warn_about_unused_upvars(&self, entry_ln: LiveNode) {
-        let upvars = match self.upvars {
+        let closure_captures = match self.closure_captures {
             None => return,
-            Some(upvars) => upvars,
+            Some(closure_captures) => closure_captures,
         };
-        for (&var_hir_id, upvar) in upvars.iter() {
+
+        // If closure_captures is Some(), upvars must be Some() too.
+        let upvars = self.upvars.unwrap();
+        for &var_hir_id in closure_captures.keys() {
+            let upvar = upvars[&var_hir_id];
             let var = self.variable(var_hir_id, upvar.span);
             let upvar_id = ty::UpvarId {
                 var_path: ty::UpvarPath { hir_id: var_hir_id },
diff --git a/compiler/rustc_save_analysis/src/sig.rs b/compiler/rustc_save_analysis/src/sig.rs
index 1bf8160e4c384..2f82d0546ba98 100644
--- a/compiler/rustc_save_analysis/src/sig.rs
+++ b/compiler/rustc_save_analysis/src/sig.rs
@@ -21,7 +21,7 @@
 // references.
 //
 // Signatures do not include visibility info. I'm not sure if this is a feature
-// or an ommission (FIXME).
+// or an omission (FIXME).
 //
 // FIXME where clauses need implementing, defs/refs in generics are mostly missing.
 
@@ -677,7 +677,7 @@ impl<'hir> Sig for hir::Variant<'hir> {
         let mut text = self.ident.to_string();
         match self.data {
             hir::VariantData::Struct(fields, r) => {
-                let id = parent_id.unwrap();
+                let id = parent_id.ok_or("Missing id for Variant's parent")?;
                 let name_def = SigElement {
                     id: id_from_hir_id(id, scx),
                     start: offset,
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index ad58f89d87da7..3a2a3adce35c9 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -318,6 +318,7 @@ symbols! {
         call_mut,
         call_once,
         caller_location,
+        capture_disjoint_fields,
         cdylib,
         ceilf32,
         ceilf64,
@@ -909,6 +910,7 @@ symbols! {
         rustc_args_required_const,
         rustc_attrs,
         rustc_builtin_macro,
+        rustc_capture_analysis,
         rustc_clean,
         rustc_const_stable,
         rustc_const_unstable,
diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_none.rs b/compiler/rustc_target/src/spec/aarch64_unknown_none.rs
index d0ad45153d677..c9f622820dea7 100644
--- a/compiler/rustc_target/src/spec/aarch64_unknown_none.rs
+++ b/compiler/rustc_target/src/spec/aarch64_unknown_none.rs
@@ -10,7 +10,6 @@ use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, Target, TargetOp
 
 pub fn target() -> Target {
     let opts = TargetOptions {
-        vendor: String::new(),
         linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
         linker: Some("rust-lld".to_owned()),
         features: "+strict-align,+neon,+fp-armv8".to_string(),
diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_none_softfloat.rs b/compiler/rustc_target/src/spec/aarch64_unknown_none_softfloat.rs
index 41bd2182905c9..0811871c993aa 100644
--- a/compiler/rustc_target/src/spec/aarch64_unknown_none_softfloat.rs
+++ b/compiler/rustc_target/src/spec/aarch64_unknown_none_softfloat.rs
@@ -10,7 +10,6 @@ use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, Target, TargetOp
 
 pub fn target() -> Target {
     let opts = TargetOptions {
-        vendor: String::new(),
         linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
         linker: Some("rust-lld".to_owned()),
         features: "+strict-align,-neon,-fp-armv8".to_string(),
diff --git a/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs b/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs
index 3685630572371..c6586b79b87f8 100644
--- a/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs
+++ b/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs
@@ -12,7 +12,6 @@ pub fn target() -> Target {
 
         options: TargetOptions {
             endian: "big".to_string(),
-            vendor: String::new(),
             linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
             executables: true,
             linker: Some("rust-lld".to_owned()),
diff --git a/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs b/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs
index 2ff3c8950c483..e3d4397f6123f 100644
--- a/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs
+++ b/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs
@@ -12,7 +12,6 @@ pub fn target() -> Target {
 
         options: TargetOptions {
             endian: "big".to_string(),
-            vendor: String::new(),
             linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
             executables: true,
             linker: Some("rust-lld".to_owned()),
diff --git a/compiler/rustc_target/src/spec/armv7a_none_eabi.rs b/compiler/rustc_target/src/spec/armv7a_none_eabi.rs
index 742b403cff90c..74deab0191652 100644
--- a/compiler/rustc_target/src/spec/armv7a_none_eabi.rs
+++ b/compiler/rustc_target/src/spec/armv7a_none_eabi.rs
@@ -10,9 +10,6 @@
 // bare-metal binaries (the `gcc` linker has the advantage that it knows where C
 // libraries and crt*.o are but it's not much of an advantage here); LLD is also
 // faster
-// - `os` set to `none`. rationale: matches `thumb` targets
-// - `env` and `vendor` are set to an empty string. rationale: matches `thumb`
-// targets
 // - `panic_strategy` set to `abort`. rationale: matches `thumb` targets
 // - `relocation-model` set to `static`; also no PIE, no relro and no dynamic
 // linking. rationale: matches `thumb` targets
@@ -21,7 +18,6 @@ use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, Target, TargetOp
 
 pub fn target() -> Target {
     let opts = TargetOptions {
-        vendor: String::new(),
         linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
         linker: Some("rust-lld".to_owned()),
         features: "+v7,+thumb2,+soft-float,-neon,+strict-align".to_string(),
diff --git a/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs b/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs
index b9cda18d6b46f..c5c720f5fbde4 100644
--- a/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs
+++ b/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs
@@ -9,7 +9,6 @@ use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, Target, TargetOp
 
 pub fn target() -> Target {
     let opts = TargetOptions {
-        vendor: String::new(),
         linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
         linker: Some("rust-lld".to_owned()),
         features: "+v7,+vfp3,-d32,+thumb2,-neon,+strict-align".to_string(),
diff --git a/compiler/rustc_target/src/spec/armv7r_none_eabi.rs b/compiler/rustc_target/src/spec/armv7r_none_eabi.rs
index 440c2434907be..3f49bd8786937 100644
--- a/compiler/rustc_target/src/spec/armv7r_none_eabi.rs
+++ b/compiler/rustc_target/src/spec/armv7r_none_eabi.rs
@@ -11,7 +11,6 @@ pub fn target() -> Target {
         arch: "arm".to_string(),
 
         options: TargetOptions {
-            vendor: String::new(),
             linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
             executables: true,
             linker: Some("rust-lld".to_owned()),
diff --git a/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs b/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs
index c1bf332a72ddf..9b2e8a8058fe7 100644
--- a/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs
+++ b/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs
@@ -11,7 +11,6 @@ pub fn target() -> Target {
         arch: "arm".to_string(),
 
         options: TargetOptions {
-            vendor: String::new(),
             linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
             executables: true,
             linker: Some("rust-lld".to_owned()),
diff --git a/compiler/rustc_target/src/spec/avr_gnu_base.rs b/compiler/rustc_target/src/spec/avr_gnu_base.rs
index 9cc10032c71da..67a7684da2c70 100644
--- a/compiler/rustc_target/src/spec/avr_gnu_base.rs
+++ b/compiler/rustc_target/src/spec/avr_gnu_base.rs
@@ -11,7 +11,6 @@ pub fn target(target_cpu: String) -> Target {
         pointer_width: 16,
         options: TargetOptions {
             c_int_width: "16".to_string(),
-            os: "unknown".to_string(),
             cpu: target_cpu.clone(),
             exe_suffix: ".elf".to_string(),
 
diff --git a/compiler/rustc_target/src/spec/fuchsia_base.rs b/compiler/rustc_target/src/spec/fuchsia_base.rs
index e467c7c8f21e9..5c39773cbe381 100644
--- a/compiler/rustc_target/src/spec/fuchsia_base.rs
+++ b/compiler/rustc_target/src/spec/fuchsia_base.rs
@@ -21,7 +21,6 @@ pub fn opts() -> TargetOptions {
 
     TargetOptions {
         os: "fuchsia".to_string(),
-        vendor: String::new(),
         linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
         linker: Some("rust-lld".to_owned()),
         lld_flavor: LldFlavor::Ld,
diff --git a/compiler/rustc_target/src/spec/mipsel_unknown_none.rs b/compiler/rustc_target/src/spec/mipsel_unknown_none.rs
index a8005927a7beb..0f9d3c3de1543 100644
--- a/compiler/rustc_target/src/spec/mipsel_unknown_none.rs
+++ b/compiler/rustc_target/src/spec/mipsel_unknown_none.rs
@@ -14,7 +14,6 @@ pub fn target() -> Target {
         arch: "mips".to_string(),
 
         options: TargetOptions {
-            vendor: String::new(),
             linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
             cpu: "mips32r2".to_string(),
             features: "+mips32r2,+soft-float,+noabicalls".to_string(),
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index f949bf95a502a..b37783c5820bf 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -713,11 +713,14 @@ pub struct TargetOptions {
     pub endian: String,
     /// Width of c_int type. Defaults to "32".
     pub c_int_width: String,
-    /// OS name to use for conditional compilation. Defaults to "none".
+    /// OS name to use for conditional compilation (`target_os`). Defaults to "none".
+    /// "none" implies a bare metal target without `std` library.
+    /// A couple of targets having `std` also use "unknown" as an `os` value,
+    /// but they are exceptions.
     pub os: String,
-    /// Environment name to use for conditional compilation. Defaults to "".
+    /// Environment name to use for conditional compilation (`target_env`). Defaults to "".
     pub env: String,
-    /// Vendor name to use for conditional compilation. Defaults to "unknown".
+    /// Vendor name to use for conditional compilation (`target_vendor`). Defaults to "unknown".
     pub vendor: String,
     /// Default linker flavor used if `-C linker-flavor` or `-C linker` are not passed
     /// on the command line. Defaults to `LinkerFlavor::Gcc`.
@@ -819,10 +822,23 @@ pub struct TargetOptions {
     /// Only useful for compiling against Illumos/Solaris,
     /// as they have a different set of linker flags. Defaults to false.
     pub is_like_solaris: bool,
-    /// Whether the target toolchain is like Windows'. Only useful for compiling against Windows,
-    /// only really used for figuring out how to find libraries, since Windows uses its own
-    /// library naming convention. Defaults to false.
+    /// Whether the target is like Windows.
+    /// This is a combination of several more specific properties represented as a single flag:
+    ///   - The target uses a Windows ABI,
+    ///   - uses PE/COFF as a format for object code,
+    ///   - uses Windows-style dllexport/dllimport for shared libraries,
+    ///   - uses import libraries and .def files for symbol exports,
+    ///   - executables support setting a subsystem.
     pub is_like_windows: bool,
+    /// Whether the target is like MSVC.
+    /// This is a combination of several more specific properties represented as a single flag:
+    ///   - The target has all the properties from `is_like_windows`
+    ///     (for in-tree targets "is_like_msvc ⇒ is_like_windows" is ensured by a unit test),
+    ///   - has some MSVC-specific Windows ABI properties,
+    ///   - uses a link.exe-like linker,
+    ///   - uses CodeView/PDB for debuginfo and natvis for its visualization,
+    ///   - uses SEH-based unwinding,
+    ///   - supports control flow guard mechanism.
     pub is_like_msvc: bool,
     /// Whether the target toolchain is like Emscripten's. Only useful for compiling with
     /// Emscripten toolchain.
diff --git a/compiler/rustc_target/src/spec/msp430_none_elf.rs b/compiler/rustc_target/src/spec/msp430_none_elf.rs
index ef966cb702ec4..cc2578aa578e8 100644
--- a/compiler/rustc_target/src/spec/msp430_none_elf.rs
+++ b/compiler/rustc_target/src/spec/msp430_none_elf.rs
@@ -9,7 +9,6 @@ pub fn target() -> Target {
 
         options: TargetOptions {
             c_int_width: "16".to_string(),
-            vendor: String::new(),
             executables: true,
 
             // The LLVM backend currently can't generate object files. To
diff --git a/compiler/rustc_target/src/spec/tests/tests_impl.rs b/compiler/rustc_target/src/spec/tests/tests_impl.rs
index f348df7d5a716..9ec8467e0ac44 100644
--- a/compiler/rustc_target/src/spec/tests/tests_impl.rs
+++ b/compiler/rustc_target/src/spec/tests/tests_impl.rs
@@ -8,6 +8,7 @@ pub(super) fn test_target(target: Target) {
 
 impl Target {
     fn check_consistency(&self) {
+        assert!(self.is_like_windows || !self.is_like_msvc);
         // Check that LLD with the given flavor is treated identically to the linker it emulates.
         // If your target really needs to deviate from the rules below, except it and document the
         // reasons.
@@ -16,6 +17,7 @@ impl Target {
                 || self.linker_flavor == LinkerFlavor::Lld(LldFlavor::Link),
             self.lld_flavor == LldFlavor::Link,
         );
+        assert_eq!(self.is_like_msvc, self.lld_flavor == LldFlavor::Link);
         for args in &[
             &self.pre_link_args,
             &self.late_link_args,
@@ -36,5 +38,18 @@ impl Target {
                 && self.post_link_objects_fallback.is_empty())
                 || self.crt_objects_fallback.is_some()
         );
+        // Keep the default "unknown" vendor instead.
+        assert_ne!(self.vendor, "");
+        if !self.can_use_os_unknown() {
+            // Keep the default "none" for bare metal targets instead.
+            assert_ne!(self.os, "unknown");
+        }
+    }
+
+    // Add your target to the whitelist if it has `std` library
+    // and you certainly want "unknown" for the OS name.
+    fn can_use_os_unknown(&self) -> bool {
+        self.llvm_target == "wasm32-unknown-unknown"
+            || (self.env == "sgx" && self.vendor == "fortanix")
     }
 }
diff --git a/compiler/rustc_target/src/spec/thumb_base.rs b/compiler/rustc_target/src/spec/thumb_base.rs
index e550467502754..ec24807fec4ea 100644
--- a/compiler/rustc_target/src/spec/thumb_base.rs
+++ b/compiler/rustc_target/src/spec/thumb_base.rs
@@ -32,7 +32,6 @@ use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, TargetOpti
 pub fn opts() -> TargetOptions {
     // See rust-lang/rfcs#1645 for a discussion about these defaults
     TargetOptions {
-        vendor: String::new(),
         linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
         executables: true,
         // In most cases, LLD is good enough
diff --git a/compiler/rustc_target/src/spec/uefi_msvc_base.rs b/compiler/rustc_target/src/spec/uefi_msvc_base.rs
index 79fe77495e731..322b6f530e9fd 100644
--- a/compiler/rustc_target/src/spec/uefi_msvc_base.rs
+++ b/compiler/rustc_target/src/spec/uefi_msvc_base.rs
@@ -46,15 +46,6 @@ pub fn opts() -> TargetOptions {
         stack_probes: true,
         singlethread: true,
         linker: Some("rust-lld".to_string()),
-        // FIXME: This should likely be `true` inherited from `msvc_base`
-        // because UEFI follows Windows ABI and uses PE/COFF.
-        // The `false` is probably causing ABI bugs right now.
-        is_like_windows: false,
-        // FIXME: This should likely be `true` inherited from `msvc_base`
-        // because UEFI follows Windows ABI and uses PE/COFF.
-        // The `false` is probably causing ABI bugs right now.
-        is_like_msvc: false,
-
         ..base
     }
 }
diff --git a/compiler/rustc_target/src/spec/wasm32_wasi.rs b/compiler/rustc_target/src/spec/wasm32_wasi.rs
index 9c697674f397a..3f44acdc36b2d 100644
--- a/compiler/rustc_target/src/spec/wasm32_wasi.rs
+++ b/compiler/rustc_target/src/spec/wasm32_wasi.rs
@@ -79,7 +79,6 @@ pub fn target() -> Target {
     let mut options = wasm32_base::options();
 
     options.os = "wasi".to_string();
-    options.vendor = String::new();
     options.linker_flavor = LinkerFlavor::Lld(LldFlavor::Wasm);
     options
         .pre_link_args
diff --git a/compiler/rustc_typeck/src/check/upvar.rs b/compiler/rustc_typeck/src/check/upvar.rs
index e9dfef718fde9..e0bbe3cb07917 100644
--- a/compiler/rustc_typeck/src/check/upvar.rs
+++ b/compiler/rustc_typeck/src/check/upvar.rs
@@ -39,10 +39,21 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::def_id::LocalDefId;
 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
 use rustc_infer::infer::UpvarRegion;
-use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId};
+use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, ProjectionKind};
 use rustc_middle::ty::{self, Ty, TyCtxt, UpvarSubsts};
+use rustc_span::sym;
 use rustc_span::{Span, Symbol};
-use std::collections::hash_map::Entry;
+
+/// Describe the relationship between the paths of two places
+/// eg:
+/// - `foo` is ancestor of `foo.bar.baz`
+/// - `foo.bar.baz` is an descendant of `foo.bar`
+/// - `foo.bar` and `foo.baz` are divergent
+enum PlaceAncestryRelation {
+    Ancestor,
+    Descendant,
+    Divergent,
+}
 
 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     pub fn closure_analyze(&self, body: &'tcx hir::Body<'tcx>) {
@@ -111,40 +122,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             None
         };
 
-        if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
-            let mut closure_captures: FxIndexMap<hir::HirId, ty::UpvarId> =
-                FxIndexMap::with_capacity_and_hasher(upvars.len(), Default::default());
-            for (&var_hir_id, _) in upvars.iter() {
-                let upvar_id = ty::UpvarId {
-                    var_path: ty::UpvarPath { hir_id: var_hir_id },
-                    closure_expr_id: closure_def_id.expect_local(),
-                };
-                debug!("seed upvar_id {:?}", upvar_id);
-                // Adding the upvar Id to the list of Upvars, which will be added
-                // to the map for the closure at the end of the for loop.
-                closure_captures.insert(var_hir_id, upvar_id);
-
-                let capture_kind = match capture_clause {
-                    hir::CaptureBy::Value => ty::UpvarCapture::ByValue(None),
-                    hir::CaptureBy::Ref => {
-                        let origin = UpvarRegion(upvar_id, span);
-                        let upvar_region = self.next_region_var(origin);
-                        let upvar_borrow =
-                            ty::UpvarBorrow { kind: ty::ImmBorrow, region: upvar_region };
-                        ty::UpvarCapture::ByRef(upvar_borrow)
-                    }
-                };
+        let local_def_id = closure_def_id.expect_local();
 
-                self.typeck_results.borrow_mut().upvar_capture_map.insert(upvar_id, capture_kind);
-            }
-            // Add the vector of upvars to the map keyed with the closure id.
-            // This gives us an easier access to them without having to call
-            // tcx.upvars again..
-            if !closure_captures.is_empty() {
-                self.typeck_results
-                    .borrow_mut()
-                    .closure_captures
-                    .insert(closure_def_id, closure_captures);
+        let mut capture_information: FxIndexMap<Place<'tcx>, ty::CaptureInfo<'tcx>> =
+            Default::default();
+        if !self.tcx.features().capture_disjoint_fields {
+            if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
+                for (&var_hir_id, _) in upvars.iter() {
+                    let place = self.place_for_root_variable(local_def_id, var_hir_id);
+
+                    debug!("seed place {:?}", place);
+
+                    let upvar_id = ty::UpvarId::new(var_hir_id, local_def_id);
+                    let capture_kind = self.init_capture_kind(capture_clause, upvar_id, span);
+                    let info = ty::CaptureInfo { expr_id: None, capture_kind };
+
+                    capture_information.insert(place, info);
+                }
             }
         }
 
@@ -153,9 +147,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let mut delegate = InferBorrowKind {
             fcx: self,
             closure_def_id,
+            closure_span: span,
+            capture_clause,
             current_closure_kind: ty::ClosureKind::LATTICE_BOTTOM,
             current_origin: None,
-            adjust_upvar_captures: ty::UpvarCaptureMap::default(),
+            capture_information,
         };
         euv::ExprUseVisitor::new(
             &mut delegate,
@@ -166,6 +162,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         )
         .consume_body(body);
 
+        debug!(
+            "For closure={:?}, capture_information={:#?}",
+            closure_def_id, delegate.capture_information
+        );
+        self.log_capture_analysis_first_pass(closure_def_id, &delegate.capture_information, span);
+
         if let Some(closure_substs) = infer_kind {
             // Unify the (as yet unbound) type variable in the closure
             // substs with the kind we inferred.
@@ -182,7 +184,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             }
         }
 
-        self.typeck_results.borrow_mut().upvar_capture_map.extend(delegate.adjust_upvar_captures);
+        self.compute_min_captures(closure_def_id, delegate);
+        self.log_closure_min_capture_info(closure_def_id, span);
+
+        self.min_captures_to_closure_captures_bridge(closure_def_id);
 
         // Now that we've analyzed the closure, we know how each
         // variable is borrowed, and we know what traits the closure
@@ -226,15 +231,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let tcx = self.tcx;
         let closure_def_id = tcx.hir().local_def_id(closure_id);
 
-        tcx.upvars_mentioned(closure_def_id)
+        self.typeck_results
+            .borrow()
+            .closure_captures
+            .get(&closure_def_id.to_def_id())
             .iter()
             .flat_map(|upvars| {
                 upvars.iter().map(|(&var_hir_id, _)| {
                     let upvar_ty = self.node_ty(var_hir_id);
-                    let upvar_id = ty::UpvarId {
-                        var_path: ty::UpvarPath { hir_id: var_hir_id },
-                        closure_expr_id: closure_def_id,
-                    };
+                    let upvar_id = ty::UpvarId::new(var_hir_id, closure_def_id);
                     let capture = self.typeck_results.borrow().upvar_capture(upvar_id);
 
                     debug!("var_id={:?} upvar_ty={:?} capture={:?}", var_hir_id, upvar_ty, capture);
@@ -250,6 +255,296 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             })
             .collect()
     }
+
+    /// Bridge for closure analysis
+    /// ----------------------------
+    ///
+    /// For closure with DefId `c`, the bridge converts structures required for supporting RFC 2229,
+    /// to structures currently used in the compiler for handling closure captures.
+    ///
+    /// For example the following structure will be converted:
+    ///
+    /// closure_min_captures
+    /// foo -> [ {foo.x, ImmBorrow}, {foo.y, MutBorrow} ]
+    /// bar -> [ {bar.z, ByValue}, {bar.q, MutBorrow} ]
+    ///
+    /// to
+    ///
+    /// 1. closure_captures
+    /// foo -> UpvarId(foo, c), bar -> UpvarId(bar, c)
+    ///
+    /// 2. upvar_capture_map
+    /// UpvarId(foo,c) -> MutBorrow, UpvarId(bar, c) -> ByValue
+    fn min_captures_to_closure_captures_bridge(&self, closure_def_id: DefId) {
+        let mut closure_captures: FxIndexMap<hir::HirId, ty::UpvarId> = Default::default();
+        let mut upvar_capture_map = ty::UpvarCaptureMap::default();
+
+        if let Some(min_captures) =
+            self.typeck_results.borrow().closure_min_captures.get(&closure_def_id)
+        {
+            for (var_hir_id, min_list) in min_captures.iter() {
+                for captured_place in min_list {
+                    let place = &captured_place.place;
+                    let capture_info = captured_place.info;
+
+                    let upvar_id = match place.base {
+                        PlaceBase::Upvar(upvar_id) => upvar_id,
+                        base => bug!("Expected upvar, found={:?}", base),
+                    };
+
+                    assert_eq!(upvar_id.var_path.hir_id, *var_hir_id);
+                    assert_eq!(upvar_id.closure_expr_id, closure_def_id.expect_local());
+
+                    closure_captures.insert(*var_hir_id, upvar_id);
+
+                    let new_capture_kind = if let Some(capture_kind) =
+                        upvar_capture_map.get(&upvar_id)
+                    {
+                        // upvar_capture_map only stores the UpvarCapture (CaptureKind),
+                        // so we create a fake capture info with no expression.
+                        let fake_capture_info =
+                            ty::CaptureInfo { expr_id: None, capture_kind: capture_kind.clone() };
+                        determine_capture_info(fake_capture_info, capture_info).capture_kind
+                    } else {
+                        capture_info.capture_kind
+                    };
+                    upvar_capture_map.insert(upvar_id, new_capture_kind);
+                }
+            }
+        }
+        debug!("For closure_def_id={:?}, closure_captures={:#?}", closure_def_id, closure_captures);
+        debug!(
+            "For closure_def_id={:?}, upvar_capture_map={:#?}",
+            closure_def_id, upvar_capture_map
+        );
+
+        if !closure_captures.is_empty() {
+            self.typeck_results
+                .borrow_mut()
+                .closure_captures
+                .insert(closure_def_id, closure_captures);
+
+            self.typeck_results.borrow_mut().upvar_capture_map.extend(upvar_capture_map);
+        }
+    }
+
+    /// Analyzes the information collected by `InferBorrowKind` to compute the min number of
+    /// Places (and corresponding capture kind) that we need to keep track of to support all
+    /// the required captured paths.
+    ///
+    /// Eg:
+    /// ```rust
+    /// struct Point { x: i32, y: i32 }
+    ///
+    /// let s: String;  // hir_id_s
+    /// let mut p: Point; // his_id_p
+    /// let c = || {
+    ///        println!("{}", s);  // L1
+    ///        p.x += 10;  // L2
+    ///        println!("{}" , p.y) // L3
+    ///        println!("{}", p) // L4
+    ///        drop(s);   // L5
+    /// };
+    /// ```
+    /// and let hir_id_L1..5 be the expressions pointing to use of a captured variable on
+    /// the lines L1..5 respectively.
+    ///
+    /// InferBorrowKind results in a structure like this:
+    ///
+    /// ```
+    /// {
+    ///       Place(base: hir_id_s, projections: [], ....) -> (hir_id_L5, ByValue),
+    ///       Place(base: hir_id_p, projections: [Field(0, 0)], ...) -> (hir_id_L2, ByRef(MutBorrow))
+    ///       Place(base: hir_id_p, projections: [Field(1, 0)], ...) -> (hir_id_L3, ByRef(ImmutBorrow))
+    ///       Place(base: hir_id_p, projections: [], ...) -> (hir_id_L4, ByRef(ImmutBorrow))
+    /// ```
+    ///
+    /// After the min capture analysis, we get:
+    /// ```
+    /// {
+    ///       hir_id_s -> [
+    ///            Place(base: hir_id_s, projections: [], ....) -> (hir_id_L4, ByValue)
+    ///       ],
+    ///       hir_id_p -> [
+    ///            Place(base: hir_id_p, projections: [], ...) -> (hir_id_L2, ByRef(MutBorrow)),
+    ///       ],
+    /// ```
+    fn compute_min_captures(
+        &self,
+        closure_def_id: DefId,
+        inferred_info: InferBorrowKind<'_, 'tcx>,
+    ) {
+        let mut root_var_min_capture_list: ty::RootVariableMinCaptureList<'_> = Default::default();
+
+        for (place, capture_info) in inferred_info.capture_information.into_iter() {
+            let var_hir_id = match place.base {
+                PlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
+                base => bug!("Expected upvar, found={:?}", base),
+            };
+
+            // Arrays are captured in entirety, drop Index projections and projections
+            // after Index projections.
+            let first_index_projection =
+                place.projections.split(|proj| ProjectionKind::Index == proj.kind).next();
+            let place = Place {
+                base_ty: place.base_ty,
+                base: place.base,
+                projections: first_index_projection.map_or(Vec::new(), |p| p.to_vec()),
+            };
+
+            let min_cap_list = match root_var_min_capture_list.get_mut(&var_hir_id) {
+                None => {
+                    let min_cap_list = vec![ty::CapturedPlace { place: place, info: capture_info }];
+                    root_var_min_capture_list.insert(var_hir_id, min_cap_list);
+                    continue;
+                }
+                Some(min_cap_list) => min_cap_list,
+            };
+
+            // Go through each entry in the current list of min_captures
+            // - if ancestor is found, update it's capture kind to account for current place's
+            // capture information.
+            //
+            // - if descendant is found, remove it from the list, and update the current place's
+            // capture information to account for the descendants's capture kind.
+            //
+            // We can never be in a case where the list contains both an ancestor and a descendant
+            // Also there can only be ancestor but in case of descendants there might be
+            // multiple.
+
+            let mut descendant_found = false;
+            let mut updated_capture_info = capture_info;
+            min_cap_list.retain(|possible_descendant| {
+                match determine_place_ancestry_relation(&place, &possible_descendant.place) {
+                    // current place is ancestor of possible_descendant
+                    PlaceAncestryRelation::Ancestor => {
+                        descendant_found = true;
+                        updated_capture_info =
+                            determine_capture_info(updated_capture_info, possible_descendant.info);
+                        false
+                    }
+
+                    _ => true,
+                }
+            });
+
+            let mut ancestor_found = false;
+            if !descendant_found {
+                for possible_ancestor in min_cap_list.iter_mut() {
+                    match determine_place_ancestry_relation(&place, &possible_ancestor.place) {
+                        // current place is descendant of possible_ancestor
+                        PlaceAncestryRelation::Descendant => {
+                            ancestor_found = true;
+                            possible_ancestor.info =
+                                determine_capture_info(possible_ancestor.info, capture_info);
+
+                            // Only one ancestor of the current place will be in the list.
+                            break;
+                        }
+                        _ => {}
+                    }
+                }
+            }
+
+            // Only need to insert when we don't have an ancestor in the existing min capture list
+            if !ancestor_found {
+                let captured_place =
+                    ty::CapturedPlace { place: place.clone(), info: updated_capture_info };
+                min_cap_list.push(captured_place);
+            }
+        }
+
+        debug!("For closure={:?}, min_captures={:#?}", closure_def_id, root_var_min_capture_list);
+
+        if !root_var_min_capture_list.is_empty() {
+            self.typeck_results
+                .borrow_mut()
+                .closure_min_captures
+                .insert(closure_def_id, root_var_min_capture_list);
+        }
+    }
+
+    fn init_capture_kind(
+        &self,
+        capture_clause: hir::CaptureBy,
+        upvar_id: ty::UpvarId,
+        closure_span: Span,
+    ) -> ty::UpvarCapture<'tcx> {
+        match capture_clause {
+            hir::CaptureBy::Value => ty::UpvarCapture::ByValue(None),
+            hir::CaptureBy::Ref => {
+                let origin = UpvarRegion(upvar_id, closure_span);
+                let upvar_region = self.next_region_var(origin);
+                let upvar_borrow = ty::UpvarBorrow { kind: ty::ImmBorrow, region: upvar_region };
+                ty::UpvarCapture::ByRef(upvar_borrow)
+            }
+        }
+    }
+
+    fn place_for_root_variable(
+        &self,
+        closure_def_id: LocalDefId,
+        var_hir_id: hir::HirId,
+    ) -> Place<'tcx> {
+        let upvar_id = ty::UpvarId::new(var_hir_id, closure_def_id);
+
+        Place {
+            base_ty: self.node_ty(var_hir_id),
+            base: PlaceBase::Upvar(upvar_id),
+            projections: Default::default(),
+        }
+    }
+
+    fn should_log_capture_analysis(&self, closure_def_id: DefId) -> bool {
+        self.tcx.has_attr(closure_def_id, sym::rustc_capture_analysis)
+    }
+
+    fn log_capture_analysis_first_pass(
+        &self,
+        closure_def_id: rustc_hir::def_id::DefId,
+        capture_information: &FxIndexMap<Place<'tcx>, ty::CaptureInfo<'tcx>>,
+        closure_span: Span,
+    ) {
+        if self.should_log_capture_analysis(closure_def_id) {
+            let mut diag =
+                self.tcx.sess.struct_span_err(closure_span, "First Pass analysis includes:");
+            for (place, capture_info) in capture_information {
+                let capture_str = construct_capture_info_string(self.tcx, place, capture_info);
+                let output_str = format!("Capturing {}", capture_str);
+
+                let span = capture_info.expr_id.map_or(closure_span, |e| self.tcx.hir().span(e));
+                diag.span_note(span, &output_str);
+            }
+            diag.emit();
+        }
+    }
+
+    fn log_closure_min_capture_info(&self, closure_def_id: DefId, closure_span: Span) {
+        if self.should_log_capture_analysis(closure_def_id) {
+            if let Some(min_captures) =
+                self.typeck_results.borrow().closure_min_captures.get(&closure_def_id)
+            {
+                let mut diag =
+                    self.tcx.sess.struct_span_err(closure_span, "Min Capture analysis includes:");
+
+                for (_, min_captures_for_var) in min_captures {
+                    for capture in min_captures_for_var {
+                        let place = &capture.place;
+                        let capture_info = &capture.info;
+
+                        let capture_str =
+                            construct_capture_info_string(self.tcx, place, capture_info);
+                        let output_str = format!("Min Capture {}", capture_str);
+
+                        let span =
+                            capture_info.expr_id.map_or(closure_span, |e| self.tcx.hir().span(e));
+                        diag.span_note(span, &output_str);
+                    }
+                }
+                diag.emit();
+            }
+        }
+    }
 }
 
 struct InferBorrowKind<'a, 'tcx> {
@@ -258,6 +553,10 @@ struct InferBorrowKind<'a, 'tcx> {
     // The def-id of the closure whose kind and upvar accesses are being inferred.
     closure_def_id: DefId,
 
+    closure_span: Span,
+
+    capture_clause: hir::CaptureBy,
+
     // The kind that we have inferred that the current closure
     // requires. Note that we *always* infer a minimal kind, even if
     // we don't always *use* that in the final result (i.e., sometimes
@@ -270,9 +569,29 @@ struct InferBorrowKind<'a, 'tcx> {
     // variable access that caused us to do so.
     current_origin: Option<(Span, Symbol)>,
 
-    // For each upvar that we access, we track the minimal kind of
-    // access we need (ref, ref mut, move, etc).
-    adjust_upvar_captures: ty::UpvarCaptureMap<'tcx>,
+    /// For each Place that is captured by the closure, we track the minimal kind of
+    /// access we need (ref, ref mut, move, etc) and the expression that resulted in such access.
+    ///
+    /// Consider closure where s.str1 is captured via an ImmutableBorrow and
+    /// s.str2 via a MutableBorrow
+    ///
+    /// ```rust
+    /// // Assume that the HirId for the variable definition is `V1`
+    /// let mut s = SomeStruct { str1: format!("s1"), str2: format!("s2") }
+    ///
+    /// let fix_s = |new_s2| {
+    ///     // Assume that the HirId for the expression `s.str1` is `E1`
+    ///     println!("Updating SomeStruct with str1=", s.str1);
+    ///     // Assume that the HirId for the expression `*s.str2` is `E2`
+    ///     s.str2 = new_s2;
+    /// }
+    /// ```
+    ///
+    /// For closure `fix_s`, (at a high level) the map contains
+    ///
+    /// Place { V1, [ProjectionKind::Field(Index=0, Variant=0)] } : CaptureKind { E1, ImmutableBorrow }
+    /// Place { V1, [ProjectionKind::Field(Index=1, Variant=0)] } : CaptureKind { E2, MutableBorrow }
+    capture_information: FxIndexMap<Place<'tcx>, ty::CaptureInfo<'tcx>>,
 }
 
 impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
@@ -314,26 +633,15 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
             var_name(tcx, upvar_id.var_path.hir_id),
         );
 
-        let new_capture = ty::UpvarCapture::ByValue(Some(usage_span));
-        match self.adjust_upvar_captures.entry(upvar_id) {
-            Entry::Occupied(mut e) => {
-                match e.get() {
-                    // We always overwrite `ByRef`, since we require
-                    // that the upvar be available by value.
-                    //
-                    // If we had a previous by-value usage without a specific
-                    // span, use ours instead. Otherwise, keep the first span
-                    // we encountered, since there isn't an obviously better one.
-                    ty::UpvarCapture::ByRef(_) | ty::UpvarCapture::ByValue(None) => {
-                        e.insert(new_capture);
-                    }
-                    _ => {}
-                }
-            }
-            Entry::Vacant(e) => {
-                e.insert(new_capture);
-            }
-        }
+        let capture_info = ty::CaptureInfo {
+            expr_id: Some(diag_expr_id),
+            capture_kind: ty::UpvarCapture::ByValue(Some(usage_span)),
+        };
+
+        let curr_info = self.capture_information[&place_with_id.place];
+        let updated_info = determine_capture_info(curr_info, capture_info);
+
+        self.capture_information[&place_with_id.place] = updated_info;
     }
 
     /// Indicates that `place_with_id` is being directly mutated (e.g., assigned
@@ -349,7 +657,7 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
             place_with_id, diag_expr_id
         );
 
-        if let PlaceBase::Upvar(upvar_id) = place_with_id.place.base {
+        if let PlaceBase::Upvar(_) = place_with_id.place.base {
             let mut borrow_kind = ty::MutBorrow;
             for pointer_ty in place_with_id.place.deref_tys() {
                 match pointer_ty.kind() {
@@ -363,7 +671,7 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
                     _ => (),
                 }
             }
-            self.adjust_upvar_deref(upvar_id, self.fcx.tcx.hir().span(diag_expr_id), borrow_kind);
+            self.adjust_upvar_deref(place_with_id, diag_expr_id, borrow_kind);
         }
     }
 
@@ -377,24 +685,20 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
             place_with_id, diag_expr_id
         );
 
-        if let PlaceBase::Upvar(upvar_id) = place_with_id.place.base {
+        if let PlaceBase::Upvar(_) = place_with_id.place.base {
             if place_with_id.place.deref_tys().any(ty::TyS::is_unsafe_ptr) {
                 // Raw pointers don't inherit mutability.
                 return;
             }
             // for a borrowed pointer to be unique, its base must be unique
-            self.adjust_upvar_deref(
-                upvar_id,
-                self.fcx.tcx.hir().span(diag_expr_id),
-                ty::UniqueImmBorrow,
-            );
+            self.adjust_upvar_deref(place_with_id, diag_expr_id, ty::UniqueImmBorrow);
         }
     }
 
     fn adjust_upvar_deref(
         &mut self,
-        upvar_id: ty::UpvarId,
-        place_span: Span,
+        place_with_id: &PlaceWithHirId<'tcx>,
+        diag_expr_id: hir::HirId,
         borrow_kind: ty::BorrowKind,
     ) {
         assert!(match borrow_kind {
@@ -411,15 +715,16 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
         // upvar, then we need to modify the
         // borrow_kind of the upvar to make sure it
         // is inferred to mutable if necessary
-        self.adjust_upvar_borrow_kind(upvar_id, borrow_kind);
+        self.adjust_upvar_borrow_kind(place_with_id, diag_expr_id, borrow_kind);
 
-        // also need to be in an FnMut closure since this is not an ImmBorrow
-        self.adjust_closure_kind(
-            upvar_id.closure_expr_id,
-            ty::ClosureKind::FnMut,
-            place_span,
-            var_name(tcx, upvar_id.var_path.hir_id),
-        );
+        if let PlaceBase::Upvar(upvar_id) = place_with_id.place.base {
+            self.adjust_closure_kind(
+                upvar_id.closure_expr_id,
+                ty::ClosureKind::FnMut,
+                tcx.hir().span(diag_expr_id),
+                var_name(tcx, upvar_id.var_path.hir_id),
+            );
+        }
     }
 
     /// We infer the borrow_kind with which to borrow upvars in a stack closure.
@@ -427,37 +732,34 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
     /// moving from left to right as needed (but never right to left).
     /// Here the argument `mutbl` is the borrow_kind that is required by
     /// some particular use.
-    fn adjust_upvar_borrow_kind(&mut self, upvar_id: ty::UpvarId, kind: ty::BorrowKind) {
-        let upvar_capture = self
-            .adjust_upvar_captures
-            .get(&upvar_id)
-            .copied()
-            .unwrap_or_else(|| self.fcx.typeck_results.borrow().upvar_capture(upvar_id));
+    fn adjust_upvar_borrow_kind(
+        &mut self,
+        place_with_id: &PlaceWithHirId<'tcx>,
+        diag_expr_id: hir::HirId,
+        kind: ty::BorrowKind,
+    ) {
+        let curr_capture_info = self.capture_information[&place_with_id.place];
+
         debug!(
-            "adjust_upvar_borrow_kind(upvar_id={:?}, upvar_capture={:?}, kind={:?})",
-            upvar_id, upvar_capture, kind
+            "adjust_upvar_borrow_kind(place={:?}, diag_expr_id={:?}, capture_info={:?}, kind={:?})",
+            place_with_id, diag_expr_id, curr_capture_info, kind
         );
 
-        match upvar_capture {
-            ty::UpvarCapture::ByValue(_) => {
-                // Upvar is already by-value, the strongest criteria.
-            }
-            ty::UpvarCapture::ByRef(mut upvar_borrow) => {
-                match (upvar_borrow.kind, kind) {
-                    // Take RHS:
-                    (ty::ImmBorrow, ty::UniqueImmBorrow | ty::MutBorrow)
-                    | (ty::UniqueImmBorrow, ty::MutBorrow) => {
-                        upvar_borrow.kind = kind;
-                        self.adjust_upvar_captures
-                            .insert(upvar_id, ty::UpvarCapture::ByRef(upvar_borrow));
-                    }
-                    // Take LHS:
-                    (ty::ImmBorrow, ty::ImmBorrow)
-                    | (ty::UniqueImmBorrow, ty::ImmBorrow | ty::UniqueImmBorrow)
-                    | (ty::MutBorrow, _) => {}
-                }
-            }
-        }
+        if let ty::UpvarCapture::ByValue(_) = curr_capture_info.capture_kind {
+            // It's already captured by value, we don't need to do anything here
+            return;
+        } else if let ty::UpvarCapture::ByRef(curr_upvar_borrow) = curr_capture_info.capture_kind {
+            // Use the same region as the current capture information
+            // Doesn't matter since only one of the UpvarBorrow will be used.
+            let new_upvar_borrow = ty::UpvarBorrow { kind, region: curr_upvar_borrow.region };
+
+            let capture_info = ty::CaptureInfo {
+                expr_id: Some(diag_expr_id),
+                capture_kind: ty::UpvarCapture::ByRef(new_upvar_borrow),
+            };
+            let updated_info = determine_capture_info(curr_capture_info, capture_info);
+            self.capture_information[&place_with_id.place] = updated_info;
+        };
     }
 
     fn adjust_closure_kind(
@@ -501,6 +803,28 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
             }
         }
     }
+
+    fn init_capture_info_for_place(
+        &mut self,
+        place_with_id: &PlaceWithHirId<'tcx>,
+        diag_expr_id: hir::HirId,
+    ) {
+        if let PlaceBase::Upvar(upvar_id) = place_with_id.place.base {
+            assert_eq!(self.closure_def_id.expect_local(), upvar_id.closure_expr_id);
+
+            let capture_kind =
+                self.fcx.init_capture_kind(self.capture_clause, upvar_id, self.closure_span);
+
+            let expr_id = Some(diag_expr_id);
+            let capture_info = ty::CaptureInfo { expr_id, capture_kind };
+
+            debug!("Capturing new place {:?}, capture_info={:?}", place_with_id, capture_info);
+
+            self.capture_information.insert(place_with_id.place.clone(), capture_info);
+        } else {
+            debug!("Not upvar: {:?}", place_with_id);
+        }
+    }
 }
 
 impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
@@ -514,7 +838,11 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
             "consume(place_with_id={:?}, diag_expr_id={:?}, mode={:?})",
             place_with_id, diag_expr_id, mode
         );
-        self.adjust_upvar_borrow_kind_for_consume(&place_with_id, diag_expr_id, mode);
+        if !self.capture_information.contains_key(&place_with_id.place) {
+            self.init_capture_info_for_place(place_with_id, diag_expr_id);
+        }
+
+        self.adjust_upvar_borrow_kind_for_consume(place_with_id, diag_expr_id, mode);
     }
 
     fn borrow(
@@ -528,6 +856,10 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
             place_with_id, diag_expr_id, bk
         );
 
+        if !self.capture_information.contains_key(&place_with_id.place) {
+            self.init_capture_info_for_place(place_with_id, diag_expr_id);
+        }
+
         match bk {
             ty::ImmBorrow => {}
             ty::UniqueImmBorrow => {
@@ -541,10 +873,174 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
 
     fn mutate(&mut self, assignee_place: &PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId) {
         debug!("mutate(assignee_place={:?}, diag_expr_id={:?})", assignee_place, diag_expr_id);
+
+        if !self.capture_information.contains_key(&assignee_place.place) {
+            self.init_capture_info_for_place(assignee_place, diag_expr_id);
+        }
+
         self.adjust_upvar_borrow_kind_for_mut(assignee_place, diag_expr_id);
     }
 }
 
+fn construct_capture_info_string(
+    tcx: TyCtxt<'_>,
+    place: &Place<'tcx>,
+    capture_info: &ty::CaptureInfo<'tcx>,
+) -> String {
+    let variable_name = match place.base {
+        PlaceBase::Upvar(upvar_id) => var_name(tcx, upvar_id.var_path.hir_id).to_string(),
+        _ => bug!("Capture_information should only contain upvars"),
+    };
+
+    let mut projections_str = String::new();
+    for (i, item) in place.projections.iter().enumerate() {
+        let proj = match item.kind {
+            ProjectionKind::Field(a, b) => format!("({:?}, {:?})", a, b),
+            ProjectionKind::Deref => String::from("Deref"),
+            ProjectionKind::Index => String::from("Index"),
+            ProjectionKind::Subslice => String::from("Subslice"),
+        };
+        if i != 0 {
+            projections_str.push_str(",");
+        }
+        projections_str.push_str(proj.as_str());
+    }
+
+    let capture_kind_str = match capture_info.capture_kind {
+        ty::UpvarCapture::ByValue(_) => "ByValue".into(),
+        ty::UpvarCapture::ByRef(borrow) => format!("{:?}", borrow.kind),
+    };
+    format!("{}[{}] -> {}", variable_name, projections_str, capture_kind_str)
+}
+
 fn var_name(tcx: TyCtxt<'_>, var_hir_id: hir::HirId) -> Symbol {
     tcx.hir().name(var_hir_id)
 }
+
+/// Helper function to determine if we need to escalate CaptureKind from
+/// CaptureInfo A to B and returns the escalated CaptureInfo.
+/// (Note: CaptureInfo contains CaptureKind and an expression that led to capture it in that way)
+///
+/// If both `CaptureKind`s are considered equivalent, then the CaptureInfo is selected based
+/// on the `CaptureInfo` containing an associated expression id.
+///
+/// If both the CaptureKind and Expression are considered to be equivalent,
+/// then `CaptureInfo` A is preferred. This can be useful in cases where we want to priortize
+/// expressions reported back to the user as part of diagnostics based on which appears earlier
+/// in the closure. This can be acheived simply by calling
+/// `determine_capture_info(existing_info, current_info)`. This works out because the
+/// expressions that occur earlier in the closure body than the current expression are processed before.
+/// Consider the following example
+/// ```rust
+/// let mut p: Point { x: 10, y: 10 };
+///
+/// let c = || {
+///     p.x     += 10;
+/// // ^ E1 ^
+///     // ...
+///     // More code
+///     // ...
+///     p.x += 10; // E2
+/// // ^ E2 ^
+/// }
+/// ```
+/// `CaptureKind` associated with both `E1` and `E2` will be ByRef(MutBorrow),
+/// and both have an expression associated, however for diagnostics we prefer reporting
+/// `E1` since it appears earlier in the closure body. When `E2` is being processed we
+/// would've already handled `E1`, and have an existing capture_information for it.
+/// Calling `determine_capture_info(existing_info_e1, current_info_e2)` will return
+/// `existing_info_e1` in this case, allowing us to point to `E1` in case of diagnostics.
+fn determine_capture_info(
+    capture_info_a: ty::CaptureInfo<'tcx>,
+    capture_info_b: ty::CaptureInfo<'tcx>,
+) -> ty::CaptureInfo<'tcx> {
+    // If the capture kind is equivalent then, we don't need to escalate and can compare the
+    // expressions.
+    let eq_capture_kind = match (capture_info_a.capture_kind, capture_info_b.capture_kind) {
+        (ty::UpvarCapture::ByValue(_), ty::UpvarCapture::ByValue(_)) => {
+            // We don't need to worry about the spans being ignored here.
+            //
+            // The expr_id in capture_info corresponds to the span that is stored within
+            // ByValue(span) and therefore it gets handled with priortizing based on
+            // expressions below.
+            true
+        }
+        (ty::UpvarCapture::ByRef(ref_a), ty::UpvarCapture::ByRef(ref_b)) => {
+            ref_a.kind == ref_b.kind
+        }
+        (ty::UpvarCapture::ByValue(_), _) | (ty::UpvarCapture::ByRef(_), _) => false,
+    };
+
+    if eq_capture_kind {
+        match (capture_info_a.expr_id, capture_info_b.expr_id) {
+            (Some(_), _) | (None, None) => capture_info_a,
+            (None, Some(_)) => capture_info_b,
+        }
+    } else {
+        // We select the CaptureKind which ranks higher based the following priority order:
+        // ByValue > MutBorrow > UniqueImmBorrow > ImmBorrow
+        match (capture_info_a.capture_kind, capture_info_b.capture_kind) {
+            (ty::UpvarCapture::ByValue(_), _) => capture_info_a,
+            (_, ty::UpvarCapture::ByValue(_)) => capture_info_b,
+            (ty::UpvarCapture::ByRef(ref_a), ty::UpvarCapture::ByRef(ref_b)) => {
+                match (ref_a.kind, ref_b.kind) {
+                    // Take LHS:
+                    (ty::UniqueImmBorrow | ty::MutBorrow, ty::ImmBorrow)
+                    | (ty::MutBorrow, ty::UniqueImmBorrow) => capture_info_a,
+
+                    // Take RHS:
+                    (ty::ImmBorrow, ty::UniqueImmBorrow | ty::MutBorrow)
+                    | (ty::UniqueImmBorrow, ty::MutBorrow) => capture_info_b,
+
+                    (ty::ImmBorrow, ty::ImmBorrow)
+                    | (ty::UniqueImmBorrow, ty::UniqueImmBorrow)
+                    | (ty::MutBorrow, ty::MutBorrow) => {
+                        bug!("Expected unequal capture kinds");
+                    }
+                }
+            }
+        }
+    }
+}
+
+/// Determines the Ancestry relationship of Place A relative to Place B
+///
+/// `PlaceAncestryRelation::Ancestor` implies Place A is ancestor of Place B
+/// `PlaceAncestryRelation::Descendant` implies Place A is descendant of Place B
+/// `PlaceAncestryRelation::Divergent` implies neither of them is the ancestor of the other.
+fn determine_place_ancestry_relation(
+    place_a: &Place<'tcx>,
+    place_b: &Place<'tcx>,
+) -> PlaceAncestryRelation {
+    // If Place A and Place B, don't start off from the same root variable, they are divergent.
+    if place_a.base != place_b.base {
+        return PlaceAncestryRelation::Divergent;
+    }
+
+    // Assume of length of projections_a = n
+    let projections_a = &place_a.projections;
+
+    // Assume of length of projections_b = m
+    let projections_b = &place_b.projections;
+
+    let mut same_initial_projections = true;
+
+    for (proj_a, proj_b) in projections_a.iter().zip(projections_b.iter()) {
+        if proj_a != proj_b {
+            same_initial_projections = false;
+            break;
+        }
+    }
+
+    if same_initial_projections {
+        // First min(n, m) projections are the same
+        // Select Ancestor/Descendant
+        if projections_b.len() >= projections_a.len() {
+            PlaceAncestryRelation::Ancestor
+        } else {
+            PlaceAncestryRelation::Descendant
+        }
+    } else {
+        PlaceAncestryRelation::Divergent
+    }
+}
diff --git a/compiler/rustc_typeck/src/expr_use_visitor.rs b/compiler/rustc_typeck/src/expr_use_visitor.rs
index 57bd89b9d3da9..83cc1da69851f 100644
--- a/compiler/rustc_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_typeck/src/expr_use_visitor.rs
@@ -18,7 +18,6 @@ use rustc_middle::ty::{self, adjustment, TyCtxt};
 use rustc_target::abi::VariantIdx;
 
 use crate::mem_categorization as mc;
-use rustc_span::Span;
 
 ///////////////////////////////////////////////////////////////////////////
 // The Delegate trait
@@ -73,6 +72,7 @@ pub enum MutateMode {
 // This is the code that actually walks the tree.
 pub struct ExprUseVisitor<'a, 'tcx> {
     mc: mc::MemCategorizationContext<'a, 'tcx>,
+    body_owner: LocalDefId,
     delegate: &'a mut dyn Delegate<'tcx>,
 }
 
@@ -110,6 +110,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
     ) -> Self {
         ExprUseVisitor {
             mc: mc::MemCategorizationContext::new(infcx, param_env, body_owner, typeck_results),
+            body_owner,
             delegate,
         }
     }
@@ -329,8 +330,8 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
                 self.consume_expr(base);
             }
 
-            hir::ExprKind::Closure(_, _, _, fn_decl_span, _) => {
-                self.walk_captures(expr, fn_decl_span);
+            hir::ExprKind::Closure(..) => {
+                self.walk_captures(expr);
             }
 
             hir::ExprKind::Box(ref base) => {
@@ -529,7 +530,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
         debug!("walk_pat(discr_place={:?}, pat={:?})", discr_place, pat);
 
         let tcx = self.tcx();
-        let ExprUseVisitor { ref mc, ref mut delegate } = *self;
+        let ExprUseVisitor { ref mc, body_owner: _, ref mut delegate } = *self;
         return_if_err!(mc.cat_pattern(discr_place.clone(), pat, |place, pat| {
             if let PatKind::Binding(_, canonical_id, ..) = pat.kind {
                 debug!("walk_pat: binding place={:?} pat={:?}", place, pat,);
@@ -569,50 +570,78 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
         }));
     }
 
-    fn walk_captures(&mut self, closure_expr: &hir::Expr<'_>, fn_decl_span: Span) {
+    /// Handle the case where the current body contains a closure.
+    ///
+    /// When the current body being handled is a closure, then we must make sure that
+    /// - The parent closure only captures Places from the nested closure that are not local to it.
+    ///
+    /// In the following example the closures `c` only captures `p.x`` even though `incr`
+    /// is a capture of the nested closure
+    ///
+    /// ```rust
+    /// let p = ..;
+    /// let c = || {
+    ///    let incr = 10;
+    ///    let nested = || p.x += incr;
+    /// }
+    /// ```
+    ///
+    /// - When reporting the Place back to the Delegate, ensure that the UpvarId uses the enclosing
+    /// closure as the DefId.
+    fn walk_captures(&mut self, closure_expr: &hir::Expr<'_>) {
         debug!("walk_captures({:?})", closure_expr);
 
-        let closure_def_id = self.tcx().hir().local_def_id(closure_expr.hir_id);
-        if let Some(upvars) = self.tcx().upvars_mentioned(closure_def_id) {
-            for &var_id in upvars.keys() {
-                let upvar_id = ty::UpvarId {
-                    var_path: ty::UpvarPath { hir_id: var_id },
-                    closure_expr_id: closure_def_id,
-                };
-                let upvar_capture = self.mc.typeck_results.upvar_capture(upvar_id);
-                let captured_place = return_if_err!(self.cat_captured_var(
-                    closure_expr.hir_id,
-                    fn_decl_span,
-                    var_id,
-                ));
-                match upvar_capture {
-                    ty::UpvarCapture::ByValue(_) => {
-                        let mode = copy_or_move(&self.mc, &captured_place);
-                        self.delegate.consume(&captured_place, captured_place.hir_id, mode);
-                    }
-                    ty::UpvarCapture::ByRef(upvar_borrow) => {
-                        self.delegate.borrow(
-                            &captured_place,
-                            captured_place.hir_id,
-                            upvar_borrow.kind,
-                        );
+        let closure_def_id = self.tcx().hir().local_def_id(closure_expr.hir_id).to_def_id();
+        let upvars = self.tcx().upvars_mentioned(self.body_owner);
+
+        // For purposes of this function, generator and closures are equivalent.
+        let body_owner_is_closure = match self.tcx().type_of(self.body_owner.to_def_id()).kind() {
+            ty::Closure(..) | ty::Generator(..) => true,
+            _ => false,
+        };
+
+        if let Some(min_captures) = self.mc.typeck_results.closure_min_captures.get(&closure_def_id)
+        {
+            for (var_hir_id, min_list) in min_captures.iter() {
+                if upvars.map_or(body_owner_is_closure, |upvars| !upvars.contains_key(var_hir_id)) {
+                    // The nested closure might be capturing the current (enclosing) closure's local variables.
+                    // We check if the root variable is ever mentioned within the enclosing closure, if not
+                    // then for the current body (if it's a closure) these aren't captures, we will ignore them.
+                    continue;
+                }
+                for captured_place in min_list {
+                    let place = &captured_place.place;
+                    let capture_info = captured_place.info;
+
+                    let upvar_id = if body_owner_is_closure {
+                        // Mark the place to be captured by the enclosing closure
+                        ty::UpvarId::new(*var_hir_id, self.body_owner)
+                    } else {
+                        ty::UpvarId::new(*var_hir_id, closure_def_id.expect_local())
+                    };
+                    let place_with_id = PlaceWithHirId::new(
+                        capture_info.expr_id.unwrap_or(closure_expr.hir_id),
+                        place.base_ty,
+                        PlaceBase::Upvar(upvar_id),
+                        place.projections.clone(),
+                    );
+                    match capture_info.capture_kind {
+                        ty::UpvarCapture::ByValue(_) => {
+                            let mode = copy_or_move(&self.mc, &place_with_id);
+                            self.delegate.consume(&place_with_id, place_with_id.hir_id, mode);
+                        }
+                        ty::UpvarCapture::ByRef(upvar_borrow) => {
+                            self.delegate.borrow(
+                                &place_with_id,
+                                place_with_id.hir_id,
+                                upvar_borrow.kind,
+                            );
+                        }
                     }
                 }
             }
         }
     }
-
-    fn cat_captured_var(
-        &mut self,
-        closure_hir_id: hir::HirId,
-        closure_span: Span,
-        var_id: hir::HirId,
-    ) -> mc::McResult<PlaceWithHirId<'tcx>> {
-        // Create the place for the variable being borrowed, from the
-        // perspective of the creator (parent) of the closure.
-        let var_ty = self.mc.node_ty(var_id)?;
-        self.mc.cat_res(closure_hir_id, closure_span, var_ty, Res::Local(var_id))
-    }
 }
 
 fn copy_or_move<'a, 'tcx>(
diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs
index 49122f53d33ad..7151d3763f01f 100644
--- a/library/alloc/src/collections/btree/map.rs
+++ b/library/alloc/src/collections/btree/map.rs
@@ -863,6 +863,30 @@ impl<K: Ord, V> BTreeMap<K, V> {
         }
     }
 
+    /// Retains only the elements specified by the predicate.
+    ///
+    /// In other words, remove all pairs `(k, v)` such that `f(&k, &mut v)` returns `false`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(btree_retain)]
+    /// use std::collections::BTreeMap;
+    ///
+    /// let mut map: BTreeMap<i32, i32> = (0..8).map(|x| (x, x*10)).collect();
+    /// // Keep only the elements with even-numbered keys.
+    /// map.retain(|&k, _| k % 2 == 0);
+    /// assert!(map.into_iter().eq(vec![(0, 0), (2, 20), (4, 40), (6, 60)]));
+    /// ```
+    #[inline]
+    #[unstable(feature = "btree_retain", issue = "79025")]
+    pub fn retain<F>(&mut self, mut f: F)
+    where
+        F: FnMut(&K, &mut V) -> bool,
+    {
+        self.drain_filter(|k, v| !f(k, v));
+    }
+
     /// Moves all elements from `other` into `Self`, leaving `other` empty.
     ///
     /// # Examples
diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs
index dd3ebcccf76a5..11dbb584abdac 100644
--- a/library/alloc/src/collections/btree/map/tests.rs
+++ b/library/alloc/src/collections/btree/map/tests.rs
@@ -808,6 +808,17 @@ fn test_range_mut() {
     map.check();
 }
 
+#[test]
+fn test_retain() {
+    let mut map: BTreeMap<i32, i32> = (0..100).map(|x| (x, x * 10)).collect();
+
+    map.retain(|&k, _| k % 2 == 0);
+    assert_eq!(map.len(), 50);
+    assert_eq!(map[&2], 20);
+    assert_eq!(map[&4], 40);
+    assert_eq!(map[&6], 60);
+}
+
 mod test_drain_filter {
     use super::*;
 
diff --git a/library/alloc/src/collections/btree/set.rs b/library/alloc/src/collections/btree/set.rs
index 684019f8f5f5e..1a807100653bc 100644
--- a/library/alloc/src/collections/btree/set.rs
+++ b/library/alloc/src/collections/btree/set.rs
@@ -798,6 +798,30 @@ impl<T: Ord> BTreeSet<T> {
         Recover::take(&mut self.map, value)
     }
 
+    /// Retains only the elements specified by the predicate.
+    ///
+    /// In other words, remove all elements `e` such that `f(&e)` returns `false`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(btree_retain)]
+    /// use std::collections::BTreeSet;
+    ///
+    /// let xs = [1, 2, 3, 4, 5, 6];
+    /// let mut set: BTreeSet<i32> = xs.iter().cloned().collect();
+    /// // Keep only the even numbers.
+    /// set.retain(|&k| k % 2 == 0);
+    /// assert!(set.iter().eq([2, 4, 6].iter()));
+    /// ```
+    #[unstable(feature = "btree_retain", issue = "79025")]
+    pub fn retain<F>(&mut self, mut f: F)
+    where
+        F: FnMut(&T) -> bool,
+    {
+        self.drain_filter(|v| !f(v));
+    }
+
     /// Moves all elements from `other` into `Self`, leaving `other` empty.
     ///
     /// # Examples
diff --git a/library/alloc/src/collections/btree/set/tests.rs b/library/alloc/src/collections/btree/set/tests.rs
index 52cde8299e418..ef40a048a382e 100644
--- a/library/alloc/src/collections/btree/set/tests.rs
+++ b/library/alloc/src/collections/btree/set/tests.rs
@@ -324,6 +324,17 @@ fn test_is_subset() {
     assert_eq!(is_subset(&[99, 100], &large), false);
 }
 
+#[test]
+fn test_retain() {
+    let xs = [1, 2, 3, 4, 5, 6];
+    let mut set: BTreeSet<i32> = xs.iter().cloned().collect();
+    set.retain(|&k| k % 2 == 0);
+    assert_eq!(set.len(), 3);
+    assert!(set.contains(&2));
+    assert!(set.contains(&4));
+    assert!(set.contains(&6));
+}
+
 #[test]
 fn test_drain_filter() {
     let mut x: BTreeSet<_> = [1].iter().copied().collect();
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 9f037890483cf..ca140b9d27828 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -178,6 +178,7 @@ const LLVM_TOOLS: &[&str] = &[
     "llvm-size",     // used to prints the size of the linker sections of a program
     "llvm-strip",    // used to discard symbols from binary files to reduce their size
     "llvm-ar",       // used for creating and modifying archive files
+    "llvm-as",       // used to convert LLVM assembly to LLVM bitcode
     "llvm-dis",      // used to disassemble LLVM bitcode
     "llc",           // used to compile LLVM bytecode
     "opt",           // used to optimize LLVM bytecode
diff --git a/src/test/mir-opt/inline/inline-compatibility.rs b/src/test/mir-opt/inline/inline-compatibility.rs
index ff9049edb4f2c..30aff0a64efb9 100644
--- a/src/test/mir-opt/inline/inline-compatibility.rs
+++ b/src/test/mir-opt/inline/inline-compatibility.rs
@@ -1,12 +1,11 @@
 // Checks that only functions with compatible attributes are inlined.
 //
 // only-x86_64
-// needs-sanitizer-address
-// compile-flags: -Zsanitizer=address
 
 #![crate_type = "lib"]
 #![feature(no_sanitize)]
 #![feature(target_feature_11)]
+#![feature(c_variadic)]
 
 // EMIT_MIR inline_compatibility.inlined_target_feature.Inline.diff
 #[target_feature(enable = "sse2")]
@@ -35,5 +34,22 @@ pub unsafe fn not_inlined_no_sanitize() {
 pub unsafe fn target_feature() {}
 
 #[inline]
-#[no_sanitize(address, memory)]
+#[no_sanitize(address)]
 pub unsafe fn no_sanitize() {}
+
+// EMIT_MIR inline_compatibility.not_inlined_c_variadic.Inline.diff
+pub unsafe fn not_inlined_c_variadic() {
+    let s = sum(4u32, 4u32, 30u32, 200u32, 1000u32);
+}
+
+#[no_mangle]
+#[inline(always)]
+unsafe extern "C" fn sum(n: u32, mut vs: ...) -> u32 {
+    let mut s = 0;
+    let mut i = 0;
+    while i != n {
+        s += vs.arg::<u32>();
+        i += 1;
+    }
+    s
+}
diff --git a/src/test/mir-opt/inline/inline-generator.rs b/src/test/mir-opt/inline/inline-generator.rs
new file mode 100644
index 0000000000000..d11b3e548f721
--- /dev/null
+++ b/src/test/mir-opt/inline/inline-generator.rs
@@ -0,0 +1,16 @@
+// ignore-wasm32-bare compiled with panic=abort by default
+#![feature(generators, generator_trait)]
+
+use std::ops::Generator;
+use std::pin::Pin;
+
+// EMIT_MIR inline_generator.main.Inline.diff
+fn main() {
+    let _r = Pin::new(&mut g()).resume(false);
+}
+
+#[inline(always)]
+pub fn g() -> impl Generator<bool> {
+    #[inline(always)]
+    |a| { yield if a { 7 } else { 13 } }
+}
diff --git a/src/test/mir-opt/inline/inline_compatibility.inlined_no_sanitize.Inline.diff b/src/test/mir-opt/inline/inline_compatibility.inlined_no_sanitize.Inline.diff
index 451ec39422fc4..c95cf47695785 100644
--- a/src/test/mir-opt/inline/inline_compatibility.inlined_no_sanitize.Inline.diff
+++ b/src/test/mir-opt/inline/inline_compatibility.inlined_no_sanitize.Inline.diff
@@ -2,24 +2,24 @@
 + // MIR for `inlined_no_sanitize` after Inline
   
   fn inlined_no_sanitize() -> () {
-      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:24:37: 24:37
-      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:25:5: 25:18
-+     scope 1 (inlined no_sanitize) {      // at $DIR/inline-compatibility.rs:25:5: 25:18
+      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:23:37: 23:37
+      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:24:5: 24:18
++     scope 1 (inlined no_sanitize) {      // at $DIR/inline-compatibility.rs:24:5: 24:18
 +     }
   
       bb0: {
-          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:25:5: 25:18
--         _1 = no_sanitize() -> bb1;       // scope 0 at $DIR/inline-compatibility.rs:25:5: 25:18
+          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:24:5: 24:18
+-         _1 = no_sanitize() -> bb1;       // scope 0 at $DIR/inline-compatibility.rs:24:5: 24:18
 -                                          // mir::Constant
--                                          // + span: $DIR/inline-compatibility.rs:25:5: 25:16
+-                                          // + span: $DIR/inline-compatibility.rs:24:5: 24:16
 -                                          // + literal: Const { ty: unsafe fn() {no_sanitize}, val: Value(Scalar(<ZST>)) }
 -     }
 - 
 -     bb1: {
-+         _1 = const ();                   // scope 1 at $DIR/inline-compatibility.rs:25:5: 25:18
-          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:25:18: 25:19
-          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:24:37: 26:2
-          return;                          // scope 0 at $DIR/inline-compatibility.rs:26:2: 26:2
++         _1 = const ();                   // scope 1 at $DIR/inline-compatibility.rs:24:5: 24:18
+          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:24:18: 24:19
+          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:23:37: 25:2
+          return;                          // scope 0 at $DIR/inline-compatibility.rs:25:2: 25:2
       }
   }
   
diff --git a/src/test/mir-opt/inline/inline_compatibility.inlined_target_feature.Inline.diff b/src/test/mir-opt/inline/inline_compatibility.inlined_target_feature.Inline.diff
index a59ddd344cb26..2bb928343229f 100644
--- a/src/test/mir-opt/inline/inline_compatibility.inlined_target_feature.Inline.diff
+++ b/src/test/mir-opt/inline/inline_compatibility.inlined_target_feature.Inline.diff
@@ -2,24 +2,24 @@
 + // MIR for `inlined_target_feature` after Inline
   
   fn inlined_target_feature() -> () {
-      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:13:40: 13:40
-      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:14:5: 14:21
-+     scope 1 (inlined target_feature) {   // at $DIR/inline-compatibility.rs:14:5: 14:21
+      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:12:40: 12:40
+      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:13:5: 13:21
++     scope 1 (inlined target_feature) {   // at $DIR/inline-compatibility.rs:13:5: 13:21
 +     }
   
       bb0: {
-          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:14:5: 14:21
--         _1 = target_feature() -> bb1;    // scope 0 at $DIR/inline-compatibility.rs:14:5: 14:21
+          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:13:5: 13:21
+-         _1 = target_feature() -> bb1;    // scope 0 at $DIR/inline-compatibility.rs:13:5: 13:21
 -                                          // mir::Constant
--                                          // + span: $DIR/inline-compatibility.rs:14:5: 14:19
+-                                          // + span: $DIR/inline-compatibility.rs:13:5: 13:19
 -                                          // + literal: Const { ty: unsafe fn() {target_feature}, val: Value(Scalar(<ZST>)) }
 -     }
 - 
 -     bb1: {
-+         _1 = const ();                   // scope 1 at $DIR/inline-compatibility.rs:14:5: 14:21
-          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:14:21: 14:22
-          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:13:40: 15:2
-          return;                          // scope 0 at $DIR/inline-compatibility.rs:15:2: 15:2
++         _1 = const ();                   // scope 1 at $DIR/inline-compatibility.rs:13:5: 13:21
+          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:13:21: 13:22
+          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:12:40: 14:2
+          return;                          // scope 0 at $DIR/inline-compatibility.rs:14:2: 14:2
       }
   }
   
diff --git a/src/test/mir-opt/inline/inline_compatibility.not_inlined_c_variadic.Inline.diff b/src/test/mir-opt/inline/inline_compatibility.not_inlined_c_variadic.Inline.diff
new file mode 100644
index 0000000000000..09bca903c80e8
--- /dev/null
+++ b/src/test/mir-opt/inline/inline_compatibility.not_inlined_c_variadic.Inline.diff
@@ -0,0 +1,25 @@
+- // MIR for `not_inlined_c_variadic` before Inline
++ // MIR for `not_inlined_c_variadic` after Inline
+  
+  fn not_inlined_c_variadic() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:41:40: 41:40
+      let _1: u32;                         // in scope 0 at $DIR/inline-compatibility.rs:42:9: 42:10
+      scope 1 {
+          debug s => _1;                   // in scope 1 at $DIR/inline-compatibility.rs:42:9: 42:10
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:42:9: 42:10
+          _1 = sum(const 4_u32, const 4_u32, const 30_u32, const 200_u32, const 1000_u32) -> bb1; // scope 0 at $DIR/inline-compatibility.rs:42:13: 42:52
+                                           // mir::Constant
+                                           // + span: $DIR/inline-compatibility.rs:42:13: 42:16
+                                           // + literal: Const { ty: unsafe extern "C" fn(u32, ...) -> u32 {sum}, val: Value(Scalar(<ZST>)) }
+      }
+  
+      bb1: {
+          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:41:40: 43:2
+          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:43:1: 43:2
+          return;                          // scope 0 at $DIR/inline-compatibility.rs:43:2: 43:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/inline/inline_compatibility.not_inlined_no_sanitize.Inline.diff b/src/test/mir-opt/inline/inline_compatibility.not_inlined_no_sanitize.Inline.diff
index 651eadc1e849c..5af3946f2e501 100644
--- a/src/test/mir-opt/inline/inline_compatibility.not_inlined_no_sanitize.Inline.diff
+++ b/src/test/mir-opt/inline/inline_compatibility.not_inlined_no_sanitize.Inline.diff
@@ -2,21 +2,21 @@
 + // MIR for `not_inlined_no_sanitize` after Inline
   
   fn not_inlined_no_sanitize() -> () {
-      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:29:41: 29:41
-      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:30:5: 30:18
+      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:28:41: 28:41
+      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:29:5: 29:18
   
       bb0: {
-          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:30:5: 30:18
-          _1 = no_sanitize() -> bb1;       // scope 0 at $DIR/inline-compatibility.rs:30:5: 30:18
+          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:29:5: 29:18
+          _1 = no_sanitize() -> bb1;       // scope 0 at $DIR/inline-compatibility.rs:29:5: 29:18
                                            // mir::Constant
-                                           // + span: $DIR/inline-compatibility.rs:30:5: 30:16
+                                           // + span: $DIR/inline-compatibility.rs:29:5: 29:16
                                            // + literal: Const { ty: unsafe fn() {no_sanitize}, val: Value(Scalar(<ZST>)) }
       }
   
       bb1: {
-          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:30:18: 30:19
-          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:29:41: 31:2
-          return;                          // scope 0 at $DIR/inline-compatibility.rs:31:2: 31:2
+          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:29:18: 29:19
+          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:28:41: 30:2
+          return;                          // scope 0 at $DIR/inline-compatibility.rs:30:2: 30:2
       }
   }
   
diff --git a/src/test/mir-opt/inline/inline_compatibility.not_inlined_target_feature.Inline.diff b/src/test/mir-opt/inline/inline_compatibility.not_inlined_target_feature.Inline.diff
index 55b9edf3adc1f..8c9fa573ce218 100644
--- a/src/test/mir-opt/inline/inline_compatibility.not_inlined_target_feature.Inline.diff
+++ b/src/test/mir-opt/inline/inline_compatibility.not_inlined_target_feature.Inline.diff
@@ -2,21 +2,21 @@
 + // MIR for `not_inlined_target_feature` after Inline
   
   fn not_inlined_target_feature() -> () {
-      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:18:44: 18:44
-      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:19:5: 19:21
+      let mut _0: ();                      // return place in scope 0 at $DIR/inline-compatibility.rs:17:44: 17:44
+      let _1: ();                          // in scope 0 at $DIR/inline-compatibility.rs:18:5: 18:21
   
       bb0: {
-          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:19:5: 19:21
-          _1 = target_feature() -> bb1;    // scope 0 at $DIR/inline-compatibility.rs:19:5: 19:21
+          StorageLive(_1);                 // scope 0 at $DIR/inline-compatibility.rs:18:5: 18:21
+          _1 = target_feature() -> bb1;    // scope 0 at $DIR/inline-compatibility.rs:18:5: 18:21
                                            // mir::Constant
-                                           // + span: $DIR/inline-compatibility.rs:19:5: 19:19
+                                           // + span: $DIR/inline-compatibility.rs:18:5: 18:19
                                            // + literal: Const { ty: unsafe fn() {target_feature}, val: Value(Scalar(<ZST>)) }
       }
   
       bb1: {
-          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:19:21: 19:22
-          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:18:44: 20:2
-          return;                          // scope 0 at $DIR/inline-compatibility.rs:20:2: 20:2
+          StorageDead(_1);                 // scope 0 at $DIR/inline-compatibility.rs:18:21: 18:22
+          _0 = const ();                   // scope 0 at $DIR/inline-compatibility.rs:17:44: 19:2
+          return;                          // scope 0 at $DIR/inline-compatibility.rs:19:2: 19:2
       }
   }
   
diff --git a/src/test/mir-opt/inline/inline_generator.main.Inline.diff b/src/test/mir-opt/inline/inline_generator.main.Inline.diff
new file mode 100644
index 0000000000000..aa32daa82dd51
--- /dev/null
+++ b/src/test/mir-opt/inline/inline_generator.main.Inline.diff
@@ -0,0 +1,123 @@
+- // MIR for `main` before Inline
++ // MIR for `main` after Inline
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/inline-generator.rs:8:11: 8:11
+      let _1: std::ops::GeneratorState<<impl std::ops::Generator<bool> as std::ops::Generator<bool>>::Yield, <impl std::ops::Generator<bool> as std::ops::Generator<bool>>::Return>; // in scope 0 at $DIR/inline-generator.rs:9:9: 9:11
+      let mut _2: std::pin::Pin<&mut impl std::ops::Generator<bool>>; // in scope 0 at $DIR/inline-generator.rs:9:14: 9:32
+      let mut _3: &mut impl std::ops::Generator<bool>; // in scope 0 at $DIR/inline-generator.rs:9:23: 9:31
+      let mut _4: impl std::ops::Generator<bool>; // in scope 0 at $DIR/inline-generator.rs:9:28: 9:31
++     let mut _7: bool;                    // in scope 0 at $DIR/inline-generator.rs:9:14: 9:46
+      scope 1 {
+          debug _r => _1;                  // in scope 1 at $DIR/inline-generator.rs:9:9: 9:11
+      }
++     scope 2 (inlined g) {                // at $DIR/inline-generator.rs:9:28: 9:31
++     }
++     scope 3 (inlined Pin::<&mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]>::new) { // at $DIR/inline-generator.rs:9:14: 9:32
++         debug pointer => _3;             // in scope 3 at $DIR/inline-generator.rs:9:14: 9:32
++         let mut _5: &mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]; // in scope 3 at $DIR/inline-generator.rs:9:14: 9:32
++         scope 4 {
++             scope 5 (inlined Pin::<&mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]>::new_unchecked) { // at $DIR/inline-generator.rs:9:14: 9:32
++                 debug pointer => _5;     // in scope 5 at $DIR/inline-generator.rs:9:14: 9:32
++                 let mut _6: &mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]; // in scope 5 at $DIR/inline-generator.rs:9:14: 9:32
++             }
++         }
++     }
++     scope 6 (inlined g::{closure#0}) {   // at $DIR/inline-generator.rs:9:14: 9:46
++         debug a => _8;                   // in scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         let mut _8: bool;                // in scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         let mut _9: u32;                 // in scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++     }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/inline-generator.rs:9:9: 9:11
+          StorageLive(_2);                 // scope 0 at $DIR/inline-generator.rs:9:14: 9:32
+          StorageLive(_3);                 // scope 0 at $DIR/inline-generator.rs:9:23: 9:31
+          StorageLive(_4);                 // scope 0 at $DIR/inline-generator.rs:9:28: 9:31
+-         _4 = g() -> bb1;                 // scope 0 at $DIR/inline-generator.rs:9:28: 9:31
+-                                          // mir::Constant
+-                                          // + span: $DIR/inline-generator.rs:9:28: 9:29
+-                                          // + literal: Const { ty: fn() -> impl std::ops::Generator<bool> {g}, val: Value(Scalar(<ZST>)) }
+-     }
+- 
+-     bb1: {
++         discriminant(_4) = 0;            // scope 2 at $DIR/inline-generator.rs:9:28: 9:31
+          _3 = &mut _4;                    // scope 0 at $DIR/inline-generator.rs:9:23: 9:31
+-         _2 = Pin::<&mut impl Generator<bool>>::new(move _3) -> [return: bb2, unwind: bb4]; // scope 0 at $DIR/inline-generator.rs:9:14: 9:32
+-                                          // mir::Constant
+-                                          // + span: $DIR/inline-generator.rs:9:14: 9:22
+-                                          // + user_ty: UserType(0)
+-                                          // + literal: Const { ty: fn(&mut impl std::ops::Generator<bool>) -> std::pin::Pin<&mut impl std::ops::Generator<bool>> {std::pin::Pin::<&mut impl std::ops::Generator<bool>>::new}, val: Value(Scalar(<ZST>)) }
+-     }
+- 
+-     bb2: {
++         StorageLive(_5);                 // scope 4 at $DIR/inline-generator.rs:9:14: 9:32
++         _5 = move _3;                    // scope 4 at $DIR/inline-generator.rs:9:14: 9:32
++         StorageLive(_6);                 // scope 5 at $DIR/inline-generator.rs:9:14: 9:32
++         _6 = move _5;                    // scope 5 at $DIR/inline-generator.rs:9:14: 9:32
++         (_2.0: &mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]) = move _6; // scope 5 at $DIR/inline-generator.rs:9:14: 9:32
++         StorageDead(_6);                 // scope 5 at $DIR/inline-generator.rs:9:14: 9:32
++         StorageDead(_5);                 // scope 4 at $DIR/inline-generator.rs:9:14: 9:32
+          StorageDead(_3);                 // scope 0 at $DIR/inline-generator.rs:9:31: 9:32
+-         _1 = <impl Generator<bool> as Generator<bool>>::resume(move _2, const false) -> [return: bb3, unwind: bb4]; // scope 0 at $DIR/inline-generator.rs:9:14: 9:46
+-                                          // mir::Constant
+-                                          // + span: $DIR/inline-generator.rs:9:33: 9:39
+-                                          // + literal: Const { ty: for<'r> fn(std::pin::Pin<&'r mut impl std::ops::Generator<bool>>, bool) -> std::ops::GeneratorState<<impl std::ops::Generator<bool> as std::ops::Generator<bool>>::Yield, <impl std::ops::Generator<bool> as std::ops::Generator<bool>>::Return> {<impl std::ops::Generator<bool> as std::ops::Generator<bool>>::resume}, val: Value(Scalar(<ZST>)) }
++         StorageLive(_7);                 // scope 0 at $DIR/inline-generator.rs:9:14: 9:46
++         _7 = const false;                // scope 0 at $DIR/inline-generator.rs:9:14: 9:46
++         _9 = discriminant((*(_2.0: &mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]))); // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         switchInt(move _9) -> [0_u32: bb3, 1_u32: bb8, 3_u32: bb7, otherwise: bb9]; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
+      }
+  
+-     bb3: {
++     bb1: {
++         StorageDead(_7);                 // scope 0 at $DIR/inline-generator.rs:9:14: 9:46
+          StorageDead(_2);                 // scope 0 at $DIR/inline-generator.rs:9:45: 9:46
+          StorageDead(_4);                 // scope 0 at $DIR/inline-generator.rs:9:46: 9:47
+          _0 = const ();                   // scope 0 at $DIR/inline-generator.rs:8:11: 10:2
+          StorageDead(_1);                 // scope 0 at $DIR/inline-generator.rs:10:1: 10:2
+          return;                          // scope 0 at $DIR/inline-generator.rs:10:2: 10:2
+      }
+  
+-     bb4 (cleanup): {
++     bb2 (cleanup): {
+          resume;                          // scope 0 at $DIR/inline-generator.rs:8:1: 10:2
++     }
++ 
++     bb3: {
++         _8 = move _7;                    // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         switchInt(_8) -> [false: bb4, otherwise: bb5]; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++     }
++ 
++     bb4: {
++         ((_1 as Yielded).0: i32) = const 13_i32; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         goto -> bb6;                     // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++     }
++ 
++     bb5: {
++         ((_1 as Yielded).0: i32) = const 7_i32; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         goto -> bb6;                     // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++     }
++ 
++     bb6: {
++         discriminant(_1) = 0;            // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         discriminant((*(_2.0: &mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]))) = 3; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         goto -> bb1;                     // scope 0 at $DIR/inline-generator.rs:15:11: 15:39
++     }
++ 
++     bb7: {
++         ((_1 as Complete).0: bool) = move _7; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         discriminant(_1) = 1;            // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         discriminant((*(_2.0: &mut [generator@$DIR/inline-generator.rs:15:5: 15:41 {bool, i32}]))) = 1; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++         goto -> bb1;                     // scope 0 at $DIR/inline-generator.rs:15:41: 15:41
++     }
++ 
++     bb8: {
++         assert(const false, "generator resumed after completion") -> [success: bb8, unwind: bb2]; // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
++     }
++ 
++     bb9: {
++         unreachable;                     // scope 6 at $DIR/inline-generator.rs:9:14: 9:46
+      }
+  }
+  
diff --git a/src/test/pretty/qpath-associated-type-bound.rs b/src/test/pretty/qpath-associated-type-bound.rs
new file mode 100644
index 0000000000000..e06885e03882b
--- /dev/null
+++ b/src/test/pretty/qpath-associated-type-bound.rs
@@ -0,0 +1,16 @@
+// pp-exact
+
+
+mod m {
+    pub trait Tr {
+        type Ts: super::Tu;
+    }
+}
+
+trait Tu {
+    fn dummy() { }
+}
+
+fn foo<T: m::Tr>() { <T as m::Tr>::Ts::dummy(); }
+
+fn main() { }
diff --git a/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs b/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs
new file mode 100644
index 0000000000000..f895a4c2104ba
--- /dev/null
+++ b/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs
@@ -0,0 +1,22 @@
+// ignore-tidy-linelength
+
+#![crate_type="lib"]
+
+pub mod internal {
+    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.mod.html'
+    pub struct r#mod;
+
+    /// See [name], [other name]
+    ///
+    /// [name]: mod
+    /// [other name]: crate::internal::mod
+    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="../../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'name'
+    // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="../../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'other name'
+    pub struct B;
+}
+
+/// See [name].
+///
+/// [name]: internal::mod
+// @has 'raw_ident_eliminate_r_hashtag/struct.A.html' '//*a[@href="../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'name'
+pub struct A;
diff --git a/src/test/ui/closures/2229_closure_analysis/arrays-completely-captured.rs b/src/test/ui/closures/2229_closure_analysis/arrays-completely-captured.rs
new file mode 100644
index 0000000000000..131af6a10c898
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/arrays-completely-captured.rs
@@ -0,0 +1,24 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| `#[warn(incomplete_features)]` on by default
+//~| see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+// Ensure that capture analysis results in arrays being completely captured.
+fn main() {
+    let mut m = [1, 2, 3, 4, 5];
+
+    let mut c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        m[0] += 10;
+        //~^ NOTE: Capturing m[] -> MutBorrow
+        //~| NOTE: Min Capture m[] -> MutBorrow
+        m[1] += 40;
+    };
+
+    c();
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/arrays-completely-captured.stderr b/src/test/ui/closures/2229_closure_analysis/arrays-completely-captured.stderr
new file mode 100644
index 0000000000000..2a350f3033192
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/arrays-completely-captured.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/arrays-completely-captured.rs:11:17
+   |
+LL |     let mut c = #[rustc_capture_analysis]
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/arrays-completely-captured.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/arrays-completely-captured.rs:14:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         m[0] += 10;
+...  |
+LL | |         m[1] += 40;
+LL | |     };
+   | |_____^
+   |
+note: Capturing m[] -> MutBorrow
+  --> $DIR/arrays-completely-captured.rs:17:9
+   |
+LL |         m[0] += 10;
+   |         ^
+
+error: Min Capture analysis includes:
+  --> $DIR/arrays-completely-captured.rs:14:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         m[0] += 10;
+...  |
+LL | |         m[1] += 40;
+LL | |     };
+   | |_____^
+   |
+note: Min Capture m[] -> MutBorrow
+  --> $DIR/arrays-completely-captured.rs:17:9
+   |
+LL |         m[0] += 10;
+   |         ^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-struct.rs b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-struct.rs
new file mode 100644
index 0000000000000..ba4955085372a
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-struct.rs
@@ -0,0 +1,33 @@
+// FIXME(arora-aman) add run-pass once 2229 is implemented
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+fn main() {
+    let mut p = Point { x: 10, y: 10 };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ First Pass analysis includes:
+    //~| Min Capture analysis includes:
+        println!("{}", p.x);
+        //~^ NOTE: Capturing p[(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture p[(0, 0)] -> ImmBorrow
+    };
+
+    // `c` should only capture `p.x`, therefore mutating `p.y` is allowed.
+    let py = &mut p.y;
+
+    c();
+    *py = 20;
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-struct.stderr b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-struct.stderr
new file mode 100644
index 0000000000000..5fac6963afd32
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-struct.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/capture-disjoint-field-struct.rs:17:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/capture-disjoint-field-struct.rs:3:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/capture-disjoint-field-struct.rs:20:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", p.x);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0)] -> ImmBorrow
+  --> $DIR/capture-disjoint-field-struct.rs:23:24
+   |
+LL |         println!("{}", p.x);
+   |                        ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/capture-disjoint-field-struct.rs:20:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", p.x);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[(0, 0)] -> ImmBorrow
+  --> $DIR/capture-disjoint-field-struct.rs:23:24
+   |
+LL |         println!("{}", p.x);
+   |                        ^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-tuple.rs b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-tuple.rs
new file mode 100644
index 0000000000000..c1693fbad7986
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-tuple.rs
@@ -0,0 +1,28 @@
+// FIXME(arora-aman) add run-pass once 2229 is implemented
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+fn main() {
+    let mut t = (10, 10);
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ First Pass analysis includes:
+    //~| Min Capture analysis includes:
+        println!("{}", t.0);
+        //~^ NOTE: Capturing t[(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture t[(0, 0)] -> ImmBorrow
+    };
+
+    // `c` only captures t.0, therefore mutating t.1 is allowed.
+    let t1 = &mut t.1;
+
+    c();
+    *t1 = 20;
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-tuple.stderr b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-tuple.stderr
new file mode 100644
index 0000000000000..1bfd63f2ace8c
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-disjoint-field-tuple.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/capture-disjoint-field-tuple.rs:12:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/capture-disjoint-field-tuple.rs:3:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/capture-disjoint-field-tuple.rs:15:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", t.0);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing t[(0, 0)] -> ImmBorrow
+  --> $DIR/capture-disjoint-field-tuple.rs:18:24
+   |
+LL |         println!("{}", t.0);
+   |                        ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/capture-disjoint-field-tuple.rs:15:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", t.0);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture t[(0, 0)] -> ImmBorrow
+  --> $DIR/capture-disjoint-field-tuple.rs:18:24
+   |
+LL |         println!("{}", t.0);
+   |                        ^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-enums.rs b/src/test/ui/closures/2229_closure_analysis/capture-enums.rs
new file mode 100644
index 0000000000000..8fb2f7f16d69c
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-enums.rs
@@ -0,0 +1,64 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+enum Info {
+    Point(i32, i32, String),
+    Meta(String, Vec<(i32, i32)>)
+}
+
+fn multi_variant_enum() {
+    let point = Info::Point(10, -10, "1".into());
+
+    let vec = Vec::new();
+    let meta = Info::Meta("meta".into(), vec);
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ First Pass analysis includes:
+    //~| Min Capture analysis includes:
+        if let Info::Point(_, _, str) = point {
+            //~^ NOTE: Capturing point[] -> ImmBorrow
+            //~| NOTE: Capturing point[(2, 0)] -> ByValue
+            //~| NOTE: Min Capture point[] -> ByValue
+            println!("{}", str);
+        }
+
+        if let Info::Meta(_, v) = meta {
+            //~^ NOTE: Capturing meta[] -> ImmBorrow
+            //~| NOTE: Capturing meta[(1, 1)] -> ByValue
+            //~| NOTE: Min Capture meta[] -> ByValue
+            println!("{:?}", v);
+        }
+    };
+
+    c();
+}
+
+enum SingleVariant {
+    Point(i32, i32, String),
+}
+
+fn single_variant_enum() {
+    let point = SingleVariant::Point(10, -10, "1".into());
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ First Pass analysis includes:
+    //~| Min Capture analysis includes:
+        let SingleVariant::Point(_, _, str) = point;
+        //~^ NOTE: Capturing point[(2, 0)] -> ByValue
+        //~| NOTE: Min Capture point[(2, 0)] -> ByValue
+        println!("{}", str);
+    };
+
+    c();
+}
+
+fn main() {}
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-enums.stderr b/src/test/ui/closures/2229_closure_analysis/capture-enums.stderr
new file mode 100644
index 0000000000000..ebe1dcb98848b
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-enums.stderr
@@ -0,0 +1,122 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/capture-enums.rs:18:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/capture-enums.rs:49:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/capture-enums.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/capture-enums.rs:21:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         if let Info::Point(_, _, str) = point {
+...  |
+LL | |         }
+LL | |     };
+   | |_____^
+   |
+note: Capturing point[] -> ImmBorrow
+  --> $DIR/capture-enums.rs:24:41
+   |
+LL |         if let Info::Point(_, _, str) = point {
+   |                                         ^^^^^
+note: Capturing point[(2, 0)] -> ByValue
+  --> $DIR/capture-enums.rs:24:41
+   |
+LL |         if let Info::Point(_, _, str) = point {
+   |                                         ^^^^^
+note: Capturing meta[] -> ImmBorrow
+  --> $DIR/capture-enums.rs:31:35
+   |
+LL |         if let Info::Meta(_, v) = meta {
+   |                                   ^^^^
+note: Capturing meta[(1, 1)] -> ByValue
+  --> $DIR/capture-enums.rs:31:35
+   |
+LL |         if let Info::Meta(_, v) = meta {
+   |                                   ^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/capture-enums.rs:21:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         if let Info::Point(_, _, str) = point {
+...  |
+LL | |         }
+LL | |     };
+   | |_____^
+   |
+note: Min Capture point[] -> ByValue
+  --> $DIR/capture-enums.rs:24:41
+   |
+LL |         if let Info::Point(_, _, str) = point {
+   |                                         ^^^^^
+note: Min Capture meta[] -> ByValue
+  --> $DIR/capture-enums.rs:31:35
+   |
+LL |         if let Info::Meta(_, v) = meta {
+   |                                   ^^^^
+
+error: First Pass analysis includes:
+  --> $DIR/capture-enums.rs:52:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let SingleVariant::Point(_, _, str) = point;
+...  |
+LL | |         println!("{}", str);
+LL | |     };
+   | |_____^
+   |
+note: Capturing point[(2, 0)] -> ByValue
+  --> $DIR/capture-enums.rs:55:47
+   |
+LL |         let SingleVariant::Point(_, _, str) = point;
+   |                                               ^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/capture-enums.rs:52:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let SingleVariant::Point(_, _, str) = point;
+...  |
+LL | |         println!("{}", str);
+LL | |     };
+   | |_____^
+   |
+note: Min Capture point[(2, 0)] -> ByValue
+  --> $DIR/capture-enums.rs:55:47
+   |
+LL |         let SingleVariant::Point(_, _, str) = point;
+   |                                               ^^^^^
+
+error: aborting due to 6 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/destructure_patterns.rs b/src/test/ui/closures/2229_closure_analysis/destructure_patterns.rs
new file mode 100644
index 0000000000000..080ca0405b477
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/destructure_patterns.rs
@@ -0,0 +1,77 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+// Test to ensure Index projections are handled properly during capture analysis
+// The array should be moved in entirety, even though only some elements are used.
+fn arrays() {
+    let arr: [String; 5] = [format!("A"), format!("B"), format!("C"), format!("D"), format!("E")];
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let [a, b, .., e] = arr;
+        //~^ NOTE: Capturing arr[Index] -> ByValue
+        //~| NOTE: Min Capture arr[] -> ByValue
+        assert_eq!(a, "A");
+        assert_eq!(b, "B");
+        assert_eq!(e, "E");
+    };
+
+    c();
+}
+
+struct Point {
+    x: i32,
+    y: i32,
+    id: String,
+}
+
+fn structs() {
+    let mut p = Point { x: 10, y: 10, id: String::new() };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let Point { x: ref mut x, y: _, id: moved_id } = p;
+        //~^ NOTE: Capturing p[(0, 0)] -> MutBorrow
+        //~| NOTE: Capturing p[(2, 0)] -> ByValue
+        //~| NOTE: Min Capture p[(0, 0)] -> MutBorrow
+        //~| NOTE: Min Capture p[(2, 0)] -> ByValue
+
+        println!("{}, {}", x, moved_id);
+    };
+    c();
+}
+
+fn tuples() {
+    let mut t = (10, String::new(), (String::new(), 42));
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let (ref mut x, ref ref_str, (moved_s, _)) = t;
+        //~^ NOTE: Capturing t[(0, 0)] -> MutBorrow
+        //~| NOTE: Capturing t[(1, 0)] -> ImmBorrow
+        //~| NOTE: Capturing t[(2, 0),(0, 0)] -> ByValue
+        //~| NOTE: Min Capture t[(0, 0)] -> MutBorrow
+        //~| NOTE: Min Capture t[(1, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture t[(2, 0),(0, 0)] -> ByValue
+
+        println!("{}, {} {}", x, ref_str, moved_s);
+    };
+    c();
+}
+
+fn main() {}
diff --git a/src/test/ui/closures/2229_closure_analysis/destructure_patterns.stderr b/src/test/ui/closures/2229_closure_analysis/destructure_patterns.stderr
new file mode 100644
index 0000000000000..06ccc2d7a88b4
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/destructure_patterns.stderr
@@ -0,0 +1,177 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/destructure_patterns.rs:12:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/destructure_patterns.rs:38:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/destructure_patterns.rs:58:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/destructure_patterns.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/destructure_patterns.rs:15:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let [a, b, .., e] = arr;
+...  |
+LL | |         assert_eq!(e, "E");
+LL | |     };
+   | |_____^
+   |
+note: Capturing arr[Index] -> ByValue
+  --> $DIR/destructure_patterns.rs:18:29
+   |
+LL |         let [a, b, .., e] = arr;
+   |                             ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/destructure_patterns.rs:15:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let [a, b, .., e] = arr;
+...  |
+LL | |         assert_eq!(e, "E");
+LL | |     };
+   | |_____^
+   |
+note: Min Capture arr[] -> ByValue
+  --> $DIR/destructure_patterns.rs:18:29
+   |
+LL |         let [a, b, .., e] = arr;
+   |                             ^^^
+
+error: First Pass analysis includes:
+  --> $DIR/destructure_patterns.rs:41:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let Point { x: ref mut x, y: _, id: moved_id } = p;
+...  |
+LL | |         println!("{}, {}", x, moved_id);
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0)] -> MutBorrow
+  --> $DIR/destructure_patterns.rs:44:58
+   |
+LL |         let Point { x: ref mut x, y: _, id: moved_id } = p;
+   |                                                          ^
+note: Capturing p[(2, 0)] -> ByValue
+  --> $DIR/destructure_patterns.rs:44:58
+   |
+LL |         let Point { x: ref mut x, y: _, id: moved_id } = p;
+   |                                                          ^
+
+error: Min Capture analysis includes:
+  --> $DIR/destructure_patterns.rs:41:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let Point { x: ref mut x, y: _, id: moved_id } = p;
+...  |
+LL | |         println!("{}, {}", x, moved_id);
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[(0, 0)] -> MutBorrow
+  --> $DIR/destructure_patterns.rs:44:58
+   |
+LL |         let Point { x: ref mut x, y: _, id: moved_id } = p;
+   |                                                          ^
+note: Min Capture p[(2, 0)] -> ByValue
+  --> $DIR/destructure_patterns.rs:44:58
+   |
+LL |         let Point { x: ref mut x, y: _, id: moved_id } = p;
+   |                                                          ^
+
+error: First Pass analysis includes:
+  --> $DIR/destructure_patterns.rs:61:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+...  |
+LL | |         println!("{}, {} {}", x, ref_str, moved_s);
+LL | |     };
+   | |_____^
+   |
+note: Capturing t[(0, 0)] -> MutBorrow
+  --> $DIR/destructure_patterns.rs:64:54
+   |
+LL |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+   |                                                      ^
+note: Capturing t[(1, 0)] -> ImmBorrow
+  --> $DIR/destructure_patterns.rs:64:54
+   |
+LL |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+   |                                                      ^
+note: Capturing t[(2, 0),(0, 0)] -> ByValue
+  --> $DIR/destructure_patterns.rs:64:54
+   |
+LL |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+   |                                                      ^
+
+error: Min Capture analysis includes:
+  --> $DIR/destructure_patterns.rs:61:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+...  |
+LL | |         println!("{}, {} {}", x, ref_str, moved_s);
+LL | |     };
+   | |_____^
+   |
+note: Min Capture t[(0, 0)] -> MutBorrow
+  --> $DIR/destructure_patterns.rs:64:54
+   |
+LL |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+   |                                                      ^
+note: Min Capture t[(1, 0)] -> ImmBorrow
+  --> $DIR/destructure_patterns.rs:64:54
+   |
+LL |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+   |                                                      ^
+note: Min Capture t[(2, 0),(0, 0)] -> ByValue
+  --> $DIR/destructure_patterns.rs:64:54
+   |
+LL |         let (ref mut x, ref ref_str, (moved_s, _)) = t;
+   |                                                      ^
+
+error: aborting due to 9 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/feature-gate-capture_disjoint_fields.rs b/src/test/ui/closures/2229_closure_analysis/feature-gate-capture_disjoint_fields.rs
new file mode 100644
index 0000000000000..a3222635b626c
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/feature-gate-capture_disjoint_fields.rs
@@ -0,0 +1,20 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+fn main() {
+    let s = format!("s");
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        println!("This uses new capture analyysis to capture s={}", s);
+        //~^ NOTE: Capturing s[] -> ImmBorrow
+        //~| NOTE: Min Capture s[] -> ImmBorrow
+    };
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/feature-gate-capture_disjoint_fields.stderr b/src/test/ui/closures/2229_closure_analysis/feature-gate-capture_disjoint_fields.stderr
new file mode 100644
index 0000000000000..a031360ed34e1
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/feature-gate-capture_disjoint_fields.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/feature-gate-capture_disjoint_fields.rs:10:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/feature-gate-capture_disjoint_fields.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/feature-gate-capture_disjoint_fields.rs:13:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("This uses new capture analyysis to capture s={}", s);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing s[] -> ImmBorrow
+  --> $DIR/feature-gate-capture_disjoint_fields.rs:16:69
+   |
+LL |         println!("This uses new capture analyysis to capture s={}", s);
+   |                                                                     ^
+
+error: Min Capture analysis includes:
+  --> $DIR/feature-gate-capture_disjoint_fields.rs:13:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("This uses new capture analyysis to capture s={}", s);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture s[] -> ImmBorrow
+  --> $DIR/feature-gate-capture_disjoint_fields.rs:16:69
+   |
+LL |         println!("This uses new capture analyysis to capture s={}", s);
+   |                                                                     ^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/filter-on-struct-member.rs b/src/test/ui/closures/2229_closure_analysis/filter-on-struct-member.rs
new file mode 100644
index 0000000000000..9466e103897fb
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/filter-on-struct-member.rs
@@ -0,0 +1,45 @@
+// FIXME(arora-aman) add run-pass once 2229 is implemented
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+struct Filter {
+    div: i32,
+}
+impl Filter {
+    fn allowed(&self, x: i32) -> bool {
+        x % self.div == 1
+    }
+}
+
+struct Data {
+    filter: Filter,
+    list: Vec<i32>,
+}
+impl Data {
+    fn update(&mut self) {
+        // The closure passed to filter only captures self.filter,
+        // therefore mutating self.list is allowed.
+        self.list.retain(
+            #[rustc_capture_analysis]
+            |v| self.filter.allowed(*v),
+            //~^ ERROR: First Pass analysis includes:
+            //~| ERROR: Min Capture analysis includes:
+            //~| NOTE: Capturing self[Deref,(0, 0)] -> ImmBorrow
+            //~| NOTE: Min Capture self[Deref,(0, 0)] -> ImmBorrow
+        );
+    }
+}
+
+fn main() {
+    let mut d = Data { filter: Filter { div: 3 }, list: Vec::new() };
+
+    for i in 1..10 {
+        d.list.push(i);
+    }
+
+    d.update();
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/filter-on-struct-member.stderr b/src/test/ui/closures/2229_closure_analysis/filter-on-struct-member.stderr
new file mode 100644
index 0000000000000..e9420fe5a0c3a
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/filter-on-struct-member.stderr
@@ -0,0 +1,35 @@
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/filter-on-struct-member.rs:3:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/filter-on-struct-member.rs:28:13
+   |
+LL |             |v| self.filter.allowed(*v),
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: Capturing self[Deref,(0, 0)] -> ImmBorrow
+  --> $DIR/filter-on-struct-member.rs:28:17
+   |
+LL |             |v| self.filter.allowed(*v),
+   |                 ^^^^^^^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/filter-on-struct-member.rs:28:13
+   |
+LL |             |v| self.filter.allowed(*v),
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: Min Capture self[Deref,(0, 0)] -> ImmBorrow
+  --> $DIR/filter-on-struct-member.rs:28:17
+   |
+LL |             |v| self.filter.allowed(*v),
+   |                 ^^^^^^^^^^^
+
+error: aborting due to 2 previous errors; 1 warning emitted
+
diff --git a/src/test/ui/closures/2229_closure_analysis/multilevel-path-1.rs b/src/test/ui/closures/2229_closure_analysis/multilevel-path-1.rs
new file mode 100644
index 0000000000000..7d2d4c104d489
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/multilevel-path-1.rs
@@ -0,0 +1,41 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+#![allow(unused)]
+
+struct Point {
+    x: i32,
+    y: i32,
+}
+struct Wrapper {
+    p: Point,
+}
+
+fn main() {
+    let mut w = Wrapper { p: Point { x: 10, y: 10 } };
+
+    // Only paths that appears within the closure that directly start off
+    // a variable defined outside the closure are captured.
+    //
+    // Therefore `w.p` is captured
+    // Note that `wp.x` doesn't start off a variable defined outside the closure.
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let wp = &w.p;
+        //~^ NOTE: Capturing w[(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture w[(0, 0)] -> ImmBorrow
+        println!("{}", wp.x);
+    };
+
+    // Since `c` captures `w.p` by an ImmBorrow, `w.p.y` can't be mutated.
+    let py = &mut w.p.y;
+    c();
+
+    *py = 20
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/multilevel-path-1.stderr b/src/test/ui/closures/2229_closure_analysis/multilevel-path-1.stderr
new file mode 100644
index 0000000000000..1c8db7952afe7
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/multilevel-path-1.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/multilevel-path-1.rs:24:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/multilevel-path-1.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/multilevel-path-1.rs:27:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let wp = &w.p;
+...  |
+LL | |         println!("{}", wp.x);
+LL | |     };
+   | |_____^
+   |
+note: Capturing w[(0, 0)] -> ImmBorrow
+  --> $DIR/multilevel-path-1.rs:30:19
+   |
+LL |         let wp = &w.p;
+   |                   ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/multilevel-path-1.rs:27:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let wp = &w.p;
+...  |
+LL | |         println!("{}", wp.x);
+LL | |     };
+   | |_____^
+   |
+note: Min Capture w[(0, 0)] -> ImmBorrow
+  --> $DIR/multilevel-path-1.rs:30:19
+   |
+LL |         let wp = &w.p;
+   |                   ^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/multilevel-path-2.rs b/src/test/ui/closures/2229_closure_analysis/multilevel-path-2.rs
new file mode 100644
index 0000000000000..540e70138e50e
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/multilevel-path-2.rs
@@ -0,0 +1,37 @@
+// FIXME(arora-aman) add run-pass once 2229 is implemented
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+#![allow(unused)]
+
+struct Point {
+    x: i32,
+    y: i32,
+}
+struct Wrapper {
+    p: Point,
+}
+
+fn main() {
+    let mut w = Wrapper { p: Point { x: 10, y: 10 } };
+
+    let c = #[rustc_capture_analysis]
+        //~^ ERROR: attributes on expressions are experimental
+        //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        println!("{}", w.p.x);
+        //~^ NOTE: Capturing w[(0, 0),(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture w[(0, 0),(0, 0)] -> ImmBorrow
+    };
+
+    // `c` only captures `w.p.x`, therefore it's safe to mutate `w.p.y`.
+    let py = &mut w.p.y;
+    c();
+
+    *py = 20
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/multilevel-path-2.stderr b/src/test/ui/closures/2229_closure_analysis/multilevel-path-2.stderr
new file mode 100644
index 0000000000000..37287f6b3bc74
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/multilevel-path-2.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/multilevel-path-2.rs:21:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/multilevel-path-2.rs:3:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/multilevel-path-2.rs:24:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", w.p.x);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing w[(0, 0),(0, 0)] -> ImmBorrow
+  --> $DIR/multilevel-path-2.rs:27:24
+   |
+LL |         println!("{}", w.p.x);
+   |                        ^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/multilevel-path-2.rs:24:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", w.p.x);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture w[(0, 0),(0, 0)] -> ImmBorrow
+  --> $DIR/multilevel-path-2.rs:27:24
+   |
+LL |         println!("{}", w.p.x);
+   |                        ^^^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/nested-closure.rs b/src/test/ui/closures/2229_closure_analysis/nested-closure.rs
new file mode 100644
index 0000000000000..88620550f2e7c
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/nested-closure.rs
@@ -0,0 +1,56 @@
+// FIXME(arora-aman) add run-pass once 2229 is implemented
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+// This testcase ensures that nested closures are handles properly
+// - The nested closure is analyzed first.
+// - The capture kind of the nested closure is accounted for by the enclosing closure
+// - Any captured path by the nested closure that starts off a local variable in the enclosing
+// closure is not listed as a capture of the enclosing closure.
+
+fn main() {
+    let mut p = Point { x: 5, y: 20 };
+
+    let mut c1 = #[rustc_capture_analysis]
+        //~^ ERROR: attributes on expressions are experimental
+        //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        println!("{}", p.x);
+        //~^ NOTE: Capturing p[(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture p[(0, 0)] -> ImmBorrow
+        let incr = 10;
+        let mut c2 = #[rustc_capture_analysis]
+        //~^ ERROR: attributes on expressions are experimental
+        //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+        || p.y += incr;
+        //~^ ERROR: First Pass analysis includes:
+        //~| ERROR: Min Capture analysis includes:
+        //~| NOTE: Capturing p[(1, 0)] -> MutBorrow
+        //~| NOTE: Capturing incr[] -> ImmBorrow
+        //~| NOTE: Min Capture p[(1, 0)] -> MutBorrow
+        //~| NOTE: Min Capture incr[] -> ImmBorrow
+        //~| NOTE: Capturing p[(1, 0)] -> MutBorrow
+        //~| NOTE: Min Capture p[(1, 0)] -> MutBorrow
+        c2();
+        println!("{}", p.y);
+    };
+
+    c1();
+
+    let px = &p.x;
+
+    println!("{}", px);
+
+    c1();
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/nested-closure.stderr b/src/test/ui/closures/2229_closure_analysis/nested-closure.stderr
new file mode 100644
index 0000000000000..21147be3f1d08
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/nested-closure.stderr
@@ -0,0 +1,110 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/nested-closure.rs:23:18
+   |
+LL |     let mut c1 = #[rustc_capture_analysis]
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/nested-closure.rs:33:22
+   |
+LL |         let mut c2 = #[rustc_capture_analysis]
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/nested-closure.rs:3:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/nested-closure.rs:36:9
+   |
+LL |         || p.y += incr;
+   |         ^^^^^^^^^^^^^^
+   |
+note: Capturing p[(1, 0)] -> MutBorrow
+  --> $DIR/nested-closure.rs:36:12
+   |
+LL |         || p.y += incr;
+   |            ^^^
+note: Capturing incr[] -> ImmBorrow
+  --> $DIR/nested-closure.rs:36:19
+   |
+LL |         || p.y += incr;
+   |                   ^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/nested-closure.rs:36:9
+   |
+LL |         || p.y += incr;
+   |         ^^^^^^^^^^^^^^
+   |
+note: Min Capture p[(1, 0)] -> MutBorrow
+  --> $DIR/nested-closure.rs:36:12
+   |
+LL |         || p.y += incr;
+   |            ^^^
+note: Min Capture incr[] -> ImmBorrow
+  --> $DIR/nested-closure.rs:36:19
+   |
+LL |         || p.y += incr;
+   |                   ^^^^
+
+error: First Pass analysis includes:
+  --> $DIR/nested-closure.rs:26:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", p.x);
+...  |
+LL | |         println!("{}", p.y);
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0)] -> ImmBorrow
+  --> $DIR/nested-closure.rs:29:24
+   |
+LL |         println!("{}", p.x);
+   |                        ^^^
+note: Capturing p[(1, 0)] -> MutBorrow
+  --> $DIR/nested-closure.rs:36:12
+   |
+LL |         || p.y += incr;
+   |            ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/nested-closure.rs:26:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", p.x);
+...  |
+LL | |         println!("{}", p.y);
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[(0, 0)] -> ImmBorrow
+  --> $DIR/nested-closure.rs:29:24
+   |
+LL |         println!("{}", p.x);
+   |                        ^^^
+note: Min Capture p[(1, 0)] -> MutBorrow
+  --> $DIR/nested-closure.rs:36:12
+   |
+LL |         || p.y += incr;
+   |            ^^^
+
+error: aborting due to 6 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/path-with-array-access.rs b/src/test/ui/closures/2229_closure_analysis/path-with-array-access.rs
new file mode 100644
index 0000000000000..16acd2f3206c9
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/path-with-array-access.rs
@@ -0,0 +1,35 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+struct Point {
+    x: f32,
+    y: f32,
+}
+
+struct Pentagon {
+    points: [Point; 5],
+}
+
+fn main() {
+    let p1 = Point { x: 10.0, y: 10.0 };
+    let p2 = Point { x: 7.5, y: 12.5 };
+    let p3 = Point { x: 15.0, y: 15.0 };
+    let p4 = Point { x: 12.5, y: 12.5 };
+    let p5 = Point { x: 20.0, y: 10.0 };
+
+    let pent = Pentagon { points: [p1, p2, p3, p4, p5] };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        println!("{}", pent.points[5].x);
+        //~^ NOTE: Capturing pent[(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture pent[(0, 0)] -> ImmBorrow
+    };
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/path-with-array-access.stderr b/src/test/ui/closures/2229_closure_analysis/path-with-array-access.stderr
new file mode 100644
index 0000000000000..3c8d07ed9ba67
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/path-with-array-access.stderr
@@ -0,0 +1,57 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/path-with-array-access.rs:25:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/path-with-array-access.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/path-with-array-access.rs:28:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", pent.points[5].x);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing pent[(0, 0)] -> ImmBorrow
+  --> $DIR/path-with-array-access.rs:31:24
+   |
+LL |         println!("{}", pent.points[5].x);
+   |                        ^^^^^^^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/path-with-array-access.rs:28:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{}", pent.points[5].x);
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture pent[(0, 0)] -> ImmBorrow
+  --> $DIR/path-with-array-access.rs:31:24
+   |
+LL |         println!("{}", pent.points[5].x);
+   |                        ^^^^^^^^^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs
new file mode 100644
index 0000000000000..aaff3531e5850
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs
@@ -0,0 +1,41 @@
+// FIXME(arora-aman) add run-pass once 2229 is implemented
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+// Test to ensure that min analysis meets capture kind for all paths captured.
+
+#[derive(Debug)]
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+fn main() {
+    let mut p = Point { x: 10, y: 20 };
+
+    //
+    // Requirements:
+    // p.x -> MutBoorrow
+    // p   -> ImmBorrow
+    //
+    // Requirements met when p is captured via MutBorrow
+    //
+    let mut c = #[rustc_capture_analysis]
+        //~^ ERROR: attributes on expressions are experimental
+        //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        p.x += 10;
+        //~^ NOTE: Capturing p[(0, 0)] -> MutBorrow
+        //~| NOTE: Min Capture p[] -> MutBorrow
+        println!("{:?}", p);
+        //~^ NOTE: Capturing p[] -> ImmBorrow
+    };
+
+    c();
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr
new file mode 100644
index 0000000000000..30d3d5f504eb9
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr
@@ -0,0 +1,62 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/simple-struct-min-capture.rs:27:17
+   |
+LL |     let mut c = #[rustc_capture_analysis]
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/simple-struct-min-capture.rs:3:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/simple-struct-min-capture.rs:30:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         p.x += 10;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0)] -> MutBorrow
+  --> $DIR/simple-struct-min-capture.rs:33:9
+   |
+LL |         p.x += 10;
+   |         ^^^
+note: Capturing p[] -> ImmBorrow
+  --> $DIR/simple-struct-min-capture.rs:36:26
+   |
+LL |         println!("{:?}", p);
+   |                          ^
+
+error: Min Capture analysis includes:
+  --> $DIR/simple-struct-min-capture.rs:30:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         p.x += 10;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[] -> MutBorrow
+  --> $DIR/simple-struct-min-capture.rs:33:9
+   |
+LL |         p.x += 10;
+   |         ^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/wild_patterns.rs b/src/test/ui/closures/2229_closure_analysis/wild_patterns.rs
new file mode 100644
index 0000000000000..90b8033d074a1
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/wild_patterns.rs
@@ -0,0 +1,75 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+// Test to ensure that we can handle cases where
+// let statements create no bindings are intialized
+// using a Place expression
+//
+// Note: Currently when feature `capture_disjoint_fields` is enabled
+// we can't handle such cases. So the test current use `_x` instead of
+// `_` until the issue is resolved.
+// Check rust-lang/project-rfc-2229#24 for status.
+
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+fn wild_struct() {
+    let p = Point { x: 10, y: 20 };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        // FIXME(arora-aman): Change `_x` to `_`
+        let Point { x: _x, y: _ } = p;
+        //~^ NOTE: Capturing p[(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture p[(0, 0)] -> ImmBorrow
+    };
+
+    c();
+}
+
+fn wild_tuple() {
+    let t = (String::new(), 10);
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        // FIXME(arora-aman): Change `_x` to `_`
+        let (_x, _) = t;
+        //~^ NOTE: Capturing t[(0, 0)] -> ByValue
+        //~| NOTE: Min Capture t[(0, 0)] -> ByValue
+    };
+
+    c();
+}
+
+fn wild_arr() {
+    let arr = [String::new(), String::new()];
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        // FIXME(arora-aman): Change `_x` to `_`
+        let [_x, _] = arr;
+        //~^ NOTE: Capturing arr[Index] -> ByValue
+        //~| NOTE: Min Capture arr[] -> ByValue
+    };
+
+    c();
+}
+
+fn main() {}
diff --git a/src/test/ui/closures/2229_closure_analysis/wild_patterns.stderr b/src/test/ui/closures/2229_closure_analysis/wild_patterns.stderr
new file mode 100644
index 0000000000000..36be8431be508
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/wild_patterns.stderr
@@ -0,0 +1,147 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/wild_patterns.rs:24:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/wild_patterns.rs:42:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/wild_patterns.rs:60:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/wild_patterns.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/wild_patterns.rs:27:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         // FIXME(arora-aman): Change `_x` to `_`
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0)] -> ImmBorrow
+  --> $DIR/wild_patterns.rs:31:37
+   |
+LL |         let Point { x: _x, y: _ } = p;
+   |                                     ^
+
+error: Min Capture analysis includes:
+  --> $DIR/wild_patterns.rs:27:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         // FIXME(arora-aman): Change `_x` to `_`
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[(0, 0)] -> ImmBorrow
+  --> $DIR/wild_patterns.rs:31:37
+   |
+LL |         let Point { x: _x, y: _ } = p;
+   |                                     ^
+
+error: First Pass analysis includes:
+  --> $DIR/wild_patterns.rs:45:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         // FIXME(arora-aman): Change `_x` to `_`
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing t[(0, 0)] -> ByValue
+  --> $DIR/wild_patterns.rs:49:23
+   |
+LL |         let (_x, _) = t;
+   |                       ^
+
+error: Min Capture analysis includes:
+  --> $DIR/wild_patterns.rs:45:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         // FIXME(arora-aman): Change `_x` to `_`
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture t[(0, 0)] -> ByValue
+  --> $DIR/wild_patterns.rs:49:23
+   |
+LL |         let (_x, _) = t;
+   |                       ^
+
+error: First Pass analysis includes:
+  --> $DIR/wild_patterns.rs:63:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         // FIXME(arora-aman): Change `_x` to `_`
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing arr[Index] -> ByValue
+  --> $DIR/wild_patterns.rs:67:23
+   |
+LL |         let [_x, _] = arr;
+   |                       ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/wild_patterns.rs:63:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         // FIXME(arora-aman): Change `_x` to `_`
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture arr[] -> ByValue
+  --> $DIR/wild_patterns.rs:67:23
+   |
+LL |         let [_x, _] = arr;
+   |                       ^^^
+
+error: aborting due to 9 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/generator/print/generator-print-verbose-2.stderr b/src/test/ui/generator/print/generator-print-verbose-2.stderr
index f23949091d912..d590f876b8e77 100644
--- a/src/test/ui/generator/print/generator-print-verbose-2.stderr
+++ b/src/test/ui/generator/print/generator-print-verbose-2.stderr
@@ -8,8 +8,8 @@ LL |     assert_send(|| {
    |     ^^^^^^^^^^^ `Cell<i32>` cannot be shared between threads safely
    |
    = help: the trait `Sync` is not implemented for `Cell<i32>`
-   = note: required because of the requirements on the impl of `Send` for `&'_#3r Cell<i32>`
-   = note: required because it appears within the type `[main::{closure#1} upvar_tys=(&'_#3r Cell<i32>) _#17t]`
+   = note: required because of the requirements on the impl of `Send` for `&'_#4r Cell<i32>`
+   = note: required because it appears within the type `[main::{closure#1} upvar_tys=(&'_#4r Cell<i32>) _#17t]`
 
 error: generator cannot be shared between threads safely
   --> $DIR/generator-print-verbose-2.rs:12:5
diff --git a/src/test/ui/issues/issue-50865-private-impl-trait/auxiliary/lib.rs b/src/test/ui/issues/issue-50865-private-impl-trait/auxiliary/lib.rs
index fb4bf2b8b44e7..f3a51b415faca 100644
--- a/src/test/ui/issues/issue-50865-private-impl-trait/auxiliary/lib.rs
+++ b/src/test/ui/issues/issue-50865-private-impl-trait/auxiliary/lib.rs
@@ -1,7 +1,3 @@
-// revisions: default miropt
-//[miropt]compile-flags: -Z mir-opt-level=2
-// ~^ This flag is for #77668, it used to be ICE.
-
 #![crate_type = "lib"]
 
 pub fn bar<P>( // Error won't happen if "bar" is not generic
diff --git a/src/test/ui/mir/mir-inlining/ice-issue-77306-1.rs b/src/test/ui/mir/mir-inlining/ice-issue-77306-1.rs
index 4d083bf232155..ccb279f7fa212 100644
--- a/src/test/ui/mir/mir-inlining/ice-issue-77306-1.rs
+++ b/src/test/ui/mir/mir-inlining/ice-issue-77306-1.rs
@@ -1,17 +1,27 @@
-// run-pass
+// Regression test for various issues related to normalization & inlining.
+// * #68347, #77306, #77668 - missed normalization during inlining.
+// * #78442 - missed normalization in validator after inlining.
+//
+// build-pass
 // compile-flags:-Zmir-opt-level=2
 
-// Previously ICEd because we did not normalize during inlining,
-// see https://github.com/rust-lang/rust/pull/77306 for more discussion.
-
 pub fn write() {
     create()()
 }
 
+pub fn write_generic<T>(_t: T) {
+    hide()();
+}
+
 pub fn create() -> impl FnOnce() {
    || ()
 }
 
+pub fn hide() -> impl Fn() {
+    write
+}
+
 fn main() {
     write();
+    write_generic(());
 }