about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlan Egerton <eggyal@gmail.com>2021-04-05 00:27:57 +0100
committerAlan Egerton <eggyal@gmail.com>2021-04-05 00:27:57 +0100
commit01be6dd37a41c2b10be2ca815b9afa509c7f737a (patch)
tree46d7f5af5261add5685c85095b5fd8dbb55bc495
parent82b2863a204e727a37a2d23efd738387d2fbab70 (diff)
parentc755ee4ce8cae6ea977d65a0288480940db721d9 (diff)
downloadrust-01be6dd37a41c2b10be2ca815b9afa509c7f737a.tar.gz
rust-01be6dd37a41c2b10be2ca815b9afa509c7f737a.zip
Merge branch 'master' of github.com:rust-lang/rust into issue-83852
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs57
-rw-r--r--compiler/rustc_errors/src/emitter.rs3
-rw-r--r--compiler/rustc_errors/src/json.rs25
-rw-r--r--compiler/rustc_errors/src/lib.rs8
-rw-r--r--compiler/rustc_expand/src/base.rs9
-rw-r--r--compiler/rustc_expand/src/expand.rs2
-rw-r--r--compiler/rustc_interface/src/passes.rs7
-rw-r--r--compiler/rustc_metadata/src/creader.rs37
-rw-r--r--compiler/rustc_middle/src/mir/mod.rs20
-rw-r--r--compiler/rustc_middle/src/mir/visit.rs2
-rw-r--r--compiler/rustc_mir/src/borrow_check/diagnostics/conflict_errors.rs2
-rw-r--r--compiler/rustc_mir/src/borrow_check/diagnostics/explain_borrow.rs2
-rw-r--r--compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs22
-rw-r--r--compiler/rustc_mir/src/borrow_check/invalidation.rs2
-rw-r--r--compiler/rustc_mir/src/borrow_check/mod.rs2
-rw-r--r--compiler/rustc_mir/src/dataflow/move_paths/builder.rs4
-rw-r--r--compiler/rustc_mir/src/transform/coverage/spans.rs4
-rw-r--r--compiler/rustc_mir_build/src/build/cfg.rs2
-rw-r--r--compiler/rustc_mir_build/src/build/expr/as_rvalue.rs32
-rw-r--r--compiler/rustc_mir_build/src/build/matches/mod.rs6
-rw-r--r--compiler/rustc_resolve/src/lib.rs15
-rw-r--r--compiler/rustc_resolve/src/macros.rs90
-rw-r--r--compiler/rustc_session/src/config.rs35
-rw-r--r--compiler/rustc_session/src/options.rs3
-rw-r--r--compiler/rustc_typeck/src/check/expectation.rs4
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs4
-rw-r--r--compiler/rustc_typeck/src/expr_use_visitor.rs21
-rw-r--r--library/alloc/src/collections/btree/node.rs332
-rw-r--r--library/alloc/tests/vec.rs15
-rw-r--r--library/core/src/iter/adapters/mod.rs23
-rw-r--r--src/bootstrap/test.rs1
-rw-r--r--src/librustdoc/config.rs7
-rw-r--r--src/librustdoc/doctest.rs107
-rw-r--r--src/librustdoc/html/highlight.rs8
-rw-r--r--src/librustdoc/html/highlight/fixtures/dos_line.html2
-rw-r--r--src/librustdoc/html/highlight/fixtures/sample.html6
-rw-r--r--src/librustdoc/html/markdown.rs15
-rw-r--r--src/librustdoc/html/render/context.rs7
-rw-r--r--src/librustdoc/html/render/mod.rs18
-rw-r--r--src/test/mir-opt/address_of.address_of_reborrow.SimplifyCfg-initial.after.mir28
-rw-r--r--src/test/mir-opt/address_of.borrow_and_cast.SimplifyCfg-initial.after.mir6
-rw-r--r--src/test/mir-opt/basic_assignment.main.SimplifyCfg-initial.after.mir4
-rw-r--r--src/test/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir2
-rw-r--r--src/test/mir-opt/issue_38669.main.SimplifyCfg-initial.after.mir2
-rw-r--r--src/test/mir-opt/issue_49232.main.mir_map.0.mir4
-rw-r--r--src/test/mir-opt/issue_72181.main.mir_map.0.32bit.mir2
-rw-r--r--src/test/mir-opt/issue_72181.main.mir_map.0.64bit.mir2
-rw-r--r--src/test/mir-opt/issue_72181_1.f.mir_map.0.mir2
-rw-r--r--src/test/mir-opt/issue_72181_1.main.mir_map.0.mir2
-rw-r--r--src/test/mir-opt/loop_test.main.SimplifyCfg-promote-consts.after.mir2
-rw-r--r--src/test/mir-opt/match_arm_scopes.complicated_match.SimplifyCfg-initial.after-ElaborateDrops.after.diff2
-rw-r--r--src/test/mir-opt/match_false_edges.full_tested_match.PromoteTemps.after.mir2
-rw-r--r--src/test/mir-opt/match_false_edges.full_tested_match2.PromoteTemps.before.mir2
-rw-r--r--src/test/mir-opt/match_false_edges.main.PromoteTemps.before.mir2
-rw-r--r--src/test/mir-opt/match_test.main.SimplifyCfg-initial.after.mir6
-rw-r--r--src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir6
-rw-r--r--src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir6
-rw-r--r--src/test/mir-opt/receiver_ptr_mutability.main.mir_map.0.mir4
-rw-r--r--src/test/mir-opt/remove_fake_borrows.match_guard.CleanupNonCodegenStatements.diff2
-rw-r--r--src/test/mir-opt/simple_match.match_bool.mir_map.0.32bit.mir2
-rw-r--r--src/test/mir-opt/simple_match.match_bool.mir_map.0.64bit.mir2
-rw-r--r--src/test/mir-opt/storage_ranges.main.nll.0.mir6
-rw-r--r--src/test/mir-opt/uniform_array_move_out.move_out_by_subslice.mir_map.0.mir2
-rw-r--r--src/test/mir-opt/uniform_array_move_out.move_out_from_end.mir_map.0.mir2
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs2
-rw-r--r--src/tools/compiletest/src/main.rs3
-rw-r--r--src/tools/compiletest/src/tests.rs3
-rw-r--r--src/tools/tidy/Cargo.toml1
-rw-r--r--src/tools/tidy/src/bins.rs169
-rw-r--r--src/tools/tidy/src/error_codes_check.rs40
-rw-r--r--src/tools/tidy/src/main.rs121
72 files changed, 898 insertions, 505 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0bb395cdcb4..2d00f31e315 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5308,6 +5308,7 @@ name = "tidy"
 version = "0.1.0"
 dependencies = [
  "cargo_metadata 0.11.1",
+ "crossbeam-utils 0.8.0",
  "lazy_static",
  "regex",
  "walkdir",
diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs
index 0da2c1c1021..1bb050a40ce 100644
--- a/compiler/rustc_builtin_macros/src/derive.rs
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -1,6 +1,6 @@
 use crate::cfg_eval::cfg_eval;
 
-use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
+use rustc_ast::{self as ast, attr, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
 use rustc_errors::{struct_span_err, Applicability};
 use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
 use rustc_feature::AttributeTemplate;
@@ -26,32 +26,39 @@ impl MultiItemModifier for Expander {
             return ExpandResult::Ready(vec![item]);
         }
 
-        let template =
-            AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
-        let attr = ecx.attribute(meta_item.clone());
-        validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template);
+        let result =
+            ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
+                let template =
+                    AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
+                let attr = attr::mk_attr_outer(meta_item.clone());
+                validate_attr::check_builtin_attribute(
+                    &sess.parse_sess,
+                    &attr,
+                    sym::derive,
+                    template,
+                );
 
-        let derives: Vec<_> = attr
-            .meta_item_list()
-            .unwrap_or_default()
-            .into_iter()
-            .filter_map(|nested_meta| match nested_meta {
-                NestedMetaItem::MetaItem(meta) => Some(meta),
-                NestedMetaItem::Literal(lit) => {
-                    // Reject `#[derive("Debug")]`.
-                    report_unexpected_literal(sess, &lit);
-                    None
-                }
-            })
-            .map(|meta| {
-                // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
-                report_path_args(sess, &meta);
-                meta.path
-            })
-            .collect();
+                attr.meta_item_list()
+                    .unwrap_or_default()
+                    .into_iter()
+                    .filter_map(|nested_meta| match nested_meta {
+                        NestedMetaItem::MetaItem(meta) => Some(meta),
+                        NestedMetaItem::Literal(lit) => {
+                            // Reject `#[derive("Debug")]`.
+                            report_unexpected_literal(sess, &lit);
+                            None
+                        }
+                    })
+                    .map(|meta| {
+                        // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
+                        report_path_args(sess, &meta);
+                        meta.path
+                    })
+                    .map(|path| (path, None))
+                    .collect()
+            });
 
