diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs
index 0d1b875cbed5b..9da4a0914a310 100644
--- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs
@@ -134,9 +134,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
                 // whether or not the right-hand side is a place expression
                 if let LocalInfo::User(BindingForm::Var(VarBindingForm {
                     opt_match_place: Some((opt_match_place, match_span)),
-                    binding_mode: _,
-                    opt_ty_info: _,
-                    pat_span: _,
+                    ..
                 })) = *local_decl.local_info()
                 {
                     let stmt_source_info = self.body.source_info(location);
diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
index bd068b29c1235..65565787014b4 100644
--- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
@@ -305,8 +305,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
                         LocalInfo::User(BindingForm::Var(mir::VarBindingForm {
                             binding_mode: BindingAnnotation(ByRef::No, Mutability::Not),
                             opt_ty_info: Some(sp),
-                            opt_match_place: _,
-                            pat_span: _,
+                            ..
                         })) => {
                             if suggest {
                                 err.span_note(sp, "the binding is already a mutable borrow");
@@ -729,6 +728,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
                 opt_ty_info: _,
                 opt_match_place: _,
                 pat_span,
+                introductions: _,
             })) => pat_span,
             _ => local_decl.source_info.span,
         };
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 1f92cc4d76399..ed0e5620dd3b2 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -749,6 +749,7 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
     sess.time("MIR_effect_checking", || {
         for def_id in tcx.hir().body_owners() {
             tcx.ensure().has_ffi_unwind_calls(def_id);
+            tcx.ensure().check_liveness(def_id);
 
             // If we need to codegen, ensure that we emit all errors from
             // `mir_drops_elaborated_and_const_checked` now, to avoid discovering
diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index 7ecac0c0e7838..3026bc51a349e 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -999,6 +999,8 @@ pub struct VarBindingForm<'tcx> {
     pub opt_match_place: Option<(Option<Place<'tcx>>, Span)>,
     /// The span of the pattern in which this variable was bound.
     pub pat_span: Span,
+    /// For each introduction place, record here the span and whether this was a shorthand pattern.
+    pub introductions: Vec<(Span, /* is_shorthand */ bool)>,
 }
 
 #[derive(Clone, Debug, TyEncodable, TyDecodable)]
@@ -1008,7 +1010,7 @@ pub enum BindingForm<'tcx> {
     /// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit.
     ImplicitSelf(ImplicitSelfKind),
     /// Reference used in a guard expression to ensure immutability.
-    RefForGuard,
+    RefForGuard(Local),
 }
 
 TrivialTypeTraversalImpls! { BindingForm<'tcx> }
@@ -1025,7 +1027,7 @@ mod binding_form_impl {
             match self {
                 Var(binding) => binding.hash_stable(hcx, hasher),
                 ImplicitSelf(kind) => kind.hash_stable(hcx, hasher),
-                RefForGuard => (),
+                RefForGuard(local) => local.hash_stable(hcx, hasher),
             }
         }
     }
@@ -1208,9 +1210,7 @@ impl<'tcx> LocalDecl<'tcx> {
             LocalInfo::User(
                 BindingForm::Var(VarBindingForm {
                     binding_mode: BindingAnnotation(ByRef::No, _),
-                    opt_ty_info: _,
-                    opt_match_place: _,
-                    pat_span: _,
+                    ..
                 }) | BindingForm::ImplicitSelf(ImplicitSelfKind::Imm),
             )
         )
@@ -1225,9 +1225,7 @@ impl<'tcx> LocalDecl<'tcx> {
             LocalInfo::User(
                 BindingForm::Var(VarBindingForm {
                     binding_mode: BindingAnnotation(ByRef::No, _),
-                    opt_ty_info: _,
-                    opt_match_place: _,
-                    pat_span: _,
+                    ..
                 }) | BindingForm::ImplicitSelf(_),
             )
         )
@@ -1244,7 +1242,7 @@ impl<'tcx> LocalDecl<'tcx> {
     /// expression that is used to access said variable for the guard of the
     /// match arm.
     pub fn is_ref_for_guard(&self) -> bool {
-        matches!(self.local_info(), LocalInfo::User(BindingForm::RefForGuard))
+        matches!(self.local_info(), LocalInfo::User(BindingForm::RefForGuard(_)))
     }
 
     /// Returns `Some` if this is a reference to a static item that is used to
diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs
index 069c8019cb2c8..831cbe5520e0e 100644
--- a/compiler/rustc_middle/src/mir/statement.rs
+++ b/compiler/rustc_middle/src/mir/statement.rs
@@ -50,7 +50,7 @@ impl<'tcx> StatementKind<'tcx> {
 impl<V, T> ProjectionElem<V, T> {
     /// Returns `true` if the target of this projection may refer to a different region of memory
     /// than the base.
-    fn is_indirect(&self) -> bool {
+    pub fn is_indirect(&self) -> bool {
         match self {
             Self::Deref => true,
 
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 5ef7a20f460ed..c3c3c397794bc 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -943,8 +943,10 @@ rustc_queries! {
         desc { |tcx| "checking privacy in {}", describe_as_module(key.to_local_def_id(), tcx) }
     }
 
-    query check_liveness(key: LocalDefId) {
-        desc { |tcx| "checking liveness of variables in `{}`", tcx.def_path_str(key) }
+    query check_liveness(key: LocalDefId) -> &'tcx rustc_index::bit_set::BitSet<abi::FieldIdx> {
+        arena_cache
+        desc { |tcx| "checking liveness of variables in `{}`", tcx.def_path_str(key.to_def_id()) }
+        cache_on_disk_if(tcx) { tcx.is_typeck_child(key.to_def_id()) }
     }
 
     /// Return the live symbols in the crate for dead code check.
diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index f10b204cd477b..436f39b36de57 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -738,6 +738,7 @@ pub enum PatKind<'tcx> {
         /// Is this the leftmost occurrence of the binding, i.e., is `var` the
         /// `HirId` of this pattern?
         is_primary: bool,
+        is_shorthand: bool,
     },
 
     /// `Foo(...)` or `Foo{...}` or `Foo`, where `Foo` is a variant name from an ADT with
diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs
index 7db64504f85ea..5bdfb022022e9 100644
--- a/compiler/rustc_middle/src/ty/closure.rs
+++ b/compiler/rustc_middle/src/ty/closure.rs
@@ -325,6 +325,8 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc
                     )
                 }
             },
+            // Just change the type to the hidden type, so we can actually project.
+            HirProjectionKind::OpaqueCast => {}
             proj => bug!("{:?} unexpected because it isn't captured", proj),
         }
     }
diff --git a/compiler/rustc_mir_build/src/build/block.rs b/compiler/rustc_mir_build/src/build/block.rs
index 00e99f330f727..956cd06dfe822 100644
--- a/compiler/rustc_mir_build/src/build/block.rs
+++ b/compiler/rustc_mir_build/src/build/block.rs
@@ -204,6 +204,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                                         block,
                                         node,
                                         span,
+                                        false,
                                         OutsideGuard,
                                         true,
                                     );
@@ -290,7 +291,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                             pattern,
                             UserTypeProjections::none(),
                             &mut |this, _, _, node, span, _, _| {
-                                this.storage_live_binding(block, node, span, OutsideGuard, true);
+                                this.storage_live_binding(
+                                    block,
+                                    node,
+                                    span,
+                                    false,
+                                    OutsideGuard,
+                                    true,
+                                );
                                 this.schedule_drop_for_binding(node, span, OutsideGuard);
                             },
                         )
diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs
index 367c391b45a49..0fb4c0bcda4e4 100644
--- a/compiler/rustc_mir_build/src/build/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/build/matches/mod.rs
@@ -627,8 +627,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 subpattern: None,
                 ..
             } => {
-                let place =
-                    self.storage_live_binding(block, var, irrefutable_pat.span, OutsideGuard, true);
+                let place = self.storage_live_binding(
+                    block,
+                    var,
+                    irrefutable_pat.span,
+                    false,
+                    OutsideGuard,
+                    true,
+                );
                 unpack!(block = self.expr_into_dest(place, block, initializer_id));
 
                 // Inject a fake read, see comments on `FakeReadCause::ForLet`.
@@ -661,8 +667,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     },
                 ascription: thir::Ascription { ref annotation, variance: _ },
             } => {
-                let place =
-                    self.storage_live_binding(block, var, irrefutable_pat.span, OutsideGuard, true);
+                let place = self.storage_live_binding(
+                    block,
+                    var,
+                    irrefutable_pat.span,
+                    false,
+                    OutsideGuard,
+                    true,
+                );
                 unpack!(block = self.expr_into_dest(place, block, initializer_id));
 
                 // Inject a fake read, see comments on `FakeReadCause::ForLet`.
@@ -855,6 +867,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         block: BasicBlock,
         var: LocalVarId,
         span: Span,
+        is_shorthand: bool,
         for_guard: ForGuard,
         schedule_drop: bool,
     ) -> Place<'tcx> {
@@ -868,6 +881,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         {
             self.schedule_drop(span, region_scope, local_id, DropKind::Storage);
         }
+        let local_info = self.local_decls[local_id].local_info.as_mut().assert_crate_local();
+        if let LocalInfo::User(BindingForm::Var(var_info)) = &mut **local_info {
+            var_info.introductions.push((span, is_shorthand));
+        }
         Place::from(local_id)
     }
 
@@ -1149,6 +1166,7 @@ struct Binding<'tcx> {
     source: Place<'tcx>,
     var_id: LocalVarId,
     binding_mode: BindingAnnotation,
+    is_shorthand: bool,
 }
 
 /// Indicates that the type of `source` must be a subtype of the
@@ -2332,6 +2350,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 block,
                 binding.var_id,
                 binding.span,
+                binding.is_shorthand,
                 RefWithinGuard,
                 schedule_drops,
             );
@@ -2345,6 +2364,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         block,
                         binding.var_id,
                         binding.span,
+                        binding.is_shorthand,
                         OutsideGuard,
                         schedule_drops,
                     );
@@ -2384,6 +2404,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     block,
                     binding.var_id,
                     binding.span,
+                    binding.is_shorthand,
                     OutsideGuard,
                     schedule_drops,
                 )
@@ -2437,6 +2458,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     opt_ty_info: None,
                     opt_match_place,
                     pat_span,
+                    introductions: Vec::new(),
                 },
             )))),
         };
@@ -2457,7 +2479,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 user_ty: None,
                 source_info,
                 local_info: ClearCrossCrate::Set(Box::new(LocalInfo::User(
-                    BindingForm::RefForGuard,
+                    BindingForm::RefForGuard(for_arm_body),
                 ))),
             });
             self.var_debug_info.push(VarDebugInfo {
diff --git a/compiler/rustc_mir_build/src/build/matches/util.rs b/compiler/rustc_mir_build/src/build/matches/util.rs
index 440be873d4ee7..7ce1ce78d69a2 100644
--- a/compiler/rustc_mir_build/src/build/matches/util.rs
+++ b/compiler/rustc_mir_build/src/build/matches/util.rs
@@ -154,12 +154,13 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
                 TestCase::Irrefutable { ascription, binding: None }
             }
 
-            PatKind::Binding { mode, var, ref subpattern, .. } => {
+            PatKind::Binding { mode, var, ref subpattern, is_shorthand, .. } => {
                 let binding = place.map(|source| super::Binding {
                     span: pattern.span,
                     source,
                     var_id: var,
                     binding_mode: mode,
+                    is_shorthand,
                 });
 
                 if let Some(subpattern) = subpattern.as_ref() {
diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs
index 6972bc00e0b2e..57e2f7a8889b3 100644
--- a/compiler/rustc_mir_build/src/build/mod.rs
+++ b/compiler/rustc_mir_build/src/build/mod.rs
@@ -60,12 +60,6 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx
                 thir::BodyTy::Const(ty) => construct_const(tcx, def, thir, expr, ty),
             };
 
-            // this must run before MIR dump, because
-            // "not all control paths return a value" is reported here.
-            //
-            // maybe move the check to a MIR pass?
-            tcx.ensure().check_liveness(def);
-
             // Don't steal here, instead steal in unsafeck. This is so that
             // pattern inline constants can be evaluated as part of building the
             // THIR of the parent function without a cycle.
@@ -947,6 +941,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                                 opt_ty_info: param.ty_span,
                                 opt_match_place: Some((None, span)),
                                 pat_span: span,
+                                introductions: vec![(span, false)],
                             }))
                         };
                     self.var_indices.insert(var, LocalsForNode::One(local));
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index 133cf8e334929..fa36360e2cad5 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -311,6 +311,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
                     ty: var_ty,
                     subpattern: self.lower_opt_pattern(sub),
                     is_primary: id == pat.hir_id,
+                    is_shorthand: false,
                 }
             }
 
@@ -328,9 +329,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
                 let res = self.typeck_results.qpath_res(qpath, pat.hir_id);
                 let subpatterns = fields
                     .iter()
-                    .map(|field| FieldPat {
-                        field: self.typeck_results.field_index(field.hir_id),
-                        pattern: self.lower_pattern(field.pat),
+                    .map(|field| {
+                        let mut pattern = self.lower_pattern(field.pat);
+                        if let PatKind::Binding { ref mut is_shorthand, .. } = pattern.kind {
+                            *is_shorthand = field.is_shorthand;
+                        }
+                        let field = self.typeck_results.field_index(field.hir_id);
+                        FieldPat { field, pattern }
                     })
                     .collect();
 
diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs
index ef15082a48138..2a0ed31ce117c 100644
--- a/compiler/rustc_mir_build/src/thir/print.rs
+++ b/compiler/rustc_mir_build/src/thir/print.rs
@@ -635,13 +635,14 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> {
                 self.print_pat(subpattern, depth_lvl + 3);
                 print_indented!(self, "}", depth_lvl + 1);
             }
-            PatKind::Binding { name, mode, var, ty, subpattern, is_primary } => {
+            PatKind::Binding { name, mode, var, ty, subpattern, is_primary, is_shorthand } => {
                 print_indented!(self, "Binding {", depth_lvl + 1);
                 print_indented!(self, format!("name: {:?}", name), depth_lvl + 2);
                 print_indented!(self, format!("mode: {:?}", mode), depth_lvl + 2);
                 print_indented!(self, format!("var: {:?}", var), depth_lvl + 2);
                 print_indented!(self, format!("ty: {:?}", ty), depth_lvl + 2);
                 print_indented!(self, format!("is_primary: {:?}", is_primary), depth_lvl + 2);
+                print_indented!(self, format!("is_shorthand: {:?}", is_shorthand), depth_lvl + 2);
 
                 if let Some(subpattern) = subpattern {
                     print_indented!(self, "subpattern: Some( ", depth_lvl + 2);
diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs
index c5adb81b614ce..d664f75c3680e 100644
--- a/compiler/rustc_mir_dataflow/src/lib.rs
+++ b/compiler/rustc_mir_dataflow/src/lib.rs
@@ -17,10 +17,11 @@ pub use self::drop_flag_effects::{
     drop_flag_effects_for_function_entry, drop_flag_effects_for_location,
     move_path_children_matching, on_all_children_bits, on_lookup_result_bits,
 };
+use self::framework::SwitchIntEdgeEffects;
 pub use self::framework::{
     fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, Backward, Direction, Engine,
     Forward, GenKill, GenKillAnalysis, JoinSemiLattice, MaybeReachable, Results, ResultsCursor,
-    ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects,
+    ResultsVisitable, ResultsVisitor,
 };
 use self::move_paths::MoveData;
 
diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl
index f9b79d72b0504..8677f0dc3cc64 100644
--- a/compiler/rustc_mir_transform/messages.ftl
+++ b/compiler/rustc_mir_transform/messages.ftl
@@ -17,13 +17,37 @@ mir_transform_ffi_unwind_call = call to {$foreign ->
 mir_transform_fn_item_ref = taking a reference to a function item does not give a function pointer
     .suggestion = cast `{$ident}` to obtain a function pointer
 
+mir_transform_maybe_string_interpolation = you might have meant to use string interpolation in this string literal
+
 mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspend point, but should not be
     .label = the value is held across this suspend point
     .note = {$reason}
     .help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point
 mir_transform_operation_will_panic = this operation will panic at runtime
 
+mir_transform_string_interpolation_only_works = string interpolation only works in `format!` invocations
+
 mir_transform_unaligned_packed_ref = reference to packed field is unaligned
     .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses
     .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
     .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
+
+mir_transform_unused_assign = value assigned to `{$name}` is never read
+    .help = maybe it is overwritten before being read?
+
+mir_transform_unused_assign_passed = value passed to `{$name}` is never read
+    .help = maybe it is overwritten before being read?
+
+mir_transform_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read
+    .help = did you mean to capture by reference instead?
+
+mir_transform_unused_var_assigned_only = variable `{$name}` is assigned to, but never used
+    .note = consider using `_{$name}` instead
+
+mir_transform_unused_var_underscore = if this is intentional, prefix it with an underscore
+
+mir_transform_unused_variable = unused variable: `{$name}`
+
+mir_transform_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable
+
+mir_transform_unused_variable_try_ignore = try ignoring the field
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 0634e321ea303..d458e13ecb4df 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -1,4 +1,7 @@
-use rustc_errors::{codes::*, Diag, DiagMessage, LintDiagnostic};
+use rustc_errors::{
+    codes::*, Applicability, Diag, DiagMessage, EmissionGuarantee, LintDiagnostic,
+    SubdiagMessageOp, Subdiagnostic,
+};
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_middle::mir::AssertKind;
 use rustc_middle::ty::TyCtxt;
@@ -91,6 +94,101 @@ pub(crate) struct FnItemRef {
     pub ident: String,
 }
 
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_unused_capture_maybe_capture_ref)]
+#[help]
+pub(crate) struct UnusedCaptureMaybeCaptureRef {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_unused_var_assigned_only)]
+#[note]
+pub(crate) struct UnusedVarAssignedOnly {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_unused_assign)]
+#[help]
+pub(crate) struct UnusedAssign {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_unused_assign_passed)]
+#[help]
+pub(crate) struct UnusedAssignPassed {
+    pub name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_unused_variable)]
+pub(crate) struct UnusedVariable {
+    pub name: String,
+    #[subdiagnostic]
+    pub string_interp: Vec<UnusedVariableStringInterp>,
+    #[subdiagnostic]
+    pub sugg: UnusedVariableSugg,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum UnusedVariableSugg {
+    #[multipart_suggestion(
+        mir_transform_unused_variable_try_ignore,
+        applicability = "machine-applicable"
+    )]
+    TryIgnore {
+        #[suggestion_part(code = "{name}: _")]
+        shorthands: Vec<Span>,
+        #[suggestion_part(code = "_")]
+        non_shorthands: Vec<Span>,
+        name: String,
+    },
+
+    #[multipart_suggestion(
+        mir_transform_unused_var_underscore,
+        applicability = "machine-applicable"
+    )]
+    TryPrefix {
+        #[suggestion_part(code = "_{name}")]
+        spans: Vec<Span>,
+        name: String,
+    },
+
+    #[help(mir_transform_unused_variable_args_in_macro)]
+    NoSugg {
+        #[primary_span]
+        span: Span,
+        name: String,
+    },
+}
+
+pub(crate) struct UnusedVariableStringInterp {
+    pub lit: Span,
+}
+
+impl Subdiagnostic for UnusedVariableStringInterp {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _: F,
+    ) {
+        diag.span_label(
+            self.lit,
+            crate::fluent_generated::mir_transform_maybe_string_interpolation,
+        );
+        diag.multipart_suggestion(
+            crate::fluent_generated::mir_transform_string_interpolation_only_works,
+            vec![
+                (self.lit.shrink_to_lo(), String::from("format!(")),
+                (self.lit.shrink_to_hi(), String::from(")")),
+            ],
+            Applicability::MachineApplicable,
+        );
+    }
+}
+
 pub(crate) struct MustNotSupend<'tcx, 'a> {
     pub tcx: TyCtxt<'tcx>,
     pub yield_sp: Span,
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index e477c068229ff..40514e3ed251c 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -84,6 +84,7 @@ mod jump_threading;
 mod known_panics_lint;
 mod large_enums;
 mod lint;
+mod liveness;
 mod lower_intrinsics;
 mod lower_slice_len;
 mod match_branches;
@@ -112,6 +113,7 @@ mod sroa;
 mod unreachable_enum_branching;
 mod unreachable_prop;
 
+use liveness::check_liveness;
 use rustc_const_eval::transform::check_consts::{self, ConstCx};
 use rustc_const_eval::transform::validate;
 use rustc_mir_dataflow::rustc_peek;
@@ -132,6 +134,7 @@ pub fn provide(providers: &mut Providers) {
         mir_for_ctfe,
         mir_coroutine_witnesses: coroutine::mir_coroutine_witnesses,
         optimized_mir,
+        check_liveness,
         is_mir_available,
         is_ctfe_mir_available: |tcx, did| is_mir_available(tcx, did),
         mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable,
@@ -400,6 +403,8 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
         }
     }
 