-        // FIXME: Try to cache intermediate results to avoid collecting same paths multiple times.
-        match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) {
+        match result {
             Ok(()) => ExpandResult::Ready(cfg_eval(ecx, item)),
             Err(Indeterminate) => ExpandResult::Retry(item),
         }
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 8ec06feb5bf..a58caf2667b 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -195,6 +195,9 @@ pub trait Emitter {
 
     fn emit_future_breakage_report(&mut self, _diags: Vec<(FutureBreakage, Diagnostic)>) {}
 
+    /// Emit list of unused externs
+    fn emit_unused_externs(&mut self, _lint_level: &str, _unused_externs: &[&str]) {}
+
     /// Checks if should show explanations about "rustc --explain"
     fn should_show_explain(&self) -> bool {
         true
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index 2bce1ac3c0a..40277006462 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -159,6 +159,19 @@ impl Emitter for JsonEmitter {
         }
     }
 
+    fn emit_unused_externs(&mut self, lint_level: &str, unused_externs: &[&str]) {
+        let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
+        let result = if self.pretty {
+            writeln!(&mut self.dst, "{}", as_pretty_json(&data))
+        } else {
+            writeln!(&mut self.dst, "{}", as_json(&data))
+        }
+        .and_then(|_| self.dst.flush());
+        if let Err(e) = result {
+            panic!("failed to print unused externs: {:?}", e);
+        }
+    }
+
     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         Some(&self.sm)
     }
@@ -322,6 +335,18 @@ struct FutureIncompatReport {
     future_incompat_report: Vec<FutureBreakageItem>,
 }
 
+// NOTE: Keep this in sync with the equivalent structs in rustdoc's
+// doctest component (as well as cargo).
+// We could unify this struct the one in rustdoc but they have different
+// ownership semantics, so doing so would create wasteful allocations.
+#[derive(Encodable)]
+struct UnusedExterns<'a, 'b, 'c> {
+    /// The severity level of the unused dependencies lint
+    lint_level: &'a str,
+    /// List of unused externs by their names.
+    unused_extern_names: &'b [&'c str],
+}
+
 impl Diagnostic {
     fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
         let sugg = diag.suggestions.iter().map(|sugg| Diagnostic {
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index ac7353730ad..0d1f55a6b00 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -765,6 +765,10 @@ impl Handler {
         self.inner.borrow_mut().emitter.emit_future_breakage_report(diags)
     }
 
+    pub fn emit_unused_externs(&self, lint_level: &str, unused_externs: &[&str]) {
+        self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs)
+    }
+
     pub fn delay_as_bug(&self, diagnostic: Diagnostic) {
         self.inner.borrow_mut().delay_as_bug(diagnostic)
     }
@@ -839,6 +843,10 @@ impl HandlerInner {
         self.emitter.emit_artifact_notification(path, artifact_type);
     }
 
+    fn emit_unused_externs(&mut self, lint_level: &str, unused_externs: &[&str]) {
+        self.emitter.emit_unused_externs(lint_level, unused_externs);
+    }
+
     fn treat_err_as_bug(&self) -> bool {
         self.flags.treat_err_as_bug.map_or(false, |c| self.err_count() >= c.get())
     }
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 594b9a82ad0..a2035ee3c6e 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -868,6 +868,8 @@ impl SyntaxExtension {
 /// Error type that denotes indeterminacy.
 pub struct Indeterminate;
 
+pub type DeriveResolutions = Vec<(ast::Path, Option<Lrc<SyntaxExtension>>)>;
+
 pub trait ResolverExpand {
     fn next_node_id(&mut self) -> NodeId;
 
@@ -904,15 +906,12 @@ pub trait ResolverExpand {
     fn resolve_derives(
         &mut self,
         expn_id: ExpnId,
-        derives: Vec<ast::Path>,
         force: bool,
+        derive_paths: &dyn Fn() -> DeriveResolutions,
     ) -> Result<(), Indeterminate>;
     /// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
     /// back from resolver.
-    fn take_derive_resolutions(
-        &mut self,
-        expn_id: ExpnId,
-    ) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>>;
+    fn take_derive_resolutions(&mut self, expn_id: ExpnId) -> Option<DeriveResolutions>;
     /// Path resolution logic for `#[cfg_accessible(path)]`.
     fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
 }
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 470788a972a..53e2b4e6acc 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -515,7 +515,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                             invocations.reserve(derives.len());
                             derives
                                 .into_iter()
-                                .map(|(_exts, path)| {
+                                .map(|(path, _exts)| {
                                     // FIXME: Consider using the derive resolutions (`_exts`)
                                     // instead of enqueuing the derives to be resolved again later.
                                     let expn_id = ExpnId::fresh(None);
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index c693155994f..02e62a2cee9 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -16,6 +16,7 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
 use rustc_hir::definitions::Definitions;
 use rustc_hir::Crate;
 use rustc_lint::LintStore;
+use rustc_metadata::creader::CStore;
 use rustc_middle::arena::Arena;
 use rustc_middle::dep_graph::DepGraph;
 use rustc_middle::middle;
@@ -831,6 +832,12 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> {
                 });
 
                 sess.time("looking_for_derive_registrar", || proc_macro_decls::find(tcx));
+
+                let cstore = tcx
+                    .cstore_as_any()
+                    .downcast_ref::<CStore>()
+                    .expect("`tcx.cstore` is not a `CStore`");
+                cstore.report_unused_deps(tcx);
             },
             {
                 par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 9578f5d5863..26db3a5f39d 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -46,6 +46,9 @@ pub struct CStore {
     /// This map is used to verify we get no hash conflicts between
     /// `StableCrateId` values.
     stable_crate_ids: FxHashMap<StableCrateId, CrateNum>,
+
+    /// Unused externs of the crate
+    unused_externs: Vec<Symbol>,
 }
 
 pub struct CrateLoader<'a> {
@@ -190,6 +193,27 @@ impl CStore {
     crate fn has_global_allocator(&self) -> bool {
         self.has_global_allocator
     }
+
+    pub fn report_unused_deps(&self, tcx: TyCtxt<'_>) {
+        // We put the check for the option before the lint_level_at_node call
+        // because the call mutates internal state and introducing it
+        // leads to some ui tests failing.
+        if !tcx.sess.opts.json_unused_externs {
+            return;
+        }
+        let level = tcx
+            .lint_level_at_node(lint::builtin::UNUSED_CRATE_DEPENDENCIES, rustc_hir::CRATE_HIR_ID)
+            .0;
+        if level != lint::Level::Allow {
+            let unused_externs =
+                self.unused_externs.iter().map(|ident| ident.to_ident_string()).collect::<Vec<_>>();
+            let unused_externs = unused_externs.iter().map(String::as_str).collect::<Vec<&str>>();
+            tcx.sess
+                .parse_sess
+                .span_diagnostic
+                .emit_unused_externs(level.as_str(), &unused_externs);
+        }
+    }
 }
 
 impl<'a> CrateLoader<'a> {
@@ -217,6 +241,7 @@ impl<'a> CrateLoader<'a> {
                 allocator_kind: None,
                 has_global_allocator: false,
                 stable_crate_ids,
+                unused_externs: Vec::new(),
             },
             used_extern_options: Default::default(),
         }
@@ -904,11 +929,17 @@ impl<'a> CrateLoader<'a> {
                 // Don't worry about pathless `--extern foo` sysroot references
                 continue;
             }
-            if self.used_extern_options.contains(&Symbol::intern(name)) {
+            let name_interned = Symbol::intern(name);
+            if self.used_extern_options.contains(&name_interned) {
                 continue;
             }
 
             // Got a real unused --extern
+            if self.sess.opts.json_unused_externs {
+                self.cstore.unused_externs.push(name_interned);
+                continue;
+            }
+
             let diag = match self.sess.opts.extern_dep_specs.get(name) {
                 Some(loc) => BuiltinLintDiagnostics::ExternDepSpec(name.clone(), loc.into()),
                 None => {
@@ -941,9 +972,9 @@ impl<'a> CrateLoader<'a> {
         self.inject_allocator_crate(krate);
         self.inject_panic_runtime(krate);
 
-        info!("{:?}", CrateDump(&self.cstore));
-
         self.report_unused_deps(krate);
+
+        info!("{:?}", CrateDump(&self.cstore));
     }
 
     pub fn process_extern_crate(
diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index b2cf1aab112..a7e9fc468b4 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -1482,7 +1482,7 @@ pub enum StatementKind<'tcx> {
     ///
     /// Note that this also is emitted for regular `let` bindings to ensure that locals that are
     /// never accessed still get some sanity checks for, e.g., `let x: ! = ..;`
-    FakeRead(FakeReadCause, Box<Place<'tcx>>),
+    FakeRead(Box<(FakeReadCause, Place<'tcx>)>),
 
     /// Write the discriminant for a variant to the enum Place.
     SetDiscriminant { place: Box<Place<'tcx>>, variant_index: VariantIdx },
@@ -1575,7 +1575,12 @@ pub enum FakeReadCause {
 
     /// `let x: !; match x {}` doesn't generate any read of x so we need to
     /// generate a read of x to check that it is initialized and safe.
-    ForMatchedPlace,
+    ///
+    /// If a closure pattern matches a Place starting with an Upvar, then we introduce a
+    /// FakeRead for that Place outside the closure, in such a case this option would be
+    /// Some(closure_def_id).
+    /// Otherwise, the value of the optional DefId will be None.
+    ForMatchedPlace(Option<DefId>),
 
     /// A fake read of the RefWithinGuard version of a bind-by-value variable
     /// in a match guard to ensure that it's value hasn't change by the time
@@ -1594,7 +1599,12 @@ pub enum FakeReadCause {
     /// but in some cases it can affect the borrow checker, as in #53695.
     /// Therefore, we insert a "fake read" here to ensure that we get
     /// appropriate errors.
-    ForLet,
+    ///
+    /// If a closure pattern matches a Place starting with an Upvar, then we introduce a
+    /// FakeRead for that Place outside the closure, in such a case this option would be
+    /// Some(closure_def_id).
+    /// Otherwise, the value of the optional DefId will be None.
+    ForLet(Option<DefId>),
 
     /// If we have an index expression like
     ///
@@ -1618,7 +1628,9 @@ impl Debug for Statement<'_> {
         use self::StatementKind::*;
         match self.kind {
             Assign(box (ref place, ref rv)) => write!(fmt, "{:?} = {:?}", place, rv),
-            FakeRead(ref cause, ref place) => write!(fmt, "FakeRead({:?}, {:?})", cause, place),
+            FakeRead(box (ref cause, ref place)) => {
+                write!(fmt, "FakeRead({:?}, {:?})", cause, place)
+            }
             Retag(ref kind, ref place) => write!(
                 fmt,
                 "Retag({}{:?})",
diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs
index 32b4cd665d0..fd504f8c5d5 100644
--- a/compiler/rustc_middle/src/mir/visit.rs
+++ b/compiler/rustc_middle/src/mir/visit.rs
@@ -380,7 +380,7 @@ macro_rules! make_mir_visitor {
                     ) => {
                         self.visit_assign(place, rvalue, location);
                     }
-                    StatementKind::FakeRead(_, place) => {
+                    StatementKind::FakeRead(box (_, place)) => {
                         self.visit_place(
                             place,
                             PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect),
diff --git a/compiler/rustc_mir/src/borrow_check/diagnostics/conflict_errors.rs b/compiler/rustc_mir/src/borrow_check/diagnostics/conflict_errors.rs
index eb942b195b2..d5deec82088 100644
--- a/compiler/rustc_mir/src/borrow_check/diagnostics/conflict_errors.rs
+++ b/compiler/rustc_mir/src/borrow_check/diagnostics/conflict_errors.rs
@@ -1728,7 +1728,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
         impl<'tcx> Visitor<'tcx> for FakeReadCauseFinder<'tcx> {
             fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
                 match statement {
-                    Statement { kind: StatementKind::FakeRead(cause, box place), .. }
+                    Statement { kind: StatementKind::FakeRead(box (cause, place)), .. }
                         if *place == self.place =>
                     {
                         self.cause = Some(*cause);
diff --git a/compiler/rustc_mir/src/borrow_check/diagnostics/explain_borrow.rs b/compiler/rustc_mir/src/borrow_check/diagnostics/explain_borrow.rs
index 06e3f4b91f6..2a388b8a72b 100644
--- a/compiler/rustc_mir/src/borrow_check/diagnostics/explain_borrow.rs
+++ b/compiler/rustc_mir/src/borrow_check/diagnostics/explain_borrow.rs
@@ -515,7 +515,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
                 let block = &self.body.basic_blocks()[location.block];
 
                 let kind = if let Some(&Statement {
-                    kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
+                    kind: StatementKind::FakeRead(box (FakeReadCause::ForLet(_), _)),
                     ..
                 }) = block.statements.get(location.statement_index)
                 {
diff --git a/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs b/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs
index 6ea0ba0a8e1..577d7d53814 100644
--- a/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs
+++ b/compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs
@@ -7,8 +7,8 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItemGroup;
 use rustc_hir::GeneratorKind;
 use rustc_middle::mir::{
-    AggregateKind, Constant, Field, Local, LocalInfo, LocalKind, Location, Operand, Place,
-    PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
+    AggregateKind, Constant, FakeReadCause, Field, Local, LocalInfo, LocalKind, Location, Operand,
+    Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
 };
 use rustc_middle::ty::print::Print;
 use rustc_middle::ty::{self, DefIdTree, Instance, Ty, TyCtxt};
@@ -795,6 +795,24 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
             }
         }
 
+        // StatementKind::FakeRead only contains a def_id if they are introduced as a result
+        // of pattern matching within a closure.
+        if let StatementKind::FakeRead(box (cause, ref place)) = stmt.kind {
+            match cause {
+                FakeReadCause::ForMatchedPlace(Some(closure_def_id))
+                | FakeReadCause::ForLet(Some(closure_def_id)) => {
+                    debug!("move_spans: def_id={:?} place={:?}", closure_def_id, place);
+                    let places = &[Operand::Move(*place)];
+                    if let Some((args_span, generator_kind, var_span)) =
+                        self.closure_span(closure_def_id, moved_place, places)
+                    {
+                        return ClosureUse { generator_kind, args_span, var_span };
+                    }
+                }
+                _ => {}
+            }
+        }
+
         let normal_ret =
             if moved_place.projection.iter().any(|p| matches!(p, ProjectionElem::Downcast(..))) {
                 PatUse(stmt.source_info.span)
diff --git a/compiler/rustc_mir/src/borrow_check/invalidation.rs b/compiler/rustc_mir/src/borrow_check/invalidation.rs
index 1055e30a3a4..9374741f837 100644
--- a/compiler/rustc_mir/src/borrow_check/invalidation.rs
+++ b/compiler/rustc_mir/src/borrow_check/invalidation.rs
@@ -63,7 +63,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
 
                 self.mutate_place(location, *lhs, Shallow(None), JustWrite);
             }
-            StatementKind::FakeRead(_, _) => {
+            StatementKind::FakeRead(box (_, _)) => {
                 // Only relevant for initialized/liveness/safety checks.
             }
             StatementKind::SetDiscriminant { place, variant_index: _ } => {
diff --git a/compiler/rustc_mir/src/borrow_check/mod.rs b/compiler/rustc_mir/src/borrow_check/mod.rs
index 583f73d5775..71db6abde43 100644
--- a/compiler/rustc_mir/src/borrow_check/mod.rs
+++ b/compiler/rustc_mir/src/borrow_check/mod.rs
@@ -574,7 +574,7 @@ impl<'cx, 'tcx> dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tc
 
                 self.mutate_place(location, (*lhs, span), Shallow(None), JustWrite, flow_state);
             }
-            StatementKind::FakeRead(_, box ref place) => {
+            StatementKind::FakeRead(box (_, ref place)) => {
                 // Read for match doesn't access any memory and is used to
                 // assert that a place is safe and live. So we don't have to
                 // do any checks here.
diff --git a/compiler/rustc_mir/src/dataflow/move_paths/builder.rs b/compiler/rustc_mir/src/dataflow/move_paths/builder.rs
index 52b6e9f3753..538d8921869 100644
--- a/compiler/rustc_mir/src/dataflow/move_paths/builder.rs
+++ b/compiler/rustc_mir/src/dataflow/move_paths/builder.rs
@@ -293,8 +293,8 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
                 }
                 self.gather_rvalue(rval);
             }
-            StatementKind::FakeRead(_, place) => {
-                self.create_move_path(**place);
+            StatementKind::FakeRead(box (_, place)) => {
+                self.create_move_path(*place);
             }
             StatementKind::LlvmInlineAsm(ref asm) => {
                 for (output, kind) in iter::zip(&*asm.outputs, &asm.asm.outputs) {
diff --git a/compiler/rustc_mir/src/transform/coverage/spans.rs b/compiler/rustc_mir/src/transform/coverage/spans.rs
index e7097ce8619..324d826b375 100644
--- a/compiler/rustc_mir/src/transform/coverage/spans.rs
+++ b/compiler/rustc_mir/src/transform/coverage/spans.rs
@@ -683,10 +683,10 @@ pub(super) fn filtered_statement_span(
         // and `_1` is the `Place` for `somenum`.
         //
         // If and when the Issue is resolved, remove this special case match pattern:
-        StatementKind::FakeRead(cause, _) if cause == FakeReadCause::ForGuardBinding => None,
+        StatementKind::FakeRead(box (cause, _)) if cause == FakeReadCause::ForGuardBinding => None,
 
         // Retain spans from all other statements
-        StatementKind::FakeRead(_, _) // Not including `ForGuardBinding`
+        StatementKind::FakeRead(box (_, _)) // Not including `ForGuardBinding`
         | StatementKind::CopyNonOverlapping(..)
         | StatementKind::Assign(_)
         | StatementKind::SetDiscriminant { .. }
diff --git a/compiler/rustc_mir_build/src/build/cfg.rs b/compiler/rustc_mir_build/src/build/cfg.rs
index e562e52f841..fd4a783d12a 100644
--- a/compiler/rustc_mir_build/src/build/cfg.rs
+++ b/compiler/rustc_mir_build/src/build/cfg.rs
@@ -80,7 +80,7 @@ impl<'tcx> CFG<'tcx> {
         cause: FakeReadCause,
         place: Place<'tcx>,
     ) {
-        let kind = StatementKind::FakeRead(cause, box place);
+        let kind = StatementKind::FakeRead(box (cause, place));
         let stmt = Statement { source_info, kind };
         self.push(block, stmt);
     }
diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
index 7f24a41b00b..822fbd91c94 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
@@ -179,24 +179,20 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 //     match x { _ => () } // fake read of `x`
                 // };
                 // ```
-                // FIXME(RFC2229): Remove feature gate once diagnostics are improved
-                if this.tcx.features().capture_disjoint_fields {
-                    for (thir_place, cause, hir_id) in fake_reads.into_iter() {
-                        let place_builder =
-                            unpack!(block = this.as_place_builder(block, thir_place));
-
-                        if let Ok(place_builder_resolved) =
-                            place_builder.try_upvars_resolved(this.tcx, this.typeck_results)
-                        {
-                            let mir_place =
-                                place_builder_resolved.into_place(this.tcx, this.typeck_results);
-                            this.cfg.push_fake_read(
-                                block,
-                                this.source_info(this.tcx.hir().span(*hir_id)),
-                                *cause,
-                                mir_place,
-                            );
-                        }
+                for (thir_place, cause, hir_id) in fake_reads.into_iter() {
+                    let place_builder = unpack!(block = this.as_place_builder(block, thir_place));
+
+                    if let Ok(place_builder_resolved) =
+                        place_builder.try_upvars_resolved(this.tcx, this.typeck_results)
+                    {
+                        let mir_place =
+                            place_builder_resolved.into_place(this.tcx, this.typeck_results);
+                        this.cfg.push_fake_read(
+                            block,
+                            this.source_info(this.tcx.hir().span(*hir_id)),
+                            *cause,
+                            mir_place,
+                        );
                     }
                 }
 
diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs
index 73fd3f0feb5..0e422dc3c63 100644
--- a/compiler/rustc_mir_build/src/build/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/build/matches/mod.rs
@@ -139,7 +139,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         // uninhabited value. If we get never patterns, those will check that
         // the place is initialized, and so this read would only be used to
         // check safety.
-        let cause_matched_place = FakeReadCause::ForMatchedPlace;
+        let cause_matched_place = FakeReadCause::ForMatchedPlace(None);
         let source_info = self.source_info(scrutinee_span);
 
         if let Ok(scrutinee_builder) =
@@ -400,7 +400,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
                 // Inject a fake read, see comments on `FakeReadCause::ForLet`.
                 let source_info = self.source_info(irrefutable_pat.span);
-                self.cfg.push_fake_read(block, source_info, FakeReadCause::ForLet, place);
+                self.cfg.push_fake_read(block, source_info, FakeReadCause::ForLet(None), place);
 
                 self.schedule_drop_for_binding(var, irrefutable_pat.span, OutsideGuard);
                 block.unit()
@@ -435,7 +435,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
                 // Inject a fake read, see comments on `FakeReadCause::ForLet`.
                 let pattern_source_info = self.source_info(irrefutable_pat.span);
-                let cause_let = FakeReadCause::ForLet;
+                let cause_let = FakeReadCause::ForLet(None);
                 self.cfg.push_fake_read(block, pattern_source_info, cause_let, place);
 
                 let ty_source_info = self.source_info(user_ty_span);
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index fe5078f26bd..d474e990211 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -37,7 +37,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
 use rustc_data_structures::ptr_key::PtrKey;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
-use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
+use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind};
 use rustc_hir::def::Namespace::*;
 use rustc_hir::def::{self, CtorOf, DefKind, NonMacroAttrKind, PartialRes};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, CRATE_DEF_INDEX};
@@ -851,6 +851,12 @@ enum BuiltinMacroState {
     AlreadySeen(Span),
 }
 
+struct DeriveData {
+    resolutions: DeriveResolutions,
+    helper_attrs: Vec<(usize, Ident)>,
+    has_derive_copy: bool,
+}
+
 /// The main resolver class.
 ///
 /// This is the visitor that walks the whole crate.
@@ -973,8 +979,9 @@ pub struct Resolver<'a> {
     output_macro_rules_scopes: FxHashMap<ExpnId, MacroRulesScopeRef<'a>>,
     /// Helper attributes that are in scope for the given expansion.
     helper_attrs: FxHashMap<ExpnId, Vec<Ident>>,
-    /// Resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
-    derive_resolutions: FxHashMap<ExpnId, Vec<(Lrc<SyntaxExtension>, ast::Path)>>,
+    /// Ready or in-progress results of resolving paths inside the `#[derive(...)]` attribute
+    /// with the given `ExpnId`.
+    derive_data: FxHashMap<ExpnId, DeriveData>,
 
     /// Avoid duplicated errors for "name already defined".
     name_already_seen: FxHashMap<Symbol, Span>,
@@ -1310,7 +1317,7 @@ impl<'a> Resolver<'a> {
             invocation_parent_scopes: Default::default(),
             output_macro_rules_scopes: Default::default(),
             helper_attrs: Default::default(),
-            derive_resolutions: Default::default(),
+            derive_data: Default::default(),
             local_macro_def_scopes: FxHashMap::default(),
             name_already_seen: FxHashMap::default(),
             potentially_unused_imports: Vec::new(),
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index d238f65c941..10e27f33c29 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -4,7 +4,7 @@
 use crate::imports::ImportResolver;
 use crate::Namespace::*;
 use crate::{AmbiguityError, AmbiguityErrorMisc, AmbiguityKind, BuiltinMacroState, Determinacy};
-use crate::{CrateLint, ParentScope, ResolutionError, Resolver, Scope, ScopeSet, Weak};
+use crate::{CrateLint, DeriveData, ParentScope, ResolutionError, Resolver, Scope, ScopeSet, Weak};
 use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment, ToNameBinding};
 use rustc_ast::{self as ast, Inline, ItemKind, ModKind, NodeId};
 use rustc_ast_lowering::ResolverAstLowering;
@@ -14,8 +14,8 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::ptr_key::PtrKey;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::struct_span_err;
-use rustc_expand::base::Annotatable;
-use rustc_expand::base::{Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind};
+use rustc_expand::base::{Annotatable, DeriveResolutions, Indeterminate, ResolverExpand};
+use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
 use rustc_expand::compile_declarative_macro;
 use rustc_expand::expand::{AstFragment, Invocation, InvocationKind, SupportsMacroExpansion};
 use rustc_feature::is_builtin_attr_name;
@@ -359,8 +359,8 @@ impl<'a> ResolverExpand for Resolver<'a> {
     fn resolve_derives(
         &mut self,
         expn_id: ExpnId,
-        derives: Vec<ast::Path>,
         force: bool,
+        derive_paths: &dyn Fn() -> DeriveResolutions,
     ) -> Result<(), Indeterminate> {
         // Block expansion of the container until we resolve all derives in it.
         // This is required for two reasons:
@@ -368,49 +368,63 @@ impl<'a> ResolverExpand for Resolver<'a> {
         //   is applied, so they have to be produced by the container's expansion rather
         //   than by individual derives.
         // - Derives in the container need to know whether one of them is a built-in `Copy`.
-        // FIXME: Try to cache intermediate results to avoid resolving same derives multiple times.
+        // Temporarily take the data to avoid borrow checker conflicts.
+        let mut derive_data = mem::take(&mut self.derive_data);
+        let entry = derive_data.entry(expn_id).or_insert_with(|| DeriveData {
+            resolutions: derive_paths(),
+            helper_attrs: Vec::new(),
+            has_derive_copy: false,
+        });
         let parent_scope = self.invocation_parent_scopes[&expn_id];
-        let mut exts = Vec::new();
-        let mut helper_attrs = Vec::new();
-        let mut has_derive_copy = false;
-        for path in derives {
-            exts.push((
-                match self.resolve_macro_path(
-                    &path,
-                    Some(MacroKind::Derive),
-                    &parent_scope,
-                    true,
-                    force,
-                ) {
-                    Ok((Some(ext), _)) => {
-                        let span =
-                            path.segments.last().unwrap().ident.span.normalize_to_macros_2_0();
-                        helper_attrs
-                            .extend(ext.helper_attrs.iter().map(|name| Ident::new(*name, span)));
-                        has_derive_copy |= ext.builtin_name == Some(sym::Copy);
-                        ext
-                    }
-                    Ok(_) | Err(Determinacy::Determined) => self.dummy_ext(MacroKind::Derive),
-                    Err(Determinacy::Undetermined) => return Err(Indeterminate),
-                },
-                path,
-            ))
+        for (i, (path, opt_ext)) in entry.resolutions.iter_mut().enumerate() {
+            if opt_ext.is_none() {
+                *opt_ext = Some(
+                    match self.resolve_macro_path(
+                        &path,
+                        Some(MacroKind::Derive),
+                        &parent_scope,
+                        true,
+                        force,
+                    ) {
+                        Ok((Some(ext), _)) => {
+                            if !ext.helper_attrs.is_empty() {
+                                let last_seg = path.segments.last().unwrap();
+                                let span = last_seg.ident.span.normalize_to_macros_2_0();
+                                entry.helper_attrs.extend(
+                                    ext.helper_attrs
+                                        .iter()
+                                        .map(|name| (i, Ident::new(*name, span))),
+                                );
+                            }
+                            entry.has_derive_copy |= ext.builtin_name == Some(sym::Copy);
+                            ext
+                        }
+                        Ok(_) | Err(Determinacy::Determined) => self.dummy_ext(MacroKind::Derive),
+                        Err(Determinacy::Undetermined) => {
+                            assert!(self.derive_data.is_empty());
+                            self.derive_data = derive_data;
+                            return Err(Indeterminate);
+                        }
+                    },
+                );
+            }
         }
-        self.derive_resolutions.insert(expn_id, exts);
-        self.helper_attrs.insert(expn_id, helper_attrs);
+        // Sort helpers in a stable way independent from the derive resolution order.
+        entry.helper_attrs.sort_by_key(|(i, _)| *i);
+        self.helper_attrs
+            .insert(expn_id, entry.helper_attrs.iter().map(|(_, ident)| *ident).collect());
         // Mark this derive as having `Copy` either if it has `Copy` itself or if its parent derive
         // has `Copy`, to support cases like `#[derive(Clone, Copy)] #[derive(Debug)]`.
-        if has_derive_copy || self.has_derive_copy(parent_scope.expansion) {
+        if entry.has_derive_copy || self.has_derive_copy(parent_scope.expansion) {
             self.containers_deriving_copy.insert(expn_id);
         }
+        assert!(self.derive_data.is_empty());
+        self.derive_data = derive_data;
         Ok(())
     }
 
-    fn take_derive_resolutions(
-        &mut self,
-        expn_id: ExpnId,
-    ) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>> {
-        self.derive_resolutions.remove(&expn_id)
+    fn take_derive_resolutions(&mut self, expn_id: ExpnId) -> Option<DeriveResolutions> {
+        self.derive_data.remove(&expn_id).map(|data| data.resolutions)
     }
 
     // The function that implements the resolution logic of `#[cfg_accessible(path)]`.
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index e39b9b9b33e..c7d561f760b 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -456,6 +456,10 @@ impl Externs {
     pub fn iter(&self) -> BTreeMapIter<'_, String, ExternEntry> {
         self.0.iter()
     }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
 }
 
 impl ExternEntry {
@@ -698,6 +702,7 @@ impl Default for Options {
             remap_path_prefix: Vec::new(),
             edition: DEFAULT_EDITION,
             json_artifact_notifications: false,
+            json_unused_externs: false,
             pretty: None,
         }
     }
@@ -1196,15 +1201,23 @@ pub fn parse_color(matches: &getopts::Matches) -> ColorConfig {
     }
 }
 
+/// Possible json config files
+pub struct JsonConfig {
+    pub json_rendered: HumanReadableErrorType,
+    pub json_artifact_notifications: bool,
+    pub json_unused_externs: bool,
+}
+
 /// Parse the `--json` flag.
 ///
 /// The first value returned is how to render JSON diagnostics, and the second
 /// is whether or not artifact notifications are enabled.
-pub fn parse_json(matches: &getopts::Matches) -> (HumanReadableErrorType, bool) {
+pub fn parse_json(matches: &getopts::Matches) -> JsonConfig {
     let mut json_rendered: fn(ColorConfig) -> HumanReadableErrorType =
         HumanReadableErrorType::Default;
     let mut json_color = ColorConfig::Never;
     let mut json_artifact_notifications = false;
+    let mut json_unused_externs = false;
     for option in matches.opt_strs("json") {
         // For now conservatively forbid `--color` with `--json` since `--json`
         // won't actually be emitting any colors and anything colorized is
@@ -1221,6 +1234,7 @@ pub fn parse_json(matches: &getopts::Matches) -> (HumanReadableErrorType, bool)
                 "diagnostic-short" => json_rendered = HumanReadableErrorType::Short,
                 "diagnostic-rendered-ansi" => json_color = ColorConfig::Always,
                 "artifacts" => json_artifact_notifications = true,
+                "unused-externs" => json_unused_externs = true,
                 s => early_error(
                     ErrorOutputType::default(),
                     &format!("unknown `--json` option `{}`", s),
@@ -1228,7 +1242,12 @@ pub fn parse_json(matches: &getopts::Matches) -> (HumanReadableErrorType, bool)
             }
         }
     }
-    (json_rendered(json_color), json_artifact_notifications)
+
+    JsonConfig {
+        json_rendered: json_rendered(json_color),
+        json_artifact_notifications,
+        json_unused_externs,
+    }
 }
 
 /// Parses the `--error-format` flag.
@@ -1806,7 +1825,8 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
 
     let edition = parse_crate_edition(matches);
 
-    let (json_rendered, json_artifact_notifications) = parse_json(matches);
+    let JsonConfig { json_rendered, json_artifact_notifications, json_unused_externs } =
+        parse_json(matches);
 
     let error_format = parse_error_format(matches, color, json_rendered);
 
@@ -1819,6 +1839,14 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
     let mut debugging_opts = build_debugging_options(matches, error_format);
     check_debug_option_stability(&debugging_opts, error_format, json_rendered);
 
+    if !debugging_opts.unstable_options && json_unused_externs {
+        early_error(
+            error_format,
+            "the `-Z unstable-options` flag must also be passed to enable \
+            the flag `--json=unused-externs`",
+        );
+    }
+
     let output_types = parse_output_types(&debugging_opts, matches, error_format);
 
     let mut cg = build_codegen_options(matches, error_format);
@@ -1979,6 +2007,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
         remap_path_prefix,
         edition,
         json_artifact_notifications,
+        json_unused_externs,
         pretty,
     }
 }
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 623062dd569..dd35cb97275 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -147,6 +147,9 @@ top_level_options!(
         // by the compiler.
         json_artifact_notifications: bool [TRACKED],
 
+        // `true` if we're emitting a JSON blob containing the unused externs
+        json_unused_externs: bool [UNTRACKED],
+
         pretty: Option<PpMode> [UNTRACKED],
     }
 );
diff --git a/compiler/rustc_typeck/src/check/expectation.rs b/compiler/rustc_typeck/src/check/expectation.rs
index 22be10a731f..e9e81034477 100644
--- a/compiler/rustc_typeck/src/check/expectation.rs
+++ b/compiler/rustc_typeck/src/check/expectation.rs
@@ -104,8 +104,8 @@ impl<'a, 'tcx> Expectation<'tcx> {
     /// for the program to type-check). `only_has_type` will return
     /// such a constraint, if it exists.
     pub(super) fn only_has_type(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
-        match self.resolve(fcx) {
-            ExpectHasType(ty) => Some(ty),
+        match self {
+            ExpectHasType(ty) => Some(fcx.resolve_vars_if_possible(ty)),
             NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) | IsLast(_) => {
                 None
             }
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 30d60514063..67714b714c9 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -161,7 +161,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         expr: &'tcx hir::Expr<'tcx>,
         expected: Expectation<'tcx>,
     ) -> Ty<'tcx> {
-        debug!(">> type-checking: expr={:?} expected={:?}", expr, expected);
+        debug!(">> type-checking: expected={:?}, expr={:?} ", expected, expr);
 
         // True if `expr` is a `Try::from_ok(())` that is a result of desugaring a try block
         // without the final expr (e.g. `try { return; }`). We don't want to generate an
@@ -224,7 +224,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         expr: &'tcx hir::Expr<'tcx>,
         expected: Expectation<'tcx>,
     ) -> Ty<'tcx> {
-        debug!("check_expr_kind(expr={:?}, expected={:?})", expr, expected);
+        debug!("check_expr_kind(expected={:?}, expr={:?})", expected, expr);
 
         let tcx = self.tcx;
         match expr.kind {
diff --git a/compiler/rustc_typeck/src/expr_use_visitor.rs b/compiler/rustc_typeck/src/expr_use_visitor.rs
index ab286bacd81..02510cb6a44 100644
--- a/compiler/rustc_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_typeck/src/expr_use_visitor.rs
@@ -280,9 +280,14 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
                 if needs_to_be_read {
                     self.borrow_expr(&discr, ty::ImmBorrow);
                 } else {
+                    let closure_def_id = match discr_place.place.base {
+                        PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id.to_def_id()),
+                        _ => None,
+                    };
+
                     self.delegate.fake_read(
                         discr_place.place.clone(),
-                        FakeReadCause::ForMatchedPlace,
+                        FakeReadCause::ForMatchedPlace(closure_def_id),
                         discr_place.hir_id,
                     );
 
@@ -578,9 +583,14 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
     }
 
     fn walk_arm(&mut self, discr_place: &PlaceWithHirId<'tcx>, arm: &hir::Arm<'_>) {
+        let closure_def_id = match discr_place.place.base {
+            PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id.to_def_id()),
+            _ => None,
+        };
+
         self.delegate.fake_read(
             discr_place.place.clone(),
-            FakeReadCause::ForMatchedPlace,
+            FakeReadCause::ForMatchedPlace(closure_def_id),
             discr_place.hir_id,
         );
         self.walk_pat(discr_place, &arm.pat);
@@ -595,9 +605,14 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
     /// Walks a pat that occurs in isolation (i.e., top-level of fn argument or
     /// let binding, and *not* a match arm or nested pat.)
     fn walk_irrefutable_pat(&mut self, discr_place: &PlaceWithHirId<'tcx>, pat: &hir::Pat<'_>) {
+        let closure_def_id = match discr_place.place.base {
+            PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id.to_def_id()),
+            _ => None,
+        };
+
         self.delegate.fake_read(
             discr_place.place.clone(),
-            FakeReadCause::ForLet,
+            FakeReadCause::ForLet(closure_def_id),
             discr_place.hir_id,
         );
         self.walk_pat(discr_place, pat);
diff --git a/library/alloc/src/collections/btree/node.rs b/library/alloc/src/collections/btree/node.rs
index 9a7119470f3..f330a1bb3dc 100644
--- a/library/alloc/src/collections/btree/node.rs
+++ b/library/alloc/src/collections/btree/node.rs
@@ -128,106 +128,6 @@ impl<K, V> InternalNode<K, V> {
 /// is not a separate type and has no destructor.
 type BoxedNode<K, V> = NonNull<LeafNode<K, V>>;
 
-/// The root node of an owned tree.
-///
-/// Note that this does not have a destructor, and must be cleaned up manually.
-pub type Root<K, V> = NodeRef<marker::Owned, K, V, marker::LeafOrInternal>;
-
-impl<K, V> Root<K, V> {
-    /// Returns a new owned tree, with its own root node that is initially empty.
-    pub fn new() -> Self {
-        NodeRef::new_leaf().forget_type()
-    }
-}
-
-impl<K, V> NodeRef<marker::Owned, K, V, marker::Leaf> {
-    fn new_leaf() -> Self {
-        Self::from_new_leaf(LeafNode::new())
-    }
-
-    fn from_new_leaf(leaf: Box<LeafNode<K, V>>) -> Self {
-        NodeRef { height: 0, node: NonNull::from(Box::leak(leaf)), _marker: PhantomData }
-    }
-}
-
-impl<K, V> NodeRef<marker::Owned, K, V, marker::Internal> {
-    fn new_internal(child: Root<K, V>) -> Self {
-        let mut new_node = unsafe { InternalNode::new() };
-        new_node.edges[0].write(child.node);
-        unsafe { NodeRef::from_new_internal(new_node, child.height + 1) }
-    }
-
-    /// # Safety
-    /// `height` must not be zero.
-    unsafe fn from_new_internal(internal: Box<InternalNode<K, V>>, height: usize) -> Self {
-        debug_assert!(height > 0);
-        let node = NonNull::from(Box::leak(internal)).cast();
-        let mut this = NodeRef { height, node, _marker: PhantomData };
-        this.borrow_mut().correct_all_childrens_parent_links();
-        this
-    }
-}
-
-impl<K, V, Type> NodeRef<marker::Owned, K, V, Type> {
-    /// Mutably borrows the owned root node. Unlike `reborrow_mut`, this is safe
-    /// because the return value cannot be used to destroy the root, and there
-    /// cannot be other references to the tree.
-    pub fn borrow_mut(&mut self) -> NodeRef<marker::Mut<'_>, K, V, Type> {
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-
-    /// Slightly mutably borrows the owned root node.
-    pub fn borrow_valmut(&mut self) -> NodeRef<marker::ValMut<'_>, K, V, Type> {
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-
-    /// Irreversibly transitions to a reference that permits traversal and offers
-    /// destructive methods and little else.
-    pub fn into_dying(self) -> NodeRef<marker::Dying, K, V, Type> {
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-}
-
-impl<K, V> NodeRef<marker::Owned, K, V, marker::LeafOrInternal> {
-    /// Adds a new internal node with a single edge pointing to the previous root node,
-    /// make that new node the root node, and return it. This increases the height by 1
-    /// and is the opposite of `pop_internal_level`.
-    pub fn push_internal_level(&mut self) -> NodeRef<marker::Mut<'_>, K, V, marker::Internal> {
-        super::mem::take_mut(self, |old_root| NodeRef::new_internal(old_root).forget_type());
-
-        // `self.borrow_mut()`, except that we just forgot we're internal now:
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-
-    /// Removes the internal root node, using its first child as the new root node.
-    /// As it is intended only to be called when the root node has only one child,
-    /// no cleanup is done on any of the keys, values and other children.
-    /// This decreases the height by 1 and is the opposite of `push_internal_level`.
-    ///
-    /// Requires exclusive access to the `Root` object but not to the root node;
-    /// it will not invalidate other handles or references to the root node.
-    ///
-    /// Panics if there is no internal level, i.e., if the root node is a leaf.
-    pub fn pop_internal_level(&mut self) {
-        assert!(self.height > 0);
-
-        let top = self.node;
-
-        // SAFETY: we asserted to be internal.
-        let internal_self = unsafe { self.borrow_mut().cast_to_internal_unchecked() };
-        // SAFETY: we borrowed `self` exclusively and its borrow type is exclusive.
-        let internal_node = unsafe { &mut *NodeRef::as_internal_ptr(&internal_self) };
-        // SAFETY: the first edge is always initialized.
-        self.node = unsafe { internal_node.edges[0].assume_init_read() };
-        self.height -= 1;
-        self.clear_parent_link();
-
-        unsafe {
-            Global.deallocate(top.cast(), Layout::new::<InternalNode<K, V>>());
-        }
-    }
-}
-
 // N.B. `NodeRef` is always covariant in `K` and `V`, even when the `BorrowType`
 // is `Mut`. This is technically wrong, but cannot result in any unsafety due to
 // internal use of `NodeRef` because we stay completely generic over `K` and `V`.
@@ -292,6 +192,11 @@ pub struct NodeRef<BorrowType, K, V, Type> {
     _marker: PhantomData<(BorrowType, Type)>,
 }
 
+/// The root node of an owned tree.
+///
+/// Note that this does not have a destructor, and must be cleaned up manually.
+pub type Root<K, V> = NodeRef<marker::Owned, K, V, marker::LeafOrInternal>;
+
 impl<'a, K: 'a, V: 'a, Type> Copy for NodeRef<marker::Immut<'a>, K, V, Type> {}
 impl<'a, K: 'a, V: 'a, Type> Clone for NodeRef<marker::Immut<'a>, K, V, Type> {
     fn clone(&self) -> Self {
@@ -307,6 +212,34 @@ unsafe impl<'a, K: Send + 'a, V: Send + 'a, Type> Send for NodeRef<marker::ValMu
 unsafe impl<K: Send, V: Send, Type> Send for NodeRef<marker::Owned, K, V, Type> {}
 unsafe impl<K: Send, V: Send, Type> Send for NodeRef<marker::Dying, K, V, Type> {}
 
+impl<K, V> NodeRef<marker::Owned, K, V, marker::Leaf> {
+    fn new_leaf() -> Self {
+        Self::from_new_leaf(LeafNode::new())
+    }
+
+    fn from_new_leaf(leaf: Box<LeafNode<K, V>>) -> Self {
+        NodeRef { height: 0, node: NonNull::from(Box::leak(leaf)), _marker: PhantomData }
+    }
+}
+
+impl<K, V> NodeRef<marker::Owned, K, V, marker::Internal> {
+    fn new_internal(child: Root<K, V>) -> Self {
+        let mut new_node = unsafe { InternalNode::new() };
+        new_node.edges[0].write(child.node);
+        unsafe { NodeRef::from_new_internal(new_node, child.height + 1) }
+    }
+
+    /// # Safety
+    /// `height` must not be zero.
+    unsafe fn from_new_internal(internal: Box<InternalNode<K, V>>, height: usize) -> Self {
+        debug_assert!(height > 0);
+        let node = NonNull::from(Box::leak(internal)).cast();
+        let mut this = NodeRef { height, node, _marker: PhantomData };
+        this.borrow_mut().correct_all_childrens_parent_links();
+        this
+    }
+}
+
 impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::Internal> {
     /// Unpack a node reference that was packed as `NodeRef::parent`.
     fn from_internal(node: NonNull<InternalNode<K, V>>, height: usize) -> Self {
@@ -420,6 +353,19 @@ impl<BorrowType: marker::BorrowType, K, V, Type> NodeRef<BorrowType, K, V, Type>
     }
 }
 
+impl<BorrowType, K, V, Type> NodeRef<BorrowType, K, V, Type> {
+    /// Could be a public implementation of PartialEq, but only used in this module.
+    fn eq(&self, other: &Self) -> bool {
+        let Self { node, height, _marker } = self;
+        if node.eq(&other.node) {
+            debug_assert_eq!(*height, other.height);
+            true
+        } else {
+            false
+        }
+    }
+}
+
 impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Immut<'a>, K, V, Type> {
     /// Exposes the leaf portion of any leaf or internal node in an immutable tree.
     fn into_leaf(self) -> &'a LeafNode<K, V> {
@@ -461,20 +407,6 @@ impl<K, V> NodeRef<marker::Dying, K, V, marker::LeafOrInternal> {
     }
 }
 
-impl<'a, K, V> NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInternal> {
-    /// Unsafely asserts to the compiler the static information that this node is a `Leaf`.
-    unsafe fn cast_to_leaf_unchecked(self) -> NodeRef<marker::Mut<'a>, K, V, marker::Leaf> {
-        debug_assert!(self.height == 0);
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-
-    /// Unsafely asserts to the compiler the static information that this node is an `Internal`.
-    unsafe fn cast_to_internal_unchecked(self) -> NodeRef<marker::Mut<'a>, K, V, marker::Internal> {
-        debug_assert!(self.height > 0);
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-}
-
 impl<'a, K, V, Type> NodeRef<marker::Mut<'a>, K, V, Type> {
     /// Temporarily takes out another, mutable reference to the same node. Beware, as
     /// this method is very dangerous, doubly so since it may not immediately appear
@@ -577,6 +509,22 @@ impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Mut<'a>, K, V, Type> {
     }
 }
 
+impl<'a, K, V> NodeRef<marker::Mut<'a>, K, V, marker::Internal> {
+    /// # Safety
+    /// Every item returned by `range` is a valid edge index for the node.
+    unsafe fn correct_childrens_parent_links<R: Iterator<Item = usize>>(&mut self, range: R) {
+        for i in range {
+            debug_assert!(i <= self.len());
+            unsafe { Handle::new_edge(self.reborrow_mut(), i) }.correct_parent_link();
+        }
+    }
+
+    fn correct_all_childrens_parent_links(&mut self) {
+        let len = self.len();
+        unsafe { self.correct_childrens_parent_links(0..=len) };
+    }
+}
+
 impl<'a, K: 'a, V: 'a> NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInternal> {
     /// Sets the node's link to its parent edge,
     /// without invalidating other references to the node.
@@ -596,6 +544,71 @@ impl<K, V> NodeRef<marker::Owned, K, V, marker::LeafOrInternal> {
     }
 }
 
+impl<K, V> NodeRef<marker::Owned, K, V, marker::LeafOrInternal> {
+    /// Returns a new owned tree, with its own root node that is initially empty.
+    pub fn new() -> Self {
+        NodeRef::new_leaf().forget_type()
+    }
+
+    /// Adds a new internal node with a single edge pointing to the previous root node,
+    /// make that new node the root node, and return it. This increases the height by 1
+    /// and is the opposite of `pop_internal_level`.
+    pub fn push_internal_level(&mut self) -> NodeRef<marker::Mut<'_>, K, V, marker::Internal> {
+        super::mem::take_mut(self, |old_root| NodeRef::new_internal(old_root).forget_type());
+
+        // `self.borrow_mut()`, except that we just forgot we're internal now:
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+
+    /// Removes the internal root node, using its first child as the new root node.
+    /// As it is intended only to be called when the root node has only one child,
+    /// no cleanup is done on any of the keys, values and other children.
+    /// This decreases the height by 1 and is the opposite of `push_internal_level`.
+    ///
+    /// Requires exclusive access to the `Root` object but not to the root node;
+    /// it will not invalidate other handles or references to the root node.
+    ///
+    /// Panics if there is no internal level, i.e., if the root node is a leaf.
+    pub fn pop_internal_level(&mut self) {
+        assert!(self.height > 0);
+
+        let top = self.node;
+
+        // SAFETY: we asserted to be internal.
+        let internal_self = unsafe { self.borrow_mut().cast_to_internal_unchecked() };
+        // SAFETY: we borrowed `self` exclusively and its borrow type is exclusive.
+        let internal_node = unsafe { &mut *NodeRef::as_internal_ptr(&internal_self) };
+        // SAFETY: the first edge is always initialized.
+        self.node = unsafe { internal_node.edges[0].assume_init_read() };
+        self.height -= 1;
+        self.clear_parent_link();
+
+        unsafe {
+            Global.deallocate(top.cast(), Layout::new::<InternalNode<K, V>>());
+        }
+    }
+}
+
+impl<K, V, Type> NodeRef<marker::Owned, K, V, Type> {
+    /// Mutably borrows the owned root node. Unlike `reborrow_mut`, this is safe
+    /// because the return value cannot be used to destroy the root, and there
+    /// cannot be other references to the tree.
+    pub fn borrow_mut(&mut self) -> NodeRef<marker::Mut<'_>, K, V, Type> {
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+
+    /// Slightly mutably borrows the owned root node.
+    pub fn borrow_valmut(&mut self) -> NodeRef<marker::ValMut<'_>, K, V, Type> {
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+
+    /// Irreversibly transitions to a reference that permits traversal and offers
+    /// destructive methods and little else.
+    pub fn into_dying(self) -> NodeRef<marker::Dying, K, V, Type> {
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+}
+
 impl<'a, K: 'a, V: 'a> NodeRef<marker::Mut<'a>, K, V, marker::Leaf> {
     /// Adds a key-value pair to the end of the node.
     pub fn push(&mut self, key: K, val: V) {
@@ -610,22 +623,6 @@ impl<'a, K: 'a, V: 'a> NodeRef<marker::Mut<'a>, K, V, marker::Leaf> {
     }
 }
 
-impl<'a, K, V> NodeRef<marker::Mut<'a>, K, V, marker::Internal> {
-    /// # Safety
-    /// Every item returned by `range` is a valid edge index for the node.
-    unsafe fn correct_childrens_parent_links<R: Iterator<Item = usize>>(&mut self, range: R) {
-        for i in range {
-            debug_assert!(i <= self.len());
-            unsafe { Handle::new_edge(self.reborrow_mut(), i) }.correct_parent_link();
-        }
-    }
-
-    fn correct_all_childrens_parent_links(&mut self) {
-        let len = self.len();
-        unsafe { self.correct_childrens_parent_links(0..=len) };
-    }
-}
-
 impl<'a, K: 'a, V: 'a> NodeRef<marker::Mut<'a>, K, V, marker::Internal> {
     /// Adds a key-value pair, and an edge to go to the right of that pair,
     /// to the end of the node.
@@ -645,6 +642,20 @@ impl<'a, K: 'a, V: 'a> NodeRef<marker::Mut<'a>, K, V, marker::Internal> {
     }
 }
 
+impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::Leaf> {
+    /// Removes any static information asserting that this node is a `Leaf` node.
+    pub fn forget_type(self) -> NodeRef<BorrowType, K, V, marker::LeafOrInternal> {
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+}
+
+impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::Internal> {
+    /// Removes any static information asserting that this node is an `Internal` node.
+    pub fn forget_type(self) -> NodeRef<BorrowType, K, V, marker::LeafOrInternal> {
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+}
+
 impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::LeafOrInternal> {
     /// Checks whether a node is an `Internal` node or a `Leaf` node.
     pub fn force(
@@ -669,6 +680,20 @@ impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::LeafOrInternal> {
     }
 }
 
+impl<'a, K, V> NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInternal> {
+    /// Unsafely asserts to the compiler the static information that this node is a `Leaf`.
+    unsafe fn cast_to_leaf_unchecked(self) -> NodeRef<marker::Mut<'a>, K, V, marker::Leaf> {
+        debug_assert!(self.height == 0);
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+
+    /// Unsafely asserts to the compiler the static information that this node is an `Internal`.
+    unsafe fn cast_to_internal_unchecked(self) -> NodeRef<marker::Mut<'a>, K, V, marker::Internal> {
+        debug_assert!(self.height > 0);
+        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
+    }
+}
+
 /// A reference to a specific key-value pair or edge within a node. The `Node` parameter
 /// must be a `NodeRef`, while the `Type` can either be `KV` (signifying a handle on a key-value
 /// pair) or `Edge` (signifying a handle on an edge).
@@ -722,19 +747,6 @@ impl<BorrowType, K, V, NodeType> Handle<NodeRef<BorrowType, K, V, NodeType>, mar
     }
 }
 
-impl<BorrowType, K, V, NodeType> NodeRef<BorrowType, K, V, NodeType> {
-    /// Could be a public implementation of PartialEq, but only used in this module.
-    fn eq(&self, other: &Self) -> bool {
-        let Self { node, height, _marker } = self;
-        if node.eq(&other.node) {
-            debug_assert_eq!(*height, other.height);
-            true
-        } else {
-            false
-        }
-    }
-}
-
 impl<BorrowType, K, V, NodeType, HandleType> PartialEq
     for Handle<NodeRef<BorrowType, K, V, NodeType>, HandleType>
 {
@@ -754,16 +766,6 @@ impl<BorrowType, K, V, NodeType, HandleType>
     }
 }
 
-impl<'a, K, V, Type> Handle<NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInternal>, Type> {
-    /// Unsafely asserts to the compiler the static information that the handle's node is a `Leaf`.
-    pub unsafe fn cast_to_leaf_unchecked(
-        self,
-    ) -> Handle<NodeRef<marker::Mut<'a>, K, V, marker::Leaf>, Type> {
-        let node = unsafe { self.node.cast_to_leaf_unchecked() };
-        Handle { node, idx: self.idx, _marker: PhantomData }
-    }
-}
-
 impl<'a, K, V, NodeType, HandleType> Handle<NodeRef<marker::Mut<'a>, K, V, NodeType>, HandleType> {
     /// Temporarily takes out another, mutable handle on the same location. Beware, as
     /// this method is very dangerous, doubly so since it may not immediately appear
@@ -1466,20 +1468,6 @@ impl<'a, K: 'a, V: 'a> BalancingContext<'a, K, V> {
     }
 }
 
-impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::Leaf> {
-    /// Removes any static information asserting that this node is a `Leaf` node.
-    pub fn forget_type(self) -> NodeRef<BorrowType, K, V, marker::LeafOrInternal> {
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-}
-
-impl<BorrowType, K, V> NodeRef<BorrowType, K, V, marker::Internal> {
-    /// Removes any static information asserting that this node is an `Internal` node.
-    pub fn forget_type(self) -> NodeRef<BorrowType, K, V, marker::LeafOrInternal> {
-        NodeRef { height: self.height, node: self.node, _marker: PhantomData }
-    }
-}
-
 impl<BorrowType, K, V> Handle<NodeRef<BorrowType, K, V, marker::Leaf>, marker::Edge> {
     pub fn forget_node_type(
         self,
@@ -1531,6 +1519,16 @@ impl<BorrowType, K, V, Type> Handle<NodeRef<BorrowType, K, V, marker::LeafOrInte
     }
 }
 
+impl<'a, K, V, Type> Handle<NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInternal>, Type> {
+    /// Unsafely asserts to the compiler the static information that the handle's node is a `Leaf`.
+    pub unsafe fn cast_to_leaf_unchecked(
+        self,
+    ) -> Handle<NodeRef<marker::Mut<'a>, K, V, marker::Leaf>, Type> {
+        let node = unsafe { self.node.cast_to_leaf_unchecked() };
+        Handle { node, idx: self.idx, _marker: PhantomData }
+    }
+}
+
 impl<'a, K, V> Handle<NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInternal>, marker::Edge> {
     /// Move the suffix after `self` from one node to another one. `right` must be empty.
     /// The first edge of `right` remains unchanged.
diff --git a/library/alloc/tests/vec.rs b/library/alloc/tests/vec.rs
index b926c697d58..4dcc5d30deb 100644
--- a/library/alloc/tests/vec.rs
+++ b/library/alloc/tests/vec.rs
@@ -1004,9 +1004,9 @@ fn test_from_iter_specialization_with_iterator_adapters() {
         .map_while(Option::Some)
         .peekable()
         .skip(1)
-        .map(|e| std::num::NonZeroUsize::new(e));
+        .map(|e| if e != usize::MAX { Ok(std::num::NonZeroUsize::new(e)) } else { Err(()) });
     assert_in_place_trait(&iter);
-    let sink = iter.collect::<Vec<_>>();
+    let sink = iter.collect::<Result<Vec<_>, _>>().unwrap();
     let sinkptr = sink.as_ptr();
     assert_eq!(srcptr, sinkptr as *const usize);
 }
@@ -1078,12 +1078,21 @@ fn test_from_iter_specialization_panic_during_drop_leaks() {
         }
     }
 
+    let mut to_free: *mut Droppable = core::ptr::null_mut();
+    let mut cap = 0;
+
     let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
-        let v = vec![Droppable::DroppedTwice(Box::new(123)), Droppable::PanicOnDrop];
+        let mut v = vec![Droppable::DroppedTwice(Box::new(123)), Droppable::PanicOnDrop];
+        to_free = v.as_mut_ptr();
+        cap = v.capacity();
         let _ = v.into_iter().take(0).collect::<Vec<_>>();
     }));
 
     assert_eq!(unsafe { DROP_COUNTER }, 1);
+    // clean up the leak to keep miri happy
+    unsafe {
+        drop(Vec::from_raw_parts(to_free, 0, cap));
+    }
 }
 
 #[test]
diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs
index ba4050757cb..61d8351d59f 100644
--- a/library/core/src/iter/adapters/mod.rs
+++ b/library/core/src/iter/adapters/mod.rs
@@ -194,3 +194,26 @@ where
         self.try_fold(init, ok(fold)).unwrap()
     }
 }
+
+#[unstable(issue = "none", feature = "inplace_iteration")]
+unsafe impl<S: Iterator, I, E> SourceIter for ResultShunt<'_, I, E>
+where
+    I: SourceIter<Source = S>,
+{
+    type Source = S;
+
+    #[inline]
+    unsafe fn as_inner(&mut self) -> &mut S {
+        // SAFETY: unsafe function forwarding to unsafe function with the same requirements
+        unsafe { SourceIter::as_inner(&mut self.iter) }
+    }
+}
+
+// SAFETY: ResultShunt::next calls I::find, which has to advance `iter` in order to
+// return `Some(_)`. Since `iter` has type `I: InPlaceIterable` it's guaranteed that
+// at least one item will be moved out from the underlying source.
+#[unstable(issue = "none", feature = "inplace_iteration")]
+unsafe impl<I, T, E> InPlaceIterable for ResultShunt<'_, I, E> where
+    I: Iterator<Item = Result<T, E>> + InPlaceIterable
+{
+}
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 69d39f5e544..117201ab3cd 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -854,6 +854,7 @@ impl Step for Tidy {
         cmd.arg(&builder.src);
         cmd.arg(&builder.initial_cargo);
         cmd.arg(&builder.out);
+        cmd.arg(builder.jobs().to_string());
         if builder.is_verbose() {
             cmd.arg("--verbose");
         }
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 246e0ebbb2b..5e9cfe148f1 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -154,6 +154,8 @@ crate struct Options {
     /// If this option is set to `true`, rustdoc will only run checks and not generate
     /// documentation.
     crate run_check: bool,
+    /// Whether doctests should emit unused externs
+    crate json_unused_externs: bool,
 }
 
 impl fmt::Debug for Options {
@@ -352,7 +354,8 @@ impl Options {
         }
 
         let color = config::parse_color(&matches);
-        let (json_rendered, _artifacts) = config::parse_json(&matches);
+        let config::JsonConfig { json_rendered, json_unused_externs, .. } =
+            config::parse_json(&matches);
         let error_format = config::parse_error_format(&matches, color, json_rendered);
 
         let codegen_options = build_codegen_options(matches, error_format);
@@ -507,7 +510,6 @@ impl Options {
         let edition = config::parse_crate_edition(&matches);
 
         let mut id_map = html::markdown::IdMap::new();
-        id_map.populate(&html::render::INITIAL_IDS);
         let external_html = match ExternalHtml::load(
             &matches.opt_strs("html-in-header"),
             &matches.opt_strs("html-before-content"),
@@ -687,6 +689,7 @@ impl Options {
             },
             crate_name,
             output_format,
+            json_unused_externs,
         })
     }
 
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 3d0ef028902..6f6ed0eb684 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -1,5 +1,5 @@
 use rustc_ast as ast;
-use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::{ColorConfig, ErrorReported};
 use rustc_hir as hir;
@@ -23,6 +23,8 @@ use std::panic;
 use std::path::PathBuf;
 use std::process::{self, Command, Stdio};
 use std::str;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::{Arc, Mutex};
 
 use crate::clean::Attributes;
 use crate::config::Options;
@@ -104,8 +106,10 @@ crate fn run(options: Options) -> Result<(), ErrorReported> {
 
     let mut test_args = options.test_args.clone();
     let display_warnings = options.display_warnings;
+    let externs = options.externs.clone();
+    let json_unused_externs = options.json_unused_externs;
 
-    let tests = interface::run_compiler(config, |compiler| {
+    let res = interface::run_compiler(config, |compiler| {
         compiler.enter(|queries| {
             let _lower_to_hir = queries.lower_to_hir()?;
 
@@ -151,12 +155,15 @@ crate fn run(options: Options) -> Result<(), ErrorReported> {
             });
             compiler.session().abort_if_errors();
 
-            let ret: Result<_, ErrorReported> = Ok(collector.tests);
+            let unused_extern_reports = collector.unused_extern_reports.clone();
+            let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
+            let ret: Result<_, ErrorReported> =
+                Ok((collector.tests, unused_extern_reports, compiling_test_count));
             ret
         })
     });
-    let tests = match tests {
-        Ok(tests) => tests,
+    let (tests, unused_extern_reports, compiling_test_count) = match res {
+        Ok(res) => res,
         Err(ErrorReported) => return Err(ErrorReported),
     };
 
@@ -168,6 +175,44 @@ crate fn run(options: Options) -> Result<(), ErrorReported> {
         Some(testing::Options::new().display_output(display_warnings)),
     );
 
+    // Collect and warn about unused externs, but only if we've gotten
+    // reports for each doctest
+    if json_unused_externs {
+        let unused_extern_reports: Vec<_> =
+            std::mem::take(&mut unused_extern_reports.lock().unwrap());
+        if unused_extern_reports.len() == compiling_test_count {
+            let extern_names = externs.iter().map(|(name, _)| name).collect::<FxHashSet<&String>>();
+            let mut unused_extern_names = unused_extern_reports
+                .iter()
+                .map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>())
+                .fold(extern_names, |uextsa, uextsb| {
+                    uextsa.intersection(&uextsb).map(|v| *v).collect::<FxHashSet<&String>>()
+                })
+                .iter()
+                .map(|v| (*v).clone())
+                .collect::<Vec<String>>();
+            unused_extern_names.sort();
+            // Take the most severe lint level
+            let lint_level = unused_extern_reports
+                .iter()
+                .map(|uexts| uexts.lint_level.as_str())
+                .max_by_key(|v| match *v {
+                    "warn" => 1,
+                    "deny" => 2,
+                    "forbid" => 3,
+                    // The allow lint level is not expected,
+                    // as if allow is specified, no message
+                    // is to be emitted.
+                    v => unreachable!("Invalid lint level '{}'", v),
+                })
+                .unwrap_or("warn")
+                .to_string();
+            let uext = UnusedExterns { lint_level, unused_extern_names };
+            let unused_extern_json = serde_json::to_string(&uext).unwrap();
+            eprintln!("{}", unused_extern_json);
+        }
+    }
+
     Ok(())
 }
 
@@ -235,6 +280,18 @@ impl DirState {
     }
 }
 
+// NOTE: Keep this in sync with the equivalent structs in rustc
+// and cargo.
+// We could unify this struct the one in rustc but they have different
+// ownership semantics, so doing so would create wasteful allocations.
+#[derive(serde::Serialize, serde::Deserialize)]
+struct UnusedExterns {
+    /// Lint level of the unused_crate_dependencies lint
+    lint_level: String,
+    /// List of unused externs by their names.
+    unused_extern_names: Vec<String>,
+}
+
 fn run_test(
     test: &str,
     cratename: &str,
@@ -253,6 +310,7 @@ fn run_test(
     outdir: DirState,
     path: PathBuf,
     test_id: &str,
+    report_unused_externs: impl Fn(UnusedExterns),
 ) -> Result<(), TestFailure> {
     let (test, line_offset, supports_color) =
         make_test(test, Some(cratename), as_test_harness, opts, edition, Some(test_id));
@@ -278,6 +336,12 @@ fn run_test(
     if as_test_harness {
         compiler.arg("--test");
     }
+    if options.json_unused_externs && !compile_fail {
+        compiler.arg("--error-format=json");
+        compiler.arg("--json").arg("unused-externs");
+        compiler.arg("-Z").arg("unstable-options");
+        compiler.arg("-W").arg("unused_crate_dependencies");
+    }
     for lib_str in &options.lib_strs {
         compiler.arg("-L").arg(&lib_str);
     }
@@ -337,7 +401,26 @@ fn run_test(
             eprint!("{}", self.0);
         }
     }
-    let out = str::from_utf8(&output.stderr).unwrap();
+    let mut out_lines = str::from_utf8(&output.stderr)
+        .unwrap()
+        .lines()
+        .filter(|l| {
+            if let Ok(uext) = serde_json::from_str::<UnusedExterns>(l) {
+                report_unused_externs(uext);
+                false
+            } else {
+                true
+            }
+        })
+        .collect::<Vec<_>>();
+
+    // Add a \n to the end to properly terminate the last line,
+    // but only if there was output to be printed
+    if out_lines.len() > 0 {
+        out_lines.push("");
+    }
+
+    let out = out_lines.join("\n");
     let _bomb = Bomb(&out);
     match (output.status.success(), compile_fail) {
         (true, true) => {
@@ -721,6 +804,8 @@ crate struct Collector {
     source_map: Option<Lrc<SourceMap>>,
     filename: Option<PathBuf>,
     visited_tests: FxHashMap<(String, usize), usize>,
+    unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
+    compiling_test_count: AtomicUsize,
 }
 
 impl Collector {
@@ -745,6 +830,8 @@ impl Collector {
             source_map,
             filename,
             visited_tests: FxHashMap::default(),
+            unused_extern_reports: Default::default(),
+            compiling_test_count: AtomicUsize::new(0),
         }
     }
 
@@ -791,6 +878,10 @@ impl Tester for Collector {
         let runtool_args = self.options.runtool_args.clone();
         let target = self.options.target.clone();
         let target_str = target.to_string();
+        let unused_externs = self.unused_extern_reports.clone();
+        if !config.compile_fail {
+            self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
+        }
 
         // FIXME(#44940): if doctests ever support path remapping, then this filename
         // needs to be the result of `SourceMap::span_to_unmapped_path`.
@@ -846,6 +937,9 @@ impl Tester for Collector {
                 test_type: testing::TestType::DocTest,
             },
             testfn: testing::DynTestFn(box move || {
+                let report_unused_externs = |uext| {
+                    unused_externs.lock().unwrap().push(uext);
+                };
                 let res = run_test(
                     &test,
                     &cratename,
@@ -864,6 +958,7 @@ impl Tester for Collector {
                     outdir,
                     path,
                     &test_id,
+                    report_unused_externs,
                 );
 
                 if let Err(err) = res {
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 7e50d72e60f..3a4319d5d9a 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -189,7 +189,9 @@ impl<'a> Classifier<'a> {
             // leading identifier.
             TokenKind::Bang if self.in_macro => {
                 self.in_macro = false;
-                Class::Macro
+                sink(Highlight::Token { text, class: None });
+                sink(Highlight::ExitSpan);
+                return;
             }
 
             // Assume that '&' or '*' is the reference or dereference operator
@@ -298,7 +300,9 @@ impl<'a> Classifier<'a> {
             },
             TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
                 self.in_macro = true;
-                Class::Macro
+                sink(Highlight::EnterSpan { class: Class::Macro });
+                sink(Highlight::Token { text, class: None });
+                return;
             }
             TokenKind::Ident => match text {
                 "ref" | "mut" => Class::RefKeyWord,
diff --git a/src/librustdoc/html/highlight/fixtures/dos_line.html b/src/librustdoc/html/highlight/fixtures/dos_line.html
index 4400f85681d..1c8dbffe78c 100644
--- a/src/librustdoc/html/highlight/fixtures/dos_line.html
+++ b/src/librustdoc/html/highlight/fixtures/dos_line.html
@@ -1,3 +1,3 @@
 <span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">foo</span>() {
-<span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;foo&quot;</span>);
+<span class="macro">println!</span>(<span class="string">&quot;foo&quot;</span>);
 }
diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html
index d937246f466..4966e0ac6bb 100644
--- a/src/librustdoc/html/highlight/fixtures/sample.html
+++ b/src/librustdoc/html/highlight/fixtures/sample.html
@@ -17,11 +17,11 @@
     <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">&amp;</span><span class="ident">foo</span>;
     <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="op">&amp;&amp;</span><span class="ident">foo</span>;
     <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">*</span><span class="ident">foo</span>;
-    <span class="macro">mac</span><span class="macro">!</span>(<span class="ident">foo</span>, <span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="ident">bar</span>);
-    <span class="macro">assert</span><span class="macro">!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op">&lt;</span> <span class="ident">N</span> <span class="op">&amp;&amp;</span> <span class="ident">index</span> <span class="op">&lt;</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
+    <span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="ident">bar</span>);
+    <span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op">&lt;</span> <span class="ident">N</span> <span class="op">&amp;&amp;</span> <span class="ident">index</span> <span class="op">&lt;</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
 }
 
-<span class="macro">macro_rules</span><span class="macro">!</span> <span class="ident">bar</span> {
+<span class="macro">macro_rules!</span> <span class="ident">bar</span> {
     (<span class="macro-nonterminal">$</span><span class="macro-nonterminal">foo</span>:<span class="ident">tt</span>) <span class="op">=</span><span class="op">&gt;</span> {};
 }
 </code></pre>
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index cdccb1c8581..509f1730557 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -1356,6 +1356,9 @@ fn init_id_map() -> FxHashMap<String, usize> {
     map.insert("rustdoc-vars".to_owned(), 1);
     map.insert("sidebar-vars".to_owned(), 1);
     map.insert("copy-path".to_owned(), 1);
+    map.insert("help".to_owned(), 1);
+    map.insert("TOC".to_owned(), 1);
+    map.insert("render-detail".to_owned(), 1);
     // This is the list of IDs used by rustdoc sections.
     map.insert("fields".to_owned(), 1);
     map.insert("variants".to_owned(), 1);
@@ -1365,6 +1368,12 @@ fn init_id_map() -> FxHashMap<String, usize> {
     map.insert("trait-implementations".to_owned(), 1);
     map.insert("synthetic-implementations".to_owned(), 1);
     map.insert("blanket-implementations".to_owned(), 1);
+    map.insert("associated-types".to_owned(), 1);
+    map.insert("associated-const".to_owned(), 1);
+    map.insert("required-methods".to_owned(), 1);
+    map.insert("provided-methods".to_owned(), 1);
+    map.insert("implementors".to_owned(), 1);
+    map.insert("synthetic-implementors".to_owned(), 1);
     map
 }
 
@@ -1373,12 +1382,6 @@ impl IdMap {
         IdMap { map: init_id_map() }
     }
 
-    crate fn populate<I: IntoIterator<Item = S>, S: AsRef<str> + ToString>(&mut self, ids: I) {
-        for id in ids {
-            let _ = self.derive(id);
-        }
-    }
-
     crate fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
         let id = match self.map.get_mut(candidate.as_ref()) {
             None => candidate.to_string(),
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 07c850a20a9..1a993f360a1 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -18,7 +18,7 @@ use super::print_item::{full_path, item_path, print_item};
 use super::write_shared::write_shared;
 use super::{
     print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS,
-    CURRENT_DEPTH, INITIAL_IDS,
+    CURRENT_DEPTH,
 };
 
 use crate::clean::{self, AttributesExt};
@@ -423,14 +423,11 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
     }
 
     fn make_child_renderer(&self) -> Self {
-        let mut id_map = IdMap::new();
-        id_map.populate(&INITIAL_IDS);
-
         Self {
             current: self.current.clone(),
             dst: self.dst.clone(),
             render_redirect_pages: self.render_redirect_pages,
-            id_map: RefCell::new(id_map),
+            id_map: RefCell::new(IdMap::new()),
             deref_id_map: RefCell::new(FxHashMap::default()),
             shared: Rc::clone(&self.shared),
             cache: Rc::clone(&self.cache),
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index a8a08fb23e0..7b656baa1b4 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -283,24 +283,6 @@ crate struct StylePath {
 
 thread_local!(crate static CURRENT_DEPTH: Cell<usize> = Cell::new(0));
 
-crate const INITIAL_IDS: [&'static str; 15] = [
-    "main",
-    "search",
-    "help",
-    "TOC",
-    "render-detail",
-    "associated-types",
-    "associated-const",
-    "required-methods",
-    "provided-methods",
-    "implementors",
-    "synthetic-implementors",
-    "implementors-list",
-    "synthetic-implementors-list",
-    "methods",
-    "implementations",
-];
-
 fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) {
     if let Some(l) = cx.src_href(item) {
         write!(buf, "<a class=\"srclink\" href=\"{}\" title=\"goto source code\">[src]</a>", l)
diff --git a/src/test/mir-opt/address_of.address_of_reborrow.SimplifyCfg-initial.after.mir b/src/test/mir-opt/address_of.address_of_reborrow.SimplifyCfg-initial.after.mir
index a9568112620..9fa478f8a82 100644
--- a/src/test/mir-opt/address_of.address_of_reborrow.SimplifyCfg-initial.after.mir
+++ b/src/test/mir-opt/address_of.address_of_reborrow.SimplifyCfg-initial.after.mir
@@ -130,12 +130,12 @@ fn address_of_reborrow() -> () {
         StorageLive(_2);                 // scope 0 at $DIR/address-of.rs:4:14: 4:21
         _2 = [const 0_i32; 10];          // scope 0 at $DIR/address-of.rs:4:14: 4:21
         _1 = &_2;                        // scope 0 at $DIR/address-of.rs:4:13: 4:21
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/address-of.rs:4:9: 4:10
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/address-of.rs:4:9: 4:10
         StorageLive(_3);                 // scope 1 at $DIR/address-of.rs:5:9: 5:14
         StorageLive(_4);                 // scope 1 at $DIR/address-of.rs:5:22: 5:29
         _4 = [const 0_i32; 10];          // scope 1 at $DIR/address-of.rs:5:22: 5:29
         _3 = &mut _4;                    // scope 1 at $DIR/address-of.rs:5:17: 5:29
-        FakeRead(ForLet, _3);            // scope 1 at $DIR/address-of.rs:5:9: 5:14
+        FakeRead(ForLet(None), _3);      // scope 1 at $DIR/address-of.rs:5:9: 5:14
         StorageLive(_5);                 // scope 2 at $DIR/address-of.rs:7:5: 7:18
         StorageLive(_6);                 // scope 2 at $DIR/address-of.rs:7:5: 7:18
         _6 = &raw const (*_1);           // scope 2 at $DIR/address-of.rs:7:5: 7:6
@@ -170,25 +170,25 @@ fn address_of_reborrow() -> () {
         StorageDead(_13);                // scope 2 at $DIR/address-of.rs:11:20: 11:21
         StorageLive(_15);                // scope 2 at $DIR/address-of.rs:13:9: 13:10
         _15 = &raw const (*_1);          // scope 2 at $DIR/address-of.rs:13:23: 13:24
-        FakeRead(ForLet, _15);           // scope 2 at $DIR/address-of.rs:13:9: 13:10
+        FakeRead(ForLet(None), _15);     // scope 2 at $DIR/address-of.rs:13:9: 13:10
         AscribeUserType(_15, o, UserTypeProjection { base: UserType(3), projs: [] }); // scope 2 at $DIR/address-of.rs:13:12: 13:20
         StorageLive(_16);                // scope 3 at $DIR/address-of.rs:14:9: 14:10
         _16 = &raw const (*_1);          // scope 3 at $DIR/address-of.rs:14:31: 14:32
-        FakeRead(ForLet, _16);           // scope 3 at $DIR/address-of.rs:14:9: 14:10
+        FakeRead(ForLet(None), _16);     // scope 3 at $DIR/address-of.rs:14:9: 14:10
         AscribeUserType(_16, o, UserTypeProjection { base: UserType(5), projs: [] }); // scope 3 at $DIR/address-of.rs:14:12: 14:28
         StorageLive(_17);                // scope 4 at $DIR/address-of.rs:15:9: 15:10
         StorageLive(_18);                // scope 4 at $DIR/address-of.rs:15:30: 15:31
         _18 = &raw const (*_1);          // scope 4 at $DIR/address-of.rs:15:30: 15:31
         _17 = move _18 as *const dyn std::marker::Send (Pointer(Unsize)); // scope 4 at $DIR/address-of.rs:15:30: 15:31
         StorageDead(_18);                // scope 4 at $DIR/address-of.rs:15:30: 15:31
-        FakeRead(ForLet, _17);           // scope 4 at $DIR/address-of.rs:15:9: 15:10
+        FakeRead(ForLet(None), _17);     // scope 4 at $DIR/address-of.rs:15:9: 15:10
         AscribeUserType(_17, o, UserTypeProjection { base: UserType(7), projs: [] }); // scope 4 at $DIR/address-of.rs:15:12: 15:27
         StorageLive(_19);                // scope 5 at $DIR/address-of.rs:16:9: 16:10
         StorageLive(_20);                // scope 5 at $DIR/address-of.rs:16:27: 16:28
         _20 = &raw const (*_1);          // scope 5 at $DIR/address-of.rs:16:27: 16:28
         _19 = move _20 as *const [i32] (Pointer(Unsize)); // scope 5 at $DIR/address-of.rs:16:27: 16:28
         StorageDead(_20);                // scope 5 at $DIR/address-of.rs:16:27: 16:28
-        FakeRead(ForLet, _19);           // scope 5 at $DIR/address-of.rs:16:9: 16:10
+        FakeRead(ForLet(None), _19);     // scope 5 at $DIR/address-of.rs:16:9: 16:10
         AscribeUserType(_19, o, UserTypeProjection { base: UserType(9), projs: [] }); // scope 5 at $DIR/address-of.rs:16:12: 16:24
         StorageLive(_21);                // scope 6 at $DIR/address-of.rs:18:5: 18:18
         StorageLive(_22);                // scope 6 at $DIR/address-of.rs:18:5: 18:18
@@ -218,25 +218,25 @@ fn address_of_reborrow() -> () {
         StorageDead(_27);                // scope 6 at $DIR/address-of.rs:21:22: 21:23
         StorageLive(_29);                // scope 6 at $DIR/address-of.rs:23:9: 23:10
         _29 = &raw const (*_3);          // scope 6 at $DIR/address-of.rs:23:23: 23:24
-        FakeRead(ForLet, _29);           // scope 6 at $DIR/address-of.rs:23:9: 23:10
+        FakeRead(ForLet(None), _29);     // scope 6 at $DIR/address-of.rs:23:9: 23:10
         AscribeUserType(_29, o, UserTypeProjection { base: UserType(13), projs: [] }); // scope 6 at $DIR/address-of.rs:23:12: 23:20
         StorageLive(_30);                // scope 7 at $DIR/address-of.rs:24:9: 24:10
         _30 = &raw const (*_3);          // scope 7 at $DIR/address-of.rs:24:31: 24:32
-        FakeRead(ForLet, _30);           // scope 7 at $DIR/address-of.rs:24:9: 24:10
+        FakeRead(ForLet(None), _30);     // scope 7 at $DIR/address-of.rs:24:9: 24:10
         AscribeUserType(_30, o, UserTypeProjection { base: UserType(15), projs: [] }); // scope 7 at $DIR/address-of.rs:24:12: 24:28
         StorageLive(_31);                // scope 8 at $DIR/address-of.rs:25:9: 25:10
         StorageLive(_32);                // scope 8 at $DIR/address-of.rs:25:30: 25:31
         _32 = &raw const (*_3);          // scope 8 at $DIR/address-of.rs:25:30: 25:31
         _31 = move _32 as *const dyn std::marker::Send (Pointer(Unsize)); // scope 8 at $DIR/address-of.rs:25:30: 25:31
         StorageDead(_32);                // scope 8 at $DIR/address-of.rs:25:30: 25:31
-        FakeRead(ForLet, _31);           // scope 8 at $DIR/address-of.rs:25:9: 25:10
+        FakeRead(ForLet(None), _31);     // scope 8 at $DIR/address-of.rs:25:9: 25:10
         AscribeUserType(_31, o, UserTypeProjection { base: UserType(17), projs: [] }); // scope 8 at $DIR/address-of.rs:25:12: 25:27
         StorageLive(_33);                // scope 9 at $DIR/address-of.rs:26:9: 26:10
         StorageLive(_34);                // scope 9 at $DIR/address-of.rs:26:27: 26:28
         _34 = &raw const (*_3);          // scope 9 at $DIR/address-of.rs:26:27: 26:28
         _33 = move _34 as *const [i32] (Pointer(Unsize)); // scope 9 at $DIR/address-of.rs:26:27: 26:28
         StorageDead(_34);                // scope 9 at $DIR/address-of.rs:26:27: 26:28
-        FakeRead(ForLet, _33);           // scope 9 at $DIR/address-of.rs:26:9: 26:10
+        FakeRead(ForLet(None), _33);     // scope 9 at $DIR/address-of.rs:26:9: 26:10
         AscribeUserType(_33, o, UserTypeProjection { base: UserType(19), projs: [] }); // scope 9 at $DIR/address-of.rs:26:12: 26:24
         StorageLive(_35);                // scope 10 at $DIR/address-of.rs:28:5: 28:16
         StorageLive(_36);                // scope 10 at $DIR/address-of.rs:28:5: 28:16
@@ -266,25 +266,25 @@ fn address_of_reborrow() -> () {
         StorageDead(_41);                // scope 10 at $DIR/address-of.rs:31:20: 31:21
         StorageLive(_43);                // scope 10 at $DIR/address-of.rs:33:9: 33:10
         _43 = &raw mut (*_3);            // scope 10 at $DIR/address-of.rs:33:21: 33:22
-        FakeRead(ForLet, _43);           // scope 10 at $DIR/address-of.rs:33:9: 33:10
+        FakeRead(ForLet(None), _43);     // scope 10 at $DIR/address-of.rs:33:9: 33:10
         AscribeUserType(_43, o, UserTypeProjection { base: UserType(23), projs: [] }); // scope 10 at $DIR/address-of.rs:33:12: 33:18
         StorageLive(_44);                // scope 11 at $DIR/address-of.rs:34:9: 34:10
         _44 = &raw mut (*_3);            // scope 11 at $DIR/address-of.rs:34:29: 34:30
-        FakeRead(ForLet, _44);           // scope 11 at $DIR/address-of.rs:34:9: 34:10
+        FakeRead(ForLet(None), _44);     // scope 11 at $DIR/address-of.rs:34:9: 34:10
         AscribeUserType(_44, o, UserTypeProjection { base: UserType(25), projs: [] }); // scope 11 at $DIR/address-of.rs:34:12: 34:26
         StorageLive(_45);                // scope 12 at $DIR/address-of.rs:35:9: 35:10
         StorageLive(_46);                // scope 12 at $DIR/address-of.rs:35:28: 35:29
         _46 = &raw mut (*_3);            // scope 12 at $DIR/address-of.rs:35:28: 35:29
         _45 = move _46 as *mut dyn std::marker::Send (Pointer(Unsize)); // scope 12 at $DIR/address-of.rs:35:28: 35:29
         StorageDead(_46);                // scope 12 at $DIR/address-of.rs:35:28: 35:29
-        FakeRead(ForLet, _45);           // scope 12 at $DIR/address-of.rs:35:9: 35:10
+        FakeRead(ForLet(None), _45);     // scope 12 at $DIR/address-of.rs:35:9: 35:10
         AscribeUserType(_45, o, UserTypeProjection { base: UserType(27), projs: [] }); // scope 12 at $DIR/address-of.rs:35:12: 35:25
         StorageLive(_47);                // scope 13 at $DIR/address-of.rs:36:9: 36:10
         StorageLive(_48);                // scope 13 at $DIR/address-of.rs:36:25: 36:26
         _48 = &raw mut (*_3);            // scope 13 at $DIR/address-of.rs:36:25: 36:26
         _47 = move _48 as *mut [i32] (Pointer(Unsize)); // scope 13 at $DIR/address-of.rs:36:25: 36:26
         StorageDead(_48);                // scope 13 at $DIR/address-of.rs:36:25: 36:26
-        FakeRead(ForLet, _47);           // scope 13 at $DIR/address-of.rs:36:9: 36:10
+        FakeRead(ForLet(None), _47);     // scope 13 at $DIR/address-of.rs:36:9: 36:10
         AscribeUserType(_47, o, UserTypeProjection { base: UserType(29), projs: [] }); // scope 13 at $DIR/address-of.rs:36:12: 36:22
         _0 = const ();                   // scope 0 at $DIR/address-of.rs:3:26: 37:2
         StorageDead(_47);                // scope 13 at $DIR/address-of.rs:37:1: 37:2
diff --git a/src/test/mir-opt/address_of.borrow_and_cast.SimplifyCfg-initial.after.mir b/src/test/mir-opt/address_of.borrow_and_cast.SimplifyCfg-initial.after.mir
index e058b0aaa8f..195f3e2e65c 100644
--- a/src/test/mir-opt/address_of.borrow_and_cast.SimplifyCfg-initial.after.mir
+++ b/src/test/mir-opt/address_of.borrow_and_cast.SimplifyCfg-initial.after.mir
@@ -24,19 +24,19 @@ fn borrow_and_cast(_1: i32) -> () {
         StorageLive(_3);                 // scope 0 at $DIR/address-of.rs:42:13: 42:15
         _3 = &_1;                        // scope 0 at $DIR/address-of.rs:42:13: 42:15
         _2 = &raw const (*_3);           // scope 0 at $DIR/address-of.rs:42:13: 42:15
-        FakeRead(ForLet, _2);            // scope 0 at $DIR/address-of.rs:42:9: 42:10
+        FakeRead(ForLet(None), _2);      // scope 0 at $DIR/address-of.rs:42:9: 42:10
         StorageDead(_3);                 // scope 0 at $DIR/address-of.rs:42:29: 42:30
         StorageLive(_4);                 // scope 1 at $DIR/address-of.rs:43:9: 43:10
         StorageLive(_5);                 // scope 1 at $DIR/address-of.rs:43:13: 43:19
         _5 = &mut _1;                    // scope 1 at $DIR/address-of.rs:43:13: 43:19
         _4 = &raw const (*_5);           // scope 1 at $DIR/address-of.rs:43:13: 43:19
-        FakeRead(ForLet, _4);            // scope 1 at $DIR/address-of.rs:43:9: 43:10
+        FakeRead(ForLet(None), _4);      // scope 1 at $DIR/address-of.rs:43:9: 43:10
         StorageDead(_5);                 // scope 1 at $DIR/address-of.rs:43:33: 43:34
         StorageLive(_6);                 // scope 2 at $DIR/address-of.rs:44:9: 44:10
         StorageLive(_7);                 // scope 2 at $DIR/address-of.rs:44:13: 44:19
         _7 = &mut _1;                    // scope 2 at $DIR/address-of.rs:44:13: 44:19
         _6 = &raw mut (*_7);             // scope 2 at $DIR/address-of.rs:44:13: 44:19
-        FakeRead(ForLet, _6);            // scope 2 at $DIR/address-of.rs:44:9: 44:10
+        FakeRead(ForLet(None), _6);      // scope 2 at $DIR/address-of.rs:44:9: 44:10
         StorageDead(_7);                 // scope 2 at $DIR/address-of.rs:44:31: 44:32
         _0 = const ();                   // scope 0 at $DIR/address-of.rs:41:32: 45:2
         StorageDead(_6);                 // scope 2 at $DIR/address-of.rs:45:1: 45:2
diff --git a/src/test/mir-opt/basic_assignment.main.SimplifyCfg-initial.after.mir b/src/test/mir-opt/basic_assignment.main.SimplifyCfg-initial.after.mir
index 7e0ca3dea4b..e751b825c05 100644
--- a/src/test/mir-opt/basic_assignment.main.SimplifyCfg-initial.after.mir
+++ b/src/test/mir-opt/basic_assignment.main.SimplifyCfg-initial.after.mir
@@ -28,7 +28,7 @@ fn main() -> () {
     bb0: {
         StorageLive(_1);                 // scope 0 at $DIR/basic_assignment.rs:11:9: 11:17
         _1 = const false;                // scope 0 at $DIR/basic_assignment.rs:11:20: 11:25
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/basic_assignment.rs:11:9: 11:17
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/basic_assignment.rs:11:9: 11:17
         StorageLive(_2);                 // scope 1 at $DIR/basic_assignment.rs:12:9: 12:17
         StorageLive(_3);                 // scope 2 at $DIR/basic_assignment.rs:16:16: 16:24
         _3 = _1;                         // scope 2 at $DIR/basic_assignment.rs:16:16: 16:24
@@ -36,7 +36,7 @@ fn main() -> () {
         StorageDead(_3);                 // scope 2 at $DIR/basic_assignment.rs:16:23: 16:24
         StorageLive(_4);                 // scope 2 at $DIR/basic_assignment.rs:18:9: 18:15
         _4 = Option::<Box<u32>>::None;   // scope 2 at $DIR/basic_assignment.rs:18:36: 18:40
-        FakeRead(ForLet, _4);            // scope 2 at $DIR/basic_assignment.rs:18:9: 18:15
+        FakeRead(ForLet(None), _4);      // scope 2 at $DIR/basic_assignment.rs:18:9: 18:15
         AscribeUserType(_4, o, UserTypeProjection { base: UserType(1), projs: [] }); // scope 2 at $DIR/basic_assignment.rs:18:17: 18:33
         StorageLive(_5);                 // scope 3 at $DIR/basic_assignment.rs:19:9: 19:15
         StorageLive(_6);                 // scope 4 at $DIR/basic_assignment.rs:23:14: 23:20
diff --git a/src/test/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir b/src/test/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir
index aa4f996c4b4..93507879a6f 100644
--- a/src/test/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir
+++ b/src/test/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir
@@ -18,7 +18,7 @@ fn match_tuple(_1: (u32, bool, Option<i32>, u32)) -> u32 {
     }
 
     bb0: {
-        FakeRead(ForMatchedPlace, _1);   // scope 0 at $DIR/exponential-or.rs:5:11: 5:12
+        FakeRead(ForMatchedPlace(None), _1); // scope 0 at $DIR/exponential-or.rs:5:11: 5:12
         switchInt((_1.0: u32)) -> [1_u32: bb2, 4_u32: bb2, otherwise: bb1]; // scope 0 at $DIR/exponential-or.rs:6:15: 6:16
     }
 
diff --git a/src/test/mir-opt/issue_38669.main.SimplifyCfg-initial.after.mir b/src/test/mir-opt/issue_38669.main.SimplifyCfg-initial.after.mir
index e9e5a101a64..8355b2d195e 100644
--- a/src/test/mir-opt/issue_38669.main.SimplifyCfg-initial.after.mir
+++ b/src/test/mir-opt/issue_38669.main.SimplifyCfg-initial.after.mir
@@ -14,7 +14,7 @@ fn main() -> () {
     bb0: {
         StorageLive(_1);                 // scope 0 at $DIR/issue-38669.rs:5:9: 5:25
         _1 = const false;                // scope 0 at $DIR/issue-38669.rs:5:28: 5:33
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/issue-38669.rs:5:9: 5:25
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/issue-38669.rs:5:9: 5:25
         goto -> bb1;                     // scope 1 at $DIR/issue-38669.rs:6:5: 11:6
     }
 
diff --git a/src/test/mir-opt/issue_49232.main.mir_map.0.mir b/src/test/mir-opt/issue_49232.main.mir_map.0.mir
index 79f5495c788..06fbbda3d9e 100644
--- a/src/test/mir-opt/issue_49232.main.mir_map.0.mir
+++ b/src/test/mir-opt/issue_49232.main.mir_map.0.mir
@@ -24,7 +24,7 @@ fn main() -> () {
         StorageLive(_2);                 // scope 0 at $DIR/issue-49232.rs:7:13: 7:19
         StorageLive(_3);                 // scope 0 at $DIR/issue-49232.rs:8:19: 8:23
         _3 = const true;                 // scope 0 at $DIR/issue-49232.rs:8:19: 8:23
-        FakeRead(ForMatchedPlace, _3);   // scope 0 at $DIR/issue-49232.rs:8:19: 8:23
+        FakeRead(ForMatchedPlace(None), _3); // scope 0 at $DIR/issue-49232.rs:8:19: 8:23
         switchInt(_3) -> [false: bb3, otherwise: bb4]; // scope 0 at $DIR/issue-49232.rs:9:17: 9:22
     }
 
@@ -51,7 +51,7 @@ fn main() -> () {
     }
 
     bb8: {
-        FakeRead(ForLet, _2);            // scope 0 at $DIR/issue-49232.rs:7:13: 7:19
+        FakeRead(ForLet(None), _2);      // scope 0 at $DIR/issue-49232.rs:7:13: 7:19
         StorageDead(_3);                 // scope 0 at $DIR/issue-49232.rs:12:10: 12:11
         StorageLive(_5);                 // scope 1 at $DIR/issue-49232.rs:13:9: 13:22
         StorageLive(_6);                 // scope 1 at $DIR/issue-49232.rs:13:14: 13:21
diff --git a/src/test/mir-opt/issue_72181.main.mir_map.0.32bit.mir b/src/test/mir-opt/issue_72181.main.mir_map.0.32bit.mir
index cf66a501e35..2e6783b7f3c 100644
--- a/src/test/mir-opt/issue_72181.main.mir_map.0.32bit.mir
+++ b/src/test/mir-opt/issue_72181.main.mir_map.0.32bit.mir
@@ -38,7 +38,7 @@ fn main() -> () {
         _2 = [move _3, move _4];         // scope 1 at $DIR/issue-72181.rs:26:13: 26:43
         StorageDead(_4);                 // scope 1 at $DIR/issue-72181.rs:26:42: 26:43
         StorageDead(_3);                 // scope 1 at $DIR/issue-72181.rs:26:42: 26:43
-        FakeRead(ForLet, _2);            // scope 1 at $DIR/issue-72181.rs:26:9: 26:10
+        FakeRead(ForLet(None), _2);      // scope 1 at $DIR/issue-72181.rs:26:9: 26:10
         StorageLive(_5);                 // scope 2 at $DIR/issue-72181.rs:27:13: 27:30
         StorageLive(_6);                 // scope 4 at $DIR/issue-72181.rs:27:24: 27:25
         _6 = const 0_usize;              // scope 4 at $DIR/issue-72181.rs:27:24: 27:25
diff --git a/src/test/mir-opt/issue_72181.main.mir_map.0.64bit.mir b/src/test/mir-opt/issue_72181.main.mir_map.0.64bit.mir
index cf66a501e35..2e6783b7f3c 100644
--- a/src/test/mir-opt/issue_72181.main.mir_map.0.64bit.mir
+++ b/src/test/mir-opt/issue_72181.main.mir_map.0.64bit.mir
@@ -38,7 +38,7 @@ fn main() -> () {
         _2 = [move _3, move _4];         // scope 1 at $DIR/issue-72181.rs:26:13: 26:43
         StorageDead(_4);                 // scope 1 at $DIR/issue-72181.rs:26:42: 26:43
         StorageDead(_3);                 // scope 1 at $DIR/issue-72181.rs:26:42: 26:43
-        FakeRead(ForLet, _2);            // scope 1 at $DIR/issue-72181.rs:26:9: 26:10
+        FakeRead(ForLet(None), _2);      // scope 1 at $DIR/issue-72181.rs:26:9: 26:10
         StorageLive(_5);                 // scope 2 at $DIR/issue-72181.rs:27:13: 27:30
         StorageLive(_6);                 // scope 4 at $DIR/issue-72181.rs:27:24: 27:25
         _6 = const 0_usize;              // scope 4 at $DIR/issue-72181.rs:27:24: 27:25
diff --git a/src/test/mir-opt/issue_72181_1.f.mir_map.0.mir b/src/test/mir-opt/issue_72181_1.f.mir_map.0.mir
index 7571d7bb94f..7def08ece22 100644
--- a/src/test/mir-opt/issue_72181_1.f.mir_map.0.mir
+++ b/src/test/mir-opt/issue_72181_1.f.mir_map.0.mir
@@ -9,7 +9,7 @@ fn f(_1: Void) -> ! {
     bb0: {
         StorageLive(_2);                 // scope 0 at $DIR/issue-72181-1.rs:10:20: 12:2
         StorageLive(_3);                 // scope 0 at $DIR/issue-72181-1.rs:11:5: 11:15
-        FakeRead(ForMatchedPlace, _1);   // scope 0 at $DIR/issue-72181-1.rs:11:11: 11:12
+        FakeRead(ForMatchedPlace(None), _1); // scope 0 at $DIR/issue-72181-1.rs:11:11: 11:12
         unreachable;                     // scope 0 at $DIR/issue-72181-1.rs:11:11: 11:12
     }
 
diff --git a/src/test/mir-opt/issue_72181_1.main.mir_map.0.mir b/src/test/mir-opt/issue_72181_1.main.mir_map.0.mir
index 1fd91c2056b..3c26b20c35e 100644
--- a/src/test/mir-opt/issue_72181_1.main.mir_map.0.mir
+++ b/src/test/mir-opt/issue_72181_1.main.mir_map.0.mir
@@ -29,7 +29,7 @@ fn main() -> () {
 
     bb1: {
         StorageDead(_3);                 // scope 2 at $DIR/issue-72181-1.rs:17:43: 17:44
-        FakeRead(ForLet, _2);            // scope 0 at $DIR/issue-72181-1.rs:16:9: 16:10
+        FakeRead(ForLet(None), _2);      // scope 0 at $DIR/issue-72181-1.rs:16:9: 16:10
         AscribeUserType(_2, o, UserTypeProjection { base: UserType(1), projs: [] }); // scope 0 at $DIR/issue-72181-1.rs:16:12: 16:16
         StorageLive(_4);                 // scope 1 at $DIR/issue-72181-1.rs:20:5: 20:9
         StorageLive(_5);                 // scope 1 at $DIR/issue-72181-1.rs:20:7: 20:8
diff --git a/src/test/mir-opt/loop_test.main.SimplifyCfg-promote-consts.after.mir b/src/test/mir-opt/loop_test.main.SimplifyCfg-promote-consts.after.mir
index f109937dbf9..99c7ac8d5b7 100644
--- a/src/test/mir-opt/loop_test.main.SimplifyCfg-promote-consts.after.mir
+++ b/src/test/mir-opt/loop_test.main.SimplifyCfg-promote-consts.after.mir
@@ -41,7 +41,7 @@ fn main() -> () {
     bb4: {
         StorageLive(_6);                 // scope 0 at $DIR/loop_test.rs:14:13: 14:14
         _6 = const 1_i32;                // scope 0 at $DIR/loop_test.rs:14:17: 14:18
-        FakeRead(ForLet, _6);            // scope 0 at $DIR/loop_test.rs:14:13: 14:14
+        FakeRead(ForLet(None), _6);      // scope 0 at $DIR/loop_test.rs:14:13: 14:14
         StorageDead(_6);                 // scope 0 at $DIR/loop_test.rs:16:5: 16:6
         goto -> bb3;                     // scope 0 at $DIR/loop_test.rs:1:1: 1:1
     }
diff --git a/src/test/mir-opt/match_arm_scopes.complicated_match.SimplifyCfg-initial.after-ElaborateDrops.after.diff b/src/test/mir-opt/match_arm_scopes.complicated_match.SimplifyCfg-initial.after-ElaborateDrops.after.diff
index 95beab2ec9a..3395cbfbdfb 100644
--- a/src/test/mir-opt/match_arm_scopes.complicated_match.SimplifyCfg-initial.after-ElaborateDrops.after.diff
+++ b/src/test/mir-opt/match_arm_scopes.complicated_match.SimplifyCfg-initial.after-ElaborateDrops.after.diff
@@ -31,7 +31,7 @@
       }
   
       bb0: {
--         FakeRead(ForMatchedPlace, _2);   // scope 0 at $DIR/match-arm-scopes.rs:14:11: 14:16
+-         FakeRead(ForMatchedPlace(None), _2); // scope 0 at $DIR/match-arm-scopes.rs:14:11: 14:16
 -         switchInt((_2.0: bool)) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/match-arm-scopes.rs:15:10: 15:15
 +         switchInt((_2.0: bool)) -> [false: bb5, otherwise: bb1]; // scope 0 at $DIR/match-arm-scopes.rs:15:10: 15:15
       }
diff --git a/src/test/mir-opt/match_false_edges.full_tested_match.PromoteTemps.after.mir b/src/test/mir-opt/match_false_edges.full_tested_match.PromoteTemps.after.mir
index 1921b935941..5af242376c9 100644
--- a/src/test/mir-opt/match_false_edges.full_tested_match.PromoteTemps.after.mir
+++ b/src/test/mir-opt/match_false_edges.full_tested_match.PromoteTemps.after.mir
@@ -27,7 +27,7 @@ fn full_tested_match() -> () {
         StorageLive(_1);                 // scope 0 at $DIR/match_false_edges.rs:15:13: 19:6
         StorageLive(_2);                 // scope 0 at $DIR/match_false_edges.rs:15:19: 15:27
         _2 = Option::<i32>::Some(const 42_i32); // scope 0 at $DIR/match_false_edges.rs:15:19: 15:27
-        FakeRead(ForMatchedPlace, _2);   // scope 0 at $DIR/match_false_edges.rs:15:19: 15:27
+        FakeRead(ForMatchedPlace(None), _2); // scope 0 at $DIR/match_false_edges.rs:15:19: 15:27
         _3 = discriminant(_2);           // scope 0 at $DIR/match_false_edges.rs:16:9: 16:16
         switchInt(move _3) -> [0_isize: bb1, 1_isize: bb2, otherwise: bb4]; // scope 0 at $DIR/match_false_edges.rs:16:9: 16:16
     }
diff --git a/src/test/mir-opt/match_false_edges.full_tested_match2.PromoteTemps.before.mir b/src/test/mir-opt/match_false_edges.full_tested_match2.PromoteTemps.before.mir
index c7b1cce061b..a4ebf8a0246 100644
--- a/src/test/mir-opt/match_false_edges.full_tested_match2.PromoteTemps.before.mir
+++ b/src/test/mir-opt/match_false_edges.full_tested_match2.PromoteTemps.before.mir
@@ -26,7 +26,7 @@ fn full_tested_match2() -> () {
         StorageLive(_1);                 // scope 0 at $DIR/match_false_edges.rs:26:13: 30:6
         StorageLive(_2);                 // scope 0 at $DIR/match_false_edges.rs:26:19: 26:27
         _2 = Option::<i32>::Some(const 42_i32); // scope 0 at $DIR/match_false_edges.rs:26:19: 26:27
-        FakeRead(ForMatchedPlace, _2);   // scope 0 at $DIR/match_false_edges.rs:26:19: 26:27
+        FakeRead(ForMatchedPlace(None), _2); // scope 0 at $DIR/match_false_edges.rs:26:19: 26:27
         _3 = discriminant(_2);           // scope 0 at $DIR/match_false_edges.rs:27:9: 27:16
         switchInt(move _3) -> [0_isize: bb1, 1_isize: bb2, otherwise: bb4]; // scope 0 at $DIR/match_false_edges.rs:27:9: 27:16
     }
diff --git a/src/test/mir-opt/match_false_edges.main.PromoteTemps.before.mir b/src/test/mir-opt/match_false_edges.main.PromoteTemps.before.mir
index 9b8ce2c1ed0..5de52b324f4 100644
--- a/src/test/mir-opt/match_false_edges.main.PromoteTemps.before.mir
+++ b/src/test/mir-opt/match_false_edges.main.PromoteTemps.before.mir
@@ -37,7 +37,7 @@ fn main() -> () {
         StorageLive(_1);                 // scope 0 at $DIR/match_false_edges.rs:35:13: 40:6
         StorageLive(_2);                 // scope 0 at $DIR/match_false_edges.rs:35:19: 35:26
         _2 = Option::<i32>::Some(const 1_i32); // scope 0 at $DIR/match_false_edges.rs:35:19: 35:26
-        FakeRead(ForMatchedPlace, _2);   // scope 0 at $DIR/match_false_edges.rs:35:19: 35:26
+        FakeRead(ForMatchedPlace(None), _2); // scope 0 at $DIR/match_false_edges.rs:35:19: 35:26
         _4 = discriminant(_2);           // scope 0 at $DIR/match_false_edges.rs:36:9: 36:17
         switchInt(move _4) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/match_false_edges.rs:36:9: 36:17
     }
diff --git a/src/test/mir-opt/match_test.main.SimplifyCfg-initial.after.mir b/src/test/mir-opt/match_test.main.SimplifyCfg-initial.after.mir
index e3bc4f80f27..5bb910947ca 100644
--- a/src/test/mir-opt/match_test.main.SimplifyCfg-initial.after.mir
+++ b/src/test/mir-opt/match_test.main.SimplifyCfg-initial.after.mir
@@ -21,12 +21,12 @@ fn main() -> () {
     bb0: {
         StorageLive(_1);                 // scope 0 at $DIR/match_test.rs:7:9: 7:10
         _1 = const 3_i32;                // scope 0 at $DIR/match_test.rs:7:13: 7:14
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/match_test.rs:7:9: 7:10
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/match_test.rs:7:9: 7:10
         StorageLive(_2);                 // scope 1 at $DIR/match_test.rs:8:9: 8:10
         _2 = const true;                 // scope 1 at $DIR/match_test.rs:8:13: 8:17
-        FakeRead(ForLet, _2);            // scope 1 at $DIR/match_test.rs:8:9: 8:10
+        FakeRead(ForLet(None), _2);      // scope 1 at $DIR/match_test.rs:8:9: 8:10
         StorageLive(_3);                 // scope 2 at $DIR/match_test.rs:12:5: 17:6
-        FakeRead(ForMatchedPlace, _1);   // scope 2 at $DIR/match_test.rs:12:11: 12:12
+        FakeRead(ForMatchedPlace(None), _1); // scope 2 at $DIR/match_test.rs:12:11: 12:12
         _6 = Le(const 0_i32, _1);        // scope 2 at $DIR/match_test.rs:13:9: 13:14
         switchInt(move _6) -> [false: bb4, otherwise: bb1]; // scope 2 at $DIR/match_test.rs:13:9: 13:14
     }
diff --git a/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir b/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir
index 8c939d5fc3d..39e6cee11b4 100644
--- a/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir
+++ b/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir
@@ -46,7 +46,7 @@ fn main() -> () {
     bb0: {
         StorageLive(_1);                 // bb0[0]: scope 0 at $DIR/region-subtyping-basic.rs:17:9: 17:14
         _1 = [const Const(Value(Scalar(0x00000001)): usize), const Const(Value(Scalar(0x00000002)): usize), const Const(Value(Scalar(0x00000003)): usize)]; // bb0[1]: scope 0 at $DIR/region-subtyping-basic.rs:17:17: 17:26
-        FakeRead(ForLet, _1);            // bb0[2]: scope 0 at $DIR/region-subtyping-basic.rs:17:9: 17:14
+        FakeRead(ForLet(None), _1);      // bb0[2]: scope 0 at $DIR/region-subtyping-basic.rs:17:9: 17:14
         StorageLive(_2);                 // bb0[3]: scope 1 at $DIR/region-subtyping-basic.rs:18:9: 18:10
         StorageLive(_3);                 // bb0[4]: scope 1 at $DIR/region-subtyping-basic.rs:18:16: 18:17
         _3 = const Const(Value(Scalar(0x00000000)): usize); // bb0[5]: scope 1 at $DIR/region-subtyping-basic.rs:18:16: 18:17
@@ -57,10 +57,10 @@ fn main() -> () {
 
     bb1: {
         _2 = &'_#3r _1[_3];              // bb1[0]: scope 1 at $DIR/region-subtyping-basic.rs:18:13: 18:18
-        FakeRead(ForLet, _2);            // bb1[1]: scope 1 at $DIR/region-subtyping-basic.rs:18:9: 18:10
+        FakeRead(ForLet(None), _2);      // bb1[1]: scope 1 at $DIR/region-subtyping-basic.rs:18:9: 18:10
         StorageLive(_6);                 // bb1[2]: scope 2 at $DIR/region-subtyping-basic.rs:19:9: 19:10
         _6 = _2;                         // bb1[3]: scope 2 at $DIR/region-subtyping-basic.rs:19:13: 19:14
-        FakeRead(ForLet, _6);            // bb1[4]: scope 2 at $DIR/region-subtyping-basic.rs:19:9: 19:10
+        FakeRead(ForLet(None), _6);      // bb1[4]: scope 2 at $DIR/region-subtyping-basic.rs:19:9: 19:10
         StorageLive(_7);                 // bb1[5]: scope 3 at $DIR/region-subtyping-basic.rs:20:8: 20:12
         _7 = const Const(Value(Scalar(0x01)): bool); // bb1[6]: scope 3 at $DIR/region-subtyping-basic.rs:20:8: 20:12
         switchInt(move _7) -> [Const(Value(Scalar(0x00)): bool): bb3, otherwise: bb2]; // bb1[7]: scope 3 at $DIR/region-subtyping-basic.rs:20:5: 24:6
diff --git a/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir b/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir
index 00704baa6c1..6021b6529f9 100644
--- a/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir
+++ b/src/test/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir
@@ -46,7 +46,7 @@ fn main() -> () {
     bb0: {
         StorageLive(_1);                 // bb0[0]: scope 0 at $DIR/region-subtyping-basic.rs:17:9: 17:14
         _1 = [const Const(Value(Scalar(0x0000000000000001)): usize), const Const(Value(Scalar(0x0000000000000002)): usize), const Const(Value(Scalar(0x0000000000000003)): usize)]; // bb0[1]: scope 0 at $DIR/region-subtyping-basic.rs:17:17: 17:26
-        FakeRead(ForLet, _1);            // bb0[2]: scope 0 at $DIR/region-subtyping-basic.rs:17:9: 17:14
+        FakeRead(ForLet(None), _1);      // bb0[2]: scope 0 at $DIR/region-subtyping-basic.rs:17:9: 17:14
         StorageLive(_2);                 // bb0[3]: scope 1 at $DIR/region-subtyping-basic.rs:18:9: 18:10
         StorageLive(_3);                 // bb0[4]: scope 1 at $DIR/region-subtyping-basic.rs:18:16: 18:17
         _3 = const Const(Value(Scalar(0x0000000000000000)): usize); // bb0[5]: scope 1 at $DIR/region-subtyping-basic.rs:18:16: 18:17
@@ -57,10 +57,10 @@ fn main() -> () {
 
     bb1: {
         _2 = &'_#3r _1[_3];              // bb1[0]: scope 1 at $DIR/region-subtyping-basic.rs:18:13: 18:18
-        FakeRead(ForLet, _2);            // bb1[1]: scope 1 at $DIR/region-subtyping-basic.rs:18:9: 18:10
+        FakeRead(ForLet(None), _2);      // bb1[1]: scope 1 at $DIR/region-subtyping-basic.rs:18:9: 18:10
         StorageLive(_6);                 // bb1[2]: scope 2 at $DIR/region-subtyping-basic.rs:19:9: 19:10
         _6 = _2;                         // bb1[3]: scope 2 at $DIR/region-subtyping-basic.rs:19:13: 19:14
-        FakeRead(ForLet, _6);            // bb1[4]: scope 2 at $DIR/region-subtyping-basic.rs:19:9: 19:10
+        FakeRead(ForLet(None), _6);      // bb1[4]: scope 2 at $DIR/region-subtyping-basic.rs:19:9: 19:10
         StorageLive(_7);                 // bb1[5]: scope 3 at $DIR/region-subtyping-basic.rs:20:8: 20:12
         _7 = const Const(Value(Scalar(0x01)): bool); // bb1[6]: scope 3 at $DIR/region-subtyping-basic.rs:20:8: 20:12
         switchInt(move _7) -> [Const(Value(Scalar(0x00)): bool): bb3, otherwise: bb2]; // bb1[7]: scope 3 at $DIR/region-subtyping-basic.rs:20:5: 24:6
diff --git a/src/test/mir-opt/receiver_ptr_mutability.main.mir_map.0.mir b/src/test/mir-opt/receiver_ptr_mutability.main.mir_map.0.mir
index d2d96ff468d..f54c8f8ab4a 100644
--- a/src/test/mir-opt/receiver_ptr_mutability.main.mir_map.0.mir
+++ b/src/test/mir-opt/receiver_ptr_mutability.main.mir_map.0.mir
@@ -36,7 +36,7 @@ fn main() -> () {
     }
 
     bb1: {
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/receiver-ptr-mutability.rs:14:9: 14:12
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/receiver-ptr-mutability.rs:14:9: 14:12
         AscribeUserType(_1, o, UserTypeProjection { base: UserType(1), projs: [] }); // scope 0 at $DIR/receiver-ptr-mutability.rs:14:14: 14:23
         StorageLive(_2);                 // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:12
         StorageLive(_3);                 // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
@@ -63,7 +63,7 @@ fn main() -> () {
         _7 = &_8;                        // scope 1 at $DIR/receiver-ptr-mutability.rs:18:35: 18:41
         _6 = &_7;                        // scope 1 at $DIR/receiver-ptr-mutability.rs:18:34: 18:41
         _5 = &(*_6);                     // scope 1 at $DIR/receiver-ptr-mutability.rs:18:34: 18:41
-        FakeRead(ForLet, _5);            // scope 1 at $DIR/receiver-ptr-mutability.rs:18:9: 18:16
+        FakeRead(ForLet(None), _5);      // scope 1 at $DIR/receiver-ptr-mutability.rs:18:9: 18:16
         AscribeUserType(_5, o, UserTypeProjection { base: UserType(3), projs: [] }); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:18: 18:31
         StorageDead(_6);                 // scope 1 at $DIR/receiver-ptr-mutability.rs:18:41: 18:42
         StorageLive(_10);                // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:16
diff --git a/src/test/mir-opt/remove_fake_borrows.match_guard.CleanupNonCodegenStatements.diff b/src/test/mir-opt/remove_fake_borrows.match_guard.CleanupNonCodegenStatements.diff
index 47027311b47..4aa388fc67b 100644
--- a/src/test/mir-opt/remove_fake_borrows.match_guard.CleanupNonCodegenStatements.diff
+++ b/src/test/mir-opt/remove_fake_borrows.match_guard.CleanupNonCodegenStatements.diff
@@ -13,7 +13,7 @@
       let mut _8: bool;                    // in scope 0 at $DIR/remove_fake_borrows.rs:8:20: 8:21
   
       bb0: {
--         FakeRead(ForMatchedPlace, _1);   // scope 0 at $DIR/remove_fake_borrows.rs:7:11: 7:12
+-         FakeRead(ForMatchedPlace(None), _1); // scope 0 at $DIR/remove_fake_borrows.rs:7:11: 7:12
 +         nop;                             // scope 0 at $DIR/remove_fake_borrows.rs:7:11: 7:12
           _3 = discriminant(_1);           // scope 0 at $DIR/remove_fake_borrows.rs:8:9: 8:16
           switchInt(move _3) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/remove_fake_borrows.rs:8:9: 8:16
diff --git a/src/test/mir-opt/simple_match.match_bool.mir_map.0.32bit.mir b/src/test/mir-opt/simple_match.match_bool.mir_map.0.32bit.mir
index 5bcb20ca72a..841cca7c381 100644
--- a/src/test/mir-opt/simple_match.match_bool.mir_map.0.32bit.mir
+++ b/src/test/mir-opt/simple_match.match_bool.mir_map.0.32bit.mir
@@ -5,7 +5,7 @@ fn match_bool(_1: bool) -> usize {
     let mut _0: usize;                   // return place in scope 0 at $DIR/simple-match.rs:5:27: 5:32
 
     bb0: {
-        FakeRead(ForMatchedPlace, _1);   // scope 0 at $DIR/simple-match.rs:6:11: 6:12
+        FakeRead(ForMatchedPlace(None), _1); // scope 0 at $DIR/simple-match.rs:6:11: 6:12
         switchInt(_1) -> [false: bb2, otherwise: bb1]; // scope 0 at $DIR/simple-match.rs:7:9: 7:13
     }
 
diff --git a/src/test/mir-opt/simple_match.match_bool.mir_map.0.64bit.mir b/src/test/mir-opt/simple_match.match_bool.mir_map.0.64bit.mir
index 5bcb20ca72a..841cca7c381 100644
--- a/src/test/mir-opt/simple_match.match_bool.mir_map.0.64bit.mir
+++ b/src/test/mir-opt/simple_match.match_bool.mir_map.0.64bit.mir
@@ -5,7 +5,7 @@ fn match_bool(_1: bool) -> usize {
     let mut _0: usize;                   // return place in scope 0 at $DIR/simple-match.rs:5:27: 5:32
 
     bb0: {
-        FakeRead(ForMatchedPlace, _1);   // scope 0 at $DIR/simple-match.rs:6:11: 6:12
+        FakeRead(ForMatchedPlace(None), _1); // scope 0 at $DIR/simple-match.rs:6:11: 6:12
         switchInt(_1) -> [false: bb2, otherwise: bb1]; // scope 0 at $DIR/simple-match.rs:7:9: 7:13
     }
 
diff --git a/src/test/mir-opt/storage_ranges.main.nll.0.mir b/src/test/mir-opt/storage_ranges.main.nll.0.mir
index 6fa83d3de62..e02580135af 100644
--- a/src/test/mir-opt/storage_ranges.main.nll.0.mir
+++ b/src/test/mir-opt/storage_ranges.main.nll.0.mir
@@ -39,7 +39,7 @@ fn main() -> () {
     bb0: {
         StorageLive(_1);                 // scope 0 at $DIR/storage_ranges.rs:4:9: 4:10
         _1 = const 0_i32;                // scope 0 at $DIR/storage_ranges.rs:4:13: 4:14
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/storage_ranges.rs:4:9: 4:10
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/storage_ranges.rs:4:9: 4:10
         StorageLive(_2);                 // scope 1 at $DIR/storage_ranges.rs:5:5: 7:6
         StorageLive(_3);                 // scope 1 at $DIR/storage_ranges.rs:6:13: 6:14
         StorageLive(_4);                 // scope 1 at $DIR/storage_ranges.rs:6:18: 6:25
@@ -48,14 +48,14 @@ fn main() -> () {
         _4 = Option::<i32>::Some(move _5); // scope 1 at $DIR/storage_ranges.rs:6:18: 6:25
         StorageDead(_5);                 // scope 1 at $DIR/storage_ranges.rs:6:24: 6:25
         _3 = &_4;                        // scope 1 at $DIR/storage_ranges.rs:6:17: 6:25
-        FakeRead(ForLet, _3);            // scope 1 at $DIR/storage_ranges.rs:6:13: 6:14
+        FakeRead(ForLet(None), _3);      // scope 1 at $DIR/storage_ranges.rs:6:13: 6:14
         _2 = const ();                   // scope 1 at $DIR/storage_ranges.rs:5:5: 7:6
         StorageDead(_4);                 // scope 1 at $DIR/storage_ranges.rs:7:5: 7:6
         StorageDead(_3);                 // scope 1 at $DIR/storage_ranges.rs:7:5: 7:6
         StorageDead(_2);                 // scope 1 at $DIR/storage_ranges.rs:7:5: 7:6
         StorageLive(_6);                 // scope 1 at $DIR/storage_ranges.rs:8:9: 8:10
         _6 = const 1_i32;                // scope 1 at $DIR/storage_ranges.rs:8:13: 8:14
-        FakeRead(ForLet, _6);            // scope 1 at $DIR/storage_ranges.rs:8:9: 8:10
+        FakeRead(ForLet(None), _6);      // scope 1 at $DIR/storage_ranges.rs:8:9: 8:10
         _0 = const ();                   // scope 0 at $DIR/storage_ranges.rs:3:11: 9:2
         StorageDead(_6);                 // scope 1 at $DIR/storage_ranges.rs:9:1: 9:2
         StorageDead(_1);                 // scope 0 at $DIR/storage_ranges.rs:9:1: 9:2
diff --git a/src/test/mir-opt/uniform_array_move_out.move_out_by_subslice.mir_map.0.mir b/src/test/mir-opt/uniform_array_move_out.move_out_by_subslice.mir_map.0.mir
index d18f6308ded..7f81d9fc482 100644
--- a/src/test/mir-opt/uniform_array_move_out.move_out_by_subslice.mir_map.0.mir
+++ b/src/test/mir-opt/uniform_array_move_out.move_out_by_subslice.mir_map.0.mir
@@ -48,7 +48,7 @@ fn move_out_by_subslice() -> () {
 
     bb4: {
         StorageDead(_2);                 // scope 0 at $DIR/uniform_array_move_out.rs:11:26: 11:27
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/uniform_array_move_out.rs:11:9: 11:10
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/uniform_array_move_out.rs:11:9: 11:10
         StorageLive(_6);                 // scope 1 at $DIR/uniform_array_move_out.rs:12:10: 12:17
         _6 = move _1[0..2];              // scope 1 at $DIR/uniform_array_move_out.rs:12:10: 12:17
         _0 = const ();                   // scope 0 at $DIR/uniform_array_move_out.rs:10:27: 13:2
diff --git a/src/test/mir-opt/uniform_array_move_out.move_out_from_end.mir_map.0.mir b/src/test/mir-opt/uniform_array_move_out.move_out_from_end.mir_map.0.mir
index eda8e5fd3af..62ab494c066 100644
--- a/src/test/mir-opt/uniform_array_move_out.move_out_from_end.mir_map.0.mir
+++ b/src/test/mir-opt/uniform_array_move_out.move_out_from_end.mir_map.0.mir
@@ -48,7 +48,7 @@ fn move_out_from_end() -> () {
 
     bb4: {
         StorageDead(_2);                 // scope 0 at $DIR/uniform_array_move_out.rs:5:26: 5:27
-        FakeRead(ForLet, _1);            // scope 0 at $DIR/uniform_array_move_out.rs:5:9: 5:10
+        FakeRead(ForLet(None), _1);      // scope 0 at $DIR/uniform_array_move_out.rs:5:9: 5:10
         StorageLive(_6);                 // scope 1 at $DIR/uniform_array_move_out.rs:6:14: 6:16
         _6 = move _1[1 of 2];            // scope 1 at $DIR/uniform_array_move_out.rs:6:14: 6:16
         _0 = const ();                   // scope 0 at $DIR/uniform_array_move_out.rs:4:24: 7:2
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index 1391f7505e2..dac4d93499d 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -212,7 +212,7 @@ fn check_statement(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, statemen
             check_rvalue(tcx, body, def_id, rval, span)
         }
 
-        StatementKind::FakeRead(_, place) |
+        StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body),
         // just an assignment
         StatementKind::SetDiscriminant { place, .. } => check_place(tcx, **place, span, body),
 
diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs
index 1d4b5e1247d..48091601861 100644
--- a/src/tools/compiletest/src/main.rs
+++ b/src/tools/compiletest/src/main.rs
@@ -909,7 +909,8 @@ fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
     // This particular form is documented in the GNU coding standards:
     // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
 
-    let mut splits = full_version_line.rsplit(' ');
+    let unbracketed_part = full_version_line.split('[').next().unwrap();
+    let mut splits = unbracketed_part.trim_end().rsplit(' ');
     let version_string = splits.next().unwrap();
 
     let mut splits = version_string.split('.');
diff --git a/src/tools/compiletest/src/tests.rs b/src/tools/compiletest/src/tests.rs
index 233f2e648dc..e6725dba260 100644
--- a/src/tools/compiletest/src/tests.rs
+++ b/src/tools/compiletest/src/tests.rs
@@ -39,6 +39,9 @@ fn test_extract_gdb_version() {
         7012000: "GNU gdb (GDB) 7.12",
         7012000: "GNU gdb (GDB) 7.12.20161027-git",
         7012050: "GNU gdb (GDB) 7.12.50.20161027-git",
+
+        9002000: "GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2",
+        10001000: "GNU gdb (GDB) 10.1 [GDB v10.1 for FreeBSD]",
     }
 }
 
diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml
index 777d7be8fdc..58c32993cb6 100644
--- a/src/tools/tidy/Cargo.toml
+++ b/src/tools/tidy/Cargo.toml
@@ -10,6 +10,7 @@ cargo_metadata = "0.11"
 regex = "1"
 lazy_static = "1"
 walkdir = "2"
+crossbeam-utils = "0.8.0"
 
 [[bin]]
 name = "rust-tidy"
diff --git a/src/tools/tidy/src/bins.rs b/src/tools/tidy/src/bins.rs
index 62cfa85577f..1d5ec5c31c6 100644
--- a/src/tools/tidy/src/bins.rs
+++ b/src/tools/tidy/src/bins.rs
@@ -5,91 +5,126 @@
 //! huge amount of bloat to the Git history, so it's good to just ensure we
 //! don't do that again.
 
-use std::path::Path;
+pub use os_impl::*;
 
 // All files are executable on Windows, so just check on Unix.
 #[cfg(windows)]
-pub fn check(_path: &Path, _output: &Path, _bad: &mut bool) {}
+mod os_impl {
+    use std::path::Path;
+
+    pub fn check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool {
+        return false;
+    }
+
+    pub fn check(_path: &Path, _bad: &mut bool) {}
+}
 
 #[cfg(unix)]
-pub fn check(path: &Path, output: &Path, bad: &mut bool) {
+mod os_impl {
     use std::fs;
     use std::os::unix::prelude::*;
+    use std::path::Path;
     use std::process::{Command, Stdio};
 
+    enum FilesystemSupport {
+        Supported,
+        Unsupported,
+        ReadOnlyFs,
+    }
+
+    use FilesystemSupport::*;
+
     fn is_executable(path: &Path) -> std::io::Result<bool> {
         Ok(path.metadata()?.mode() & 0o111 != 0)
     }
 
-    // We want to avoid false positives on filesystems that do not support the
-    // executable bit. This occurs on some versions of Window's linux subsystem,
-    // for example.
-    //
-    // We try to create the temporary file first in the src directory, which is
-    // the preferred location as it's most likely to be on the same filesystem,
-    // and then in the output (`build`) directory if that fails. Sometimes we
-    // see the source directory mounted as read-only which means we can't
-    // readily create a file there to test.
-    //
-    // See #36706 and #74753 for context.
-    let mut temp_path = path.join("tidy-test-file");
-    match fs::File::create(&temp_path).or_else(|_| {
-        temp_path = output.join("tidy-test-file");
-        fs::File::create(&temp_path)
-    }) {
-        Ok(file) => {
-            let exec = is_executable(&temp_path).unwrap_or(false);
-            std::mem::drop(file);
-            std::fs::remove_file(&temp_path).expect("Deleted temp file");
-            if exec {
-                // If the file is executable, then we assume that this
-                // filesystem does not track executability, so skip this check.
-                return;
-            }
+    pub fn check_filesystem_support(sources: &[&Path], output: &Path) -> bool {
+        // We want to avoid false positives on filesystems that do not support the
+        // executable bit. This occurs on some versions of Window's linux subsystem,
+        // for example.
+        //
+        // We try to create the temporary file first in the src directory, which is
+        // the preferred location as it's most likely to be on the same filesystem,
+        // and then in the output (`build`) directory if that fails. Sometimes we
+        // see the source directory mounted as read-only which means we can't
+        // readily create a file there to test.
+        //
+        // See #36706 and #74753 for context.
+
+        fn check_dir(dir: &Path) -> FilesystemSupport {
+            let path = dir.join("tidy-test-file");
+            match fs::File::create(&path) {
+                Ok(file) => {
+                    let exec = is_executable(&path).unwrap_or(false);
+                    std::mem::drop(file);
+                    std::fs::remove_file(&path).expect("Deleted temp file");
+                    // If the file is executable, then we assume that this
+                    // filesystem does not track executability, so skip this check.
+                    return if exec { Unsupported } else { Supported };
+                }
+                Err(e) => {
+                    // If the directory is read-only or we otherwise don't have rights,
+                    // just don't run this check.
+                    //
+                    // 30 is the "Read-only filesystem" code at least in one CI
+                    //    environment.
+                    if e.raw_os_error() == Some(30) {
+                        eprintln!("tidy: Skipping binary file check, read-only filesystem");
+                        return ReadOnlyFs;
+                    }
+
+                    panic!("unable to create temporary file `{:?}`: {:?}", path, e);
+                }
+            };
         }
-        Err(e) => {
-            // If the directory is read-only or we otherwise don't have rights,
-            // just don't run this check.
-            //
-            // 30 is the "Read-only filesystem" code at least in one CI
-            //    environment.
-            if e.raw_os_error() == Some(30) {
-                eprintln!("tidy: Skipping binary file check, read-only filesystem");
-                return;
-            }
 
-            panic!("unable to create temporary file `{:?}`: {:?}", temp_path, e);
+        for &source_dir in sources {
+            match check_dir(source_dir) {
+                Unsupported => return false,
+                ReadOnlyFs => {
+                    return match check_dir(output) {
+                        Supported => true,
+                        _ => false,
+                    };
+                }
+                _ => {}
+            }
         }
+
+        return true;
     }
 
-    super::walk_no_read(
-        path,
-        &mut |path| super::filter_dirs(path) || path.ends_with("src/etc"),
-        &mut |entry| {
-            let file = entry.path();
-            let filename = file.file_name().unwrap().to_string_lossy();
-            let extensions = [".py", ".sh"];
-            if extensions.iter().any(|e| filename.ends_with(e)) {
-                return;
-            }
+    #[cfg(unix)]
+    pub fn check(path: &Path, bad: &mut bool) {
+        crate::walk_no_read(
+            path,
+            &mut |path| crate::filter_dirs(path) || path.ends_with("src/etc"),
+            &mut |entry| {
+                let file = entry.path();
+                let filename = file.file_name().unwrap().to_string_lossy();
+                let extensions = [".py", ".sh"];
+                if extensions.iter().any(|e| filename.ends_with(e)) {
+                    return;
+                }
 
-            if t!(is_executable(&file), file) {
-                let rel_path = file.strip_prefix(path).unwrap();
-                let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");
-                let output = Command::new("git")
-                    .arg("ls-files")
-                    .arg(&git_friendly_path)
-                    .current_dir(path)
-                    .stderr(Stdio::null())
-                    .output()
-                    .unwrap_or_else(|e| {
-                        panic!("could not run git ls-files: {}", e);
-                    });
-                let path_bytes = rel_path.as_os_str().as_bytes();
-                if output.status.success() && output.stdout.starts_with(path_bytes) {
-                    tidy_error!(bad, "binary checked into source: {}", file.display());
+                if t!(is_executable(&file), file) {
+                    let rel_path = file.strip_prefix(path).unwrap();
+                    let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");
+                    let output = Command::new("git")
+                        .arg("ls-files")
+                        .arg(&git_friendly_path)
+                        .current_dir(path)
+                        .stderr(Stdio::null())
+                        .output()
+                        .unwrap_or_else(|e| {
+                            panic!("could not run git ls-files: {}", e);
+                        });
+                    let path_bytes = rel_path.as_os_str().as_bytes();
+                    if output.status.success() && output.stdout.starts_with(path_bytes) {
+                        tidy_error!(bad, "binary checked into source: {}", file.display());
+                    }
                 }
-            }
-        },
-    )
+            },
+        )
+    }
 }
diff --git a/src/tools/tidy/src/error_codes_check.rs b/src/tools/tidy/src/error_codes_check.rs
index a7199fdfce6..55f824b63f2 100644
--- a/src/tools/tidy/src/error_codes_check.rs
+++ b/src/tools/tidy/src/error_codes_check.rs
@@ -48,6 +48,8 @@ fn check_error_code_explanation(
 }
 
 fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &str) -> bool {
+    let mut ignore_found = false;
+
     for line in f.lines() {
         let s = line.trim();
         if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
@@ -56,13 +58,13 @@ fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &str) -> bool {
         if s.starts_with("```") {
             if s.contains("compile_fail") && s.contains(err_code) {
                 return true;
-            } else if s.contains('(') {
+            } else if s.contains("ignore") {
                 // It's very likely that we can't actually make it fail compilation...
-                return true;
+                ignore_found = true;
             }
         }
     }
-    false
+    ignore_found
 }
 
 macro_rules! some_or_continue {
@@ -164,18 +166,32 @@ fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, boo
     }
 }
 
-pub fn check(path: &Path, bad: &mut bool) {
+pub fn check(paths: &[&Path], bad: &mut bool) {
     let mut errors = Vec::new();
+    let mut found_explanations = 0;
+    let mut found_tests = 0;
     println!("Checking which error codes lack tests...");
     let mut error_codes: HashMap<String, bool> = HashMap::new();
-    super::walk(path, &mut |path| super::filter_dirs(path), &mut |entry, contents| {
-        let file_name = entry.file_name();
-        if file_name == "error_codes.rs" {
-            extract_error_codes(contents, &mut error_codes, entry.path(), &mut errors);
-        } else if entry.path().extension() == Some(OsStr::new("stderr")) {
-            extract_error_codes_from_tests(contents, &mut error_codes);
-        }
-    });
+    for path in paths {
+        super::walk(path, &mut |path| super::filter_dirs(path), &mut |entry, contents| {
+            let file_name = entry.file_name();
+            if file_name == "error_codes.rs" {
+                extract_error_codes(contents, &mut error_codes, entry.path(), &mut errors);
+                found_explanations += 1;
+            } else if entry.path().extension() == Some(OsStr::new("stderr")) {
+                extract_error_codes_from_tests(contents, &mut error_codes);
+                found_tests += 1;
+            }
+        });
+    }
+    if found_explanations == 0 {
+        eprintln!("No error code explanation was tested!");
+        *bad = true;
+    }
+    if found_tests == 0 {
+        eprintln!("No error code was found in compilation errors!");
+        *bad = true;
+    }
     if errors.is_empty() {
         println!("Found {} error codes", error_codes.len());
 
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 2ac96e404ac..10356a2fdc5 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -6,15 +6,23 @@
 
 use tidy::*;
 
+use crossbeam_utils::thread::{scope, ScopedJoinHandle};
+use std::collections::VecDeque;
 use std::env;
+use std::num::NonZeroUsize;
 use std::path::PathBuf;
 use std::process;
+use std::str::FromStr;
+use std::sync::atomic::{AtomicBool, Ordering};
 
 fn main() {
     let root_path: PathBuf = env::args_os().nth(1).expect("need path to root of repo").into();
     let cargo: PathBuf = env::args_os().nth(2).expect("need path to cargo").into();
     let output_directory: PathBuf =
         env::args_os().nth(3).expect("need path to output directory").into();
+    let concurrency: NonZeroUsize =
+        FromStr::from_str(&env::args().nth(4).expect("need concurrency"))
+            .expect("concurrency must be a number");
 
     let src_path = root_path.join("src");
     let library_path = root_path.join("library");
@@ -22,45 +30,84 @@ fn main() {
 
     let args: Vec<String> = env::args().skip(1).collect();
 
-    let mut bad = false;
     let verbose = args.iter().any(|s| *s == "--verbose");
 
-    // Checks over tests.
-    debug_artifacts::check(&src_path, &mut bad);
-    ui_tests::check(&src_path, &mut bad);
-
-    // Checks that only make sense for the compiler.
-    errors::check(&compiler_path, &mut bad);
-    error_codes_check::check(&src_path, &mut bad);
-
-    // Checks that only make sense for the std libs.
-    pal::check(&library_path, &mut bad);
-
-    // Checks that need to be done for both the compiler and std libraries.
-    unit_tests::check(&src_path, &mut bad);
-    unit_tests::check(&compiler_path, &mut bad);
-    unit_tests::check(&library_path, &mut bad);
-
-    bins::check(&src_path, &output_directory, &mut bad);
-    bins::check(&compiler_path, &output_directory, &mut bad);
-    bins::check(&library_path, &output_directory, &mut bad);
-
-    style::check(&src_path, &mut bad);
-    style::check(&compiler_path, &mut bad);
-    style::check(&library_path, &mut bad);
-
-    edition::check(&src_path, &mut bad);
-    edition::check(&compiler_path, &mut bad);
-    edition::check(&library_path, &mut bad);
-
-    let collected = features::check(&src_path, &compiler_path, &library_path, &mut bad, verbose);
-    unstable_book::check(&src_path, collected, &mut bad);
-
-    // Checks that are done on the cargo workspace.
-    deps::check(&root_path, &cargo, &mut bad);
-    extdeps::check(&root_path, &mut bad);
-
-    if bad {
+    let bad = std::sync::Arc::new(AtomicBool::new(false));
+
+    scope(|s| {
+        let mut handles: VecDeque<ScopedJoinHandle<'_, ()>> =
+            VecDeque::with_capacity(concurrency.get());
+
+        macro_rules! check {
+            ($p:ident $(, $args:expr)* ) => {
+                while handles.len() >= concurrency.get() {
+                    handles.pop_front().unwrap().join().unwrap();
+                }
+
+                let handle = s.spawn(|_| {
+                    let mut flag = false;
+                    $p::check($($args),* , &mut flag);
+                    if (flag) {
+                        bad.store(true, Ordering::Relaxed);
+                    }
+                });
+                handles.push_back(handle);
+            }
+        }
+
+        // Checks that are done on the cargo workspace.
+        check!(deps, &root_path, &cargo);
+        check!(extdeps, &root_path);
+
+        // Checks over tests.
+        check!(debug_artifacts, &src_path);
+        check!(ui_tests, &src_path);
+
+        // Checks that only make sense for the compiler.
+        check!(errors, &compiler_path);
+        check!(error_codes_check, &[&src_path, &compiler_path]);
+
+        // Checks that only make sense for the std libs.
+        check!(pal, &library_path);
+
+        // Checks that need to be done for both the compiler and std libraries.
+        check!(unit_tests, &src_path);
+        check!(unit_tests, &compiler_path);
+        check!(unit_tests, &library_path);
+
+        if bins::check_filesystem_support(
+            &[&src_path, &compiler_path, &library_path],
+            &output_directory,
+        ) {
+            check!(bins, &src_path);
+            check!(bins, &compiler_path);
+            check!(bins, &library_path);
+        }
+
+        check!(style, &src_path);
+        check!(style, &compiler_path);
+        check!(style, &library_path);
+
+        check!(edition, &src_path);
+        check!(edition, &compiler_path);
+        check!(edition, &library_path);
+
+        let collected = {
+            while handles.len() >= concurrency.get() {
+                handles.pop_front().unwrap().join().unwrap();
+            }
+            let mut flag = false;
+            let r = features::check(&src_path, &compiler_path, &library_path, &mut flag, verbose);
+            if flag {
+                bad.store(true, Ordering::Relaxed);
+            }
+            r
+        };
+        check!(unstable_book, &src_path, collected);
+    })
+    .unwrap();
+
+    if bad.load(Ordering::Relaxed) {
         eprintln!("some tidy checks failed");
         process::exit(1);
     }