+    tcx.ensure_with_value().check_liveness(def);
+
     let (body, _) = tcx.mir_promoted(def);
     let mut body = body.steal();
     if let Some(error_reported) = mir_borrowck.tainted_by_errors {
diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs
new file mode 100644
index 0000000000000..cd502b57e9952
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/liveness.rs
@@ -0,0 +1,1101 @@
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::LocalDefId;
+use rustc_index::bit_set::BitSet;
+use rustc_index::IndexVec;
+use rustc_middle::mir::visit::{
+    MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor,
+};
+use rustc_middle::mir::*;
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_mir_dataflow::{
+    fmt::DebugWithContext, Analysis, AnalysisDomain, Backward, GenKill, GenKillAnalysis,
+    ResultsCursor,
+};
+use rustc_session::lint;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use rustc_target::abi::FieldIdx;
+
+use crate::errors;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum AccessKind {
+    Param,
+    Assign,
+    Capture,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum CaptureKind {
+    Closure(ty::ClosureKind),
+    Coroutine,
+    CoroutineClosure,
+    None,
+}
+
+struct AssignmentResult {
+    /// Set of locals that are live at least once. This is used to report fully unused locals.
+    ever_live: BitSet<PlaceIndex>,
+    /// Set of locals that have a non-trivial drop. This is used to skip reporting unused
+    /// assignment if it would be used by the `Drop` impl.
+    ever_dropped: BitSet<PlaceIndex>,
+    /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any
+    /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.
+    ///
+    /// For each local, we return a map: for each source position, whether the statement is live
+    /// and which kind of access it performs. When we encounter multiple statements at the same
+    /// location, we only increase the liveness, in order to avoid false positives.
+    assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, (bool, AccessKind)>>,
+}
+
+#[tracing::instrument(level = "debug", skip(tcx))]
+pub fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> BitSet<FieldIdx> {
+    // Don't run unused pass for #[naked]
+    if tcx.has_attr(def_id.to_def_id(), sym::naked) {
+        return BitSet::new_empty(0);
+    }
+
+    // Don't run unused pass for #[derive]
+    let parent = tcx.parent(tcx.typeck_root_def_id(def_id.to_def_id()));
+    if let DefKind::Impl { of_trait: true } = tcx.def_kind(parent)
+        && tcx.has_attr(parent, sym::automatically_derived)
+    {
+        return BitSet::new_empty(0);
+    }
+
+    let mut body = &*tcx.mir_promoted(def_id).0.borrow();
+    let mut body_mem;
+
+    // Don't run if there are errors.
+    if body.tainted_by_errors.is_some() {
+        return BitSet::new_empty(0);
+    }
+
+    let mut checked_places = PlaceSet::default();
+    checked_places.insert_locals(&body.local_decls);
+
+    // The body is the one of a closure or generator, so we also want to analyse captures.
+    let (capture_kind, num_captures) = if tcx.is_closure_like(def_id.to_def_id()) {
+        let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;
+        let mut self_is_ref = false;
+        if let ty::Ref(_, ty, _) = self_ty.kind() {
+            self_ty = *ty;
+            self_is_ref = true;
+        }
+
+        let (capture_kind, args) = match self_ty.kind() {
+            ty::Closure(_, args) => {
+                (CaptureKind::Closure(args.as_closure().kind()), ty::UpvarArgs::Closure(args))
+            }
+            &ty::Coroutine(_, args) => (CaptureKind::Coroutine, ty::UpvarArgs::Coroutine(args)),
+            &ty::CoroutineClosure(_, args) => {
+                (CaptureKind::CoroutineClosure, ty::UpvarArgs::CoroutineClosure(args))
+            }
+            _ => bug!("expected closure or generator, found {:?}", self_ty),
+        };
+
+        let captures = tcx.closure_captures(def_id);
+        checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys());
+
+        // `FnMut` closures can modify captured values and carry those
+        // modified values with them in subsequent calls. To model this behaviour,
+        // we consider the `FnMut` closure as jumping to `bb0` upon return.
+        if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind
+            && checked_places.captures.iter().any(|(_, by_ref)| !by_ref)
+        {
+            // FIXME: stop cloning the body.
+            body_mem = body.clone();
+            for bbdata in body_mem.basic_blocks_mut() {
+                if let TerminatorKind::Return = bbdata.terminator().kind {
+                    bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK };
+                }
+            }
+            body = &body_mem;
+        }
+
+        (capture_kind, args.upvar_tys().len())
+    } else {
+        (CaptureKind::None, 0)
+    };
+
+    // Get the remaining variables' names from debuginfo.
+    checked_places.record_debuginfo(&body.var_debug_info);
+
+    let self_assignment = find_self_assignments(&checked_places, body);
+
+    let mut live =
+        MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment }
+            .into_engine(tcx, body)
+            .iterate_to_fixpoint()
+            .into_results_cursor(body);
+
+    let AssignmentResult { mut ever_live, ever_dropped, mut assignments } =
+        find_dead_assignments(tcx, &checked_places, &mut live, body);
+
+    // Match guards introduce a different local to freeze the guarded value as immutable.
+    // Having two locals, we need to make sure that we do not report an unused_variable
+    // when the guard local is used but not the arm local, or vice versa, like in this example.
+    //
+    //    match 5 {
+    //      x if x > 2 => {}
+    //      ^    ^- This is `local`
+    //      +------ This is `arm_local`
+    //      _ => {}
+    //    }
+    //
+    for (index, place) in checked_places.iter() {
+        let local = place.local;
+        if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =
+            body.local_decls[local].local_info()
+        {
+            debug_assert!(place.projection.is_empty());
+
+            // Local to use in the arm.
+            let Some((arm_index, _proj)) = checked_places.get(arm_local.into()) else { continue };
+            debug_assert_ne!(index, arm_index);
+            debug_assert_eq!(_proj, &[]);
+
+            // Mark the arm local as used if the guard local is used.
+            if ever_live.contains(index) {
+                ever_live.insert(arm_index);
+            }
+
+            // Some assignments are common to both locals in the source code.
+            // Sadly, we can only detect this using the `source_info`.
+            // Therefore, we loop over all the assignments we have for the guard local:
+            // - if they already appeared for the arm local, the assignment is live if one of the
+            //   two versions is live;
+            // - if it does not appear for the arm local, it happened inside the guard, so we add
+            //   it as-is.
+            let guard_assignments = std::mem::take(&mut assignments[index]);
+            let arm_assignments = &mut assignments[arm_index];
+            for (source_info, (live, kind)) in guard_assignments {
+                match arm_assignments.entry(source_info) {
+                    IndexEntry::Vacant(v) => {
+                        v.insert((live, kind));
+                    }
+                    IndexEntry::Occupied(mut o) => {
+                        o.get_mut().0 |= live;
+                    }
+                }
+            }
+        }
+    }
+
+    // Report to caller the set of dead captures.
+    let mut dead_captures = BitSet::new_empty(num_captures);
+
+    // First, report fully unused locals.
+    debug!("report fully unused places");
+    for (index, place) in checked_places.iter() {
+        if ever_live.contains(index) {
+            continue;
+        }
+
+        // This is a capture: let the enclosing function report the unused variable.
+        if is_capture(*place) {
+            debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL);
+            for p in place.projection {
+                if let PlaceElem::Field(f, _) = p {
+                    dead_captures.insert(*f);
+                    break;
+                }
+            }
+            continue;
+        }
+
+        let Some((ref name, def_span)) = checked_places.names[index] else { continue };
+        if name.is_empty() || name.starts_with('_') || name == "self" {
+            continue;
+        }
+
+        let local = place.local;
+        let decl = &body.local_decls[local];
+
+        if decl.from_compiler_desugaring() {
+            continue;
+        }
+
+        // Only report actual user-defined binding from now on.
+        let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };
+        let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { continue };
+
+        let introductions = &binding.introductions;
+
+        // #117284, when `ident_span` and `def_span` have different contexts
+        // we can't provide a good suggestion, instead we pointed out the spans from macro
+        let from_macro = def_span.from_expansion()
+            && introductions.iter().any(|(ident_span, _)| ident_span.eq_ctxt(def_span));
+
+        let statements = &mut assignments[index];
+        if statements.is_empty() {
+            let sugg = if from_macro {
+                errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() }
+            } else {
+                errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name: name.clone() }
+            };
+            tcx.emit_node_span_lint(
+                lint::builtin::UNUSED_VARIABLES,
+                hir_id,
+                def_span,
+                errors::UnusedVariable {
+                    name: name.clone(),
+                    string_interp: maybe_suggest_literal_matching_name(body, name),
+                    sugg,
+                },
+            );
+            continue;
+        }
+
+        // Idiomatic rust assigns a value to a local upon definition. However, we do not want to
+        // warn twice, for the unused local and for the unused assignment. Therefore, we remove
+        // from the list of assignments the ones that happen at the definition site.
+        statements.retain(|source_info, _| {
+            source_info.span.find_ancestor_inside(binding.pat_span).is_none()
+        });
+
+        // Extra assignments that we recognize thanks to the initialization span. We need to
+        // take care of macro contexts here to be accurate.
+        if let Some((_, initializer_span)) = binding.opt_match_place {
+            statements.retain(|source_info, _| {
+                let within = source_info.span.find_ancestor_inside(initializer_span);
+                let outer_initializer_span =
+                    initializer_span.find_ancestor_in_same_ctxt(source_info.span);
+                within.is_none()
+                    && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))
+            });
+        }
+
+        if !statements.is_empty() {
+            // We have a dead local with outstanding assignments and with non-trivial drop.
+            // This is probably a drop-guard, so we do not issue a warning there.
+            if ever_dropped.contains(index) {
+                continue;
+            }
+
+            tcx.emit_node_span_lint(
+                lint::builtin::UNUSED_VARIABLES,
+                hir_id,
+                def_span,
+                errors::UnusedVarAssignedOnly { name: name.clone() },
+            );
+            continue;
+        }
+
+        // We do not have outstanding assignments, suggest renaming the binding.
+        let spans = introductions.iter().map(|(span, _)| *span).collect::<Vec<_>>();
+
+        let any_shorthand = introductions.iter().any(|(_, is_shorthand)| *is_shorthand);
+
+        let sugg = if any_shorthand {
+            errors::UnusedVariableSugg::TryIgnore {
+                name: name.clone(),
+                shorthands: introductions
+                    .iter()
+                    .filter_map(
+                        |&(span, is_shorthand)| {
+                            if is_shorthand { Some(span) } else { None }
+                        },
+                    )
+                    .collect(),
+                non_shorthands: introductions
+                    .iter()
+                    .filter_map(
+                        |&(span, is_shorthand)| {
+                            if !is_shorthand { Some(span) } else { None }
+                        },
+                    )
+                    .collect(),
+            }
+        } else if from_macro {
+            errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() }
+        } else if !introductions.is_empty() {
+            errors::UnusedVariableSugg::TryPrefix {
+                name: name.clone(),
+                spans: introductions.iter().map(|&(span, _)| span).collect(),
+            }
+        } else {
+            errors::UnusedVariableSugg::TryPrefix { name: name.clone(), spans: vec![def_span] }
+        };
+
+        tcx.emit_node_span_lint(
+            lint::builtin::UNUSED_VARIABLES,
+            hir_id,
+            spans,
+            errors::UnusedVariable {
+                name: name.clone(),
+                string_interp: maybe_suggest_literal_matching_name(body, name),
+                sugg,
+            },
+        );
+    }
+
+    // Second, report unused assignments that do not correspond to initialization.
+    // Initializations have been removed in the previous loop reporting unused variables.
+    debug!("report dead assignments");
+    for (index, statements) in assignments.into_iter_enumerated() {
+        if statements.is_empty() {
+            continue;
+        }
+
+        let Some((ref name, decl_span)) = checked_places.names[index] else { continue };
+        if name.is_empty() || name.starts_with('_') || name == "self" {
+            continue;
+        }
+
+        // We have outstanding assignments and with non-trivial drop.
+        // This is probably a drop-guard, so we do not issue a warning there.
+        if ever_dropped.contains(index) {
+            continue;
+        }
+
+        // We probed MIR in reverse order for dataflow.
+        // We revert the vector to give a consistent order to the user.
+        for (source_info, (live, kind)) in statements.into_iter().rev() {
+            if live {
+                continue;
+            }
+
+            // Report the dead assignment.
+            let Some(hir_id) = source_info.scope.lint_root(&body.source_scopes) else { continue };
+
+            match kind {
+                AccessKind::Assign => tcx.emit_node_span_lint(
+                    lint::builtin::UNUSED_ASSIGNMENTS,
+                    hir_id,
+                    source_info.span,
+                    errors::UnusedAssign { name: name.clone() },
+                ),
+                AccessKind::Param => tcx.emit_node_span_lint(
+                    lint::builtin::UNUSED_ASSIGNMENTS,
+                    hir_id,
+                    source_info.span,
+                    errors::UnusedAssignPassed { name: name.clone() },
+                ),
+                AccessKind::Capture => tcx.emit_node_span_lint(
+                    lint::builtin::UNUSED_ASSIGNMENTS,
+                    hir_id,
+                    decl_span,
+                    errors::UnusedCaptureMaybeCaptureRef { name: name.clone() },
+                ),
+            }
+        }
+    }
+
+    dead_captures
+}
+
+/// Small helper to make semantics easier to read.
+#[inline]
+fn is_capture(place: PlaceRef<'_>) -> bool {
+    if !place.projection.is_empty() {
+        debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL);
+        true
+    } else {
+        false
+    }
+}
+
+/// Give a diagnostic when any of the string constants look like a naked format string that would
+/// interpolate our dead local.
+fn maybe_suggest_literal_matching_name(
+    body: &Body<'_>,
+    name: &str,
+) -> Vec<errors::UnusedVariableStringInterp> {
+    struct LiteralFinder<'body, 'tcx> {
+        body: &'body Body<'tcx>,
+        name: String,
+        name_colon: String,
+        found: Vec<errors::UnusedVariableStringInterp>,
+    }
+
+    impl<'tcx> Visitor<'tcx> for LiteralFinder<'_, 'tcx> {
+        fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, loc: Location) {
+            if let ty::Ref(_, ref_ty, _) = constant.ty().kind()
+                && ref_ty.kind() == &ty::Str
+            {
+                let rendered_constant = constant.const_.to_string();
+                if rendered_constant.contains(&self.name)
+                    || rendered_constant.contains(&self.name_colon)
+                {
+                    let lit = self.body.source_info(loc).span;
+                    self.found.push(errors::UnusedVariableStringInterp { lit });
+                }
+            }
+        }
+    }
+
+    let mut finder = LiteralFinder {
+        body,
+        name: format!("{{{name}}}"),
+        name_colon: format!("{{{name}:"),
+        found: vec![],
+    };
+    finder.visit_body(body);
+    finder.found
+}
+
+/// Compute self-assignments of the form `a += b`.
+///
+/// MIR building generates 2 statements and 1 terminator for such assignments:
+/// - _temp = CheckedBinaryOp(a, b)
+/// - assert(!_temp.1)
+/// - a = _temp.0
+///
+/// This function tries to detect this pattern in order to avoid marking statement as a definition
+/// and use. This will let the analysis be dictated by the next use of `a`.
+///
+/// Note that we will still need to account for the use of `b`.
+fn find_self_assignments<'tcx>(
+    checked_places: &PlaceSet<'tcx>,
+    body: &Body<'tcx>,
+) -> FxHashSet<Location> {
+    let mut self_assign = FxHashSet::default();
+
+    const FIELD_0: FieldIdx = FieldIdx::from_u32(0);
+    const FIELD_1: FieldIdx = FieldIdx::from_u32(1);
+
+    for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
+        for (statement_index, stmt) in bb_data.statements.iter().enumerate() {
+            let StatementKind::Assign(box (first_place, rvalue)) = &stmt.kind else { continue };
+            match rvalue {
+                // Straight self-assignment.
+                Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => {
+                    if lhs != first_place {
+                        continue;
+                    }
+
+                    // We ignore indirect self-assignment, because both occurences of `dest` are uses.
+                    let is_indirect = checked_places
+                        .get(first_place.as_ref())
+                        .map_or(false, |(_, projections)| is_indirect(projections));
+                    if is_indirect {
+                        continue;
+                    }
+
+                    self_assign.insert(Location { block: bb, statement_index });
+
+                    // Checked division verifies overflow before performing the division, so we
+                    // need to go and ignore this check in the predecessor block.
+                    if let BinOp::Div | BinOp::Rem = op
+                        && statement_index == 0
+                        && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()
+                        && let TerminatorKind::Assert { msg, .. } =
+                            &body.basic_blocks[pred].terminator().kind
+                        && let AssertKind::Overflow(..) = **msg
+                        && let len = body.basic_blocks[pred].statements.len()
+                        && len >= 2
+                    {
+                        // BitAnd of two checks.
+                        self_assign.insert(Location { block: pred, statement_index: len - 1 });
+                        // `lhs == MIN`.
+                        self_assign.insert(Location { block: pred, statement_index: len - 2 });
+                    }
+                }
+                // For checked binary ops, the MIR builder inserts an assertion in between.
+                Rvalue::CheckedBinaryOp(_, box (Operand::Copy(lhs), _)) => {
+                    // Checked binary ops only appear at the end of the block, before the assertion.
+                    if statement_index + 1 != bb_data.statements.len() {
+                        continue;
+                    }
+
+                    let TerminatorKind::Assert {
+                        cond,
+                        target,
+                        msg: box AssertKind::Overflow(..),
+                        ..
+                    } = &bb_data.terminator().kind
+                    else {
+                        continue;
+                    };
+                    let Some(assign) = body.basic_blocks[*target].statements.first() else {
+                        continue;
+                    };
+                    let StatementKind::Assign(box (dest, Rvalue::Use(Operand::Move(temp)))) =
+                        assign.kind
+                    else {
+                        continue;
+                    };
+
+                    if dest != *lhs {
+                        continue;
+                    }
+
+                    let Operand::Move(cond) = cond else { continue };
+                    let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {
+                        continue;
+                    };
+                    let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {
+                        continue;
+                    };
+
+                    // We ignore indirect self-assignment, because both occurences of `dest` are uses.
+                    let is_indirect = checked_places
+                        .get(dest.as_ref())
+                        .map_or(false, |(_, projections)| is_indirect(projections));
+                    if is_indirect {
+                        continue;
+                    }
+
+                    if first_place.local == temp.local
+                        && first_place.local == cond.local
+                        && first_place.projection.is_empty()
+                    {
+                        // Original block
+                        self_assign.insert(Location {
+                            block: bb,
+                            statement_index: bb_data.statements.len() - 1,
+                        });
+                        self_assign.insert(Location {
+                            block: bb,
+                            statement_index: bb_data.statements.len(),
+                        });
+                        // Target block
+                        self_assign.insert(Location { block: *target, statement_index: 0 });
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+
+    self_assign
+}
+
+#[derive(Default, Debug)]
+struct PlaceSet<'tcx> {
+    places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,
+    names: IndexVec<PlaceIndex, Option<(String, Span)>>,
+
+    /// Places corresponding to locals, common case.
+    locals: IndexVec<Local, Option<PlaceIndex>>,
+
+    // Handling of captures.
+    /// If `_1` is a reference, we need to add a `Deref` to the matched place.
+    capture_field_pos: usize,
+    /// Captured fields.
+    captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,
+}
+
+impl<'tcx> PlaceSet<'tcx> {
+    fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {
+        self.locals = IndexVec::from_elem(None, &decls);
+        for (local, decl) in decls.iter_enumerated() {
+            // Record all user-written locals for the analysis.
+            // We also keep the `RefForGuard` locals (more on that below).
+            if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =
+                decl.local_info()
+            {
+                let index = self.places.push(local.into());
+                self.locals[local] = Some(index);
+                let _index = self.names.push(None);
+                debug_assert_eq!(index, _index);
+            }
+        }
+    }
+
+    fn insert_captures(
+        &mut self,
+        tcx: TyCtxt<'tcx>,
+        self_is_ref: bool,
+        captures: &[&'tcx ty::CapturedPlace<'tcx>],
+        upvars: &ty::List<Ty<'tcx>>,
+    ) {
+        // We should not track the environment local separately.
+        debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);
+
+        let self_place = Place {
+            local: ty::CAPTURE_STRUCT_LOCAL,
+            projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),
+        };
+        if self_is_ref {
+            self.capture_field_pos = 1;
+        }
+
+        for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {
+            let f = FieldIdx::from_usize(f);
+            let elem = PlaceElem::Field(f, ty);
+            let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));
+            let place = if by_ref {
+                self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)
+            } else {
+                self_place.project_deeper(&[elem], tcx)
+            };
+            let index = self.places.push(place.as_ref());
+            let _f = self.captures.push((index, by_ref));
+            debug_assert_eq!(_f, f);
+
+            // Record a variable name from the capture, because it is much friendlier than the
+            // debuginfo name.
+            self.names.insert(index, (capture.to_string(tcx), capture.get_path_span(tcx)));
+        }
+    }
+
+    fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {
+        for var_debug_info in var_debug_info {
+            if let VarDebugInfoContents::Place(place) = var_debug_info.value
+                && let Some(index) = self.locals[place.local]
+            {
+                self.names.get_or_insert_with(index, || {
+                    (var_debug_info.name.to_string(), var_debug_info.source_info.span)
+                });
+            }
+        }
+    }
+
+    fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {
+        if let Some(index) = self.locals[place.local] {
+            return Some((index, place.projection));
+        }
+        if place.local == ty::CAPTURE_STRUCT_LOCAL
+            && !self.captures.is_empty()
+            && self.capture_field_pos < place.projection.len()
+            && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]
+            && let Some((index, by_ref)) = self.captures.get(f)
+        {
+            let mut start = self.capture_field_pos + 1;
+            if *by_ref {
+                // Account for an extra Deref.
+                start += 1;
+            }
+            // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.
+            if start <= place.projection.len() {
+                let projection = &place.projection[start..];
+                return Some((*index, projection));
+            }
+        }
+        None
+    }
+
+    fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {
+        self.places.iter_enumerated()
+    }
+
+    fn len(&self) -> usize {
+        self.places.len()
+    }
+}
+
+/// Collect all assignments to checked locals.
+///
+/// Assignments are collected, even if they are live. Dead assignments are reported, and live
+/// assignments are used to make diagnostics correct for match guards.
+fn find_dead_assignments<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    checked_places: &PlaceSet<'tcx>,
+    cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,
+    body: &Body<'tcx>,
+) -> AssignmentResult {
+    let mut ever_live = BitSet::new_empty(checked_places.len());
+    let mut ever_dropped = BitSet::new_empty(checked_places.len());
+    let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(
+        Default::default(),
+        &checked_places.places,
+    );
+
+    let mut check_place =
+        |place: Place<'tcx>, kind, source_info: SourceInfo, live: &BitSet<PlaceIndex>| {
+            if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {
+                if !is_indirect(extra_projections) {
+                    match assignments[index].entry(source_info) {
+                        IndexEntry::Vacant(v) => {
+                            v.insert((live.contains(index), kind));
+                        }
+                        IndexEntry::Occupied(mut o) => {
+                            // There were already a sighting. Mark this statement as live if it was,
+                            // to avoid false positives.
+                            o.get_mut().0 |= live.contains(index);
+                        }
+                    }
+                }
+            }
+        };
+
+    let param_env = tcx.param_env(body.source.def_id());
+    let mut record_drop = |place: Place<'tcx>| {
+        if let Some((index, &[])) = checked_places.get(place.as_ref()) {
+            let ty = place.ty(&body.local_decls, tcx).ty;
+            let needs_drop = matches!(
+                ty.kind(),
+                ty::Closure(..)
+                    | ty::Coroutine(..)
+                    | ty::Tuple(..)
+                    | ty::Adt(..)
+                    | ty::Dynamic(..)
+                    | ty::Array(..)
+                    | ty::Slice(..)
+                    | ty::Alias(ty::Opaque, ..)
+            ) && ty.needs_drop(tcx, param_env);
+            if needs_drop {
+                ever_dropped.insert(index);
+            }
+        }
+    };
+
+    for (bb, bb_data) in traversal::postorder(body) {
+        cursor.seek_to_block_end(bb);
+        let live = cursor.get();
+        ever_live.union(live);
+
+        let terminator = bb_data.terminator();
+        match &terminator.kind {
+            TerminatorKind::Call { destination: place, .. }
+            | TerminatorKind::Yield { resume_arg: place, .. } => {
+                check_place(*place, AccessKind::Assign, terminator.source_info, live);
+                record_drop(*place)
+            }
+            TerminatorKind::Drop { place, .. } => record_drop(*place),
+            TerminatorKind::InlineAsm { operands, .. } => {
+                for operand in operands {
+                    if let InlineAsmOperand::Out { place: Some(place), .. }
+                    | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand
+                    {
+                        check_place(*place, AccessKind::Assign, terminator.source_info, live);
+                    }
+                }
+            }
+            _ => {}
+        }
+
+        for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
+            cursor.seek_before_primary_effect(Location { block: bb, statement_index });
+            let live = cursor.get();
+            ever_live.union(live);
+            match &statement.kind {
+                StatementKind::Assign(box (place, _))
+                | StatementKind::Deinit(box place)
+                | StatementKind::SetDiscriminant { box place, .. } => {
+                    check_place(*place, AccessKind::Assign, statement.source_info, live);
+                }
+                StatementKind::Retag(_, _)
+                | StatementKind::StorageLive(_)
+                | StatementKind::StorageDead(_)
+                | StatementKind::Coverage(_)
+                | StatementKind::Intrinsic(_)
+                | StatementKind::Nop
+                | StatementKind::FakeRead(_)
+                | StatementKind::PlaceMention(_)
+                | StatementKind::ConstEvalCounter
+                | StatementKind::AscribeUserType(_, _) => (),
+            }
+        }
+    }
+
+    // Check liveness of function arguments on entry.
+    {
+        cursor.seek_to_block_start(START_BLOCK);
+        let live = cursor.get();
+        ever_live.union(live);
+
+        // Verify that arguments and captured values are useful.
+        for (index, place) in checked_places.iter() {
+            let kind = if is_capture(*place) {
+                // This is a by-ref capture, an assignment to it will modify surrounding
+                // environment, so we do not report it.
+                if place.projection.last() == Some(&PlaceElem::Deref) {
+                    continue;
+                }
+
+                AccessKind::Capture
+            } else if body.local_kind(place.local) == LocalKind::Arg {
+                AccessKind::Param
+            } else {
+                continue;
+            };
+            let source_info = body.local_decls[place.local].source_info;
+            assignments[index].insert(source_info, (live.contains(index), kind));
+        }
+    }
+
+    AssignmentResult { ever_live, ever_dropped, assignments }
+}
+
+rustc_index::newtype_index! {
+    pub struct PlaceIndex {}
+}
+
+impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {
+    fn fmt_with(
+        &self,
+        ctxt: &MaybeLivePlaces<'_, '_>,
+        f: &mut std::fmt::Formatter<'_>,
+    ) -> std::fmt::Result {
+        std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)
+    }
+}
+
+pub struct MaybeLivePlaces<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    checked_places: &'a PlaceSet<'tcx>,
+    capture_kind: CaptureKind,
+    self_assignment: FxHashSet<Location>,
+}
+
+impl<'tcx> MaybeLivePlaces<'_, 'tcx> {
+    fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, 'tcx, T> {
+        TransferFunction {
+            tcx: self.tcx,
+            checked_places: &self.checked_places,
+            capture_kind: self.capture_kind,
+            trans,
+            self_assignment: &self.self_assignment,
+        }
+    }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeLivePlaces<'_, 'tcx> {
+    type Domain = BitSet<PlaceIndex>;
+    type Direction = Backward;
+
+    const NAME: &'static str = "liveness-lint";
+
+    fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {
+        // bottom = not live
+        BitSet::new_empty(self.checked_places.len())
+    }
+
+    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
+        // No variables are live until we observe a use
+    }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {
+    type Idx = PlaceIndex;
+
+    fn domain_size(&self, _: &Body<'tcx>) -> usize {
+        self.checked_places.len()
+    }
+
+    fn statement_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        statement: &Statement<'tcx>,
+        location: Location,
+    ) {
+        self.transfer_function(trans).visit_statement(statement, location);
+    }
+
+    fn terminator_effect<'mir>(
+        &mut self,
+        trans: &mut Self::Domain,
+        terminator: &'mir Terminator<'tcx>,
+        location: Location,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        self.transfer_function(trans).visit_terminator(terminator, location);
+        terminator.edges()
+    }
+
+    fn call_return_effect(
+        &mut self,
+        _trans: &mut Self::Domain,
+        _block: BasicBlock,
+        _return_places: CallReturnPlaces<'_, 'tcx>,
+    ) {
+        // FIXME: what should happen here?
+    }
+}
+
+struct TransferFunction<'a, 'tcx, T> {
+    tcx: TyCtxt<'tcx>,
+    checked_places: &'a PlaceSet<'tcx>,
+    trans: &'a mut T,
+    capture_kind: CaptureKind,
+    self_assignment: &'a FxHashSet<Location>,
+}
+
+impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, 'tcx, T>
+where
+    T: GenKill<PlaceIndex>,
+{
+    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+        match statement.kind {
+            // `ForLet` fake read erroneously marks the just-assigned local as live.
+            // This defeats the purpose of the analysis for `let` bindings.
+            StatementKind::FakeRead(box (FakeReadCause::ForLet(..), _)) => return,
+            // Handle self-assignment by restricting the read/write they do.
+            StatementKind::Assign(box (ref dest, ref rvalue))
+                if self.self_assignment.contains(&location) =>
+            {
+                if let Rvalue::CheckedBinaryOp(_, box (_, ref rhs)) = rvalue {
+                    // We are computing the binary operation:
+                    // - the LHS will be assigned, so we don't read it;
+                    // - the RHS still needs to be read.
+                    self.visit_operand(rhs, location);
+                    self.visit_place(
+                        dest,
+                        PlaceContext::MutatingUse(MutatingUseContext::Store),
+                        location,
+                    );
+                } else if let Rvalue::BinaryOp(_, box (_, ref rhs)) = rvalue {
+                    // We are computing the binary operation:
+                    // - the LHS is being updated, so we don't read it;
+                    // - the RHS still needs to be read.
+                    self.visit_operand(rhs, location);
+                } else {
+                    // This is the second part of a checked self-assignment,
+                    // we are assigning the result.
+                    // We do not consider the write to the destination as a `def`.
+                    // `self_assignment` must be false if the assignment is indirect.
+                    self.visit_rvalue(rvalue, location);
+                }
+            }
+            _ => self.super_statement(statement, location),
+        }
+    }
+
+    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+        // By-ref captures could be read by the surrounding environement, so we mark
+        // them as live upon yield and return.
+        match terminator.kind {
+            TerminatorKind::Return
+            | TerminatorKind::Yield { .. }
+            | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.
+                if self.capture_kind != CaptureKind::None =>
+            {
+                // All indirect captures have an effect on the environment, so we mark them as live.
+                for (index, place) in self.checked_places.iter() {
+                    if place.local == ty::CAPTURE_STRUCT_LOCAL
+                        && place.projection.last() == Some(&PlaceElem::Deref)
+                    {
+                        self.trans.gen(index);
+                    }
+                }
+            }
+            // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.
+            TerminatorKind::Drop { .. } => {}
+            // Ignore assertions since they must be triggered by actual code.
+            TerminatorKind::Assert { .. } => {}
+            _ => self.super_terminator(terminator, location),
+        }
+    }
+
+    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
+        match rvalue {
+            // When a closure/generator does not use some of its captures, do not consider these
+            // captures as live in the surrounding function. This allows to report unused variables,
+            // even if they have been (uselessly) captured.
+            Rvalue::Aggregate(
+                box AggregateKind::Closure(def_id, _) | box AggregateKind::Coroutine(def_id, _),
+                ref operands,
+            ) => {
+                if let Some(def_id) = def_id.as_local() {
+                    let dead_captures = self.tcx.check_liveness(def_id);
+                    for (field, operand) in
+                        operands.iter_enumerated().take(dead_captures.domain_size())
+                    {
+                        if !dead_captures.contains(field) {
+                            self.visit_operand(operand, location);
+                        }
+                    }
+                }
+            }
+            _ => self.super_rvalue(rvalue, location),
+        }
+    }
+
+    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
+        if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {
+            for i in (extra_projections.len()..=place.projection.len()).rev() {
+                let place_part =
+                    PlaceRef { local: place.local, projection: &place.projection[..i] };
+                let extra_projections = &place.projection[i..];
+
+                if let Some(&elem) = extra_projections.get(0) {
+                    self.visit_projection_elem(place_part, elem, context, location);
+                }
+            }
+
+            match DefUse::for_place(extra_projections, context) {
+                Some(DefUse::Def) => self.trans.kill(index),
+                Some(DefUse::Use) => self.trans.gen(index),
+                None => {}
+            }
+        } else {
+            self.super_place(place, context, location)
+        }
+    }
+
+    fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
+        if let Some((index, _proj)) = self.checked_places.get(local.into()) {
+            debug_assert_eq!(_proj, &[]);
+            match DefUse::for_place(&[], context) {
+                Some(DefUse::Def) => self.trans.kill(index),
+                Some(DefUse::Use) => self.trans.gen(index),
+                _ => {}
+            }
+        }
+    }
+}
+
+#[derive(Eq, PartialEq, Debug, Clone)]
+enum DefUse {
+    Def,
+    Use,
+}
+
+fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {
+    proj.iter().any(|p| p.is_indirect())
+}
+
+impl DefUse {
+    fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {
+        let is_indirect = is_indirect(projection);
+        match context {
+            PlaceContext::MutatingUse(
+                MutatingUseContext::Store
+                | MutatingUseContext::Deinit
+                | MutatingUseContext::SetDiscriminant,
+            ) => {
+                if is_indirect {
+                    // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
+                    // use.
+                    Some(DefUse::Use)
+                } else if projection.is_empty() {
+                    Some(DefUse::Def)
+                } else {
+                    None
+                }
+            }
+
+            // For the associated terminators, this is only a `Def` when the terminator returns
+            // "successfully." As such, we handle this case separately in `call_return_effect`
+            // above. However, if the place looks like `*_5`, this is still unconditionally a use of
+            // `_5`.
+            PlaceContext::MutatingUse(
+                MutatingUseContext::Call
+                | MutatingUseContext::Yield
+                | MutatingUseContext::AsmOutput,
+            ) => is_indirect.then_some(DefUse::Use),
+
+            // All other contexts are uses...
+            PlaceContext::MutatingUse(
+                MutatingUseContext::AddressOf
+                | MutatingUseContext::Borrow
+                | MutatingUseContext::Drop
+                | MutatingUseContext::Retag,
+            )
+            | PlaceContext::NonMutatingUse(
+                NonMutatingUseContext::AddressOf
+                | NonMutatingUseContext::Copy
+                | NonMutatingUseContext::Inspect
+                | NonMutatingUseContext::Move
+                | NonMutatingUseContext::FakeBorrow
+                | NonMutatingUseContext::SharedBorrow
+                | NonMutatingUseContext::PlaceMention,
+            ) => Some(DefUse::Use),
+
+            PlaceContext::NonUse(
+                NonUseContext::StorageLive
+                | NonUseContext::StorageDead
+                | NonUseContext::AscribeUserTy(_)
+                | NonUseContext::VarDebugInfo,
+            ) => None,
+
+            PlaceContext::MutatingUse(MutatingUseContext::Projection)
+            | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
+                unreachable!("A projection could be a def or a use and must be handled separately")
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 7fc523ffe0dea..34c1915355000 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -445,7 +445,6 @@ passes_macro_export_on_decl_macro =
 passes_macro_use =
     `#[{$name}]` only has an effect on `extern crate` and modules
 
-passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal
 passes_missing_const_err =
     attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
     .help = make the function or method const
@@ -669,8 +668,6 @@ passes_skipping_const_checks = skipping const checks
 passes_stability_promotable =
     attribute cannot be applied to an expression
 
-passes_string_interpolation_only_works = string interpolation only works in `format!` invocations
-
 passes_target_feature_on_statement =
     {passes_should_be_applied_to_fn}
     .warn = {-passes_previously_accepted}
@@ -729,15 +726,6 @@ passes_unused =
     unused attribute
     .suggestion = remove this attribute
 
-passes_unused_assign = value assigned to `{$name}` is never read
-    .help = maybe it is overwritten before being read?
-
-passes_unused_assign_passed = value passed to `{$name}` is never read
-    .help = maybe it is overwritten before being read?
-
-passes_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read
-    .help = did you mean to capture by reference instead?
-
 passes_unused_default_method_body_const_note =
     `default_method_body_is_const` has been replaced with `#[const_trait]` on traits
 
@@ -758,25 +746,6 @@ passes_unused_multiple =
 passes_unused_no_lints_note =
     attribute `{$name}` without any lints has no effect
 
-passes_unused_var_assigned_only = variable `{$name}` is assigned to, but never used
-    .note = consider using `_{$name}` instead
-
-passes_unused_var_maybe_capture_ref = unused variable: `{$name}`
-    .help = did you mean to capture by reference instead?
-
-passes_unused_var_remove_field = unused variable: `{$name}`
-passes_unused_var_remove_field_suggestion = try removing the field
-
-passes_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable
-
-passes_unused_variable_try_ignore = unused variable: `{$name}`
-    .suggestion = try ignoring the field
-
-passes_unused_variable_try_prefix = unused variable: `{$name}`
-    .label = unused variable
-    .suggestion = if this is intentional, prefix it with an underscore
-
-
 passes_used_compiler_linker =
     `used(compiler)` and `used(linker)` can't be used together
 
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 3f26ea4507d41..a3ca8ae629b10 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -7,7 +7,7 @@ use crate::fluent_generated as fluent;
 use rustc_ast::Label;
 use rustc_errors::{
     codes::*, Applicability, Diag, DiagCtxt, DiagSymbolList, Diagnostic, EmissionGuarantee, Level,
-    MultiSpan, SubdiagMessageOp, Subdiagnostic,
+    MultiSpan,
 };
 use rustc_hir::{self as hir, ExprKind, Target};
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
@@ -1640,45 +1640,6 @@ pub struct UnreachableDueToUninhabited<'desc, 'tcx> {
     pub ty: Ty<'tcx>,
 }
 
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_var_maybe_capture_ref)]
-#[help]
-pub struct UnusedVarMaybeCaptureRef {
-    pub name: String,
-}
-
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_capture_maybe_capture_ref)]
-#[help]
-pub struct UnusedCaptureMaybeCaptureRef {
-    pub name: String,
-}
-
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_var_remove_field)]
-pub struct UnusedVarRemoveField {
-    pub name: String,
-    #[subdiagnostic]
-    pub sugg: UnusedVarRemoveFieldSugg,
-}
-
-#[derive(Subdiagnostic)]
-#[multipart_suggestion(
-    passes_unused_var_remove_field_suggestion,
-    applicability = "machine-applicable"
-)]
-pub struct UnusedVarRemoveFieldSugg {
-    #[suggestion_part(code = "")]
-    pub spans: Vec<Span>,
-}
-
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_var_assigned_only)]
-#[note]
-pub struct UnusedVarAssignedOnly {
-    pub name: String,
-}
-
 #[derive(LintDiagnostic)]
 #[diag(passes_unnecessary_stable_feature)]
 pub struct UnnecessaryStableFeature {
@@ -1703,86 +1664,6 @@ pub struct UnnecessaryPartialStableFeature {
 #[note]
 pub struct IneffectiveUnstableImpl;
 
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_assign)]
-#[help]
-pub struct UnusedAssign {
-    pub name: String,
-}
-
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_assign_passed)]
-#[help]
-pub struct UnusedAssignPassed {
-    pub name: String,
-}
-
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_variable_try_prefix)]
-pub struct UnusedVariableTryPrefix {
-    #[label]
-    pub label: Option<Span>,
-    #[subdiagnostic]
-    pub string_interp: Vec<UnusedVariableStringInterp>,
-    #[subdiagnostic]
-    pub sugg: UnusedVariableSugg,
-    pub name: String,
-}
-
-#[derive(Subdiagnostic)]
-pub enum UnusedVariableSugg {
-    #[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")]
-    TryPrefixSugg {
-        #[suggestion_part(code = "_{name}")]
-        spans: Vec<Span>,
-        name: String,
-    },
-    #[help(passes_unused_variable_args_in_macro)]
-    NoSugg {
-        #[primary_span]
-        span: Span,
-        name: String,
-    },
-}
-
-pub struct UnusedVariableStringInterp {
-    pub lit: Span,
-    pub lo: Span,
-    pub hi: Span,
-}
-
-impl Subdiagnostic for UnusedVariableStringInterp {
-    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
-        self,
-        diag: &mut Diag<'_, G>,
-        _f: F,
-    ) {
-        diag.span_label(self.lit, crate::fluent_generated::passes_maybe_string_interpolation);
-        diag.multipart_suggestion(
-            crate::fluent_generated::passes_string_interpolation_only_works,
-            vec![(self.lo, String::from("format!(")), (self.hi, String::from(")"))],
-            Applicability::MachineApplicable,
-        );
-    }
-}
-
-#[derive(LintDiagnostic)]
-#[diag(passes_unused_variable_try_ignore)]
-pub struct UnusedVarTryIgnore {
-    #[subdiagnostic]
-    pub sugg: UnusedVarTryIgnoreSugg,
-}
-
-#[derive(Subdiagnostic)]
-#[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")]
-pub struct UnusedVarTryIgnoreSugg {
-    #[suggestion_part(code = "{name}: _")]
-    pub shorthands: Vec<Span>,
-    #[suggestion_part(code = "_")]
-    pub non_shorthands: Vec<Span>,
-    pub name: String,
-}
-
 #[derive(LintDiagnostic)]
 #[diag(passes_attr_crate_level)]
 #[note]
diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs
index e03052bcfede8..3d7399d38e403 100644
--- a/compiler/rustc_passes/src/lib.rs
+++ b/compiler/rustc_passes/src/lib.rs
@@ -34,7 +34,6 @@ pub mod hir_stats;
 mod lang_items;
 pub mod layout_test;
 mod lib_features;
-mod liveness;
 pub mod loops;
 mod naked_functions;
 mod reachable;
@@ -55,7 +54,6 @@ pub fn provide(providers: &mut Providers) {
     lib_features::provide(providers);
     loops::provide(providers);
     naked_functions::provide(providers);
-    liveness::provide(providers);
     reachable::provide(providers);
     stability::provide(providers);
     upvars::provide(providers);
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
deleted file mode 100644
index 125084f47505e..0000000000000
--- a/compiler/rustc_passes/src/liveness.rs
+++ /dev/null
@@ -1,1747 +0,0 @@
-//! A classic liveness analysis based on dataflow over the AST. Computes,
-//! for each local variable in a function, whether that variable is live
-//! at a given point. Program execution points are identified by their
-//! IDs.
-//!
-//! # Basic idea
-//!
-//! The basic model is that each local variable is assigned an index. We
-//! represent sets of local variables using a vector indexed by this
-//! index. The value in the vector is either 0, indicating the variable
-//! is dead, or the ID of an expression that uses the variable.
-//!
-//! We conceptually walk over the AST in reverse execution order. If we
-//! find a use of a variable, we add it to the set of live variables. If
-//! we find an assignment to a variable, we remove it from the set of live
-//! variables. When we have to merge two flows, we take the union of
-//! those two flows -- if the variable is live on both paths, we simply
-//! pick one ID. In the event of loops, we continue doing this until a
-//! fixed point is reached.
-//!
-//! ## Checking initialization
-//!
-//! At the function entry point, all variables must be dead. If this is
-//! not the case, we can report an error using the ID found in the set of
-//! live variables, which identifies a use of the variable which is not
-//! dominated by an assignment.
-//!
-//! ## Checking moves
-//!
-//! After each explicit move, the variable must be dead.
-//!
-//! ## Computing last uses
-//!
-//! Any use of the variable where the variable is dead afterwards is a
-//! last use.
-//!
-//! # Implementation details
-//!
-//! The actual implementation contains two (nested) walks over the AST.
-//! The outer walk has the job of building up the ir_maps instance for the
-//! enclosing function. On the way down the tree, it identifies those AST
-//! nodes and variable IDs that will be needed for the liveness analysis
-//! and assigns them contiguous IDs. The liveness ID for an AST node is
-//! called a `live_node` (it's a newtype'd `u32`) and the ID for a variable
-//! is called a `variable` (another newtype'd `u32`).
-//!
-//! On the way back up the tree, as we are about to exit from a function
-//! declaration we allocate a `liveness` instance. Now that we know
-//! precisely how many nodes and variables we need, we can allocate all
-//! the various arrays that we will need to precisely the right size. We then
-//! perform the actual propagation on the `liveness` instance.
-//!
-//! This propagation is encoded in the various `propagate_through_*()`
-//! methods. It effectively does a reverse walk of the AST; whenever we
-//! reach a loop node, we iterate until a fixed point is reached.
-//!
-//! ## The `RWU` struct
-//!
-//! At each live node `N`, we track three pieces of information for each
-//! variable `V` (these are encapsulated in the `RWU` struct):
-//!
-//! - `reader`: the `LiveNode` ID of some node which will read the value
-//!    that `V` holds on entry to `N`. Formally: a node `M` such
-//!    that there exists a path `P` from `N` to `M` where `P` does not
-//!    write `V`. If the `reader` is `None`, then the current
-//!    value will never be read (the variable is dead, essentially).
-//!
-//! - `writer`: the `LiveNode` ID of some node which will write the
-//!    variable `V` and which is reachable from `N`. Formally: a node `M`
-//!    such that there exists a path `P` from `N` to `M` and `M` writes
-//!    `V`. If the `writer` is `None`, then there is no writer
-//!    of `V` that follows `N`.
-//!
-//! - `used`: a boolean value indicating whether `V` is *used*. We
-//!   distinguish a *read* from a *use* in that a *use* is some read that
-//!   is not just used to generate a new value. For example, `x += 1` is
-//!   a read but not a use. This is used to generate better warnings.
-//!
-//! ## Special nodes and variables
-//!
-//! We generate various special nodes for various, well, special purposes.
-//! These are described in the `Liveness` struct.
-
-use crate::errors;
-
-use self::LiveNodeKind::*;
-use self::VarKind::*;
-
-use rustc_data_structures::fx::FxIndexMap;
-use rustc_hir as hir;
-use rustc_hir::def::*;
-use rustc_hir::def_id::LocalDefId;
-use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet};
-use rustc_index::IndexVec;
-use rustc_middle::query::Providers;
-use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt};
-use rustc_session::lint;
-use rustc_span::symbol::{kw, sym, Symbol};
-use rustc_span::{BytePos, Span};
-
-use std::io;
-use std::io::prelude::*;
-use std::rc::Rc;
-
-mod rwu_table;
-
-rustc_index::newtype_index! {
-    #[debug_format = "v({})"]
-    pub struct Variable {}
-}
-
-rustc_index::newtype_index! {
-    #[debug_format = "ln({})"]
-    pub struct LiveNode {}
-}
-
-#[derive(Copy, Clone, PartialEq, Debug)]
-enum LiveNodeKind {
-    UpvarNode(Span),
-    ExprNode(Span, HirId),
-    VarDefNode(Span, HirId),
-    ClosureNode,
-    ExitNode,
-    ErrNode,
-}
-
-fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
-    let sm = tcx.sess.source_map();
-    match lnk {
-        UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)),
-        ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
-        VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
-        ClosureNode => "Closure node".to_owned(),
-        ExitNode => "Exit node".to_owned(),
-        ErrNode => "Error node".to_owned(),
-    }
-}
-
-fn check_liveness(tcx: TyCtxt<'_>, def_id: LocalDefId) {
-    // Don't run unused pass for #[derive()]
-    let parent = tcx.local_parent(def_id);
-    if let DefKind::Impl { .. } = tcx.def_kind(parent)
-        && tcx.has_attr(parent, sym::automatically_derived)
-    {
-        return;
-    }
-
-    // Don't run unused pass for #[naked]
-    if tcx.has_attr(def_id.to_def_id(), sym::naked) {
-        return;
-    }
-
-    let mut maps = IrMaps::new(tcx);
-    let body_id = tcx.hir().body_owned_by(def_id);
-    let hir_id = tcx.hir().body_owner(body_id);
-    let body = tcx.hir().body(body_id);
-
-    if let Some(upvars) = tcx.upvars_mentioned(def_id) {
-        for &var_hir_id in upvars.keys() {
-            let var_name = tcx.hir().name(var_hir_id);
-            maps.add_variable(Upvar(var_hir_id, var_name));
-        }
-    }
-
-    // gather up the various local variables, significant expressions,
-    // and so forth:
-    maps.visit_body(body);
-
-    // compute liveness
-    let mut lsets = Liveness::new(&mut maps, def_id);
-    let entry_ln = lsets.compute(body, hir_id);
-    lsets.log_liveness(entry_ln, body_id.hir_id);
-
-    // check for various error conditions
-    lsets.visit_body(body);
-    lsets.warn_about_unused_upvars(entry_ln);
-    lsets.warn_about_unused_args(body, entry_ln);
-}
-
-pub fn provide(providers: &mut Providers) {
-    *providers = Providers { check_liveness, ..*providers };
-}
-
-// ______________________________________________________________________
-// Creating ir_maps
-//
-// This is the first pass and the one that drives the main
-// computation. It walks up and down the IR once. On the way down,
-// we count for each function the number of variables as well as
-// liveness nodes. A liveness node is basically an expression or
-// capture clause that does something of interest: either it has
-// interesting control flow or it uses/defines a local variable.
-//
-// On the way back up, at each function node we create liveness sets
-// (we now know precisely how big to make our various vectors and so
-// forth) and then do the data-flow propagation to compute the set
-// of live variables at each program point.
-//
-// Finally, we run back over the IR one last time and, using the
-// computed liveness, check various safety conditions. For example,
-// there must be no live nodes at the definition site for a variable
-// unless it has an initializer. Similarly, each non-mutable local
-// variable must not be assigned if there is some successor
-// assignment. And so forth.
-
-struct CaptureInfo {
-    ln: LiveNode,
-    var_hid: HirId,
-}
-
-#[derive(Copy, Clone, Debug)]
-struct LocalInfo {
-    id: HirId,
-    name: Symbol,
-    is_shorthand: bool,
-}
-
-#[derive(Copy, Clone, Debug)]
-enum VarKind {
-    Param(HirId, Symbol),
-    Local(LocalInfo),
-    Upvar(HirId, Symbol),
-}
-
-struct CollectLitsVisitor<'tcx> {
-    lit_exprs: Vec<&'tcx hir::Expr<'tcx>>,
-}
-
-impl<'tcx> Visitor<'tcx> for CollectLitsVisitor<'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
-        if let hir::ExprKind::Lit(_) = expr.kind {
-            self.lit_exprs.push(expr);
-        }
-        intravisit::walk_expr(self, expr);
-    }
-}
-
-struct IrMaps<'tcx> {
-    tcx: TyCtxt<'tcx>,
-    live_node_map: HirIdMap<LiveNode>,
-    variable_map: HirIdMap<Variable>,
-    capture_info_map: HirIdMap<Rc<Vec<CaptureInfo>>>,
-    var_kinds: IndexVec<Variable, VarKind>,
-    lnks: IndexVec<LiveNode, LiveNodeKind>,
-}
-
-impl<'tcx> IrMaps<'tcx> {
-    fn new(tcx: TyCtxt<'tcx>) -> IrMaps<'tcx> {
-        IrMaps {
-            tcx,
-            live_node_map: HirIdMap::default(),
-            variable_map: HirIdMap::default(),
-            capture_info_map: Default::default(),
-            var_kinds: IndexVec::new(),
-            lnks: IndexVec::new(),
-        }
-    }
-
-    fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode {
-        let ln = self.lnks.push(lnk);
-
-        debug!("{:?} is of kind {}", ln, live_node_kind_to_string(lnk, self.tcx));
-
-        ln
-    }
-
-    fn add_live_node_for_node(&mut self, hir_id: HirId, lnk: LiveNodeKind) {
-        let ln = self.add_live_node(lnk);
-        self.live_node_map.insert(hir_id, ln);
-
-        debug!("{:?} is node {:?}", ln, hir_id);
-    }
-
-    fn add_variable(&mut self, vk: VarKind) -> Variable {
-        let v = self.var_kinds.push(vk);
-
-        match vk {
-            Local(LocalInfo { id: node_id, .. }) | Param(node_id, _) | Upvar(node_id, _) => {
-                self.variable_map.insert(node_id, v);
-            }
-        }
-
-        debug!("{:?} is {:?}", v, vk);
-
-        v
-    }
-
-    fn variable(&self, hir_id: HirId, span: Span) -> Variable {
-        match self.variable_map.get(&hir_id) {
-            Some(&var) => var,
-            None => {
-                span_bug!(span, "no variable registered for id {:?}", hir_id);
-            }
-        }
-    }
-
-    fn variable_name(&self, var: Variable) -> Symbol {
-        match self.var_kinds[var] {
-            Local(LocalInfo { name, .. }) | Param(_, name) | Upvar(_, name) => name,
-        }
-    }
-
-    fn variable_is_shorthand(&self, var: Variable) -> bool {
-        match self.var_kinds[var] {
-            Local(LocalInfo { is_shorthand, .. }) => is_shorthand,
-            Param(..) | Upvar(..) => false,
-        }
-    }
-
-    fn set_captures(&mut self, hir_id: HirId, cs: Vec<CaptureInfo>) {
-        self.capture_info_map.insert(hir_id, Rc::new(cs));
-    }
-
-    fn collect_shorthand_field_ids(&self, pat: &hir::Pat<'tcx>) -> HirIdSet {
-        // For struct patterns, take note of which fields used shorthand
-        // (`x` rather than `x: x`).
-        let mut shorthand_field_ids = HirIdSet::default();
-
-        pat.walk_always(|pat| {
-            if let hir::PatKind::Struct(_, fields, _) = pat.kind {
-                let short = fields.iter().filter(|f| f.is_shorthand);
-                shorthand_field_ids.extend(short.map(|f| f.pat.hir_id));
-            }
-        });
-
-        shorthand_field_ids
-    }
-
-    fn add_from_pat(&mut self, pat: &hir::Pat<'tcx>) {
-        let shorthand_field_ids = self.collect_shorthand_field_ids(pat);
-
-        pat.each_binding(|_, hir_id, _, ident| {
-            self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id));
-            self.add_variable(Local(LocalInfo {
-                id: hir_id,
-                name: ident.name,
-                is_shorthand: shorthand_field_ids.contains(&hir_id),
-            }));
-        });
-    }
-}
-
-impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
-    fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
-        self.add_from_pat(local.pat);
-        if local.els.is_some() {
-            self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id));
-        }
-        intravisit::walk_local(self, local);
-    }
-
-    fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
-        self.add_from_pat(&arm.pat);
-        intravisit::walk_arm(self, arm);
-    }
-
-    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
-        let shorthand_field_ids = self.collect_shorthand_field_ids(param.pat);
-        param.pat.each_binding(|_bm, hir_id, _x, ident| {
-            let var = match param.pat.kind {
-                rustc_hir::PatKind::Struct(..) => Local(LocalInfo {
-                    id: hir_id,
-                    name: ident.name,
-                    is_shorthand: shorthand_field_ids.contains(&hir_id),
-                }),
-                _ => Param(hir_id, ident.name),
-            };
-            self.add_variable(var);
-        });
-        intravisit::walk_param(self, param);
-    }
-
-    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
-        match expr.kind {
-            // live nodes required for uses or definitions of variables:
-            hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
-                debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res);
-                if let Res::Local(_var_hir_id) = path.res {
-                    self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
-                }
-            }
-            hir::ExprKind::Closure(closure) => {
-                // Interesting control flow (for loops can contain labeled
-                // breaks or continues)
-                self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
-
-                // Make a live_node for each mentioned variable, with the span
-                // being the location that the variable is used. This results
-                // in better error messages than just pointing at the closure
-                // construction site.
-                let mut call_caps = Vec::new();
-                if let Some(upvars) = self.tcx.upvars_mentioned(closure.def_id) {
-                    call_caps.extend(upvars.keys().map(|var_id| {
-                        let upvar = upvars[var_id];
-                        let upvar_ln = self.add_live_node(UpvarNode(upvar.span));
-                        CaptureInfo { ln: upvar_ln, var_hid: *var_id }
-                    }));
-                }
-                self.set_captures(expr.hir_id, call_caps);
-            }
-
-            hir::ExprKind::Let(let_expr) => {
-                self.add_from_pat(let_expr.pat);
-            }
-
-            // live nodes required for interesting control flow:
-            hir::ExprKind::If(..)
-            | hir::ExprKind::Match(..)
-            | hir::ExprKind::Loop(..)
-            | hir::ExprKind::Yield(..) => {
-                self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
-            }
-            hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => {
-                self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
-            }
-
-            // Inline assembly may contain labels.
-            hir::ExprKind::InlineAsm(asm) if asm.contains_label() => {
-                self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
-                intravisit::walk_expr(self, expr);
-            }
-
-            // otherwise, live nodes are not required:
-            hir::ExprKind::Index(..)
-            | hir::ExprKind::Field(..)
-            | hir::ExprKind::Array(..)
-            | hir::ExprKind::Call(..)
-            | hir::ExprKind::MethodCall(..)
-            | hir::ExprKind::Tup(..)
-            | hir::ExprKind::Binary(..)
-            | hir::ExprKind::AddrOf(..)
-            | hir::ExprKind::Cast(..)
-            | hir::ExprKind::DropTemps(..)
-            | hir::ExprKind::Unary(..)
-            | hir::ExprKind::Break(..)
-            | hir::ExprKind::Continue(_)
-            | hir::ExprKind::Lit(_)
-            | hir::ExprKind::ConstBlock(..)
-            | hir::ExprKind::Ret(..)
-            | hir::ExprKind::Become(..)
-            | hir::ExprKind::Block(..)
-            | hir::ExprKind::Assign(..)
-            | hir::ExprKind::AssignOp(..)
-            | hir::ExprKind::Struct(..)
-            | hir::ExprKind::Repeat(..)
-            | hir::ExprKind::InlineAsm(..)
-            | hir::ExprKind::OffsetOf(..)
-            | hir::ExprKind::Type(..)
-            | hir::ExprKind::Err(_)
-            | hir::ExprKind::Path(hir::QPath::TypeRelative(..))
-            | hir::ExprKind::Path(hir::QPath::LangItem(..)) => {}
-        }
-        intravisit::walk_expr(self, expr);
-    }
-}
-
-// ______________________________________________________________________
-// Computing liveness sets
-//
-// Actually we compute just a bit more than just liveness, but we use
-// the same basic propagation framework in all cases.
-
-const ACC_READ: u32 = 1;
-const ACC_WRITE: u32 = 2;
-const ACC_USE: u32 = 4;
-
-struct Liveness<'a, 'tcx> {
-    ir: &'a mut IrMaps<'tcx>,
-    typeck_results: &'a ty::TypeckResults<'tcx>,
-    param_env: ty::ParamEnv<'tcx>,
-    closure_min_captures: Option<&'tcx RootVariableMinCaptureList<'tcx>>,
-    successors: IndexVec<LiveNode, Option<LiveNode>>,
-    rwu_table: rwu_table::RWUTable,
-
-    /// A live node representing a point of execution before closure entry &
-    /// after closure exit. Used to calculate liveness of captured variables
-    /// through calls to the same closure. Used for Fn & FnMut closures only.
-    closure_ln: LiveNode,
-    /// A live node representing every 'exit' from the function, whether it be
-    /// by explicit return, panic, or other means.
-    exit_ln: LiveNode,
-
-    // mappings from loop node ID to LiveNode
-    // ("break" label should map to loop node ID,
-    // it probably doesn't now)
-    break_ln: HirIdMap<LiveNode>,
-    cont_ln: HirIdMap<LiveNode>,
-}
-
-impl<'a, 'tcx> Liveness<'a, 'tcx> {
-    fn new(ir: &'a mut IrMaps<'tcx>, body_owner: LocalDefId) -> Liveness<'a, 'tcx> {
-        let typeck_results = ir.tcx.typeck(body_owner);
-        let param_env = ir.tcx.param_env(body_owner);
-        let closure_min_captures = typeck_results.closure_min_captures.get(&body_owner);
-        let closure_ln = ir.add_live_node(ClosureNode);
-        let exit_ln = ir.add_live_node(ExitNode);
-
-        let num_live_nodes = ir.lnks.len();
-        let num_vars = ir.var_kinds.len();
-
-        Liveness {
-            ir,
-            typeck_results,
-            param_env,
-            closure_min_captures,
-            successors: IndexVec::from_elem_n(None, num_live_nodes),
-            rwu_table: rwu_table::RWUTable::new(num_live_nodes, num_vars),
-            closure_ln,
-            exit_ln,
-            break_ln: Default::default(),
-            cont_ln: Default::default(),
-        }
-    }
-
-    fn live_node(&self, hir_id: HirId, span: Span) -> LiveNode {
-        match self.ir.live_node_map.get(&hir_id) {
-            Some(&ln) => ln,
-            None => {
-                // This must be a mismatch between the ir_map construction
-                // above and the propagation code below; the two sets of
-                // code have to agree about which AST nodes are worth
-                // creating liveness nodes for.
-                span_bug!(span, "no live node registered for node {:?}", hir_id);
-            }
-        }
-    }
-
-    fn variable(&self, hir_id: HirId, span: Span) -> Variable {
-        self.ir.variable(hir_id, span)
-    }
-
-    fn define_bindings_in_pat(&mut self, pat: &hir::Pat<'_>, mut succ: LiveNode) -> LiveNode {
-        // In an or-pattern, only consider the first non-never pattern; any later patterns
-        // must have the same bindings, and we also consider that pattern
-        // to be the "authoritative" set of ids.
-        pat.each_binding_or_first(&mut |_, hir_id, pat_sp, ident| {
-            let ln = self.live_node(hir_id, pat_sp);
-            let var = self.variable(hir_id, ident.span);
-            self.init_from_succ(ln, succ);
-            self.define(ln, var);
-            succ = ln;
-        });
-        succ
-    }
-
-    fn live_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
-        self.rwu_table.get_reader(ln, var)
-    }
-
-    // Is this variable live on entry to any of its successor nodes?
-    fn live_on_exit(&self, ln: LiveNode, var: Variable) -> bool {
-        let successor = self.successors[ln].unwrap();
-        self.live_on_entry(successor, var)
-    }
-
-    fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
-        self.rwu_table.get_used(ln, var)
-    }
-
-    fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
-        self.rwu_table.get_writer(ln, var)
-    }
-
-    fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> bool {
-        match self.successors[ln] {
-            Some(successor) => self.assigned_on_entry(successor, var),
-            None => {
-                self.ir.tcx.dcx().delayed_bug("no successor");
-                true
-            }
-        }
-    }
-
-    fn write_vars<F>(&self, wr: &mut dyn Write, mut test: F) -> io::Result<()>
-    where
-        F: FnMut(Variable) -> bool,
-    {
-        for var_idx in 0..self.ir.var_kinds.len() {
-            let var = Variable::from(var_idx);
-            if test(var) {
-                write!(wr, " {var:?}")?;
-            }
-        }
-        Ok(())
-    }
-
-    #[allow(unused_must_use)]
-    fn ln_str(&self, ln: LiveNode) -> String {
-        let mut wr = Vec::new();
-        {
-            let wr = &mut wr as &mut dyn Write;
-            write!(wr, "[{:?} of kind {:?} reads", ln, self.ir.lnks[ln]);
-            self.write_vars(wr, |var| self.rwu_table.get_reader(ln, var));
-            write!(wr, "  writes");
-            self.write_vars(wr, |var| self.rwu_table.get_writer(ln, var));
-            write!(wr, "  uses");
-            self.write_vars(wr, |var| self.rwu_table.get_used(ln, var));
-
-            write!(wr, "  precedes {:?}]", self.successors[ln]);
-        }
-        String::from_utf8(wr).unwrap()
-    }
-
-    fn log_liveness(&self, entry_ln: LiveNode, hir_id: hir::HirId) {
-        // hack to skip the loop unless debug! is enabled:
-        debug!(
-            "^^ liveness computation results for body {} (entry={:?})",
-            {
-                for ln_idx in 0..self.ir.lnks.len() {
-                    debug!("{:?}", self.ln_str(LiveNode::from(ln_idx)));
-                }
-                hir_id
-            },
-            entry_ln
-        );
-    }
-
-    fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) {
-        self.successors[ln] = Some(succ_ln);
-
-        // It is not necessary to initialize the RWUs here because they are all
-        // empty when created, and the sets only grow during iterations.
-    }
-
-    fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) {
-        // more efficient version of init_empty() / merge_from_succ()
-        self.successors[ln] = Some(succ_ln);
-        self.rwu_table.copy(ln, succ_ln);
-        debug!("init_from_succ(ln={}, succ={})", self.ln_str(ln), self.ln_str(succ_ln));
-    }
-
-    fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) -> bool {
-        if ln == succ_ln {
-            return false;
-        }
-
-        let changed = self.rwu_table.union(ln, succ_ln);
-        debug!("merge_from_succ(ln={:?}, succ={}, changed={})", ln, self.ln_str(succ_ln), changed);
-        changed
-    }
-
-    // Indicates that a local variable was *defined*; we know that no
-    // uses of the variable can precede the definition (resolve checks
-    // this) so we just clear out all the data.
-    fn define(&mut self, writer: LiveNode, var: Variable) {
-        let used = self.rwu_table.get_used(writer, var);
-        self.rwu_table.set(writer, var, rwu_table::RWU { reader: false, writer: false, used });
-        debug!("{:?} defines {:?}: {}", writer, var, self.ln_str(writer));
-    }
-
-    // Either read, write, or both depending on the acc bitset
-    fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) {
-        debug!("{:?} accesses[{:x}] {:?}: {}", ln, acc, var, self.ln_str(ln));
-
-        let mut rwu = self.rwu_table.get(ln, var);
-
-        if (acc & ACC_WRITE) != 0 {
-            rwu.reader = false;
-            rwu.writer = true;
-        }
-
-        // Important: if we both read/write, must do read second
-        // or else the write will override.
-        if (acc & ACC_READ) != 0 {
-            rwu.reader = true;
-        }
-
-        if (acc & ACC_USE) != 0 {
-            rwu.used = true;
-        }
-
-        self.rwu_table.set(ln, var, rwu);
-    }
-
-    fn compute(&mut self, body: &hir::Body<'_>, hir_id: HirId) -> LiveNode {
-        debug!("compute: for body {:?}", body.id().hir_id);
-
-        // # Liveness of captured variables
-        //
-        // When computing the liveness for captured variables we take into
-        // account how variable is captured (ByRef vs ByValue) and what is the
-        // closure kind (Coroutine / FnOnce vs Fn / FnMut).
-        //
-        // Variables captured by reference are assumed to be used on the exit
-        // from the closure.
-        //
-        // In FnOnce closures, variables captured by value are known to be dead
-        // on exit since it is impossible to call the closure again.
-        //
-        // In Fn / FnMut closures, variables captured by value are live on exit
-        // if they are live on the entry to the closure, since only the closure
-        // itself can access them on subsequent calls.
-
-        if let Some(closure_min_captures) = self.closure_min_captures {
-            // Mark upvars captured by reference as used after closure exits.
-            for (&var_hir_id, min_capture_list) in closure_min_captures {
-                for captured_place in min_capture_list {
-                    match captured_place.info.capture_kind {
-                        ty::UpvarCapture::ByRef(_) => {
-                            let var = self.variable(
-                                var_hir_id,
-                                captured_place.get_capture_kind_span(self.ir.tcx),
-                            );
-                            self.acc(self.exit_ln, var, ACC_READ | ACC_USE);
-                        }
-                        ty::UpvarCapture::ByValue => {}
-                    }
-                }
-            }
-        }
-
-        let succ = self.propagate_through_expr(body.value, self.exit_ln);
-
-        if self.closure_min_captures.is_none() {
-            // Either not a closure, or closure without any captured variables.
-            // No need to determine liveness of captured variables, since there
-            // are none.
-            return succ;
-        }
-
-        let ty = self.typeck_results.node_type(hir_id);
-        match ty.kind() {
-            ty::Closure(_def_id, args) => match args.as_closure().kind() {
-                ty::ClosureKind::Fn => {}
-                ty::ClosureKind::FnMut => {}
-                ty::ClosureKind::FnOnce => return succ,
-            },
-            ty::CoroutineClosure(_def_id, args) => match args.as_coroutine_closure().kind() {
-                ty::ClosureKind::Fn => {}
-                ty::ClosureKind::FnMut => {}
-                ty::ClosureKind::FnOnce => return succ,
-            },
-            ty::Coroutine(..) => return succ,
-            _ => {
-                span_bug!(
-                    body.value.span,
-                    "{} has upvars so it should have a closure type: {:?}",
-                    hir_id,
-                    ty
-                );
-            }
-        };
-
-        // Propagate through calls to the closure.
-        loop {
-            self.init_from_succ(self.closure_ln, succ);
-            for param in body.params {
-                param.pat.each_binding(|_bm, hir_id, _x, ident| {
-                    let var = self.variable(hir_id, ident.span);
-                    self.define(self.closure_ln, var);
-                })
-            }
-
-            if !self.merge_from_succ(self.exit_ln, self.closure_ln) {
-                break;
-            }
-            assert_eq!(succ, self.propagate_through_expr(body.value, self.exit_ln));
-        }
-
-        succ
-    }
-
-    fn propagate_through_block(&mut self, blk: &hir::Block<'_>, succ: LiveNode) -> LiveNode {
-        if blk.targeted_by_break {
-            self.break_ln.insert(blk.hir_id, succ);
-        }
-        let succ = self.propagate_through_opt_expr(blk.expr, succ);
-        blk.stmts.iter().rev().fold(succ, |succ, stmt| self.propagate_through_stmt(stmt, succ))
-    }
-
-    fn propagate_through_stmt(&mut self, stmt: &hir::Stmt<'_>, succ: LiveNode) -> LiveNode {
-        match stmt.kind {
-            hir::StmtKind::Let(local) => {
-                // Note: we mark the variable as defined regardless of whether
-                // there is an initializer. Initially I had thought to only mark
-                // the live variable as defined if it was initialized, and then we
-                // could check for uninit variables just by scanning what is live
-                // at the start of the function. But that doesn't work so well for
-                // immutable variables defined in a loop:
-                //     loop { let x; x = 5; }
-                // because the "assignment" loops back around and generates an error.
-                //
-                // So now we just check that variables defined w/o an
-                // initializer are not live at the point of their
-                // initialization, which is mildly more complex than checking
-                // once at the func header but otherwise equivalent.
-
-                if let Some(els) = local.els {
-                    // Eventually, `let pat: ty = init else { els };` is mostly equivalent to
-                    // `let (bindings, ...) = match init { pat => (bindings, ...), _ => els };`
-                    // except that extended lifetime applies at the `init` location.
-                    //
-                    //       (e)
-                    //        |
-                    //        v
-                    //      (expr)
-                    //      /   \
-                    //     |     |
-                    //     v     v
-                    // bindings  els
-                    //     |
-                    //     v
-                    // ( succ )
-                    //
-                    if let Some(init) = local.init {
-                        let else_ln = self.propagate_through_block(els, succ);
-                        let ln = self.live_node(local.hir_id, local.span);
-                        self.init_from_succ(ln, succ);
-                        self.merge_from_succ(ln, else_ln);
-                        let succ = self.propagate_through_expr(init, ln);
-                        self.define_bindings_in_pat(local.pat, succ)
-                    } else {
-                        span_bug!(
-                            stmt.span,
-                            "variable is uninitialized but an unexpected else branch is found"
-                        )
-                    }
-                } else {
-                    let succ = self.propagate_through_opt_expr(local.init, succ);
-                    self.define_bindings_in_pat(local.pat, succ)
-                }
-            }
-            hir::StmtKind::Item(..) => succ,
-            hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => {
-                self.propagate_through_expr(expr, succ)
-            }
-        }
-    }
-
-    fn propagate_through_exprs(&mut self, exprs: &[Expr<'_>], succ: LiveNode) -> LiveNode {
-        exprs.iter().rev().fold(succ, |succ, expr| self.propagate_through_expr(expr, succ))
-    }
-
-    fn propagate_through_opt_expr(
-        &mut self,
-        opt_expr: Option<&Expr<'_>>,
-        succ: LiveNode,
-    ) -> LiveNode {
-        opt_expr.map_or(succ, |expr| self.propagate_through_expr(expr, succ))
-    }
-
-    fn propagate_through_expr(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
-        debug!("propagate_through_expr: {:?}", expr);
-
-        match expr.kind {
-            // Interesting cases with control flow or which gen/kill
-            hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
-                self.access_path(expr.hir_id, path, succ, ACC_READ | ACC_USE)
-            }
-
-            hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(e, succ),
-
-            hir::ExprKind::Closure { .. } => {
-                debug!("{:?} is an ExprKind::Closure", expr);
-
-                // the construction of a closure itself is not important,
-                // but we have to consider the closed over variables.
-                let caps = self
-                    .ir
-                    .capture_info_map
-                    .get(&expr.hir_id)
-                    .cloned()
-                    .unwrap_or_else(|| span_bug!(expr.span, "no registered caps"));
-
-                caps.iter().rev().fold(succ, |succ, cap| {
-                    self.init_from_succ(cap.ln, succ);
-                    let var = self.variable(cap.var_hid, expr.span);
-                    self.acc(cap.ln, var, ACC_READ | ACC_USE);
-                    cap.ln
-                })
-            }
-
-            hir::ExprKind::Let(let_expr) => {
-                let succ = self.propagate_through_expr(let_expr.init, succ);
-                self.define_bindings_in_pat(let_expr.pat, succ)
-            }
-
-            // Note that labels have been resolved, so we don't need to look
-            // at the label ident
-            hir::ExprKind::Loop(ref blk, ..) => self.propagate_through_loop(expr, blk, succ),
-
-            hir::ExprKind::Yield(e, ..) => {
-                let yield_ln = self.live_node(expr.hir_id, expr.span);
-                self.init_from_succ(yield_ln, succ);
-                self.merge_from_succ(yield_ln, self.exit_ln);
-                self.propagate_through_expr(e, yield_ln)
-            }
-
-            hir::ExprKind::If(ref cond, ref then, ref else_opt) => {
-                //
-                //     (cond)
-                //       |
-                //       v
-                //     (expr)
-                //     /   \
-                //    |     |
-                //    v     v
-                //  (then)(els)
-                //    |     |
-                //    v     v
-                //   (  succ  )
-                //
-                let else_ln = self.propagate_through_opt_expr(else_opt.as_deref(), succ);
-                let then_ln = self.propagate_through_expr(then, succ);
-                let ln = self.live_node(expr.hir_id, expr.span);
-                self.init_from_succ(ln, else_ln);
-                self.merge_from_succ(ln, then_ln);
-                self.propagate_through_expr(cond, ln)
-            }
-
-            hir::ExprKind::Match(ref e, arms, _) => {
-                //
-                //      (e)
-                //       |
-                //       v
-                //     (expr)
-                //     / | \
-                //    |  |  |
-                //    v  v  v
-                //   (..arms..)
-                //    |  |  |
-                //    v  v  v
-                //   (  succ  )
-                //
-                //
-                let ln = self.live_node(expr.hir_id, expr.span);
-                self.init_empty(ln, succ);
-                for arm in arms {
-                    let body_succ = self.propagate_through_expr(arm.body, succ);
-
-                    let guard_succ = arm
-                        .guard
-                        .as_ref()
-                        .map_or(body_succ, |g| self.propagate_through_expr(g, body_succ));
-                    let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ);
-                    self.merge_from_succ(ln, arm_succ);
-                }
-                self.propagate_through_expr(e, ln)
-            }
-
-            hir::ExprKind::Ret(ref o_e) => {
-                // Ignore succ and subst exit_ln.
-                self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln)
-            }
-
-            hir::ExprKind::Become(e) => {
-                // Ignore succ and subst exit_ln.
-                self.propagate_through_expr(e, self.exit_ln)
-            }
-
-            hir::ExprKind::Break(label, ref opt_expr) => {
-                // Find which label this break jumps to
-                let target = match label.target_id {
-                    Ok(hir_id) => self.break_ln.get(&hir_id),
-                    Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
-                }
-                .cloned();
-
-                // Now that we know the label we're going to,
-                // look it up in the break loop nodes table
-
-                match target {
-                    Some(b) => self.propagate_through_opt_expr(opt_expr.as_deref(), b),
-                    None => span_bug!(expr.span, "`break` to unknown label"),
-                }
-            }
-
-            hir::ExprKind::Continue(label) => {
-                // Find which label this expr continues to
-                let sc = label
-                    .target_id
-                    .unwrap_or_else(|err| span_bug!(expr.span, "loop scope error: {}", err));
-
-                // Now that we know the label we're going to,
-                // look it up in the continue loop nodes table
-                self.cont_ln.get(&sc).cloned().unwrap_or_else(|| {
-                    self.ir.tcx.dcx().span_delayed_bug(expr.span, "continue to unknown label");
-                    self.ir.add_live_node(ErrNode)
-                })
-            }
-
-            hir::ExprKind::Assign(ref l, ref r, _) => {
-                // see comment on places in
-                // propagate_through_place_components()
-                let succ = self.write_place(l, succ, ACC_WRITE);
-                let succ = self.propagate_through_place_components(l, succ);
-                self.propagate_through_expr(r, succ)
-            }
-
-            hir::ExprKind::AssignOp(_, ref l, ref r) => {
-                // an overloaded assign op is like a method call
-                if self.typeck_results.is_method_call(expr) {
-                    let succ = self.propagate_through_expr(l, succ);
-                    self.propagate_through_expr(r, succ)
-                } else {
-                    // see comment on places in
-                    // propagate_through_place_components()
-                    let succ = self.write_place(l, succ, ACC_WRITE | ACC_READ);
-                    let succ = self.propagate_through_expr(r, succ);
-                    self.propagate_through_place_components(l, succ)
-                }
-            }
-
-            // Uninteresting cases: just propagate in rev exec order
-            hir::ExprKind::Array(exprs) => self.propagate_through_exprs(exprs, succ),
-
-            hir::ExprKind::Struct(_, fields, ref with_expr) => {
-                let succ = self.propagate_through_opt_expr(with_expr.as_deref(), succ);
-                fields
-                    .iter()
-                    .rev()
-                    .fold(succ, |succ, field| self.propagate_through_expr(field.expr, succ))
-            }
-
-            hir::ExprKind::Call(ref f, args) => {
-                let succ = self.check_is_ty_uninhabited(expr, succ);
-                let succ = self.propagate_through_exprs(args, succ);
-                self.propagate_through_expr(f, succ)
-            }
-
-            hir::ExprKind::MethodCall(.., receiver, args, _) => {
-                let succ = self.check_is_ty_uninhabited(expr, succ);
-                let succ = self.propagate_through_exprs(args, succ);
-                self.propagate_through_expr(receiver, succ)
-            }
-
-            hir::ExprKind::Tup(exprs) => self.propagate_through_exprs(exprs, succ),
-
-            hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => {
-                let r_succ = self.propagate_through_expr(r, succ);
-
-                let ln = self.live_node(expr.hir_id, expr.span);
-                self.init_from_succ(ln, succ);
-                self.merge_from_succ(ln, r_succ);
-
-                self.propagate_through_expr(l, ln)
-            }
-
-            hir::ExprKind::Index(ref l, ref r, _) | hir::ExprKind::Binary(_, ref l, ref r) => {
-                let r_succ = self.propagate_through_expr(r, succ);
-                self.propagate_through_expr(l, r_succ)
-            }
-
-            hir::ExprKind::AddrOf(_, _, ref e)
-            | hir::ExprKind::Cast(ref e, _)
-            | hir::ExprKind::Type(ref e, _)
-            | hir::ExprKind::DropTemps(ref e)
-            | hir::ExprKind::Unary(_, ref e)
-            | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(e, succ),
-
-            hir::ExprKind::InlineAsm(asm) => {
-                //
-                //     (inputs)
-                //        |
-                //        v
-                //     (outputs)
-                //    /         \
-                //    |         |
-                //    v         v
-                // (labels)(fallthrough)
-                //    |         |
-                //    v         v
-                // ( succ / exit_ln )
-
-                // Handle non-returning asm
-                let mut succ =
-                    if self.typeck_results.expr_ty(expr).is_never() { self.exit_ln } else { succ };
-
-                // Do a first pass for labels only
-                if asm.contains_label() {
-                    let ln = self.live_node(expr.hir_id, expr.span);
-                    self.init_from_succ(ln, succ);
-                    for (op, _op_sp) in asm.operands.iter().rev() {
-                        match op {
-                            hir::InlineAsmOperand::Label { block } => {
-                                let label_ln = self.propagate_through_block(block, succ);
-                                self.merge_from_succ(ln, label_ln);
-                            }
-                            hir::InlineAsmOperand::In { .. }
-                            | hir::InlineAsmOperand::Out { .. }
-                            | hir::InlineAsmOperand::InOut { .. }
-                            | hir::InlineAsmOperand::SplitInOut { .. }
-                            | hir::InlineAsmOperand::Const { .. }
-                            | hir::InlineAsmOperand::SymFn { .. }
-                            | hir::InlineAsmOperand::SymStatic { .. } => {}
-                        }
-                    }
-                    succ = ln;
-                }
-
-                // Do a second pass for writing outputs only
-                for (op, _op_sp) in asm.operands.iter().rev() {
-                    match op {
-                        hir::InlineAsmOperand::In { .. }
-                        | hir::InlineAsmOperand::Const { .. }
-                        | hir::InlineAsmOperand::SymFn { .. }
-                        | hir::InlineAsmOperand::SymStatic { .. }
-                        | hir::InlineAsmOperand::Label { .. } => {}
-                        hir::InlineAsmOperand::Out { expr, .. } => {
-                            if let Some(expr) = expr {
-                                succ = self.write_place(expr, succ, ACC_WRITE);
-                            }
-                        }
-                        hir::InlineAsmOperand::InOut { expr, .. } => {
-                            succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE | ACC_USE);
-                        }
-                        hir::InlineAsmOperand::SplitInOut { out_expr, .. } => {
-                            if let Some(expr) = out_expr {
-                                succ = self.write_place(expr, succ, ACC_WRITE);
-                            }
-                        }
-                    }
-                }
-
-                // Then do a third pass for inputs
-                for (op, _op_sp) in asm.operands.iter().rev() {
-                    match op {
-                        hir::InlineAsmOperand::In { expr, .. } => {
-                            succ = self.propagate_through_expr(expr, succ)
-                        }
-                        hir::InlineAsmOperand::Out { expr, .. } => {
-                            if let Some(expr) = expr {
-                                succ = self.propagate_through_place_components(expr, succ);
-                            }
-                        }
-                        hir::InlineAsmOperand::InOut { expr, .. } => {
-                            succ = self.propagate_through_place_components(expr, succ);
-                        }
-                        hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
-                            if let Some(expr) = out_expr {
-                                succ = self.propagate_through_place_components(expr, succ);
-                            }
-                            succ = self.propagate_through_expr(in_expr, succ);
-                        }
-                        hir::InlineAsmOperand::Const { .. }
-                        | hir::InlineAsmOperand::SymFn { .. }
-                        | hir::InlineAsmOperand::SymStatic { .. }
-                        | hir::InlineAsmOperand::Label { .. } => {}
-                    }
-                }
-                succ
-            }
-
-            hir::ExprKind::Lit(..)
-            | hir::ExprKind::ConstBlock(..)
-            | hir::ExprKind::Err(_)
-            | hir::ExprKind::Path(hir::QPath::TypeRelative(..))
-            | hir::ExprKind::Path(hir::QPath::LangItem(..))
-            | hir::ExprKind::OffsetOf(..) => succ,
-
-            // Note that labels have been resolved, so we don't need to look
-            // at the label ident
-            hir::ExprKind::Block(ref blk, _) => self.propagate_through_block(blk, succ),
-        }
-    }
-
-    fn propagate_through_place_components(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
-        // # Places
-        //
-        // In general, the full flow graph structure for an
-        // assignment/move/etc can be handled in one of two ways,
-        // depending on whether what is being assigned is a "tracked
-        // value" or not. A tracked value is basically a local
-        // variable or argument.
-        //
-        // The two kinds of graphs are:
-        //
-        //    Tracked place          Untracked place
-        // ----------------------++-----------------------
-        //                       ||
-        //         |             ||           |
-        //         v             ||           v
-        //     (rvalue)          ||       (rvalue)
-        //         |             ||           |
-        //         v             ||           v
-        // (write of place)      ||   (place components)
-        //         |             ||           |
-        //         v             ||           v
-        //      (succ)           ||        (succ)
-        //                       ||
-        // ----------------------++-----------------------
-        //
-        // I will cover the two cases in turn:
-        //
-        // # Tracked places
-        //
-        // A tracked place is a local variable/argument `x`. In
-        // these cases, the link_node where the write occurs is linked
-        // to node id of `x`. The `write_place()` routine generates
-        // the contents of this node. There are no subcomponents to
-        // consider.
-        //
-        // # Non-tracked places
-        //
-        // These are places like `x[5]` or `x.f`. In that case, we
-        // basically ignore the value which is written to but generate
-        // reads for the components---`x` in these two examples. The
-        // components reads are generated by
-        // `propagate_through_place_components()` (this fn).
-        //
-        // # Illegal places
-        //
-        // It is still possible to observe assignments to non-places;
-        // these errors are detected in the later pass borrowck. We
-        // just ignore such cases and treat them as reads.
-
-        match expr.kind {
-            hir::ExprKind::Path(_) => succ,
-            hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(e, succ),
-            _ => self.propagate_through_expr(expr, succ),
-        }
-    }
-
-    // see comment on propagate_through_place()
-    fn write_place(&mut self, expr: &Expr<'_>, succ: LiveNode, acc: u32) -> LiveNode {
-        match expr.kind {
-            hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
-                self.access_path(expr.hir_id, path, succ, acc)
-            }
-
-            // We do not track other places, so just propagate through
-            // to their subcomponents. Also, it may happen that
-            // non-places occur here, because those are detected in the
-            // later pass borrowck.
-            _ => succ,
-        }
-    }
-
-    fn access_var(
-        &mut self,
-        hir_id: HirId,
-        var_hid: HirId,
-        succ: LiveNode,
-        acc: u32,
-        span: Span,
-    ) -> LiveNode {
-        let ln = self.live_node(hir_id, span);
-        if acc != 0 {
-            self.init_from_succ(ln, succ);
-            let var = self.variable(var_hid, span);
-            self.acc(ln, var, acc);
-        }
-        ln
-    }
-
-    fn access_path(
-        &mut self,
-        hir_id: HirId,
-        path: &hir::Path<'_>,
-        succ: LiveNode,
-        acc: u32,
-    ) -> LiveNode {
-        match path.res {
-            Res::Local(hid) => self.access_var(hir_id, hid, succ, acc, path.span),
-            _ => succ,
-        }
-    }
-
-    fn propagate_through_loop(
-        &mut self,
-        expr: &Expr<'_>,
-        body: &hir::Block<'_>,
-        succ: LiveNode,
-    ) -> LiveNode {
-        /*
-        We model control flow like this:
-
-              (expr) <-+
-                |      |
-                v      |
-              (body) --+
-
-        Note that a `continue` expression targeting the `loop` will have a successor of `expr`.
-        Meanwhile, a `break` expression will have a successor of `succ`.
-        */
-
-        // first iteration:
-        let ln = self.live_node(expr.hir_id, expr.span);
-        self.init_empty(ln, succ);
-        debug!("propagate_through_loop: using id for loop body {} {:?}", expr.hir_id, body);
-
-        self.break_ln.insert(expr.hir_id, succ);
-
-        self.cont_ln.insert(expr.hir_id, ln);
-
-        let body_ln = self.propagate_through_block(body, ln);
-
-        // repeat until fixed point is reached:
-        while self.merge_from_succ(ln, body_ln) {
-            assert_eq!(body_ln, self.propagate_through_block(body, ln));
-        }
-
-        ln
-    }
-
-    fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
-        let ty = self.typeck_results.expr_ty(expr);
-        let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
-        if ty.is_inhabited_from(self.ir.tcx, m, self.param_env) {
-            return succ;
-        }
-        match self.ir.lnks[succ] {
-            LiveNodeKind::ExprNode(succ_span, succ_id) => {
-                self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression");
-            }
-            LiveNodeKind::VarDefNode(succ_span, succ_id) => {
-                self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition");
-            }
-            _ => {}
-        };
-        self.exit_ln
-    }
-
-    fn warn_about_unreachable<'desc>(
-        &mut self,
-        orig_span: Span,
-        orig_ty: Ty<'tcx>,
-        expr_span: Span,
-        expr_id: HirId,
-        descr: &'desc str,
-    ) {
-        if !orig_ty.is_never() {
-            // Unreachable code warnings are already emitted during type checking.
-            // However, during type checking, full type information is being
-            // calculated but not yet available, so the check for diverging
-            // expressions due to uninhabited result types is pretty crude and
-            // only checks whether ty.is_never(). Here, we have full type
-            // information available and can issue warnings for less obviously
-            // uninhabited types (e.g. empty enums). The check above is used so
-            // that we do not emit the same warning twice if the uninhabited type
-            // is indeed `!`.
-
-            self.ir.tcx.emit_node_span_lint(
-                lint::builtin::UNREACHABLE_CODE,
-                expr_id,
-                expr_span,
-                errors::UnreachableDueToUninhabited {
-                    expr: expr_span,
-                    orig: orig_span,
-                    descr,
-                    ty: orig_ty,
-                },
-            );
-        }
-    }
-}
-
-// _______________________________________________________________________
-// Checking for error conditions
-
-impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
-    fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
-        self.check_unused_vars_in_pat(local.pat, None, None, |spans, hir_id, ln, var| {
-            if local.init.is_some() {
-                self.warn_about_dead_assign(spans, hir_id, ln, var);
-            }
-        });
-
-        intravisit::walk_local(self, local);
-    }
-
-    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
-        check_expr(self, ex);
-        intravisit::walk_expr(self, ex);
-    }
-
-    fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
-        self.check_unused_vars_in_pat(arm.pat, None, None, |_, _, _, _| {});
-        intravisit::walk_arm(self, arm);
-    }
-}
-
-fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
-    match expr.kind {
-        hir::ExprKind::Assign(ref l, ..) => {
-            this.check_place(l);
-        }
-
-        hir::ExprKind::AssignOp(_, ref l, _) => {
-            if !this.typeck_results.is_method_call(expr) {
-                this.check_place(l);
-            }
-        }
-
-        hir::ExprKind::InlineAsm(asm) => {
-            for (op, _op_sp) in asm.operands {
-                match op {
-                    hir::InlineAsmOperand::Out { expr, .. } => {
-                        if let Some(expr) = expr {
-                            this.check_place(expr);
-                        }
-                    }
-                    hir::InlineAsmOperand::InOut { expr, .. } => {
-                        this.check_place(expr);
-                    }
-                    hir::InlineAsmOperand::SplitInOut { out_expr, .. } => {
-                        if let Some(out_expr) = out_expr {
-                            this.check_place(out_expr);
-                        }
-                    }
-                    _ => {}
-                }
-            }
-        }
-
-        hir::ExprKind::Let(let_expr) => {
-            this.check_unused_vars_in_pat(let_expr.pat, None, None, |_, _, _, _| {});
-        }
-
-        // no correctness conditions related to liveness
-        hir::ExprKind::Call(..)
-        | hir::ExprKind::MethodCall(..)
-        | hir::ExprKind::Match(..)
-        | hir::ExprKind::Loop(..)
-        | hir::ExprKind::Index(..)
-        | hir::ExprKind::Field(..)
-        | hir::ExprKind::Array(..)
-        | hir::ExprKind::Tup(..)
-        | hir::ExprKind::Binary(..)
-        | hir::ExprKind::Cast(..)
-        | hir::ExprKind::If(..)
-        | hir::ExprKind::DropTemps(..)
-        | hir::ExprKind::Unary(..)
-        | hir::ExprKind::Ret(..)
-        | hir::ExprKind::Become(..)
-        | hir::ExprKind::Break(..)
-        | hir::ExprKind::Continue(..)
-        | hir::ExprKind::Lit(_)
-        | hir::ExprKind::ConstBlock(..)
-        | hir::ExprKind::Block(..)
-        | hir::ExprKind::AddrOf(..)
-        | hir::ExprKind::OffsetOf(..)
-        | hir::ExprKind::Struct(..)
-        | hir::ExprKind::Repeat(..)
-        | hir::ExprKind::Closure { .. }
-        | hir::ExprKind::Path(_)
-        | hir::ExprKind::Yield(..)
-        | hir::ExprKind::Type(..)
-        | hir::ExprKind::Err(_) => {}
-    }
-}
-
-impl<'tcx> Liveness<'_, 'tcx> {
-    fn check_place(&mut self, expr: &'tcx Expr<'tcx>) {
-        match expr.kind {
-            hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
-                if let Res::Local(var_hid) = path.res {
-                    // Assignment to an immutable variable or argument: only legal
-                    // if there is no later assignment. If this local is actually
-                    // mutable, then check for a reassignment to flag the mutability
-                    // as being used.
-                    let ln = self.live_node(expr.hir_id, expr.span);
-                    let var = self.variable(var_hid, expr.span);
-                    self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var);
-                }
-            }
-            _ => {
-                // For other kinds of places, no checks are required,
-                // and any embedded expressions are actually rvalues
-                intravisit::walk_expr(self, expr);
-            }
-        }
-    }
-
-    fn should_warn(&self, var: Variable) -> Option<String> {
-        let name = self.ir.variable_name(var);
-        if name == kw::Empty {
-            return None;
-        }
-        let name = name.as_str();
-        if name.as_bytes()[0] == b'_' {
-            return None;
-        }
-        Some(name.to_owned())
-    }
-
-    fn warn_about_unused_upvars(&self, entry_ln: LiveNode) {
-        let Some(closure_min_captures) = self.closure_min_captures else {
-            return;
-        };
-
-        // If closure_min_captures is Some(), upvars must be Some() too.
-        for (&var_hir_id, min_capture_list) in closure_min_captures {
-            for captured_place in min_capture_list {
-                match captured_place.info.capture_kind {
-                    ty::UpvarCapture::ByValue => {}
-                    ty::UpvarCapture::ByRef(..) => continue,
-                };
-                let span = captured_place.get_capture_kind_span(self.ir.tcx);
-                let var = self.variable(var_hir_id, span);
-                if self.used_on_entry(entry_ln, var) {
-                    if !self.live_on_entry(entry_ln, var) {
-                        if let Some(name) = self.should_warn(var) {
-                            self.ir.tcx.emit_node_span_lint(
-                                lint::builtin::UNUSED_ASSIGNMENTS,
-                                var_hir_id,
-                                vec![span],
-                                errors::UnusedCaptureMaybeCaptureRef { name },
-                            );
-                        }
-                    }
-                } else {
-                    if let Some(name) = self.should_warn(var) {
-                        self.ir.tcx.emit_node_span_lint(
-                            lint::builtin::UNUSED_VARIABLES,
-                            var_hir_id,
-                            vec![span],
-                            errors::UnusedVarMaybeCaptureRef { name },
-                        );
-                    }
-                }
-            }
-        }
-    }
-
-    fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) {
-        for p in body.params {
-            self.check_unused_vars_in_pat(
-                p.pat,
-                Some(entry_ln),
-                Some(body),
-                |spans, hir_id, ln, var| {
-                    if !self.live_on_entry(ln, var)
-                        && let Some(name) = self.should_warn(var)
-                    {
-                        self.ir.tcx.emit_node_span_lint(
-                            lint::builtin::UNUSED_ASSIGNMENTS,
-                            hir_id,
-                            spans,
-                            errors::UnusedAssignPassed { name },
-                        );
-                    }
-                },
-            );
-        }
-    }
-
-    fn check_unused_vars_in_pat(
-        &self,
-        pat: &hir::Pat<'_>,
-        entry_ln: Option<LiveNode>,
-        opt_body: Option<&hir::Body<'_>>,
-        on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable),
-    ) {
-        // In an or-pattern, only consider the variable; any later patterns must have the same
-        // bindings, and we also consider the first pattern to be the "authoritative" set of ids.
-        // However, we should take the ids and spans of variables with the same name from the later
-        // patterns so the suggestions to prefix with underscores will apply to those too.
-        let mut vars: FxIndexMap<Symbol, (LiveNode, Variable, Vec<(HirId, Span, Span)>)> =
-            <_>::default();
-
-        pat.each_binding(|_, hir_id, pat_sp, ident| {
-            let ln = entry_ln.unwrap_or_else(|| self.live_node(hir_id, pat_sp));
-            let var = self.variable(hir_id, ident.span);
-            let id_and_sp = (hir_id, pat_sp, ident.span);
-            vars.entry(self.ir.variable_name(var))
-                .and_modify(|(.., hir_ids_and_spans)| hir_ids_and_spans.push(id_and_sp))
-                .or_insert_with(|| (ln, var, vec![id_and_sp]));
-        });
-
-        let can_remove = match pat.kind {
-            hir::PatKind::Struct(_, fields, true) => {
-                // if all fields are shorthand, remove the struct field, otherwise, mark with _ as prefix
-                fields.iter().all(|f| f.is_shorthand)
-            }
-            _ => false,
-        };
-
-        for (_, (ln, var, hir_ids_and_spans)) in vars {
-            if self.used_on_entry(ln, var) {
-                let id = hir_ids_and_spans[0].0;
-                let spans =
-                    hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect();
-                on_used_on_entry(spans, id, ln, var);
-            } else {
-                self.report_unused(hir_ids_and_spans, ln, var, can_remove, pat, opt_body);
-            }
-        }
-    }
-
-    #[instrument(skip(self), level = "INFO")]
-    fn report_unused(
-        &self,
-        hir_ids_and_spans: Vec<(HirId, Span, Span)>,
-        ln: LiveNode,
-        var: Variable,
-        can_remove: bool,
-        pat: &hir::Pat<'_>,
-        opt_body: Option<&hir::Body<'_>>,
-    ) {
-        let first_hir_id = hir_ids_and_spans[0].0;
-        if let Some(name) = self.should_warn(var).filter(|name| name != "self") {
-            // annoying: for parameters in funcs like `fn(x: i32)
-            // {ret}`, there is only one node, so asking about
-            // assigned_on_exit() is not meaningful.
-            let is_assigned =
-                if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) };
-
-            if is_assigned {
-                self.ir.tcx.emit_node_span_lint(
-                    lint::builtin::UNUSED_VARIABLES,
-                    first_hir_id,
-                    hir_ids_and_spans
-                        .into_iter()
-                        .map(|(_, _, ident_span)| ident_span)
-                        .collect::<Vec<_>>(),
-                    errors::UnusedVarAssignedOnly { name },
-                )
-            } else if can_remove {
-                let spans = hir_ids_and_spans
-                    .iter()
-                    .map(|(_, pat_span, _)| {
-                        let span = self
-                            .ir
-                            .tcx
-                            .sess
-                            .source_map()
-                            .span_extend_to_next_char(*pat_span, ',', true);
-                        span.with_hi(BytePos(span.hi().0 + 1))
-                    })
-                    .collect();
-                self.ir.tcx.emit_node_span_lint(
-                    lint::builtin::UNUSED_VARIABLES,
-                    first_hir_id,
-                    hir_ids_and_spans.iter().map(|(_, pat_span, _)| *pat_span).collect::<Vec<_>>(),
-                    errors::UnusedVarRemoveField {
-                        name,
-                        sugg: errors::UnusedVarRemoveFieldSugg { spans },
-                    },
-                );
-            } else {
-                let (shorthands, non_shorthands): (Vec<_>, Vec<_>) =
-                    hir_ids_and_spans.iter().copied().partition(|(hir_id, _, ident_span)| {
-                        let var = self.variable(*hir_id, *ident_span);
-                        self.ir.variable_is_shorthand(var)
-                    });
-
-                // If we have both shorthand and non-shorthand, prefer the "try ignoring
-                // the field" message, and suggest `_` for the non-shorthands. If we only
-                // have non-shorthand, then prefix with an underscore instead.
-                if !shorthands.is_empty() {
-                    let shorthands =
-                        shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect();
-                    let non_shorthands =
-                        non_shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect();
-
-                    self.ir.tcx.emit_node_span_lint(
-                        lint::builtin::UNUSED_VARIABLES,
-                        first_hir_id,
-                        hir_ids_and_spans
-                            .iter()
-                            .map(|(_, pat_span, _)| *pat_span)
-                            .collect::<Vec<_>>(),
-                        errors::UnusedVarTryIgnore {
-                            sugg: errors::UnusedVarTryIgnoreSugg {
-                                shorthands,
-                                non_shorthands,
-                                name,
-                            },
-                        },
-                    );
-                } else {
-                    // #117284, when `pat_span` and `ident_span` have different contexts
-                    // we can't provide a good suggestion, instead we pointed out the spans from macro
-                    let from_macro = non_shorthands
-                        .iter()
-                        .find(|(_, pat_span, ident_span)| {
-                            !pat_span.eq_ctxt(*ident_span) && pat_span.from_expansion()
-                        })
-                        .map(|(_, pat_span, _)| *pat_span);
-                    let non_shorthands = non_shorthands
-                        .into_iter()
-                        .map(|(_, _, ident_span)| ident_span)
-                        .collect::<Vec<_>>();
-
-                    let suggestions = self.string_interp_suggestions(&name, opt_body);
-                    let sugg = if let Some(span) = from_macro {
-                        errors::UnusedVariableSugg::NoSugg { span, name: name.clone() }
-                    } else {
-                        errors::UnusedVariableSugg::TryPrefixSugg {
-                            spans: non_shorthands,
-                            name: name.clone(),
-                        }
-                    };
-
-                    self.ir.tcx.emit_node_span_lint(
-                        lint::builtin::UNUSED_VARIABLES,
-                        first_hir_id,
-                        hir_ids_and_spans
-                            .iter()
-                            .map(|(_, _, ident_span)| *ident_span)
-                            .collect::<Vec<_>>(),
-                        errors::UnusedVariableTryPrefix {
-                            label: if !suggestions.is_empty() { Some(pat.span) } else { None },
-                            name,
-                            sugg,
-                            string_interp: suggestions,
-                        },
-                    );
-                }
-            }
-        }
-    }
-
-    fn string_interp_suggestions(
-        &self,
-        name: &str,
-        opt_body: Option<&hir::Body<'_>>,
-    ) -> Vec<errors::UnusedVariableStringInterp> {
-        let mut suggs = Vec::new();
-        let Some(opt_body) = opt_body else {
-            return suggs;
-        };
-        let mut visitor = CollectLitsVisitor { lit_exprs: vec![] };
-        intravisit::walk_body(&mut visitor, opt_body);
-        for lit_expr in visitor.lit_exprs {
-            let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue };
-            let rustc_ast::LitKind::Str(syb, _) = litx.node else {
-                continue;
-            };
-            let name_str: &str = syb.as_str();
-            let name_pa = format!("{{{name}}}");
-            if name_str.contains(&name_pa) {
-                suggs.push(errors::UnusedVariableStringInterp {
-                    lit: lit_expr.span,
-                    lo: lit_expr.span.shrink_to_lo(),
-                    hi: lit_expr.span.shrink_to_hi(),
-                });
-            }
-        }
-        suggs
-    }
-
-    fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
-        if !self.live_on_exit(ln, var)
-            && let Some(name) = self.should_warn(var)
-        {
-            self.ir.tcx.emit_node_span_lint(
-                lint::builtin::UNUSED_ASSIGNMENTS,
-                hir_id,
-                spans,
-                errors::UnusedAssign { name },
-            );
-        }
-    }
-}
diff --git a/compiler/rustc_passes/src/liveness/rwu_table.rs b/compiler/rustc_passes/src/liveness/rwu_table.rs
deleted file mode 100644
index 053bf5c234acf..0000000000000
--- a/compiler/rustc_passes/src/liveness/rwu_table.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use crate::liveness::{LiveNode, Variable};
-use std::iter;
-
-#[derive(Clone, Copy)]
-pub(super) struct RWU {
-    pub(super) reader: bool,
-    pub(super) writer: bool,
-    pub(super) used: bool,
-}
-
-/// Conceptually, this is like a `Vec<Vec<RWU>>`. But the number of
-/// RWU's can get very large, so it uses a more compact representation.
-pub(super) struct RWUTable {
-    /// Total number of live nodes.
-    live_nodes: usize,
-    /// Total number of variables.
-    vars: usize,
-
-    /// A compressed representation of `RWU`s.
-    ///
-    /// Each word represents 2 different `RWU`s packed together. Each packed RWU
-    /// is stored in 4 bits: a reader bit, a writer bit, a used bit and a
-    /// padding bit.
-    ///
-    /// The data for each live node is contiguous and starts at a word boundary,
-    /// so there might be an unused space left.
-    words: Vec<u8>,
-    /// Number of words per each live node.
-    live_node_words: usize,
-}
-
-impl RWUTable {
-    const RWU_READER: u8 = 0b0001;
-    const RWU_WRITER: u8 = 0b0010;
-    const RWU_USED: u8 = 0b0100;
-    const RWU_MASK: u8 = 0b1111;
-
-    /// Size of packed RWU in bits.
-    const RWU_BITS: usize = 4;
-    /// Size of a word in bits.
-    const WORD_BITS: usize = std::mem::size_of::<u8>() * 8;
-    /// Number of packed RWUs that fit into a single word.
-    const WORD_RWU_COUNT: usize = Self::WORD_BITS / Self::RWU_BITS;
-
-    pub(super) fn new(live_nodes: usize, vars: usize) -> RWUTable {
-        let live_node_words = (vars + Self::WORD_RWU_COUNT - 1) / Self::WORD_RWU_COUNT;
-        Self { live_nodes, vars, live_node_words, words: vec![0u8; live_node_words * live_nodes] }
-    }
-
-    fn word_and_shift(&self, ln: LiveNode, var: Variable) -> (usize, u32) {
-        assert!(ln.index() < self.live_nodes);
-        assert!(var.index() < self.vars);
-
-        let var = var.index();
-        let word = var / Self::WORD_RWU_COUNT;
-        let shift = Self::RWU_BITS * (var % Self::WORD_RWU_COUNT);
-        (ln.index() * self.live_node_words + word, shift as u32)
-    }
-
-    fn pick2_rows_mut(&mut self, a: LiveNode, b: LiveNode) -> (&mut [u8], &mut [u8]) {
-        assert!(a.index() < self.live_nodes);
-        assert!(b.index() < self.live_nodes);
-        assert!(a != b);
-
-        let a_start = a.index() * self.live_node_words;
-        let b_start = b.index() * self.live_node_words;
-
-        unsafe {
-            let ptr = self.words.as_mut_ptr();
-            (
-                std::slice::from_raw_parts_mut(ptr.add(a_start), self.live_node_words),
-                std::slice::from_raw_parts_mut(ptr.add(b_start), self.live_node_words),
-            )
-        }
-    }
-
-    pub(super) fn copy(&mut self, dst: LiveNode, src: LiveNode) {
-        if dst == src {
-            return;
-        }
-
-        let (dst_row, src_row) = self.pick2_rows_mut(dst, src);
-        dst_row.copy_from_slice(src_row);
-    }
-
-    /// Sets `dst` to the union of `dst` and `src`, returns true if `dst` was
-    /// changed.
-    pub(super) fn union(&mut self, dst: LiveNode, src: LiveNode) -> bool {
-        if dst == src {
-            return false;
-        }
-
-        let mut changed = false;
-        let (dst_row, src_row) = self.pick2_rows_mut(dst, src);
-        for (dst_word, src_word) in iter::zip(dst_row, &*src_row) {
-            let old = *dst_word;
-            let new = *dst_word | src_word;
-            *dst_word = new;
-            changed |= old != new;
-        }
-        changed
-    }
-
-    pub(super) fn get_reader(&self, ln: LiveNode, var: Variable) -> bool {
-        let (word, shift) = self.word_and_shift(ln, var);
-        (self.words[word] >> shift) & Self::RWU_READER != 0
-    }
-
-    pub(super) fn get_writer(&self, ln: LiveNode, var: Variable) -> bool {
-        let (word, shift) = self.word_and_shift(ln, var);
-        (self.words[word] >> shift) & Self::RWU_WRITER != 0
-    }
-
-    pub(super) fn get_used(&self, ln: LiveNode, var: Variable) -> bool {
-        let (word, shift) = self.word_and_shift(ln, var);
-        (self.words[word] >> shift) & Self::RWU_USED != 0
-    }
-
-    pub(super) fn get(&self, ln: LiveNode, var: Variable) -> RWU {
-        let (word, shift) = self.word_and_shift(ln, var);
-        let rwu_packed = self.words[word] >> shift;
-        RWU {
-            reader: rwu_packed & Self::RWU_READER != 0,
-            writer: rwu_packed & Self::RWU_WRITER != 0,
-            used: rwu_packed & Self::RWU_USED != 0,
-        }
-    }
-
-    pub(super) fn set(&mut self, ln: LiveNode, var: Variable, rwu: RWU) {
-        let mut packed = 0;
-        if rwu.reader {
-            packed |= Self::RWU_READER;
-        }
-        if rwu.writer {
-            packed |= Self::RWU_WRITER;
-        }
-        if rwu.used {
-            packed |= Self::RWU_USED;
-        }
-
-        let (word, shift) = self.word_and_shift(ln, var);
-        let word = &mut self.words[word];
-        *word = (*word & !(Self::RWU_MASK << shift)) | (packed << shift)
-    }
-}
diff --git a/src/tools/clippy/tests/ui/needless_match.fixed b/src/tools/clippy/tests/ui/needless_match.fixed
index a936eb463f964..9aeaddf66d6a1 100644
--- a/src/tools/clippy/tests/ui/needless_match.fixed
+++ b/src/tools/clippy/tests/ui/needless_match.fixed
@@ -1,7 +1,7 @@
 #![warn(clippy::needless_match)]
 #![allow(clippy::manual_map)]
 #![allow(dead_code)]
-
+#![allow(unused)]
 #[derive(Clone, Copy)]
 enum Simple {
     A,
diff --git a/src/tools/clippy/tests/ui/needless_match.rs b/src/tools/clippy/tests/ui/needless_match.rs
index b1dd6ff075d88..0504f2c2f7731 100644
--- a/src/tools/clippy/tests/ui/needless_match.rs
+++ b/src/tools/clippy/tests/ui/needless_match.rs
@@ -1,7 +1,7 @@
 #![warn(clippy::needless_match)]
 #![allow(clippy::manual_map)]
 #![allow(dead_code)]
-
+#![allow(unused)]
 #[derive(Clone, Copy)]
 enum Simple {
     A,
diff --git a/src/tools/clippy/tests/ui/useless_conversion.fixed b/src/tools/clippy/tests/ui/useless_conversion.fixed
index ce00fde2f9930..bcb808bd8ee37 100644
--- a/src/tools/clippy/tests/ui/useless_conversion.fixed
+++ b/src/tools/clippy/tests/ui/useless_conversion.fixed
@@ -1,5 +1,5 @@
 #![deny(clippy::useless_conversion)]
-#![allow(clippy::needless_if, clippy::unnecessary_wraps)]
+#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)]
 
 fn test_generic<T: Copy>(val: T) -> T {
     let _ = val;
diff --git a/src/tools/clippy/tests/ui/useless_conversion.rs b/src/tools/clippy/tests/ui/useless_conversion.rs
index 399796195868d..129dad7ad8c27 100644
--- a/src/tools/clippy/tests/ui/useless_conversion.rs
+++ b/src/tools/clippy/tests/ui/useless_conversion.rs
@@ -1,5 +1,5 @@
 #![deny(clippy::useless_conversion)]
-#![allow(clippy::needless_if, clippy::unnecessary_wraps)]
+#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)]
 
 fn test_generic<T: Copy>(val: T) -> T {
     let _ = T::from(val);
diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs
index c8d785f83e8f8..dd97d1208b291 100644
--- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs
@@ -323,6 +323,8 @@ impl ChangeFixture {
             }
         }
 
+        let _ = file_id;
+
         let root = match current_source_root_kind {
             SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
             SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 65ea685b37c60..246b92230bafb 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -17,7 +17,7 @@ use std::path::{Path, PathBuf};
 const ENTRY_LIMIT: usize = 900;
 // FIXME: The following limits should be reduced eventually.
 
-const ISSUES_ENTRY_LIMIT: usize = 1733;
+const ISSUES_ENTRY_LIMIT: usize = 1734;
 const ROOT_ENTRY_LIMIT: usize = 861;
 
 const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
diff --git a/tests/ui/borrowck/borrowck-assign-to-subfield.rs b/tests/ui/borrowck/borrowck-assign-to-subfield.rs
index 807941d9c8547..3fc959428cae0 100644
--- a/tests/ui/borrowck/borrowck-assign-to-subfield.rs
+++ b/tests/ui/borrowck/borrowck-assign-to-subfield.rs
@@ -1,6 +1,7 @@
 //@ run-pass
 //@ pretty-expanded FIXME #23616
 
+#[allow(unused)]
 pub fn main() {
     struct A {
         a: isize,
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs
index c550af21f0753..641066b002273 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs
@@ -13,13 +13,11 @@ struct Point {
 
 pub fn f() {
     let mut a = 1;
-    let mut c = Point{ x:1, y:0 };
+    let mut c = Point { x: 1, y: 0 };
 
     // Captured by value, but variable is dead on entry.
     (move || {
-        // This will not trigger a warning for unused variable as
-        // c.x will be treated as a Non-tracked place
-        c.x = 1;
+        c.x = 1; //~ WARN value captured by `c.x` is never read
         println!("{}", c.x);
         a = 1; //~ WARN value captured by `a` is never read
         println!("{}", a);
@@ -27,10 +25,10 @@ pub fn f() {
 
     // Read and written to, but never actually used.
     (move || {
-        // This will not trigger a warning for unused variable as
-        // c.x will be treated as a Non-tracked place
-        c.x += 1;
-        a += 1; //~ WARN unused variable: `a`
+        c.x += 1; //~  WARN value captured by `c.x` is never read
+                  //~| WARN value assigned to `c.x` is never read
+        a += 1; //~  WARN value captured by `a` is never read
+                //~| WARN value assigned to `a` is never read
     })();
 
     (move || {
@@ -45,10 +43,7 @@ pub fn f() {
     let b = Box::new(42);
     (move || {
         println!("{}", c.x);
-        // Never read because this is FnOnce closure.
-        // This will not trigger a warning for unused variable as
-        // c.x will be treated as a Non-tracked place
-        c.x += 1;
+        c.x += 1; //~ WARN value assigned to `c.x` is never read
         println!("{}", a);
         a += 1; //~ WARN value assigned to `a` is never read
         drop(b);
@@ -56,35 +51,32 @@ pub fn f() {
 }
 
 #[derive(Debug)]
-struct MyStruct<'a>  {
-    x: Option<& 'a str>,
+struct MyStruct<'a> {
+    x: Option<&'a str>,
     y: i32,
 }
 
 pub fn nested() {
-    let mut a : Option<& str>;
+    let mut a: Option<&str>;
     a = None;
-    let mut b : Option<& str>;
+    let mut b: Option<&str>;
     b = None;
-    let mut d = MyStruct{ x: None, y: 1};
-    let mut e = MyStruct{ x: None, y: 1};
+    let mut d = MyStruct { x: None, y: 1 };
+    let mut e = MyStruct { x: None, y: 1 };
     (|| {
         (|| {
-            // This will not trigger a warning for unused variable as
-            // d.x will be treated as a Non-tracked place
-            d.x = Some("d1");
+            d.x = Some("d1"); //~ WARN value assigned to `d.x` is never read
             d.x = Some("d2");
             a = Some("d1"); //~ WARN value assigned to `a` is never read
             a = Some("d2");
         })();
         (move || {
-            // This will not trigger a warning for unused variable as
-            //e.x will be treated as a Non-tracked place
-            e.x = Some("e1");
-            e.x = Some("e2");
-            b = Some("e1"); //~ WARN value assigned to `b` is never read
-                            //~| WARN unused variable: `b`
-            b = Some("e2"); //~ WARN value assigned to `b` is never read
+            e.x = Some("e1"); //~  WARN value captured by `e.x` is never read
+                              //~| WARN value assigned to `e.x` is never read
+            e.x = Some("e2"); //~  WARN value assigned to `e.x` is never read
+            b = Some("e1"); //~  WARN value captured by `b` is never read
+                            //~| WARN value assigned to `b` is never read
+            b = Some("e2"); //~  WARN value assigned to `b` is never read
         })();
     })();
 }
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr
index cf414adc0b943..f697662d3ee93 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr
@@ -1,10 +1,10 @@
-warning: value captured by `a` is never read
-  --> $DIR/liveness.rs:24:9
+warning: value assigned to `c.x` is never read
+  --> $DIR/liveness.rs:46:9
    |
-LL |         a = 1;
-   |         ^
+LL |         c.x += 1;
+   |         ^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
 note: the lint level is defined here
   --> $DIR/liveness.rs:5:9
    |
@@ -12,54 +12,125 @@ LL | #![warn(unused)]
    |         ^^^^^^
    = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
 
-warning: unused variable: `a`
-  --> $DIR/liveness.rs:33:9
+warning: value assigned to `a` is never read
+  --> $DIR/liveness.rs:48:9
+   |
+LL |         a += 1;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `c.x` is never read
+  --> $DIR/liveness.rs:28:9
+   |
+LL |         c.x += 1;
+   |         ^^^
+   |
+   = help: did you mean to capture by reference instead?
+
+warning: value assigned to `c.x` is never read
+  --> $DIR/liveness.rs:28:9
+   |
+LL |         c.x += 1;
+   |         ^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `a` is never read
+  --> $DIR/liveness.rs:30:9
    |
 LL |         a += 1;
    |         ^
    |
    = help: did you mean to capture by reference instead?
-   = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
 
 warning: value assigned to `a` is never read
-  --> $DIR/liveness.rs:53:9
+  --> $DIR/liveness.rs:30:9
    |
 LL |         a += 1;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `c.x` is never read
+  --> $DIR/liveness.rs:20:9
+   |
+LL |         c.x = 1;
+   |         ^^^
+   |
+   = help: did you mean to capture by reference instead?
+
+warning: value captured by `a` is never read
+  --> $DIR/liveness.rs:22:9
+   |
+LL |         a = 1;
    |         ^
    |
+   = help: did you mean to capture by reference instead?
+
+warning: value captured by `e.x` is never read
+  --> $DIR/liveness.rs:74:13
+   |
+LL |             e.x = Some("e1");
+   |             ^^^
+   |
+   = help: did you mean to capture by reference instead?
+
+warning: value assigned to `e.x` is never read
+  --> $DIR/liveness.rs:74:13
+   |
+LL |             e.x = Some("e1");
+   |             ^^^^^^^^^^^^^^^^
+   |
    = help: maybe it is overwritten before being read?
 
-warning: value assigned to `a` is never read
+warning: value assigned to `e.x` is never read
+  --> $DIR/liveness.rs:76:13
+   |
+LL |             e.x = Some("e2");
+   |             ^^^^^^^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `b` is never read
   --> $DIR/liveness.rs:77:13
    |
-LL |             a = Some("d1");
+LL |             b = Some("e1");
    |             ^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 
 warning: value assigned to `b` is never read
-  --> $DIR/liveness.rs:85:13
+  --> $DIR/liveness.rs:77:13
    |
 LL |             b = Some("e1");
-   |             ^
+   |             ^^^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
 warning: value assigned to `b` is never read
-  --> $DIR/liveness.rs:87:13
+  --> $DIR/liveness.rs:79:13
    |
 LL |             b = Some("e2");
-   |             ^
+   |             ^^^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
-warning: unused variable: `b`
-  --> $DIR/liveness.rs:85:13
+warning: value assigned to `d.x` is never read
+  --> $DIR/liveness.rs:68:13
    |
-LL |             b = Some("e1");
-   |             ^
+LL |             d.x = Some("d1");
+   |             ^^^^^^^^^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
+
+warning: value assigned to `a` is never read
+  --> $DIR/liveness.rs:70:13
+   |
+LL |             a = Some("d1");
+   |             ^^^^^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
 
-warning: 7 warnings emitted
+warning: 16 warnings emitted
 
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs
index bcd4d1090a411..69b0a3eb7a5fc 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs
@@ -12,14 +12,16 @@ struct MyStruct {
 
 pub fn unintentional_copy_one() {
     let mut a = 1;
-    let mut last = MyStruct{ a: 1, b: 1};
+    //~^ unused variable: `a`
+    let mut last = MyStruct { a: 1, b: 1 };
+    //~^ unused variable: `last`
     let mut f = move |s| {
-        // This will not trigger a warning for unused variable
-        // as last.a will be treated as a Non-tracked place
         last.a = s;
+        //~^ WARN value captured by `last.a` is never read
+        //~| WARN value assigned to `last.a` is never read
         a = s;
-        //~^ WARN value assigned to `a` is never read
-        //~| WARN unused variable: `a`
+        //~^ WARN value captured by `a` is never read
+        //~| WARN value assigned to `a` is never read
     };
     f(2);
     f(3);
@@ -28,12 +30,16 @@ pub fn unintentional_copy_one() {
 
 pub fn unintentional_copy_two() {
     let mut a = 1;
-    let mut sum = MyStruct{ a: 1, b: 0};
+    //~^ WARN unused variable: `a`
+    let mut sum = MyStruct { a: 1, b: 0 };
+    //~^ WARN unused variable: `sum`
     (1..10).for_each(move |x| {
-        // This will not trigger a warning for unused variable
-        // as sum.b will be treated as a Non-tracked place
         sum.b += x;
-        a += x; //~ WARN unused variable: `a`
+        //~^ WARN value captured by `sum.b` is never read
+        //~| WARN value assigned to `sum.b` is never read
+        a += x;
+        //~^ WARN value captured by `a` is never read
+        //~| WARN value assigned to `a` is never read
     });
 }
 
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr
index 0410de4c7982a..35b6d547eece0 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr
@@ -1,10 +1,10 @@
-warning: value assigned to `a` is never read
-  --> $DIR/liveness_unintentional_copy.rs:20:9
+warning: value captured by `last.a` is never read
+  --> $DIR/liveness_unintentional_copy.rs:19:9
    |
-LL |         a = s;
-   |         ^
+LL |         last.a = s;
+   |         ^^^^^^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 note: the lint level is defined here
   --> $DIR/liveness_unintentional_copy.rs:4:9
    |
@@ -12,22 +12,87 @@ LL | #![warn(unused)]
    |         ^^^^^^
    = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
 
-warning: unused variable: `a`
-  --> $DIR/liveness_unintentional_copy.rs:20:9
+warning: value assigned to `last.a` is never read
+  --> $DIR/liveness_unintentional_copy.rs:19:9
+   |
+LL |         last.a = s;
+   |         ^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `a` is never read
+  --> $DIR/liveness_unintentional_copy.rs:22:9
    |
 LL |         a = s;
    |         ^
    |
    = help: did you mean to capture by reference instead?
-   = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
+
+warning: value assigned to `a` is never read
+  --> $DIR/liveness_unintentional_copy.rs:22:9
+   |
+LL |         a = s;
+   |         ^^^^^
+   |
+   = help: maybe it is overwritten before being read?
 
 warning: unused variable: `a`
-  --> $DIR/liveness_unintentional_copy.rs:36:9
+  --> $DIR/liveness_unintentional_copy.rs:14:9
+   |
+LL |     let mut a = 1;
+   |         ^^^^^ help: if this is intentional, prefix it with an underscore: `_a`
+   |
+   = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
+
+warning: unused variable: `last`
+  --> $DIR/liveness_unintentional_copy.rs:16:9
+   |
+LL |     let mut last = MyStruct { a: 1, b: 1 };
+   |         ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_last`
+
+warning: value captured by `sum.b` is never read
+  --> $DIR/liveness_unintentional_copy.rs:37:9
+   |
+LL |         sum.b += x;
+   |         ^^^^^
+   |
+   = help: did you mean to capture by reference instead?
+
+warning: value assigned to `sum.b` is never read
+  --> $DIR/liveness_unintentional_copy.rs:37:9
+   |
+LL |         sum.b += x;
+   |         ^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `a` is never read
+  --> $DIR/liveness_unintentional_copy.rs:40:9
    |
 LL |         a += x;
    |         ^
    |
    = help: did you mean to capture by reference instead?
 
-warning: 3 warnings emitted
+warning: value assigned to `a` is never read
+  --> $DIR/liveness_unintentional_copy.rs:40:9
+   |
+LL |         a += x;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: unused variable: `a`
+  --> $DIR/liveness_unintentional_copy.rs:32:9
+   |
+LL |     let mut a = 1;
+   |         ^^^^^ help: if this is intentional, prefix it with an underscore: `_a`
+
+warning: unused variable: `sum`
+  --> $DIR/liveness_unintentional_copy.rs:34:9
+   |
+LL |     let mut sum = MyStruct { a: 1, b: 0 };
+   |         ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_sum`
+
+warning: 12 warnings emitted
 
diff --git a/tests/ui/closures/2229_closure_analysis/issue-87987.rs b/tests/ui/closures/2229_closure_analysis/issue-87987.rs
index f79a8f1b57100..b599af2e6ccee 100644
--- a/tests/ui/closures/2229_closure_analysis/issue-87987.rs
+++ b/tests/ui/closures/2229_closure_analysis/issue-87987.rs
@@ -9,6 +9,7 @@ struct Props {
 fn main() {
     // Test 1
     let props_2 = Props { field_1: 1, field_2: 1 };
+    //~^ WARN unused variable: `props_2`
 
     let _ = || {
         let _: Props = props_2;
diff --git a/tests/ui/closures/2229_closure_analysis/issue-87987.stderr b/tests/ui/closures/2229_closure_analysis/issue-87987.stderr
index 5696a010c3f80..e141eaa97768b 100644
--- a/tests/ui/closures/2229_closure_analysis/issue-87987.stderr
+++ b/tests/ui/closures/2229_closure_analysis/issue-87987.stderr
@@ -1,3 +1,11 @@
+warning: unused variable: `props_2`
+  --> $DIR/issue-87987.rs:11:9
+   |
+LL |     let props_2 = Props { field_1: 1, field_2: 1 };
+   |         ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_props_2`
+   |
+   = note: `#[warn(unused_variables)]` on by default
+
 warning: fields `field_1` and `field_2` are never read
   --> $DIR/issue-87987.rs:5:5
    |
@@ -10,5 +18,5 @@ LL |     field_2: u32,
    |
    = note: `#[warn(dead_code)]` on by default
 
-warning: 1 warning emitted
+warning: 2 warnings emitted
 
diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr b/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr
index cf8bd7a0a2765..40274c88318c3 100644
--- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr
+++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr
@@ -1,8 +1,8 @@
-warning: unused variable: `g2`
-  --> $DIR/destructure-pattern-closure-within-closure.rs:10:17
+warning: unused variable: `t2`
+  --> $DIR/destructure-pattern-closure-within-closure.rs:13:21
    |
-LL |         let (_, g2) = g;
-   |                 ^^ help: if this is intentional, prefix it with an underscore: `_g2`
+LL |             let (_, t2) = t;
+   |                     ^^ help: if this is intentional, prefix it with an underscore: `_t2`
    |
 note: the lint level is defined here
   --> $DIR/destructure-pattern-closure-within-closure.rs:3:9
@@ -11,11 +11,11 @@ LL | #![warn(unused)]
    |         ^^^^^^
    = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
 
-warning: unused variable: `t2`
-  --> $DIR/destructure-pattern-closure-within-closure.rs:13:21
+warning: unused variable: `g2`
+  --> $DIR/destructure-pattern-closure-within-closure.rs:10:17
    |
-LL |             let (_, t2) = t;
-   |                     ^^ help: if this is intentional, prefix it with an underscore: `_t2`
+LL |         let (_, g2) = g;
+   |                 ^^ help: if this is intentional, prefix it with an underscore: `_g2`
 
 warning: 2 warnings emitted
 
diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs
index 6d6779ca6bd20..4a26ec07c9d9c 100644
--- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs
+++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs
@@ -43,6 +43,7 @@ fn test3() {
 
 fn test4() {
     let t = (String::from("Hello"), String::from("World"));
+    //~^ WARN unused variable: `t`
 
     let c = ||  {
         let (_, _) = t;
@@ -80,7 +81,9 @@ fn test7() {
 
 fn test8() {
     let x = 0;
+    //~^ WARN unused variable: `x`
     let tup = (1, 2);
+    //~^ WARN unused variable: `tup`
     let p = Point { x: 10, y: 20 };
 
     let c = || {
diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr
index 7706f68ba5b44..6523f2b34d537 100644
--- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr
+++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr
@@ -29,11 +29,29 @@ warning: unused variable: `t2`
 LL |         let (_, t2) = t;
    |                 ^^ help: if this is intentional, prefix it with an underscore: `_t2`
 
+warning: unused variable: `t`
+  --> $DIR/destructure_patterns.rs:45:9
+   |
+LL |     let t = (String::from("Hello"), String::from("World"));
+   |         ^ help: if this is intentional, prefix it with an underscore: `_t`
+
 warning: unused variable: `x`
-  --> $DIR/destructure_patterns.rs:88:21
+  --> $DIR/destructure_patterns.rs:91:21
    |
 LL |         let Point { x, y } = p;
    |                     ^ help: try ignoring the field: `x: _`
 
-warning: 5 warnings emitted
+warning: unused variable: `x`
+  --> $DIR/destructure_patterns.rs:83:9
+   |
+LL |     let x = 0;
+   |         ^ help: if this is intentional, prefix it with an underscore: `_x`
+
+warning: unused variable: `tup`
+  --> $DIR/destructure_patterns.rs:85:9
+   |
+LL |     let tup = (1, 2);
+   |         ^^^ help: if this is intentional, prefix it with an underscore: `_tup`
+
+warning: 8 warnings emitted
 
diff --git a/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs b/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs
index 39dd6cb031f79..da25c0146c543 100644
--- a/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs
+++ b/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs
@@ -1,6 +1,6 @@
 //@ aux-build:trait_object_lt_defaults_lib.rs
 //@ run-pass
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 extern crate trait_object_lt_defaults_lib;
 
 // Tests that `A<'a, 3, dyn Test>` is short for `A<'a, 3, dyn Test + 'a>`
diff --git a/tests/ui/issues/issue-11958.rs b/tests/ui/issues/issue-11958.rs
index 9185c5158af64..6291f5e4033a4 100644
--- a/tests/ui/issues/issue-11958.rs
+++ b/tests/ui/issues/issue-11958.rs
@@ -5,7 +5,8 @@
 
 pub fn main() {
     let mut x = 1;
+    //~^ WARN unused variable: `x`
     let _thunk = Box::new(move|| { x = 2; });
-    //~^ WARN value assigned to `x` is never read
-    //~| WARN unused variable: `x`
+    //~^ WARN value captured by `x` is never read
+    //~| WARN value assigned to `x` is never read
 }
diff --git a/tests/ui/issues/issue-11958.stderr b/tests/ui/issues/issue-11958.stderr
index 5dca4c2f01d56..f593057362477 100644
--- a/tests/ui/issues/issue-11958.stderr
+++ b/tests/ui/issues/issue-11958.stderr
@@ -1,20 +1,27 @@
-warning: value assigned to `x` is never read
-  --> $DIR/issue-11958.rs:8:36
+warning: value captured by `x` is never read
+  --> $DIR/issue-11958.rs:9:36
    |
 LL |     let _thunk = Box::new(move|| { x = 2; });
    |                                    ^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
    = note: `#[warn(unused_assignments)]` on by default
 
-warning: unused variable: `x`
-  --> $DIR/issue-11958.rs:8:36
+warning: value assigned to `x` is never read
+  --> $DIR/issue-11958.rs:9:36
    |
 LL |     let _thunk = Box::new(move|| { x = 2; });
-   |                                    ^
+   |                                    ^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: unused variable: `x`
+  --> $DIR/issue-11958.rs:7:9
+   |
+LL |     let mut x = 1;
+   |         ^^^^^ help: if this is intentional, prefix it with an underscore: `_x`
    |
-   = help: did you mean to capture by reference instead?
    = note: `#[warn(unused_variables)]` on by default
 
-warning: 2 warnings emitted
+warning: 3 warnings emitted
 
diff --git a/tests/ui/issues/issue-24353.rs b/tests/ui/issues/issue-24353.rs
index 369fc238d1173..c754b9d631a4e 100644
--- a/tests/ui/issues/issue-24353.rs
+++ b/tests/ui/issues/issue-24353.rs
@@ -4,5 +4,6 @@ fn main() {
     return ();
 
     let x = ();
+    //~^ WARN unused variable: `x`
     x
 }
diff --git a/tests/ui/issues/issue-24353.stderr b/tests/ui/issues/issue-24353.stderr
new file mode 100644
index 0000000000000..bd7699a1bbb0c
--- /dev/null
+++ b/tests/ui/issues/issue-24353.stderr
@@ -0,0 +1,10 @@
+warning: unused variable: `x`
+  --> $DIR/issue-24353.rs:6:9
+   |
+LL |     let x = ();
+   |         ^ help: if this is intentional, prefix it with an underscore: `_x`
+   |
+   = note: `#[warn(unused_variables)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/lint/dead-code/issue-85071-2.rs b/tests/ui/lint/dead-code/issue-85071-2.rs
index 5db8735899410..86a8d70a4e4ba 100644
--- a/tests/ui/lint/dead-code/issue-85071-2.rs
+++ b/tests/ui/lint/dead-code/issue-85071-2.rs
@@ -18,5 +18,4 @@ fn main() {
     let x = s.f();
     //~^ WARNING: unused variable: `x`
     let _y = x;
-    //~^ WARNING: unreachable definition
 }
diff --git a/tests/ui/lint/dead-code/issue-85071-2.stderr b/tests/ui/lint/dead-code/issue-85071-2.stderr
index 5e963183d094b..d30dfc178bfec 100644
--- a/tests/ui/lint/dead-code/issue-85071-2.stderr
+++ b/tests/ui/lint/dead-code/issue-85071-2.stderr
@@ -1,23 +1,3 @@
-warning: unreachable definition
-  --> $DIR/issue-85071-2.rs:20:9
-   |
-LL |     let x = s.f();
-   |             ----- any code following this expression is unreachable
-LL |
-LL |     let _y = x;
-   |         ^^ unreachable definition
-   |
-note: this expression has type `Foo`, which is uninhabited
-  --> $DIR/issue-85071-2.rs:18:13
-   |
-LL |     let x = s.f();
-   |             ^^^^^
-note: the lint level is defined here
-  --> $DIR/issue-85071-2.rs:7:26
-   |
-LL | #![warn(unused_variables,unreachable_code)]
-   |                          ^^^^^^^^^^^^^^^^
-
 warning: unused variable: `x`
   --> $DIR/issue-85071-2.rs:18:9
    |
@@ -30,5 +10,5 @@ note: the lint level is defined here
 LL | #![warn(unused_variables,unreachable_code)]
    |         ^^^^^^^^^^^^^^^^
 
-warning: 2 warnings emitted
+warning: 1 warning emitted
 
diff --git a/tests/ui/lint/dead-code/issue-85071.rs b/tests/ui/lint/dead-code/issue-85071.rs
index 84f2c9fc74eeb..033cc75f47c67 100644
--- a/tests/ui/lint/dead-code/issue-85071.rs
+++ b/tests/ui/lint/dead-code/issue-85071.rs
@@ -15,5 +15,4 @@ fn main() {
     let x = f();
     //~^ WARNING: unused variable: `x`
     let _ = x;
-    //~^ WARNING: unreachable expression
 }
diff --git a/tests/ui/lint/dead-code/issue-85071.stderr b/tests/ui/lint/dead-code/issue-85071.stderr
index 721fb8148d96b..8236c725624d0 100644
--- a/tests/ui/lint/dead-code/issue-85071.stderr
+++ b/tests/ui/lint/dead-code/issue-85071.stderr
@@ -1,23 +1,3 @@
-warning: unreachable expression
-  --> $DIR/issue-85071.rs:17:13
-   |
-LL |     let x = f();
-   |             --- any code following this expression is unreachable
-LL |
-LL |     let _ = x;
-   |             ^ unreachable expression
-   |
-note: this expression has type `Foo`, which is uninhabited
-  --> $DIR/issue-85071.rs:15:13
-   |
-LL |     let x = f();
-   |             ^^^
-note: the lint level is defined here
-  --> $DIR/issue-85071.rs:9:26
-   |
-LL | #![warn(unused_variables,unreachable_code)]
-   |                          ^^^^^^^^^^^^^^^^
-
 warning: unused variable: `x`
   --> $DIR/issue-85071.rs:15:9
    |
@@ -30,5 +10,5 @@ note: the lint level is defined here
 LL | #![warn(unused_variables,unreachable_code)]
    |         ^^^^^^^^^^^^^^^^
 
-warning: 2 warnings emitted
+warning: 1 warning emitted
 
diff --git a/tests/ui/lint/future-incompat-json-test.stderr b/tests/ui/lint/future-incompat-json-test.stderr
index bf29b9577db5e..2e7d0cf626f1d 100644
--- a/tests/ui/lint/future-incompat-json-test.stderr
+++ b/tests/ui/lint/future-incompat-json-test.stderr
@@ -1,4 +1,4 @@
-{"$message_type":"future_incompat","future_incompat_report":[{"diagnostic":{"$message_type":"diagnostic","message":"unused variable: `x`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":"    let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":"    let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":"_x","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `x`
+{"$message_type":"future_incompat","future_incompat_report":[{"diagnostic":{"$message_type":"diagnostic","message":"unused variable: `x`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":"    let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":"    let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":"_x","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `x`
   --> $DIR/future-incompat-json-test.rs:9:9
    |
 LL |     let x = 1;
diff --git a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs
index 8e74531e7762a..5b5843a8ddbb6 100644
--- a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs
+++ b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs
@@ -13,4 +13,5 @@ macro_rules! pat {
 
 fn main() {
     let pat!(value) = Value { value: () };
+    //~^ WARN value assigned to `value` is never read
 }
diff --git a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr
new file mode 100644
index 0000000000000..e30d433cdfa38
--- /dev/null
+++ b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr
@@ -0,0 +1,11 @@
+warning: value assigned to `value` is never read
+  --> $DIR/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs:15:14
+   |
+LL |     let pat!(value) = Value { value: () };
+   |              ^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+   = note: `#[warn(unused_assignments)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/lint/lint-uppercase-variables.stderr b/tests/ui/lint/lint-uppercase-variables.stderr
index 9220828014fda..42ec9364bc6e6 100644
--- a/tests/ui/lint/lint-uppercase-variables.stderr
+++ b/tests/ui/lint/lint-uppercase-variables.stderr
@@ -12,6 +12,12 @@ error[E0170]: pattern binding `Foo` is named the same as one of the variants of
 LL |     let Foo = foo::Foo::Foo;
    |         ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
 
+error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
+  --> $DIR/lint-uppercase-variables.rs:33:17
+   |
+LL |     fn in_param(Foo: foo::Foo) {}
+   |                 ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
+
 warning: unused variable: `Foo`
   --> $DIR/lint-uppercase-variables.rs:22:9
    |
@@ -31,12 +37,6 @@ warning: unused variable: `Foo`
 LL |     let Foo = foo::Foo::Foo;
    |         ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
 
-error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
-  --> $DIR/lint-uppercase-variables.rs:33:17
-   |
-LL |     fn in_param(Foo: foo::Foo) {}
-   |                 ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
-
 warning: unused variable: `Foo`
   --> $DIR/lint-uppercase-variables.rs:33:17
    |
diff --git a/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr b/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr
index 817e16fdcaa06..f116d4b6b2fb9 100644
--- a/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr
+++ b/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr
@@ -2,11 +2,19 @@ warning: unused variable: `x`
   --> $DIR/expect_lint_from_macro.rs:9:13
    |
 LL |         let x = 0;
-   |             ^ help: if this is intentional, prefix it with an underscore: `_x`
+   |             ^
 ...
 LL |     trigger_unused_variables_macro!();
    |     --------------------------------- in this macro invocation
    |
+help: `x` is captured in macro and introduced a unused variable
+  --> $DIR/expect_lint_from_macro.rs:9:13
+   |
+LL |         let x = 0;
+   |             ^
+...
+LL |     trigger_unused_variables_macro!();
+   |     --------------------------------- in this macro invocation
 note: the lint level is defined here
   --> $DIR/expect_lint_from_macro.rs:5:9
    |
@@ -18,11 +26,19 @@ warning: unused variable: `x`
   --> $DIR/expect_lint_from_macro.rs:9:13
    |
 LL |         let x = 0;
-   |             ^ help: if this is intentional, prefix it with an underscore: `_x`
+   |             ^
 ...
 LL |     trigger_unused_variables_macro!();
    |     --------------------------------- in this macro invocation
    |
+help: `x` is captured in macro and introduced a unused variable
+  --> $DIR/expect_lint_from_macro.rs:9:13
+   |
+LL |         let x = 0;
+   |             ^
+...
+LL |     trigger_unused_variables_macro!();
+   |     --------------------------------- in this macro invocation
    = note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 warning: 2 warnings emitted
diff --git a/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr b/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr
index 169f03aed9417..20be60a23c63c 100644
--- a/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr
+++ b/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr
@@ -1,3 +1,13 @@
+warning: variable does not need to be mutable
+  --> $DIR/force_warn_expected_lints_fulfilled.rs:32:9
+   |
+LL |     let mut what_does_the_fox_say = "*ding* *deng* *dung*";
+   |         ----^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         help: remove this `mut`
+   |
+   = note: requested on the command line with `--force-warn unused-mut`
+
 warning: unused variable: `x`
   --> $DIR/force_warn_expected_lints_fulfilled.rs:20:9
    |
@@ -12,16 +22,6 @@ warning: unused variable: `fox_name`
 LL |     let fox_name = "Sir Nibbles";
    |         ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_fox_name`
 
-warning: variable does not need to be mutable
-  --> $DIR/force_warn_expected_lints_fulfilled.rs:32:9
-   |
-LL |     let mut what_does_the_fox_say = "*ding* *deng* *dung*";
-   |         ----^^^^^^^^^^^^^^^^^^^^^
-   |         |
-   |         help: remove this `mut`
-   |
-   = note: requested on the command line with `--force-warn unused-mut`
-
 warning: unused variable: `this_should_fulfill_the_expectation`
   --> $DIR/force_warn_expected_lints_fulfilled.rs:43:9
    |
diff --git a/tests/ui/lint/unused/issue-117284-arg-in-macro.rs b/tests/ui/lint/unused/issue-117284-arg-in-macro.rs
index eea0f4c594dc8..3d622b6991300 100644
--- a/tests/ui/lint/unused/issue-117284-arg-in-macro.rs
+++ b/tests/ui/lint/unused/issue-117284-arg-in-macro.rs
@@ -1,7 +1,7 @@
 #![deny(unused_variables)]
 macro_rules! make_var {
     ($struct:ident, $var:ident) => {
-        let $var = $struct.$var;
+        let $var = $struct.$var; //~ ERROR unused variable: `var`
     };
 }
 
@@ -12,6 +12,6 @@ struct MyStruct {
 
 fn main() {
     let s = MyStruct { var: 42 };
-    make_var!(s, var); //~ ERROR unused variable: `var`
+    make_var!(s, var);
     let a = 1; //~ ERROR unused variable: `a`
 }
diff --git a/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr b/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr
index 84efaa4f3687b..b4a6871713c06 100644
--- a/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr
+++ b/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr
@@ -1,8 +1,11 @@
 error: unused variable: `var`
-  --> $DIR/issue-117284-arg-in-macro.rs:15:18
+  --> $DIR/issue-117284-arg-in-macro.rs:4:13
    |
+LL |         let $var = $struct.$var;
+   |             ^^^^
+...
 LL |     make_var!(s, var);
-   |                  ^^^
+   |     ----------------- in this macro invocation
    |
 help: `var` is captured in macro and introduced a unused variable
   --> $DIR/issue-117284-arg-in-macro.rs:4:13
diff --git a/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr b/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr
index fe2e3afc82ec8..c378b307b8b54 100644
--- a/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr
+++ b/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr
@@ -1,27 +1,45 @@
-warning: unused variable: `i_think_continually`
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:26:9
+warning: variable does not need to be mutable
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9
    |
-LL |     let i_think_continually = 2;
-   |         ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_i_think_continually`
+LL |     let mut mut_unused_var = 1;
+   |         ----^^^^^^^^^^^^^^
+   |         |
+   |         help: remove this `mut`
    |
 note: the lint level is defined here
   --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:5:9
    |
 LL | #![warn(unused)] // UI tests pass `-A unused` (#43896)
    |         ^^^^^^
+   = note: `#[warn(unused_mut)]` implied by `#[warn(unused)]`
+
+warning: variable does not need to be mutable
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10
+   |
+LL |     let (mut var, unused_var) = (1, 2);
+   |          ----^^^
+   |          |
+   |          help: remove this `mut`
+
+warning: unused variable: `i_think_continually`
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:26:9
+   |
+LL |     let i_think_continually = 2;
+   |         ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_i_think_continually`
+   |
    = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
 
 warning: unused variable: `mut_unused_var`
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:13
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9
    |
 LL |     let mut mut_unused_var = 1;
-   |             ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_mut_unused_var`
+   |         ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_mut_unused_var`
 
 warning: unused variable: `var`
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:14
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10
    |
 LL |     let (mut var, unused_var) = (1, 2);
-   |              ^^^ help: if this is intentional, prefix it with an underscore: `_var`
+   |          ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_var`
 
 warning: unused variable: `unused_var`
   --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:19
@@ -36,22 +54,13 @@ LL |     if let SoulHistory { corridors_of_light,
    |                          ^^^^^^^^^^^^^^^^^^ help: try ignoring the field: `corridors_of_light: _`
 
 warning: variable `hours_are_suns` is assigned to, but never used
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:46:30
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:46:26
    |
 LL |                          mut hours_are_suns,
-   |                              ^^^^^^^^^^^^^^
+   |                          ^^^^^^^^^^^^^^^^^^
    |
    = note: consider using `_hours_are_suns` instead
 
-warning: value assigned to `hours_are_suns` is never read
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9
-   |
-LL |         hours_are_suns = false;
-   |         ^^^^^^^^^^^^^^
-   |
-   = help: maybe it is overwritten before being read?
-   = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
-
 warning: unused variable: `fire`
   --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:52:32
    |
@@ -94,23 +103,14 @@ warning: unused variable: `case`
 LL |         Tuple(Large::Suit { case }, ()) => {}
    |                             ^^^^ help: try ignoring the field: `case: _`
 
-warning: variable does not need to be mutable
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9
-   |
-LL |     let mut mut_unused_var = 1;
-   |         ----^^^^^^^^^^^^^^
-   |         |
-   |         help: remove this `mut`
+warning: value assigned to `hours_are_suns` is never read
+  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9
    |
-   = note: `#[warn(unused_mut)]` implied by `#[warn(unused)]`
-
-warning: variable does not need to be mutable
-  --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10
+LL |         hours_are_suns = false;
+   |         ^^^^^^^^^^^^^^^^^^^^^^
    |
-LL |     let (mut var, unused_var) = (1, 2);
-   |          ----^^^
-   |          |
-   |          help: remove this `mut`
+   = help: maybe it is overwritten before being read?
+   = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
 
 warning: 16 warnings emitted
 
diff --git a/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr b/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr
index f2e6168998c44..c501aa25f1352 100644
--- a/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr
+++ b/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr
@@ -11,6 +11,12 @@ LL | #![deny(unused)]
    |         ^^^^^^
    = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]`
 
+error: unused variable: `x`
+  --> $DIR/issue-54180-unused-ref-field.rs:29:45
+   |
+LL |     let _: i32 = points.iter().map(|Point { x, y }| y).sum();
+   |                                             ^ help: try ignoring the field: `x: _`
+
 error: unused variable: `f1`
   --> $DIR/issue-54180-unused-ref-field.rs:26:13
    |
@@ -23,11 +29,5 @@ error: unused variable: `x`
 LL |         Point { y, ref mut x } => y,
    |                    ^^^^^^^^^ help: try ignoring the field: `x: _`
 
-error: unused variable: `x`
-  --> $DIR/issue-54180-unused-ref-field.rs:29:45
-   |
-LL |     let _: i32 = points.iter().map(|Point { x, y }| y).sum();
-   |                                             ^ help: try ignoring the field: `x: _`
-
 error: aborting due to 4 previous errors
 
diff --git a/tests/ui/lint/unused/lint-unused-variables.stderr b/tests/ui/lint/unused/lint-unused-variables.stderr
index 09729eeba7923..dc60ef374c0a8 100644
--- a/tests/ui/lint/unused/lint-unused-variables.stderr
+++ b/tests/ui/lint/unused/lint-unused-variables.stderr
@@ -10,18 +10,18 @@ note: the lint level is defined here
 LL | #![deny(unused_variables)]
    |         ^^^^^^^^^^^^^^^^
 
-error: unused variable: `a`
-  --> $DIR/lint-unused-variables.rs:22:9
-   |
-LL |         a: i32,
-   |         ^ help: if this is intentional, prefix it with an underscore: `_a`
-
 error: unused variable: `b`
   --> $DIR/lint-unused-variables.rs:14:5
    |
 LL |     b: i32,
    |     ^ help: if this is intentional, prefix it with an underscore: `_b`
 
+error: unused variable: `a`
+  --> $DIR/lint-unused-variables.rs:22:9
+   |
+LL |         a: i32,
+   |         ^ help: if this is intentional, prefix it with an underscore: `_a`
+
 error: unused variable: `b`
   --> $DIR/lint-unused-variables.rs:29:9
    |
@@ -58,17 +58,17 @@ error: unused variable: `b`
 LL |         b: i32,
    |         ^ help: if this is intentional, prefix it with an underscore: `_b`
 
-error: unused variable: `a`
-  --> $DIR/lint-unused-variables.rs:68:9
-   |
-LL |         a: i32,
-   |         ^ help: if this is intentional, prefix it with an underscore: `_a`
-
 error: unused variable: `b`
   --> $DIR/lint-unused-variables.rs:74:9
    |
 LL |         b: i32,
    |         ^ help: if this is intentional, prefix it with an underscore: `_b`
 
+error: unused variable: `a`
+  --> $DIR/lint-unused-variables.rs:68:9
+   |
+LL |         a: i32,
+   |         ^ help: if this is intentional, prefix it with an underscore: `_a`
+
 error: aborting due to 11 previous errors
 
diff --git a/tests/ui/liveness/liveness-asm.stderr b/tests/ui/liveness/liveness-asm.stderr
index 57d89e44dcb86..ca1c07046d0ef 100644
--- a/tests/ui/liveness/liveness-asm.stderr
+++ b/tests/ui/liveness/liveness-asm.stderr
@@ -1,8 +1,8 @@
 warning: value assigned to `src` is never read
-  --> $DIR/liveness-asm.rs:14:32
+  --> $DIR/liveness-asm.rs:14:5
    |
 LL |     asm!("/*{0}*/", inout(reg) src);
-   |                                ^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 note: the lint level is defined here
@@ -12,10 +12,10 @@ LL | #![warn(unused_assignments)]
    |         ^^^^^^^^^^^^^^^^^^
 
 warning: value assigned to `src` is never read
-  --> $DIR/liveness-asm.rs:24:39
+  --> $DIR/liveness-asm.rs:24:5
    |
 LL |     asm!("/*{0}*/", inout(reg) src => src);
-   |                                       ^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
diff --git a/tests/ui/liveness/liveness-consts.rs b/tests/ui/liveness/liveness-consts.rs
index 40d30fb9113a9..7e56bf8c2cda4 100644
--- a/tests/ui/liveness/liveness-consts.rs
+++ b/tests/ui/liveness/liveness-consts.rs
@@ -4,16 +4,18 @@
 
 pub static A: i32 = {
     let mut i = 0;
-    let mut a = 0; //~ WARN variable `a` is assigned to, but never used
+    let mut a = 0;
+    //~^ WARN variable `a` is assigned to, but never used
     while i < 10 {
         i += 1;
         a += 1;
+        //~^ WARN value assigned to `a` is never read
     }
     i
 };
 
 pub const B: u32 = {
-    let mut b = 1;
+    let mut b = 1; //~ WARN value assigned to `b` is never read
     b += 1; //~ WARN value assigned to `b` is never read
     b = 42;
     b
diff --git a/tests/ui/liveness/liveness-consts.stderr b/tests/ui/liveness/liveness-consts.stderr
index 34ce39473379e..2d2750fbaddff 100644
--- a/tests/ui/liveness/liveness-consts.stderr
+++ b/tests/ui/liveness/liveness-consts.stderr
@@ -1,5 +1,5 @@
 warning: unused variable: `e`
-  --> $DIR/liveness-consts.rs:24:13
+  --> $DIR/liveness-consts.rs:26:13
    |
 LL |         let e = 1;
    |             ^ help: if this is intentional, prefix it with an underscore: `_e`
@@ -12,53 +12,69 @@ LL | #![warn(unused)]
    = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
 
 warning: unused variable: `s`
-  --> $DIR/liveness-consts.rs:33:24
+  --> $DIR/liveness-consts.rs:35:24
    |
 LL | pub fn f(x: [u8; { let s = 17; 100 }]) -> [u8;  { let z = 18; 100 }] {
    |                        ^ help: if this is intentional, prefix it with an underscore: `_s`
 
 warning: unused variable: `z`
-  --> $DIR/liveness-consts.rs:33:55
+  --> $DIR/liveness-consts.rs:35:55
    |
 LL | pub fn f(x: [u8; { let s = 17; 100 }]) -> [u8;  { let z = 18; 100 }] {
    |                                                       ^ help: if this is intentional, prefix it with an underscore: `_z`
 
 warning: variable `a` is assigned to, but never used
-  --> $DIR/liveness-consts.rs:7:13
+  --> $DIR/liveness-consts.rs:7:9
    |
 LL |     let mut a = 0;
-   |             ^
+   |         ^^^^^
    |
    = note: consider using `_a` instead
 
+warning: value assigned to `a` is never read
+  --> $DIR/liveness-consts.rs:11:9
+   |
+LL |         a += 1;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+   = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
+
 warning: value assigned to `b` is never read
-  --> $DIR/liveness-consts.rs:17:5
+  --> $DIR/liveness-consts.rs:18:17
+   |
+LL |     let mut b = 1;
+   |                 ^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value assigned to `b` is never read
+  --> $DIR/liveness-consts.rs:19:5
    |
 LL |     b += 1;
-   |     ^
+   |     ^^^^^^
    |
    = help: maybe it is overwritten before being read?
-   = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
 
 warning: unused variable: `z`
-  --> $DIR/liveness-consts.rs:60:13
+  --> $DIR/liveness-consts.rs:62:13
    |
 LL |         let z = 42;
    |             ^ help: if this is intentional, prefix it with an underscore: `_z`
 
 warning: value assigned to `t` is never read
-  --> $DIR/liveness-consts.rs:42:9
+  --> $DIR/liveness-consts.rs:44:9
    |
 LL |         t = t + t;
-   |         ^
+   |         ^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
 warning: unused variable: `w`
-  --> $DIR/liveness-consts.rs:49:13
+  --> $DIR/liveness-consts.rs:51:13
    |
 LL |         let w = 10;
    |             ^ help: if this is intentional, prefix it with an underscore: `_w`
 
-warning: 8 warnings emitted
+warning: 10 warnings emitted
 
diff --git a/tests/ui/liveness/liveness-dead.stderr b/tests/ui/liveness/liveness-dead.stderr
index de6d5bd993d62..4f7dda49cd03e 100644
--- a/tests/ui/liveness/liveness-dead.stderr
+++ b/tests/ui/liveness/liveness-dead.stderr
@@ -1,8 +1,8 @@
 error: value assigned to `x` is never read
-  --> $DIR/liveness-dead.rs:9:13
+  --> $DIR/liveness-dead.rs:9:24
    |
 LL |     let mut x: isize = 3;
-   |             ^
+   |                        ^
    |
    = help: maybe it is overwritten before being read?
 note: the lint level is defined here
@@ -15,15 +15,15 @@ error: value assigned to `x` is never read
   --> $DIR/liveness-dead.rs:17:5
    |
 LL |     x = 4;
-   |     ^
+   |     ^^^^^
    |
    = help: maybe it is overwritten before being read?
 
 error: value passed to `x` is never read
-  --> $DIR/liveness-dead.rs:20:11
+  --> $DIR/liveness-dead.rs:20:7
    |
 LL | fn f4(mut x: i32) {
-   |           ^
+   |       ^^^^^
    |
    = help: maybe it is overwritten before being read?
 
@@ -31,7 +31,7 @@ error: value assigned to `x` is never read
   --> $DIR/liveness-dead.rs:27:5
    |
 LL |     x = 4;
-   |     ^
+   |     ^^^^^
    |
    = help: maybe it is overwritten before being read?
 
diff --git a/tests/ui/liveness/liveness-unused.rs b/tests/ui/liveness/liveness-unused.rs
index ba635e6638c8c..a781481055391 100644
--- a/tests/ui/liveness/liveness-unused.rs
+++ b/tests/ui/liveness/liveness-unused.rs
@@ -38,6 +38,7 @@ fn f3b() {
     //~^ ERROR variable `z` is assigned to, but never used
     loop {
         z += 4;
+        //~^ ERROR value assigned to `z` is never read
     }
 }
 
@@ -45,6 +46,7 @@ fn f3b() {
 fn f3c() {
     let mut z = 3;
     loop { z += 4; }
+    //~^ ERROR value assigned to `z` is never read
 }
 
 #[allow(unused_variables)]
@@ -54,6 +56,16 @@ fn f3d() {
     x += 4;
 }
 
+fn f3e() {
+    let a = 13;
+    let mut z = 3;
+    //~^ ERROR variable `z` is assigned to, but never used
+    loop {
+        z += a;
+        //~^ ERROR value assigned to `z` is never read
+    }
+}
+
 fn f4() {
     match Some(3) {
       Some(i) => {
@@ -67,7 +79,15 @@ enum tri {
     a(isize), b(isize), c(isize)
 }
 
-fn f4b() -> isize {
+fn f4b() {
+    match tri::a(3) {
+      tri::a(i) | tri::b(i) | tri::c(i) => {
+          //~^ ERROR unused variable: `i`
+      }
+    }
+}
+
+fn f4c() -> isize {
     match tri::a(3) {
       tri::a(i) | tri::b(i) | tri::c(i) => {
         i
@@ -75,6 +95,13 @@ fn f4b() -> isize {
     }
 }
 
+fn f4d() {
+    match tri::a(3) {
+      tri::a(i) | tri::b(i) | tri::c(i) if i == 0 => {}
+      _ => {}
+    }
+}
+
 fn f5a() {
     for x in 1..10 { }
     //~^ ERROR unused variable: `x`
@@ -137,5 +164,88 @@ fn f7() {
     drop(a);
 }
 
+fn f8(a: u32) {
+    let _ = a;
+}
+
+fn f9() {
+    let mut a = 10;
+    //~^ ERROR variable `a` is assigned to, but never used
+    let b = 13;
+    let c = 13;
+    let d = 13;
+    let e = 13;
+    let f = 13;
+    let g = 13;
+    let h = 13;
+
+    a += b;
+    //~^ ERROR value assigned to `a` is never read
+    a -= c;
+    //~^ ERROR value assigned to `a` is never read
+    a *= d;
+    //~^ ERROR value assigned to `a` is never read
+    a /= e;
+    //~^ ERROR value assigned to `a` is never read
+    a |= f;
+    //~^ ERROR value assigned to `a` is never read
+    a &= g;
+    //~^ ERROR value assigned to `a` is never read
+    a %= h;
+    //~^ ERROR value assigned to `a` is never read
+}
+
+fn f9b() {
+    let mut a = 10;
+    let b = 13;
+    let c = 13;
+    let d = 13;
+    let e = 13;
+    let f = 13;
+    let g = 13;
+    let h = 13;
+
+    a += b;
+    a -= c;
+    a *= d;
+    a /= e;
+    a |= f;
+    a &= g;
+    a %= h;
+
+    let _ = a;
+}
+
+fn f9c() {
+    let mut a = 10.;
+    //~^ ERROR variable `a` is assigned to, but never used
+    let b = 13.;
+    let c = 13.;
+    let d = 13.;
+    let e = 13.;
+    let f = 13.;
+
+    a += b;
+    //~^ ERROR value assigned to `a` is never read
+    a -= c;
+    //~^ ERROR value assigned to `a` is never read
+    a *= d;
+    //~^ ERROR value assigned to `a` is never read
+    a /= e;
+    //~^ ERROR value assigned to `a` is never read
+    a %= f;
+    //~^ ERROR value assigned to `a` is never read
+}
+
+fn f10<T>(mut a: T, b: T) {
+    //~^ ERROR variable `a` is assigned to, but never used
+    a = b;
+    //~^ ERROR value assigned to `a` is never read
+}
+
+fn f10b<T>(mut a: Box<T>, b: Box<T>) {
+    a = b;
+}
+
 fn main() {
 }
diff --git a/tests/ui/liveness/liveness-unused.stderr b/tests/ui/liveness/liveness-unused.stderr
index f6c478ddbc72c..24fa7d04b8538 100644
--- a/tests/ui/liveness/liveness-unused.stderr
+++ b/tests/ui/liveness/liveness-unused.stderr
@@ -1,5 +1,5 @@
 warning: unreachable statement
-  --> $DIR/liveness-unused.rs:92:9
+  --> $DIR/liveness-unused.rs:119:9
    |
 LL |         continue;
    |         -------- any code following this expression is unreachable
@@ -44,10 +44,10 @@ LL |     let x = 3;
    |         ^ help: if this is intentional, prefix it with an underscore: `_x`
 
 error: variable `x` is assigned to, but never used
-  --> $DIR/liveness-unused.rs:30:13
+  --> $DIR/liveness-unused.rs:30:9
    |
 LL |     let mut x = 3;
-   |             ^
+   |         ^^^^^
    |
    = note: consider using `_x` instead
 
@@ -55,7 +55,7 @@ error: value assigned to `x` is never read
   --> $DIR/liveness-unused.rs:32:5
    |
 LL |     x += 4;
-   |     ^
+   |     ^^^^^^
    |
    = help: maybe it is overwritten before being read?
 note: the lint level is defined here
@@ -65,39 +65,82 @@ LL | #![deny(unused_assignments)]
    |         ^^^^^^^^^^^^^^^^^^
 
 error: variable `z` is assigned to, but never used
-  --> $DIR/liveness-unused.rs:37:13
+  --> $DIR/liveness-unused.rs:37:9
    |
 LL |     let mut z = 3;
-   |             ^
+   |         ^^^^^
    |
    = note: consider using `_z` instead
 
+error: value assigned to `z` is never read
+  --> $DIR/liveness-unused.rs:40:9
+   |
+LL |         z += 4;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `z` is never read
+  --> $DIR/liveness-unused.rs:48:12
+   |
+LL |     loop { z += 4; }
+   |            ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: variable `z` is assigned to, but never used
+  --> $DIR/liveness-unused.rs:61:9
+   |
+LL |     let mut z = 3;
+   |         ^^^^^
+   |
+   = note: consider using `_z` instead
+
+error: value assigned to `z` is never read
+  --> $DIR/liveness-unused.rs:64:9
+   |
+LL |         z += a;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
 error: unused variable: `i`
-  --> $DIR/liveness-unused.rs:59:12
+  --> $DIR/liveness-unused.rs:71:12
    |
 LL |       Some(i) => {
    |            ^ help: if this is intentional, prefix it with an underscore: `_i`
 
+error: unused variable: `i`
+  --> $DIR/liveness-unused.rs:84:14
+   |
+LL |       tri::a(i) | tri::b(i) | tri::c(i) => {
+   |              ^           ^           ^
+   |
+help: if this is intentional, prefix it with an underscore
+   |
+LL |       tri::a(_i) | tri::b(_i) | tri::c(_i) => {
+   |              ~~           ~~           ~~
+
 error: unused variable: `x`
-  --> $DIR/liveness-unused.rs:79:9
+  --> $DIR/liveness-unused.rs:106:9
    |
 LL |     for x in 1..10 { }
    |         ^ help: if this is intentional, prefix it with an underscore: `_x`
 
 error: unused variable: `x`
-  --> $DIR/liveness-unused.rs:84:10
+  --> $DIR/liveness-unused.rs:111:10
    |
 LL |     for (x, _) in [1, 2, 3].iter().enumerate() { }
    |          ^ help: if this is intentional, prefix it with an underscore: `_x`
 
 error: unused variable: `x`
-  --> $DIR/liveness-unused.rs:89:13
+  --> $DIR/liveness-unused.rs:116:13
    |
 LL |     for (_, x) in [1, 2, 3].iter().enumerate() {
    |             ^ help: if this is intentional, prefix it with an underscore: `_x`
 
 error: variable `x` is assigned to, but never used
-  --> $DIR/liveness-unused.rs:112:9
+  --> $DIR/liveness-unused.rs:139:9
    |
 LL |     let x;
    |         ^
@@ -105,12 +148,140 @@ LL |     let x;
    = note: consider using `_x` instead
 
 error: value assigned to `x` is never read
-  --> $DIR/liveness-unused.rs:116:9
+  --> $DIR/liveness-unused.rs:143:9
    |
 LL |         x = 0;
-   |         ^
+   |         ^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: variable `a` is assigned to, but never used
+  --> $DIR/liveness-unused.rs:172:9
+   |
+LL |     let mut a = 10;
+   |         ^^^^^
+   |
+   = note: consider using `_a` instead
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:182:5
+   |
+LL |     a += b;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:184:5
+   |
+LL |     a -= c;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:186:5
+   |
+LL |     a *= d;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:188:5
+   |
+LL |     a /= e;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:190:5
+   |
+LL |     a |= f;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:192:5
+   |
+LL |     a &= g;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:194:5
+   |
+LL |     a %= h;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: variable `a` is assigned to, but never used
+  --> $DIR/liveness-unused.rs:220:9
+   |
+LL |     let mut a = 10.;
+   |         ^^^^^
+   |
+   = note: consider using `_a` instead
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:228:5
+   |
+LL |     a += b;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:230:5
+   |
+LL |     a -= c;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:232:5
+   |
+LL |     a *= d;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:234:5
+   |
+LL |     a /= e;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:236:5
+   |
+LL |     a %= f;
+   |     ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+error: variable `a` is assigned to, but never used
+  --> $DIR/liveness-unused.rs:240:11
+   |
+LL | fn f10<T>(mut a: T, b: T) {
+   |           ^^^^^
+   |
+   = note: consider using `_a` instead
+
+error: value assigned to `a` is never read
+  --> $DIR/liveness-unused.rs:242:5
+   |
+LL |     a = b;
+   |     ^
    |
    = help: maybe it is overwritten before being read?
 
-error: aborting due to 13 previous errors; 1 warning emitted
+error: aborting due to 34 previous errors; 1 warning emitted
 
diff --git a/tests/ui/liveness/liveness-upvars.rs b/tests/ui/liveness/liveness-upvars.rs
index 7898b97888230..1f60d7d72eef1 100644
--- a/tests/ui/liveness/liveness-upvars.rs
+++ b/tests/ui/liveness/liveness-upvars.rs
@@ -7,8 +7,8 @@
 pub fn unintentional_copy_one() {
     let mut last = None;
     let mut f = move |s| {
-        last = Some(s); //~  WARN value assigned to `last` is never read
-                        //~| WARN unused variable: `last`
+        last = Some(s); //~  WARN value captured by `last` is never read
+                        //~| WARN value assigned to `last` is never read
     };
     f("a");
     f("b");
@@ -19,7 +19,9 @@ pub fn unintentional_copy_one() {
 pub fn unintentional_copy_two() {
     let mut sum = 0;
     (1..10).for_each(move |x| {
-        sum += x; //~ WARN unused variable: `sum`
+        sum += x;
+        //~^ WARN value captured by `sum` is never read
+        //~| WARN value assigned to `sum` is never read
     });
     dbg!(sum);
 }
@@ -39,11 +41,14 @@ pub fn f() {
 
     // Read and written to, but never actually used.
     let _ = move || {
-        c += 1; //~ WARN unused variable: `c`
+        c += 1;
+        //~^ WARN value captured by `c` is never read
+        //~|  WARN value assigned to `c` is never read
     };
     let _ = async move {
-        c += 1; //~  WARN value assigned to `c` is never read
-                //~| WARN unused variable: `c`
+        c += 1;
+        //~^ WARN value captured by `c` is never read
+        //~|  WARN value assigned to `c` is never read
     };
 
     let _ = move || {
@@ -74,8 +79,8 @@ pub fn nested() {
             d = Some("d2");
         };
         let _ = move || {
-            e = Some("e1"); //~  WARN value assigned to `e` is never read
-                            //~| WARN unused variable: `e`
+            e = Some("e1"); //~  WARN value captured by `e` is never read
+                            //~| WARN value assigned to `e` is never read
             e = Some("e2"); //~  WARN value assigned to `e` is never read
         };
     };
@@ -84,7 +89,8 @@ pub fn nested() {
 pub fn g<T: Default>(mut v: T) {
     let _ = |r| {
         if r {
-            v = T::default(); //~ WARN value assigned to `v` is never read
+            v = T::default();
+            //~^ WARN value assigned to `v` is never read
         } else {
             drop(v);
         }
@@ -96,8 +102,8 @@ pub fn h<T: Copy + Default + std::fmt::Debug>() {
     let _ = move |b| {
         loop {
             if b {
-                z = T::default(); //~  WARN value assigned to `z` is never read
-                                  //~| WARN unused variable: `z`
+                z = T::default(); //~  WARN value captured by `z` is never read
+                                  //~| WARN value assigned to `z` is never read
             } else {
                 return;
             }
@@ -123,7 +129,7 @@ pub fn async_coroutine() {
 
     let _ = async move {
         state = 4;  //~  WARN value assigned to `state` is never read
-                    //~| WARN unused variable: `state`
+                    //~| WARN value captured by `state` is never read
         yield_now().await;
         state = 5;  //~ WARN value assigned to `state` is never read
     };
diff --git a/tests/ui/liveness/liveness-upvars.stderr b/tests/ui/liveness/liveness-upvars.stderr
index 82f62371ec59d..cfed2830164ad 100644
--- a/tests/ui/liveness/liveness-upvars.stderr
+++ b/tests/ui/liveness/liveness-upvars.stderr
@@ -1,10 +1,10 @@
-warning: value assigned to `last` is never read
+warning: value captured by `last` is never read
   --> $DIR/liveness-upvars.rs:10:9
    |
 LL |         last = Some(s);
    |         ^^^^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 note: the lint level is defined here
   --> $DIR/liveness-upvars.rs:4:9
    |
@@ -12,16 +12,15 @@ LL | #![warn(unused)]
    |         ^^^^^^
    = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
 
-warning: unused variable: `last`
+warning: value assigned to `last` is never read
   --> $DIR/liveness-upvars.rs:10:9
    |
 LL |         last = Some(s);
-   |         ^^^^
+   |         ^^^^^^^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
-   = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
+   = help: maybe it is overwritten before being read?
 
-warning: unused variable: `sum`
+warning: value captured by `sum` is never read
   --> $DIR/liveness-upvars.rs:22:9
    |
 LL |         sum += x;
@@ -29,24 +28,32 @@ LL |         sum += x;
    |
    = help: did you mean to capture by reference instead?
 
-warning: value captured by `c` is never read
-  --> $DIR/liveness-upvars.rs:32:9
+warning: value assigned to `sum` is never read
+  --> $DIR/liveness-upvars.rs:22:9
    |
-LL |         c = 1;
-   |         ^
+LL |         sum += x;
+   |         ^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
 
-warning: value captured by `c` is never read
-  --> $DIR/liveness-upvars.rs:36:9
+warning: value assigned to `c` is never read
+  --> $DIR/liveness-upvars.rs:69:9
    |
-LL |         c = 1;
-   |         ^
+LL |         c += 1;
+   |         ^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
 
-warning: unused variable: `c`
-  --> $DIR/liveness-upvars.rs:42:9
+warning: value assigned to `c` is never read
+  --> $DIR/liveness-upvars.rs:63:9
+   |
+LL |         c += 1;
+   |         ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `c` is never read
+  --> $DIR/liveness-upvars.rs:49:9
    |
 LL |         c += 1;
    |         ^
@@ -54,15 +61,15 @@ LL |         c += 1;
    = help: did you mean to capture by reference instead?
 
 warning: value assigned to `c` is never read
-  --> $DIR/liveness-upvars.rs:45:9
+  --> $DIR/liveness-upvars.rs:49:9
    |
 LL |         c += 1;
-   |         ^
+   |         ^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
-warning: unused variable: `c`
-  --> $DIR/liveness-upvars.rs:45:9
+warning: value captured by `c` is never read
+  --> $DIR/liveness-upvars.rs:44:9
    |
 LL |         c += 1;
    |         ^
@@ -70,116 +77,124 @@ LL |         c += 1;
    = help: did you mean to capture by reference instead?
 
 warning: value assigned to `c` is never read
-  --> $DIR/liveness-upvars.rs:58:9
+  --> $DIR/liveness-upvars.rs:44:9
    |
 LL |         c += 1;
-   |         ^
+   |         ^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
-warning: value assigned to `c` is never read
-  --> $DIR/liveness-upvars.rs:64:9
+warning: value captured by `c` is never read
+  --> $DIR/liveness-upvars.rs:38:9
    |
-LL |         c += 1;
+LL |         c = 1;
    |         ^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 
-warning: value assigned to `d` is never read
-  --> $DIR/liveness-upvars.rs:73:13
+warning: value captured by `c` is never read
+  --> $DIR/liveness-upvars.rs:34:9
    |
-LL |             d = Some("d1");
+LL |         c = 1;
+   |         ^
+   |
+   = help: did you mean to capture by reference instead?
+
+warning: value captured by `e` is never read
+  --> $DIR/liveness-upvars.rs:82:13
+   |
+LL |             e = Some("e1");
    |             ^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 
 warning: value assigned to `e` is never read
-  --> $DIR/liveness-upvars.rs:77:13
+  --> $DIR/liveness-upvars.rs:82:13
    |
 LL |             e = Some("e1");
-   |             ^
+   |             ^^^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
 warning: value assigned to `e` is never read
-  --> $DIR/liveness-upvars.rs:79:13
+  --> $DIR/liveness-upvars.rs:84:13
    |
 LL |             e = Some("e2");
-   |             ^
+   |             ^^^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
-warning: unused variable: `e`
-  --> $DIR/liveness-upvars.rs:77:13
+warning: value assigned to `d` is never read
+  --> $DIR/liveness-upvars.rs:78:13
    |
-LL |             e = Some("e1");
-   |             ^
+LL |             d = Some("d1");
+   |             ^^^^^^^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
 
 warning: value assigned to `v` is never read
-  --> $DIR/liveness-upvars.rs:87:13
+  --> $DIR/liveness-upvars.rs:92:13
    |
 LL |             v = T::default();
    |             ^
    |
    = help: maybe it is overwritten before being read?
 
-warning: value assigned to `z` is never read
-  --> $DIR/liveness-upvars.rs:99:17
+warning: value captured by `z` is never read
+  --> $DIR/liveness-upvars.rs:105:17
    |
 LL |                 z = T::default();
    |                 ^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 
-warning: unused variable: `z`
-  --> $DIR/liveness-upvars.rs:99:17
+warning: value assigned to `z` is never read
+  --> $DIR/liveness-upvars.rs:105:17
    |
 LL |                 z = T::default();
-   |                 ^
+   |                 ^^^^^^^^^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
 
-warning: value assigned to `state` is never read
-  --> $DIR/liveness-upvars.rs:125:9
+warning: value captured by `state` is never read
+  --> $DIR/liveness-upvars.rs:131:9
    |
 LL |         state = 4;
    |         ^^^^^
    |
-   = help: maybe it is overwritten before being read?
+   = help: did you mean to capture by reference instead?
 
 warning: value assigned to `state` is never read
-  --> $DIR/liveness-upvars.rs:128:9
+  --> $DIR/liveness-upvars.rs:131:9
    |
-LL |         state = 5;
-   |         ^^^^^
+LL |         state = 4;
+   |         ^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
-warning: unused variable: `state`
-  --> $DIR/liveness-upvars.rs:125:9
+warning: value assigned to `state` is never read
+  --> $DIR/liveness-upvars.rs:134:9
    |
-LL |         state = 4;
-   |         ^^^^^
+LL |         state = 5;
+   |         ^^^^^^^^^
    |
-   = help: did you mean to capture by reference instead?
+   = help: maybe it is overwritten before being read?
 
 warning: value assigned to `s` is never read
-  --> $DIR/liveness-upvars.rs:137:9
+  --> $DIR/liveness-upvars.rs:143:9
    |
 LL |         s = 1;
-   |         ^
+   |         ^^^^^
    |
    = help: maybe it is overwritten before being read?
 
 warning: value assigned to `s` is never read
-  --> $DIR/liveness-upvars.rs:139:9
+  --> $DIR/liveness-upvars.rs:145:9
    |
 LL |         s = yield ();
-   |         ^
+   |         ^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
 
-warning: 22 warnings emitted
+warning: 24 warnings emitted
 
diff --git a/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs b/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs
index edbd9f35d4dfa..9deee6129ca76 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 
 trait Test {
     fn foo(&self) { }
diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs b/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs
index 986fc83679934..baf24696f41a4 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 
 use std::fmt::Display;
 
diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs
index 3c88f2b9f3765..5edbb24e53570 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 
 trait Test {
     fn foo(&self) { }
diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs
index 412695f708670..e2e4656cc9a0b 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 
 trait Test {
     fn foo(&self) { }
diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs
index 591f843a284a2..29db02abc8f67 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 
 trait Test {
     fn foo(&self) { }
diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs
index bc47b8d46a11e..e0c8d0ee4c145 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 
 use std::fmt::Display;
 
diff --git a/tests/ui/object-lifetime/object-lifetime-default-inferred.rs b/tests/ui/object-lifetime/object-lifetime-default-inferred.rs
index 53b9c4886450e..2f6e1bb06e29c 100644
--- a/tests/ui/object-lifetime/object-lifetime-default-inferred.rs
+++ b/tests/ui/object-lifetime/object-lifetime-default-inferred.rs
@@ -4,7 +4,7 @@
 
 //@ pretty-expanded FIXME #23616
 
-#![allow(dead_code)]
+#![allow(dead_code, unused)]
 #![feature(generic_arg_infer)]
 
 trait Test {
diff --git a/tests/ui/parser/intersection-patterns-1.fixed b/tests/ui/parser/intersection-patterns-1.fixed
index f63d57472cf10..7a2e74c2600df 100644
--- a/tests/ui/parser/intersection-patterns-1.fixed
+++ b/tests/ui/parser/intersection-patterns-1.fixed
@@ -9,6 +9,7 @@
 //@ run-rustfix
 
 #![allow(unused_variables)]
+#![allow(unused_assignments)]
 
 fn main() {
     let s: Option<u8> = None;
diff --git a/tests/ui/parser/intersection-patterns-1.rs b/tests/ui/parser/intersection-patterns-1.rs
index 3a457659aac2e..fb18f14840011 100644
--- a/tests/ui/parser/intersection-patterns-1.rs
+++ b/tests/ui/parser/intersection-patterns-1.rs
@@ -9,6 +9,7 @@
 //@ run-rustfix
 
 #![allow(unused_variables)]
+#![allow(unused_assignments)]
 
 fn main() {
     let s: Option<u8> = None;
diff --git a/tests/ui/parser/intersection-patterns-1.stderr b/tests/ui/parser/intersection-patterns-1.stderr
index dc968656c91ff..a760f58a1b97b 100644
--- a/tests/ui/parser/intersection-patterns-1.stderr
+++ b/tests/ui/parser/intersection-patterns-1.stderr
@@ -1,5 +1,5 @@
 error: pattern on wrong side of `@`
-  --> $DIR/intersection-patterns-1.rs:17:9
+  --> $DIR/intersection-patterns-1.rs:18:9
    |
 LL |         Some(x) @ y => {}
    |         -------^^^-
@@ -9,7 +9,7 @@ LL |         Some(x) @ y => {}
    |         help: switch the order: `y @ Some(x)`
 
 error: pattern on wrong side of `@`
-  --> $DIR/intersection-patterns-1.rs:27:9
+  --> $DIR/intersection-patterns-1.rs:28:9
    |
 LL |         1 ..= 5 @ e => {}
    |         -------^^^-
diff --git a/tests/ui/pattern/bindings-after-at/bind-by-copy.rs b/tests/ui/pattern/bindings-after-at/bind-by-copy.rs
index 3d26b5e87d93f..d766411e4f980 100644
--- a/tests/ui/pattern/bindings-after-at/bind-by-copy.rs
+++ b/tests/ui/pattern/bindings-after-at/bind-by-copy.rs
@@ -1,5 +1,6 @@
 //@ run-pass
 #![allow(unused)]
+#![warn(unused_assignments)]
 
 // Test copy
 
@@ -34,10 +35,12 @@ pub fn main() {
     let mut x@B {b, ..} = B {a: 10, b: C {c: 20}};
     assert_eq!(x.a, 10);
     x.b.c = 30;
+    //~^ WARN value assigned to `x` is never read
     assert_eq!(b.c, 20);
     let mut y@D {d, ..} = D {a: 10, d: C {c: 20}};
     assert_eq!(y.a, 10);
     y.d.c = 30;
+    //~^ WARN value assigned to `y` is never read
     assert_eq!(d.c, 20);
 
     match (E::E { a: 10, e: C { c: 20 } }) {
@@ -50,7 +53,9 @@ pub fn main() {
     }
     match (E::E { a: 10, e: C { c: 20 } }) {
         mut x @ E::E{ a, e: C { mut c } } => {
+            //~^ WARN value assigned to `a` is never read
             x = E::NotE;
+            //~^ WARN value assigned to `x` is never read
             c += 30;
             assert_eq!(c, 50);
         }
diff --git a/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr b/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr
new file mode 100644
index 0000000000000..d775b69ef0a5e
--- /dev/null
+++ b/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr
@@ -0,0 +1,39 @@
+warning: value assigned to `x` is never read
+  --> $DIR/bind-by-copy.rs:37:5
+   |
+LL |     x.b.c = 30;
+   |     ^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+note: the lint level is defined here
+  --> $DIR/bind-by-copy.rs:3:9
+   |
+LL | #![warn(unused_assignments)]
+   |         ^^^^^^^^^^^^^^^^^^
+
+warning: value assigned to `y` is never read
+  --> $DIR/bind-by-copy.rs:42:5
+   |
+LL |     y.d.c = 30;
+   |     ^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value assigned to `x` is never read
+  --> $DIR/bind-by-copy.rs:57:13
+   |
+LL |             x = E::NotE;
+   |             ^^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value assigned to `a` is never read
+  --> $DIR/bind-by-copy.rs:55:23
+   |
+LL |         mut x @ E::E{ a, e: C { mut c } } => {
+   |                       ^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: 4 warnings emitted
+
diff --git a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs
index 3090f68c72b77..b3dc9e5842334 100644
--- a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs
+++ b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs
@@ -58,6 +58,7 @@ fn match_with_or() {
 fn nested_mixed() {
     match (&Some(5), &Some(6)) {
         (Some(a), &Some(mut b)) => {
+            //~^ WARN value assigned to `b` is never read
             // Here, the `a` will be `&i32`, because in the first half of the tuple
             // we hit a non-reference pattern and shift into `ref` mode.
             //
diff --git a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr
new file mode 100644
index 0000000000000..aa0aed2d8d790
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr
@@ -0,0 +1,11 @@
+warning: value assigned to `b` is never read
+  --> $DIR/general.rs:60:25
+   |
+LL |         (Some(a), &Some(mut b)) => {
+   |                         ^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+   = note: `#[warn(unused_assignments)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr
new file mode 100644
index 0000000000000..b210619f8d780
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr
@@ -0,0 +1,11 @@
+warning: value assigned to `small` is never read
+  --> $DIR/std-panic-locations.rs:46:31
+   |
+LL |     assert_panicked(move || { small[1] += 1; });
+   |                               ^^^^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+   = note: `#[warn(unused_assignments)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr
new file mode 100644
index 0000000000000..b210619f8d780
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr
@@ -0,0 +1,11 @@
+warning: value assigned to `small` is never read
+  --> $DIR/std-panic-locations.rs:46:31
+   |
+LL |     assert_panicked(move || { small[1] += 1; });
+   |                               ^^^^^^^^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+   = note: `#[warn(unused_assignments)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs
index bd62a6447851f..62338819625d4 100644
--- a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs
+++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs
@@ -44,6 +44,7 @@ fn main() {
     assert_panicked(move || { small[1]; });
     assert_panicked(move || { small.index_mut(1); });
     assert_panicked(move || { small[1] += 1; });
+    //~^ WARN value assigned to `small` is never read
 
     let sorted: BTreeMap<bool, bool> = Default::default();
     assert_panicked(|| { sorted.index(&false); });
diff --git a/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr b/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr
index 16e1af46059a4..733f2db159a9d 100644
--- a/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr
+++ b/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr
@@ -10,12 +10,6 @@ note: the lint level is defined here
 LL | #![deny(unused_variables)]
    |         ^^^^^^^^^^^^^^^^
 
-error: unused variable: `a`
-  --> $DIR/param-attrs-cfg.rs:41:27
-   |
-LL |         #[cfg(something)] a: i32,
-   |                           ^ help: if this is intentional, prefix it with an underscore: `_a`
-
 error: unused variable: `b`
   --> $DIR/param-attrs-cfg.rs:30:23
    |
@@ -28,6 +22,12 @@ error: unused variable: `c`
 LL |     #[cfg_attr(nothing, cfg(nothing))] c: i32,
    |                                        ^ help: if this is intentional, prefix it with an underscore: `_c`
 
+error: unused variable: `a`
+  --> $DIR/param-attrs-cfg.rs:41:27
+   |
+LL |         #[cfg(something)] a: i32,
+   |                           ^ help: if this is intentional, prefix it with an underscore: `_a`
+
 error: unused variable: `b`
   --> $DIR/param-attrs-cfg.rs:48:27
    |
@@ -100,12 +100,6 @@ error: unused variable: `c`
 LL |         #[cfg_attr(nothing, cfg(nothing))] c: i32,
    |                                            ^ help: if this is intentional, prefix it with an underscore: `_c`
 
-error: unused variable: `a`
-  --> $DIR/param-attrs-cfg.rs:107:27
-   |
-LL |         #[cfg(something)] a: i32,
-   |                           ^ help: if this is intentional, prefix it with an underscore: `_a`
-
 error: unused variable: `b`
   --> $DIR/param-attrs-cfg.rs:113:27
    |
@@ -118,5 +112,11 @@ error: unused variable: `c`
 LL |         #[cfg_attr(nothing, cfg(nothing))] c: i32,
    |                                            ^ help: if this is intentional, prefix it with an underscore: `_c`
 
+error: unused variable: `a`
+  --> $DIR/param-attrs-cfg.rs:107:27
+   |
+LL |         #[cfg(something)] a: i32,
+   |                           ^ help: if this is intentional, prefix it with an underscore: `_a`
+
 error: aborting due to 19 previous errors
 
diff --git a/tests/ui/suggestions/try-removing-the-field.rs b/tests/ui/suggestions/try-removing-the-field.rs
index dc1bde082c4f1..29da1964354ec 100644
--- a/tests/ui/suggestions/try-removing-the-field.rs
+++ b/tests/ui/suggestions/try-removing-the-field.rs
@@ -10,7 +10,7 @@ struct Foo {
 
 fn use_foo(x: Foo) -> i32 {
     let Foo { foo, bar, .. } = x; //~ WARNING unused variable: `bar`
-                                  //~| help: try removing the field
+                                  //~| help: try ignoring the field
     return foo;
 }
 
@@ -24,7 +24,7 @@ fn use_match(x: Foo) {
 
     match x {
         Foo { foo, .. } => { //~ WARNING unused variable
-                             //~| help: try removing the field
+                             //~| help: try ignoring the field
         }
     }
 }
diff --git a/tests/ui/suggestions/try-removing-the-field.stderr b/tests/ui/suggestions/try-removing-the-field.stderr
index 7a6013d4a6eab..06175c9d672d6 100644
--- a/tests/ui/suggestions/try-removing-the-field.stderr
+++ b/tests/ui/suggestions/try-removing-the-field.stderr
@@ -2,9 +2,7 @@ warning: unused variable: `bar`
   --> $DIR/try-removing-the-field.rs:12:20
    |
 LL |     let Foo { foo, bar, .. } = x;
-   |                    ^^^-
-   |                    |
-   |                    help: try removing the field
+   |                    ^^^ help: try ignoring the field: `bar: _`
    |
    = note: `#[warn(unused_variables)]` on by default
 
@@ -18,9 +16,7 @@ warning: unused variable: `foo`
   --> $DIR/try-removing-the-field.rs:26:15
    |
 LL |         Foo { foo, .. } => {
-   |               ^^^-
-   |               |
-   |               help: try removing the field
+   |               ^^^ help: try ignoring the field: `foo: _`
 
 warning: 3 warnings emitted
 
diff --git a/tests/ui/suggestions/unused-closure-argument.stderr b/tests/ui/suggestions/unused-closure-argument.stderr
index 55195ce50a13e..9f70501389c43 100644
--- a/tests/ui/suggestions/unused-closure-argument.stderr
+++ b/tests/ui/suggestions/unused-closure-argument.stderr
@@ -1,8 +1,8 @@
 error: unused variable: `x`
-  --> $DIR/unused-closure-argument.rs:12:23
+  --> $DIR/unused-closure-argument.rs:17:15
    |
-LL |         .map(|Point { x, y }| y)
-   |                       ^ help: try ignoring the field: `x: _`
+LL |         .map(|x| 4)
+   |               ^ help: if this is intentional, prefix it with an underscore: `_x`
    |
 note: the lint level is defined here
   --> $DIR/unused-closure-argument.rs:1:9
@@ -11,10 +11,10 @@ LL | #![deny(unused_variables)]
    |         ^^^^^^^^^^^^^^^^
 
 error: unused variable: `x`
-  --> $DIR/unused-closure-argument.rs:17:15
+  --> $DIR/unused-closure-argument.rs:12:23
    |
-LL |         .map(|x| 4)
-   |               ^ help: if this is intentional, prefix it with an underscore: `_x`
+LL |         .map(|Point { x, y }| y)
+   |                       ^ help: try ignoring the field: `x: _`
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/thir-print/thir-tree-match.stdout b/tests/ui/thir-print/thir-tree-match.stdout
index b248d2a82684f..ddfacaeaa2c27 100644
--- a/tests/ui/thir-print/thir-tree-match.stdout
+++ b/tests/ui/thir-print/thir-tree-match.stdout
@@ -16,6 +16,7 @@ params: [
                         var: LocalVarId(HirId(DefId(0:16 ~ thir_tree_match[fcf8]::has_match).2))
                         ty: Foo
                         is_primary: true
+                        is_shorthand: false
                         subpattern: None
                     }
                 }
diff --git a/tests/ui/type/issue-100584.stderr b/tests/ui/type/issue-100584.stderr
index e1db14d1f001b..0a0d96f2c9dbc 100644
--- a/tests/ui/type/issue-100584.stderr
+++ b/tests/ui/type/issue-100584.stderr
@@ -2,7 +2,7 @@ error: unused variable: `xyza`
   --> $DIR/issue-100584.rs:2:8
    |
 LL | fn foo(xyza: &str) {
-   |        ^^^^ unused variable
+   |        ^^^^
 LL |
 LL |     let _ = "{xyza}";
    |             -------- you might have meant to use string interpolation in this string literal
@@ -26,7 +26,7 @@ error: unused variable: `xyza`
   --> $DIR/issue-100584.rs:7:9
    |
 LL | fn foo3(xyza: &str) {
-   |         ^^^^ unused variable
+   |         ^^^^
 LL |
 LL |     let _ = "aaa{xyza}bbb";
    |             -------------- you might have meant to use string interpolation in this string literal
diff --git a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs
index 42287ac50701b..ee425abfd1e27 100644
--- a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs
+++ b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs
@@ -21,8 +21,9 @@ fn main() {
 
     call(move || {
         // this mutates a moved copy, and hence doesn't affect original
-        counter += 1; //~  WARN value assigned to `counter` is never read
-                      //~| WARN unused variable: `counter`
+        counter += 1;
+        //~^ WARN value captured by `counter` is never read
+        //~| WARN value assigned to `counter` is never read
     });
     assert_eq!(counter, 88);
 }
diff --git a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr
index 6450cc30ac05f..3f155948866ea 100644
--- a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr
+++ b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr
@@ -1,27 +1,27 @@
-warning: unused variable: `item`
-  --> $DIR/unboxed-closures-counter-not-moved.rs:15:13
+warning: value captured by `counter` is never read
+  --> $DIR/unboxed-closures-counter-not-moved.rs:24:9
    |
-LL |         for item in y {
-   |             ^^^^ help: if this is intentional, prefix it with an underscore: `_item`
+LL |         counter += 1;
+   |         ^^^^^^^
    |
-   = note: `#[warn(unused_variables)]` on by default
+   = help: did you mean to capture by reference instead?
+   = note: `#[warn(unused_assignments)]` on by default
 
 warning: value assigned to `counter` is never read
   --> $DIR/unboxed-closures-counter-not-moved.rs:24:9
    |
 LL |         counter += 1;
-   |         ^^^^^^^
+   |         ^^^^^^^^^^^^
    |
    = help: maybe it is overwritten before being read?
-   = note: `#[warn(unused_assignments)]` on by default
 
-warning: unused variable: `counter`
-  --> $DIR/unboxed-closures-counter-not-moved.rs:24:9
+warning: unused variable: `item`
+  --> $DIR/unboxed-closures-counter-not-moved.rs:15:13
    |
-LL |         counter += 1;
-   |         ^^^^^^^
+LL |         for item in y {
+   |             ^^^^ help: if this is intentional, prefix it with an underscore: `_item`
    |
-   = help: did you mean to capture by reference instead?
+   = note: `#[warn(unused_variables)]` on by default
 
 warning: 3 warnings emitted
 
diff --git a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs
index d883053d2763e..0e3744bbe00c2 100644
--- a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs
+++ b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs
@@ -14,11 +14,17 @@ fn set(x: &mut usize) { *x = 42; }
 fn main() {
     {
         let mut x = 0_usize;
-        move || x += 1; //~ WARN unused variable: `x`
+        //~^ WARN unused variable: `x`
+        move || x += 1;
+        //~^ WARN value captured by `x` is never read
+        //~| WARN value assigned to `x` is never read
     }
     {
         let mut x = 0_usize;
-        move || x += 1; //~ WARN unused variable: `x`
+        //~^ WARN unused variable: `x`
+        move || x += 1;
+        //~^ WARN value captured by `x` is never read
+        //~| WARN value assigned to `x` is never read
     }
     {
         let mut x = 0_usize;
diff --git a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr
index 5c06f4e621c17..ec177ad011f55 100644
--- a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr
+++ b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr
@@ -1,19 +1,49 @@
-warning: unused variable: `x`
-  --> $DIR/unboxed-closures-move-mutable.rs:17:17
+warning: value captured by `x` is never read
+  --> $DIR/unboxed-closures-move-mutable.rs:25:17
    |
 LL |         move || x += 1;
    |                 ^
    |
    = help: did you mean to capture by reference instead?
-   = note: `#[warn(unused_variables)]` on by default
+   = note: `#[warn(unused_assignments)]` on by default
 
-warning: unused variable: `x`
-  --> $DIR/unboxed-closures-move-mutable.rs:21:17
+warning: value assigned to `x` is never read
+  --> $DIR/unboxed-closures-move-mutable.rs:25:17
+   |
+LL |         move || x += 1;
+   |                 ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: value captured by `x` is never read
+  --> $DIR/unboxed-closures-move-mutable.rs:18:17
    |
 LL |         move || x += 1;
    |                 ^
    |
    = help: did you mean to capture by reference instead?
 
-warning: 2 warnings emitted
+warning: value assigned to `x` is never read
+  --> $DIR/unboxed-closures-move-mutable.rs:18:17
+   |
+LL |         move || x += 1;
+   |                 ^^^^^^
+   |
+   = help: maybe it is overwritten before being read?
+
+warning: unused variable: `x`
+  --> $DIR/unboxed-closures-move-mutable.rs:16:13
+   |
+LL |         let mut x = 0_usize;
+   |             ^^^^^ help: if this is intentional, prefix it with an underscore: `_x`
+   |
+   = note: `#[warn(unused_variables)]` on by default
+
+warning: unused variable: `x`
+  --> $DIR/unboxed-closures-move-mutable.rs:23:13
+   |
+LL |         let mut x = 0_usize;
+   |             ^^^^^ help: if this is intentional, prefix it with an underscore: `_x`
+
+warning: 6 warnings emitted