about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir-ty/src/layout/tests/closure.rs4
-rw-r--r--crates/hir-ty/src/mir.rs8
-rw-r--r--crates/hir-ty/src/mir/borrowck.rs88
-rw-r--r--crates/hir-ty/src/mir/eval.rs1
-rw-r--r--crates/hir-ty/src/mir/lower.rs19
-rw-r--r--crates/hir-ty/src/mir/monomorphization.rs1
-rw-r--r--crates/hir-ty/src/mir/pretty.rs5
-rw-r--r--crates/hir/src/diagnostics.rs6
-rw-r--r--crates/hir/src/lib.rs15
-rw-r--r--crates/ide-diagnostics/src/handlers/field_shorthand.rs6
-rw-r--r--crates/ide-diagnostics/src/handlers/incorrect_case.rs18
-rw-r--r--crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs22
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_fields.rs3
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_match_arms.rs6
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_unsafe.rs48
-rw-r--r--crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs8
-rw-r--r--crates/ide-diagnostics/src/handlers/mutability_errors.rs66
-rw-r--r--crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs20
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs36
-rw-r--r--crates/ide-diagnostics/src/handlers/typed_hole.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/unused_variables.rs110
-rw-r--r--crates/ide-diagnostics/src/lib.rs2
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs12
-rw-r--r--crates/test-utils/src/minicore.rs38
24 files changed, 404 insertions, 142 deletions
diff --git a/crates/hir-ty/src/layout/tests/closure.rs b/crates/hir-ty/src/layout/tests/closure.rs
index bbe855a14de..939025461f3 100644
--- a/crates/hir-ty/src/layout/tests/closure.rs
+++ b/crates/hir-ty/src/layout/tests/closure.rs
@@ -186,9 +186,9 @@ fn capture_specific_fields() {
 fn match_pattern() {
     size_and_align_expr! {
         struct X(i64, i32, (u8, i128));
-        let y: X = X(2, 5, (7, 3));
+        let _y: X = X(2, 5, (7, 3));
         move |x: i64| {
-            match y {
+            match _y {
                 _ => x,
             }
         }
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index e953058ccca..797f4c1248d 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -280,7 +280,7 @@ impl ProjectionId {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct Place {
     pub local: LocalId,
     pub projection: ProjectionId,
@@ -1007,7 +1007,7 @@ pub enum Rvalue {
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub enum StatementKind {
     Assign(Place, Rvalue),
-    //FakeRead(Box<(FakeReadCause, Place)>),
+    FakeRead(Place),
     //SetDiscriminant {
     //    place: Box<Place>,
     //    variant_index: VariantIdx,
@@ -1109,7 +1109,9 @@ impl MirBody {
                             }
                         }
                     }
-                    StatementKind::Deinit(p) => f(p, &mut self.projection_store),
+                    StatementKind::FakeRead(p) | StatementKind::Deinit(p) => {
+                        f(p, &mut self.projection_store)
+                    }
                     StatementKind::StorageLive(_)
                     | StatementKind::StorageDead(_)
                     | StatementKind::Nop => (),
diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs
index 41fb129652a..74c5efd6c3f 100644
--- a/crates/hir-ty/src/mir/borrowck.rs
+++ b/crates/hir-ty/src/mir/borrowck.rs
@@ -24,6 +24,7 @@ use super::{
 pub enum MutabilityReason {
     Mut { spans: Vec<MirSpan> },
     Not,
+    Unused,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -144,7 +145,8 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef>
                         }
                     }
                 },
-                StatementKind::Deinit(_)
+                StatementKind::FakeRead(_)
+                | StatementKind::Deinit(_)
                 | StatementKind::StorageLive(_)
                 | StatementKind::StorageDead(_)
                 | StatementKind::Nop => (),
@@ -264,7 +266,10 @@ fn ever_initialized_map(
                         is_ever_initialized = false;
                     }
                 }
-                StatementKind::Deinit(_) | StatementKind::Nop | StatementKind::StorageLive(_) => (),
+                StatementKind::Deinit(_)
+                | StatementKind::FakeRead(_)
+                | StatementKind::Nop
+                | StatementKind::StorageLive(_) => (),
             }
         }
         let Some(terminator) = &block.terminator else {
@@ -331,16 +336,37 @@ fn ever_initialized_map(
     result
 }
 
+fn push_mut_span(local: LocalId, span: MirSpan, result: &mut ArenaMap<LocalId, MutabilityReason>) {
+    match &mut result[local] {
+        MutabilityReason::Mut { spans } => spans.push(span),
+        it @ (MutabilityReason::Not | MutabilityReason::Unused) => {
+            *it = MutabilityReason::Mut { spans: vec![span] }
+        }
+    };
+}
+
+fn record_usage(local: LocalId, result: &mut ArenaMap<LocalId, MutabilityReason>) {
+    match &mut result[local] {
+        it @ MutabilityReason::Unused => {
+            *it = MutabilityReason::Not;
+        }
+        _ => (),
+    };
+}
+
+fn record_usage_for_operand(arg: &Operand, result: &mut ArenaMap<LocalId, MutabilityReason>) {
+    if let Operand::Copy(p) | Operand::Move(p) = arg {
+        record_usage(p.local, result);
+    }
+}
+
 fn mutability_of_locals(
     db: &dyn HirDatabase,
     body: &MirBody,
 ) -> ArenaMap<LocalId, MutabilityReason> {
     let mut result: ArenaMap<LocalId, MutabilityReason> =
-        body.locals.iter().map(|it| (it.0, MutabilityReason::Not)).collect();
-    let mut push_mut_span = |local, span| match &mut result[local] {
-        MutabilityReason::Mut { spans } => spans.push(span),
-        it @ MutabilityReason::Not => *it = MutabilityReason::Mut { spans: vec![span] },
-    };
+        body.locals.iter().map(|it| (it.0, MutabilityReason::Unused)).collect();
+
     let ever_init_maps = ever_initialized_map(db, body);
     for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
         let block = &body.basic_blocks[block_id];
@@ -350,23 +376,51 @@ fn mutability_of_locals(
                     match place_case(db, body, place) {
                         ProjectionCase::Direct => {
                             if ever_init_map.get(place.local).copied().unwrap_or_default() {
-                                push_mut_span(place.local, statement.span);
+                                push_mut_span(place.local, statement.span, &mut result);
                             } else {
                                 ever_init_map.insert(place.local, true);
                             }
                         }
                         ProjectionCase::DirectPart => {
                             // Partial initialization is not supported, so it is definitely `mut`
-                            push_mut_span(place.local, statement.span);
+                            push_mut_span(place.local, statement.span, &mut result);
+                        }
+                        ProjectionCase::Indirect => {
+                            record_usage(place.local, &mut result);
                         }
-                        ProjectionCase::Indirect => (),
+                    }
+                    match value {
+                        Rvalue::CopyForDeref(p)
+                        | Rvalue::Discriminant(p)
+                        | Rvalue::Len(p)
+                        | Rvalue::Ref(_, p) => {
+                            record_usage(p.local, &mut result);
+                        }
+                        Rvalue::Use(o)
+                        | Rvalue::Repeat(o, _)
+                        | Rvalue::Cast(_, o, _)
+                        | Rvalue::UnaryOp(_, o) => record_usage_for_operand(o, &mut result),
+                        Rvalue::CheckedBinaryOp(_, o1, o2) => {
+                            for o in [o1, o2] {
+                                record_usage_for_operand(o, &mut result);
+                            }
+                        }
+                        Rvalue::Aggregate(_, args) => {
+                            for arg in args.iter() {
+                                record_usage_for_operand(arg, &mut result);
+                            }
+                        }
+                        Rvalue::ShallowInitBox(_, _) | Rvalue::ShallowInitBoxWithAlloc(_) => (),
                     }
                     if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
                         if place_case(db, body, p) != ProjectionCase::Indirect {
-                            push_mut_span(p.local, statement.span);
+                            push_mut_span(p.local, statement.span, &mut result);
                         }
                     }
                 }
+                StatementKind::FakeRead(p) => {
+                    record_usage(p.local, &mut result);
+                }
                 StatementKind::StorageDead(p) => {
                     ever_init_map.insert(*p, false);
                 }
@@ -386,15 +440,21 @@ fn mutability_of_locals(
             | TerminatorKind::FalseEdge { .. }
             | TerminatorKind::FalseUnwind { .. }
             | TerminatorKind::GeneratorDrop
-            | TerminatorKind::SwitchInt { .. }
             | TerminatorKind::Drop { .. }
             | TerminatorKind::DropAndReplace { .. }
             | TerminatorKind::Assert { .. }
             | TerminatorKind::Yield { .. } => (),
-            TerminatorKind::Call { destination, .. } => {
+            TerminatorKind::SwitchInt { discr, targets: _ } => {
+                record_usage_for_operand(discr, &mut result);
+            }
+            TerminatorKind::Call { destination, args, func, .. } => {
+                record_usage_for_operand(func, &mut result);
+                for arg in args.iter() {
+                    record_usage_for_operand(arg, &mut result);
+                }
                 if destination.projection.lookup(&body.projection_store).len() == 0 {
                     if ever_init_map.get(destination.local).copied().unwrap_or_default() {
-                        push_mut_span(destination.local, MirSpan::Unknown);
+                        push_mut_span(destination.local, MirSpan::Unknown, &mut result);
                     } else {
                         ever_init_map.insert(destination.local, true);
                     }
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index 4364e0d323b..98c78f7f305 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -842,6 +842,7 @@ impl Evaluator<'_> {
                             }
                             StatementKind::Deinit(_) => not_supported!("de-init statement"),
                             StatementKind::StorageLive(_)
+                            | StatementKind::FakeRead(_)
                             | StatementKind::StorageDead(_)
                             | StatementKind::Nop => (),
                         }
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index dd2dba717f9..9905d522146 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -529,6 +529,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
                 else {
                     return Ok(None);
                 };
+                self.push_fake_read(current, cond_place, expr_id.into());
                 let (then_target, else_target) =
                     self.pattern_match(current, None, cond_place, *pat)?;
                 self.write_bytes_to_place(
@@ -668,6 +669,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
                 else {
                     return Ok(None);
                 };
+                self.push_fake_read(current, cond_place, expr_id.into());
                 let mut end = None;
                 for MatchArm { pat, guard, expr } in arms.iter() {
                     let (then, mut otherwise) =
@@ -1299,6 +1301,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
             return Ok(None);
         };
         if matches!(&self.body.exprs[lhs], Expr::Underscore) {
+            self.push_fake_read_for_operand(current, rhs_op, span);
             return Ok(Some(current));
         }
         if matches!(
@@ -1575,6 +1578,16 @@ impl<'ctx> MirLowerCtx<'ctx> {
         self.result.basic_blocks[block].statements.push(statement);
     }
 
+    fn push_fake_read(&mut self, block: BasicBlockId, p: Place, span: MirSpan) {
+        self.push_statement(block, StatementKind::FakeRead(p).with_span(span));
+    }
+
+    fn push_fake_read_for_operand(&mut self, block: BasicBlockId, operand: Operand, span: MirSpan) {
+        if let Operand::Move(p) | Operand::Copy(p) = operand {
+            self.push_fake_read(block, p, span);
+        }
+    }
+
     fn push_assignment(
         &mut self,
         block: BasicBlockId,
@@ -1733,6 +1746,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
                             return Ok(None);
                         };
                         current = c;
+                        self.push_fake_read(current, init_place, span);
                         (current, else_block) =
                             self.pattern_match(current, None, init_place, *pat)?;
                         match (else_block, else_branch) {
@@ -1760,13 +1774,14 @@ impl<'ctx> MirLowerCtx<'ctx> {
                         }
                     }
                 }
-                hir_def::hir::Statement::Expr { expr, has_semi: _ } => {
+                &hir_def::hir::Statement::Expr { expr, has_semi: _ } => {
                     let scope2 = self.push_drop_scope();
-                    let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else {
+                    let Some((p, c)) = self.lower_expr_as_place(current, expr, true)? else {
                         scope2.pop_assume_dropped(self);
                         scope.pop_assume_dropped(self);
                         return Ok(None);
                     };
+                    self.push_fake_read(c, p, expr.into());
                     current = scope2.pop_and_drop(self, c);
                 }
             }
diff --git a/crates/hir-ty/src/mir/monomorphization.rs b/crates/hir-ty/src/mir/monomorphization.rs
index df16d0d8201..7d2bb95d931 100644
--- a/crates/hir-ty/src/mir/monomorphization.rs
+++ b/crates/hir-ty/src/mir/monomorphization.rs
@@ -248,6 +248,7 @@ impl Filler<'_> {
                         | Rvalue::CopyForDeref(_) => (),
                     },
                     StatementKind::Deinit(_)
+                    | StatementKind::FakeRead(_)
                     | StatementKind::StorageLive(_)
                     | StatementKind::StorageDead(_)
                     | StatementKind::Nop => (),
diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs
index 0108859ff32..6e42bee97f7 100644
--- a/crates/hir-ty/src/mir/pretty.rs
+++ b/crates/hir-ty/src/mir/pretty.rs
@@ -233,6 +233,11 @@ impl<'a> MirPrettyCtx<'a> {
                             this.place(p);
                             wln!(this, ");");
                         }
+                        StatementKind::FakeRead(p) => {
+                            w!(this, "FakeRead(");
+                            this.place(p);
+                            wln!(this, ");");
+                        }
                         StatementKind::Nop => wln!(this, "Nop;"),
                     }
                 }
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 479138b67f8..66ad95c5597 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -66,6 +66,7 @@ diagnostics![
     UnresolvedModule,
     UnresolvedProcMacro,
     UnusedMut,
+    UnusedVariable,
 ];
 
 #[derive(Debug)]
@@ -271,6 +272,11 @@ pub struct UnusedMut {
 }
 
 #[derive(Debug)]
+pub struct UnusedVariable {
+    pub local: Local,
+}
+
+#[derive(Debug)]
 pub struct MovedOutOfRef {
     pub ty: Type,
     pub span: InFile<SyntaxNodePtr>,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index b215ed38f28..a6c6c0dbb8b 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -98,7 +98,7 @@ pub use crate::{
         ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
         UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
         UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
-        UnresolvedProcMacro, UnusedMut,
+        UnresolvedProcMacro, UnusedMut, UnusedVariable,
     },
     has_source::HasSource,
     semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@@ -1697,9 +1697,20 @@ impl DefWithBody {
                         // Skip synthetic bindings
                         continue;
                     }
-                    let need_mut = &mol[local];
+                    let mut need_mut = &mol[local];
+                    if body[binding_id].name.as_str() == Some("self")
+                        && need_mut == &mir::MutabilityReason::Unused
+                    {
+                        need_mut = &mir::MutabilityReason::Not;
+                    }
                     let local = Local { parent: self.into(), binding_id };
                     match (need_mut, local.is_mut(db)) {
+                        (mir::MutabilityReason::Unused, _) => {
+                            let should_ignore = matches!(body[binding_id].name.as_str(), Some(it) if it.starts_with("_"));
+                            if !should_ignore {
+                                acc.push(UnusedVariable { local }.into())
+                            }
+                        }
                         (mir::MutabilityReason::Mut { .. }, true)
                         | (mir::MutabilityReason::Not, false) => (),
                         (mir::MutabilityReason::Mut { spans }, false) => {
diff --git a/crates/ide-diagnostics/src/handlers/field_shorthand.rs b/crates/ide-diagnostics/src/handlers/field_shorthand.rs
index 3b69640af9b..9ed8199ae4d 100644
--- a/crates/ide-diagnostics/src/handlers/field_shorthand.rs
+++ b/crates/ide-diagnostics/src/handlers/field_shorthand.rs
@@ -166,7 +166,7 @@ fn main() {
         check_diagnostics(
             r#"
 struct A { a: &'static str }
-fn f(a: A) { let A { a: hello } = a; }
+fn f(a: A) { let A { a: _hello } = a; }
 "#,
         );
         check_diagnostics(
@@ -181,12 +181,14 @@ fn f(a: A) { let A { 0: 0 } = a; }
 struct A { a: &'static str }
 fn f(a: A) {
     let A { a$0: a } = a;
+    _ = a;
 }
 "#,
             r#"
 struct A { a: &'static str }
 fn f(a: A) {
     let A { a } = a;
+    _ = a;
 }
 "#,
         );
@@ -196,12 +198,14 @@ fn f(a: A) {
 struct A { a: &'static str, b: &'static str }
 fn f(a: A) {
     let A { a$0: a, b } = a;
+    _ = (a, b);
 }
 "#,
             r#"
 struct A { a: &'static str, b: &'static str }
 fn f(a: A) {
     let A { a, b } = a;
+    _ = (a, b);
 }
 "#,
         );
diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs
index 235062bf531..7824011db67 100644
--- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs
+++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs
@@ -175,10 +175,10 @@ fn NonSnakeCaseName() {}
     fn incorrect_function_params() {
         check_diagnostics(
             r#"
-fn foo(SomeParam: u8) {}
+fn foo(SomeParam: u8) { _ = SomeParam; }
     // ^^^^^^^^^ 💡 warn: Parameter `SomeParam` should have snake_case name, e.g. `some_param`
 
-fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
+fn foo2(ok_param: &str, CAPS_PARAM: u8) { _ = (ok_param, CAPS_PARAM); }
                      // ^^^^^^^^^^ 💡 warn: Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
 "#,
         );
@@ -188,6 +188,7 @@ fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
     fn incorrect_variable_names() {
         check_diagnostics(
             r#"
+#[allow(unused)]
 fn foo() {
     let SOME_VALUE = 10;
      // ^^^^^^^^^^ 💡 warn: Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
@@ -294,6 +295,7 @@ impl someStruct {
     // ^^^^^^^^ 💡 warn: Function `SomeFunc` should have snake_case name, e.g. `some_func`
         let WHY_VAR_IS_CAPS = 10;
          // ^^^^^^^^^^^^^^^ 💡 warn: Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
+        _ = WHY_VAR_IS_CAPS;
     }
 }
 "#,
@@ -306,6 +308,7 @@ impl someStruct {
             r#"
 enum Option { Some, None }
 
+#[allow(unused)]
 fn main() {
     match Option::None {
         None => (),
@@ -322,6 +325,7 @@ fn main() {
             r#"
 enum Option { Some, None }
 
+#[allow(unused)]
 fn main() {
     match Option::None {
         SOME_VAR @ None => (),
@@ -349,7 +353,9 @@ enum E {
 }
 
 mod F {
-    fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {}
+    fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {
+        _ = BAD_NAME_HI;
+    }
 }
     "#,
         );
@@ -395,7 +401,7 @@ fn qualify() {
 
     #[test] // Issue #8809.
     fn parenthesized_parameter() {
-        check_diagnostics(r#"fn f((O): _) {}"#)
+        check_diagnostics(r#"fn f((O): _) { _ = O; }"#)
     }
 
     #[test]
@@ -472,7 +478,9 @@ mod CheckBadStyle {
 
 mod F {
     #![allow(non_snake_case)]
-    fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
+    fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {
+        _ = BAD_NAME_HI;
+    }
 }
 
 #[allow(non_snake_case, non_camel_case_types)]
diff --git a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
index 8265e0b1c11..ede9858c726 100644
--- a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
+++ b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
@@ -131,7 +131,7 @@ fn f() { zero(); }
     fn simple_free_fn_one() {
         check_diagnostics(
             r#"
-fn one(arg: u8) {}
+fn one(_arg: u8) {}
 fn f() { one(); }
           //^^ error: expected 1 argument, found 0
 "#,
@@ -139,7 +139,7 @@ fn f() { one(); }
 
         check_diagnostics(
             r#"
-fn one(arg: u8) {}
+fn one(_arg: u8) {}
 fn f() { one(1); }
 "#,
         );
@@ -176,7 +176,7 @@ fn f() {
         check_diagnostics(
             r#"
 struct S;
-impl S { fn method(&self, arg: u8) {} }
+impl S { fn method(&self, _arg: u8) {} }
 
             fn f() {
                 S.method();
@@ -187,7 +187,7 @@ impl S { fn method(&self, arg: u8) {} }
         check_diagnostics(
             r#"
 struct S;
-impl S { fn method(&self, arg: u8) {} }
+impl S { fn method(&self, _arg: u8) {} }
 
 fn f() {
     S::method(&S, 0);
@@ -335,8 +335,8 @@ struct S;
 
 impl S {
     fn method(#[cfg(NEVER)] self) {}
-    fn method2(#[cfg(NEVER)] self, arg: u8) {}
-    fn method3(self, #[cfg(NEVER)] arg: u8) {}
+    fn method2(#[cfg(NEVER)] self, _arg: u8) {}
+    fn method3(self, #[cfg(NEVER)] _arg: u8) {}
 }
 
 extern "C" {
@@ -365,8 +365,8 @@ fn main() {
             r#"
 #[rustc_legacy_const_generics(1, 3)]
 fn mixed<const N1: &'static str, const N2: bool>(
-    a: u8,
-    b: i8,
+    _a: u8,
+    _b: i8,
 ) {}
 
 fn f() {
@@ -376,8 +376,8 @@ fn f() {
 
 #[rustc_legacy_const_generics(1, 3)]
 fn b<const N1: u8, const N2: u8>(
-    a: u8,
-    b: u8,
+    _a: u8,
+    _b: u8,
 ) {}
 
 fn g() {
@@ -403,7 +403,7 @@ fn f(
   // ^^ error: this pattern has 0 fields, but the corresponding tuple struct has 2 fields
     S(e, f, .., g, d): S
   //        ^^^^^^^^^ error: this pattern has 4 fields, but the corresponding tuple struct has 2 fields
-) {}
+) { _ = (a, b, c, d, e, f, g); }
 "#,
         )
     }
diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs
index acc31cd117a..3178c7fa2bc 100644
--- a/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -290,6 +290,7 @@ fn x(a: S) {
 struct S { s: u32 }
 fn x(a: S) {
     let S { ref s } = a;
+    _ = s;
 }
 ",
         )
@@ -626,7 +627,7 @@ struct TestStruct { one: i32, two: i64 }
 
 fn test_fn() {
     let one = 1;
-    let s = TestStruct{ one, two: 2 };
+    let _s = TestStruct{ one, two: 2 };
 }
         "#,
         );
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 06b03d3d198..84267d3d906 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -19,6 +19,7 @@ pub(crate) fn missing_match_arms(
 mod tests {
     use crate::tests::check_diagnostics;
 
+    #[track_caller]
     fn check_diagnostics_no_bails(ra_fixture: &str) {
         cov_mark::check_count!(validate_match_bailed_out, 0);
         crate::tests::check_diagnostics(ra_fixture)
@@ -564,6 +565,7 @@ fn bang(never: !) {
             r#"
 enum Option<T> { Some(T), None }
 
+#[allow(unused)]
 fn main() {
     // `Never` is deliberately not defined so that it's an uninferred type.
     match Option::<Never>::None {
@@ -719,7 +721,7 @@ fn main() {
             r#"
 struct S { a: char}
 fn main(v: S) {
-    match v { S{ a }      => {} }
+    match v { S{ a }      => { _ = a; } }
     match v { S{ a: _x }  => {} }
     match v { S{ a: 'a' } => {} }
     match v { S{..}       => {} }
@@ -901,7 +903,7 @@ enum E{ A, B }
 fn foo() {
     match &E::A {
         E::A => {}
-        x => {}
+        _x => {}
     }
 }",
         );
diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
index 70b26009bae..0f695b2745a 100644
--- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
@@ -100,9 +100,9 @@ mod tests {
             r#"
 fn main() {
     let x = &5 as *const usize;
-    unsafe { let y = *x; }
-    let z = *x;
-}         //^^💡 error: this operation is unsafe and requires an unsafe function or block
+    unsafe { let _y = *x; }
+    let _z = *x;
+}          //^^💡 error: this operation is unsafe and requires an unsafe function or block
 "#,
         )
     }
@@ -116,13 +116,13 @@ struct HasUnsafe;
 impl HasUnsafe {
     unsafe fn unsafe_fn(&self) {
         let x = &5 as *const usize;
-        let y = *x;
+        let _y = *x;
     }
 }
 
 unsafe fn unsafe_fn() {
     let x = &5 as *const usize;
-    let y = *x;
+    let _y = *x;
 }
 
 fn main() {
@@ -152,10 +152,10 @@ struct Ty {
 static mut STATIC_MUT: Ty = Ty { a: 0 };
 
 fn main() {
-    let x = STATIC_MUT.a;
-          //^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
+    let _x = STATIC_MUT.a;
+           //^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
     unsafe {
-        let x = STATIC_MUT.a;
+        let _x = STATIC_MUT.a;
     }
 }
 "#,
@@ -187,13 +187,13 @@ fn main() {
             r#"
 fn main() {
     let x = &5 as *const usize;
-    let z = *x$0;
+    let _z = *x$0;
 }
 "#,
             r#"
 fn main() {
     let x = &5 as *const usize;
-    let z = unsafe { *x };
+    let _z = unsafe { *x };
 }
 "#,
         );
@@ -231,7 +231,7 @@ struct S(usize);
 impl S {
     unsafe fn func(&self) {
         let x = &self.0 as *const usize;
-        let z = *x;
+        let _z = *x;
     }
 }
 fn main() {
@@ -244,7 +244,7 @@ struct S(usize);
 impl S {
     unsafe fn func(&self) {
         let x = &self.0 as *const usize;
-        let z = *x;
+        let _z = *x;
     }
 }
 fn main() {
@@ -267,7 +267,7 @@ struct Ty {
 static mut STATIC_MUT: Ty = Ty { a: 0 };
 
 fn main() {
-    let x = STATIC_MUT$0.a;
+    let _x = STATIC_MUT$0.a;
 }
 "#,
             r#"
@@ -278,7 +278,7 @@ struct Ty {
 static mut STATIC_MUT: Ty = Ty { a: 0 };
 
 fn main() {
-    let x = unsafe { STATIC_MUT.a };
+    let _x = unsafe { STATIC_MUT.a };
 }
 "#,
         )
@@ -382,16 +382,16 @@ fn main() {
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x;
-    x = STATIC_MUT$0;
+    let _x;
+    _x = STATIC_MUT$0;
 }
 "#,
             r#"
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x;
-    x = unsafe { STATIC_MUT };
+    let _x;
+    _x = unsafe { STATIC_MUT };
 }
 "#,
         )
@@ -405,14 +405,14 @@ fn main() {
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x = STATIC_MUT$0 + 1;
+    let _x = STATIC_MUT$0 + 1;
 }
 "#,
             r#"
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x = unsafe { STATIC_MUT } + 1;
+    let _x = unsafe { STATIC_MUT } + 1;
 }
 "#,
         )
@@ -425,14 +425,14 @@ fn main() {
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x = &STATIC_MUT$0;
+    let _x = &STATIC_MUT$0;
 }
 "#,
             r#"
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x = unsafe { &STATIC_MUT };
+    let _x = unsafe { &STATIC_MUT };
 }
 "#,
         )
@@ -445,14 +445,14 @@ fn main() {
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x = &&STATIC_MUT$0;
+    let _x = &&STATIC_MUT$0;
 }
 "#,
             r#"
 static mut STATIC_MUT: u8 = 0;
 
 fn main() {
-    let x = unsafe { &&STATIC_MUT };
+    let _x = unsafe { &&STATIC_MUT };
 }
 "#,
         )
diff --git a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
index 3aa4aa97026..20175b3fd53 100644
--- a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
+++ b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
@@ -29,6 +29,7 @@ fn main() {
     let a = &X;
     let b = *a;
       //^ error: cannot move `X` out of reference
+    _ = b;
 }
 "#,
         );
@@ -46,6 +47,7 @@ fn main() {
     let b = a.0;
       //^ error: cannot move `X` out of reference
     let y = a.1;
+    _ = (b, y);
 }
 "#,
         );
@@ -59,8 +61,8 @@ fn main() {
 struct X;
 fn main() {
     static S: X = X;
-    let s = S;
-      //^ error: cannot move `X` out of reference
+    let _s = S;
+      //^^ error: cannot move `X` out of reference
 }
 "#,
         );
@@ -165,7 +167,7 @@ enum X {
 
 fn main() {
     let x = &X::Bar;
-    let c = || match *x {
+    let _c = || match *x {
         X::Foo(t) => t,
         _ => 5,
     };
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index d056e5c85cc..ee096a100aa 100644
--- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -324,6 +324,7 @@ fn main() {
     let x_own = 2;
     let ref mut x_ref = x_own;
       //^^^^^^^^^^^^^ 💡 error: cannot mutate immutable variable `x_own`
+    _ = x_ref;
 }
 "#,
         );
@@ -331,7 +332,7 @@ fn main() {
             r#"
 struct Foo;
 impl Foo {
-    fn method(&mut self, x: i32) {}
+    fn method(&mut self, _x: i32) {}
 }
 fn main() {
     let x = Foo;
@@ -391,6 +392,7 @@ fn main() {
           //^^^^^ 💡 warn: variable does not need to be mutable
             x = 7;
           //^^^^^ 💡 error: cannot mutate immutable variable `x`
+            _ = y;
         }
     }
 }
@@ -404,12 +406,14 @@ fn main() {
         // there would be no mutability error for locals in dead code. Rustc tries to
         // not emit `unused_mut` in this case, but since it works without `mut`, and
         // special casing it is not trivial, we emit it.
+
+        // Update: now MIR based `unused-variable` is taking over `unused-mut` for the same reason.
         check_diagnostics(
             r#"
 fn main() {
     return;
     let mut x = 2;
-      //^^^^^ 💡 warn: variable does not need to be mutable
+      //^^^^^ warn: unused variable
     &mut x;
 }
 "#,
@@ -419,7 +423,7 @@ fn main() {
 fn main() {
     loop {}
     let mut x = 2;
-      //^^^^^ 💡 warn: variable does not need to be mutable
+      //^^^^^ warn: unused variable
     &mut x;
 }
 "#,
@@ -440,7 +444,7 @@ fn main(b: bool) {
         g();
     }
     let mut x = 2;
-      //^^^^^ 💡 warn: variable does not need to be mutable
+      //^^^^^ warn: unused variable
     &mut x;
 }
 "#,
@@ -454,7 +458,7 @@ fn main(b: bool) {
         return;
     }
     let mut x = 2;
-      //^^^^^ 💡 warn: variable does not need to be mutable
+      //^^^^^ warn: unused variable
     &mut x;
 }
 "#,
@@ -536,6 +540,7 @@ fn main() {
             (k @ 5, ref mut t) if { continue; } => {
                   //^^^^^^^^^ 💡 error: cannot mutate immutable variable `z`
                 *t = 5;
+                _ = k;
             }
             _ => {
                 let y = (1, 2);
@@ -588,6 +593,7 @@ fn main() {
         b = 1;
         c = (2, 3);
         d = 3;
+        _ = (c, b, d);
     }
 }
 "#,
@@ -600,6 +606,7 @@ fn main() {
             r#"
 fn f(mut x: i32) {
    //^^^^^ 💡 warn: variable does not need to be mutable
+   f(x + 2);
 }
 "#,
         );
@@ -615,8 +622,11 @@ fn f(x: i32) {
             r#"
 fn f((x, y): (i32, i32)) {
     let t = [0; 2];
-   x = 5;
- //^^^^^ 💡 error: cannot mutate immutable variable `x`
+    x = 5;
+  //^^^^^ 💡 error: cannot mutate immutable variable `x`
+    _ = x;
+    _ = y;
+    _ = t;
 }
 "#,
         );
@@ -645,6 +655,7 @@ fn f(x: [(i32, u8); 10]) {
           //^^^^^ 💡 warn: variable does not need to be mutable
         a = 2;
       //^^^^^ 💡 error: cannot mutate immutable variable `a`
+        _ = b;
     }
 }
 "#,
@@ -666,6 +677,7 @@ fn f(x: [(i32, u8); 10]) {
           //^^^^^ 💡 error: cannot mutate immutable variable `a`
             c = 2;
           //^^^^^ 💡 error: cannot mutate immutable variable `c`
+            _ = (b, d);
         }
     }
 }
@@ -696,18 +708,18 @@ fn f() {
     fn overloaded_index() {
         check_diagnostics(
             r#"
-//- minicore: index
+//- minicore: index, copy
 use core::ops::{Index, IndexMut};
 
 struct Foo;
 impl Index<usize> for Foo {
     type Output = (i32, u8);
-    fn index(&self, index: usize) -> &(i32, u8) {
+    fn index(&self, _index: usize) -> &(i32, u8) {
         &(5, 2)
     }
 }
 impl IndexMut<usize> for Foo {
-    fn index_mut(&mut self, index: usize) -> &mut (i32, u8) {
+    fn index_mut(&mut self, _index: usize) -> &mut (i32, u8) {
         &mut (5, 2)
     }
 }
@@ -715,26 +727,32 @@ fn f() {
     let mut x = Foo;
       //^^^^^ 💡 warn: variable does not need to be mutable
     let y = &x[2];
+    _ = (x, y);
     let x = Foo;
     let y = &mut x[2];
                //^💡 error: cannot mutate immutable variable `x`
+    _ = (x, y);
     let mut x = &mut Foo;
       //^^^^^ 💡 warn: variable does not need to be mutable
     let y: &mut (i32, u8) = &mut x[2];
+    _ = (x, y);
     let x = Foo;
     let ref mut y = x[7];
                   //^ 💡 error: cannot mutate immutable variable `x`
+    _ = (x, y);
     let (ref mut y, _) = x[3];
                        //^ 💡 error: cannot mutate immutable variable `x`
+    _ = y;
     match x[10] {
         //^ 💡 error: cannot mutate immutable variable `x`
-        (ref y, _) => (),
-        (_, ref mut y) => (),
+        (ref y, 5) => _ = y,
+        (_, ref mut y) => _ = y,
     }
     let mut x = Foo;
     let mut i = 5;
       //^^^^^ 💡 warn: variable does not need to be mutable
     let y = &mut x[i];
+    _ = y;
 }
 "#,
         );
@@ -744,7 +762,7 @@ fn f() {
     fn overloaded_deref() {
         check_diagnostics(
             r#"
-//- minicore: deref_mut
+//- minicore: deref_mut, copy
 use core::ops::{Deref, DerefMut};
 
 struct Foo;
@@ -763,21 +781,27 @@ fn f() {
     let mut x = Foo;
       //^^^^^ 💡 warn: variable does not need to be mutable
     let y = &*x;
+    _ = (x, y);
     let x = Foo;
     let y = &mut *x;
                //^^ 💡 error: cannot mutate immutable variable `x`
+    _ = (x, y);
     let x = Foo;
+      //^ warn: unused variable
     let x = Foo;
     let y: &mut (i32, u8) = &mut x;
                           //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+    _ = (x, y);
     let ref mut y = *x;
                   //^^ 💡 error: cannot mutate immutable variable `x`
+    _ = y;
     let (ref mut y, _) = *x;
                        //^^ 💡 error: cannot mutate immutable variable `x`
+    _ = y;
     match *x {
         //^^ 💡 error: cannot mutate immutable variable `x`
-        (ref y, _) => (),
-        (_, ref mut y) => (),
+        (ref y, 5) => _ = y,
+        (_, ref mut y) => _ = y,
     }
 }
 "#,
@@ -866,6 +890,7 @@ pub fn test() {
             data: 0
         }
     );
+    _ = tree;
 }
 "#,
         );
@@ -925,6 +950,7 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
             let x = X;
             let closure4 = || { x.mutate(); };
                               //^ 💡 error: cannot mutate immutable variable `x`
+            _ = (closure2, closure3, closure4);
         }
                     "#,
         );
@@ -941,7 +967,9 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
                 z = 3;
                 let mut k = z;
                   //^^^^^ 💡 warn: variable does not need to be mutable
+                _ = k;
             };
+            _ = (x, closure);
         }
                     "#,
         );
@@ -958,6 +986,7 @@ fn f() {
             }
         }
     };
+    _ = closure;
 }
             "#,
         );
@@ -972,7 +1001,8 @@ fn f() {
     let mut x = X;
     let c2 = || { x = X; x };
     let mut x = X;
-    let c2 = move || { x = X; };
+    let c3 = move || { x = X; };
+    _ = (c1, c2, c3);
 }
             "#,
         );
@@ -1023,7 +1053,7 @@ fn x(t: &[u8]) {
 
             a = 2;
           //^^^^^ 💡 error: cannot mutate immutable variable `a`
-
+            _ = b;
         }
         _ => {}
     }
@@ -1079,6 +1109,7 @@ fn f() {
     let x = Box::new(5);
     let closure = || *x = 2;
                     //^ 💡 error: cannot mutate immutable variable `x`
+    _ = closure;
 }
 "#,
         );
@@ -1156,6 +1187,7 @@ macro_rules! mac {
 fn main2() {
     let mut x = mac![];
       //^^^^^ 💡 warn: variable does not need to be mutable
+    _ = x;
 }
         "#,
         );
diff --git a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
index 083ef3e8dc1..d15233d15c2 100644
--- a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
+++ b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
@@ -74,8 +74,8 @@ mod tests {
             r#"
 //- minicore: iterators
 fn foo() {
-    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
-}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
+    let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
 "#,
         );
     }
@@ -117,7 +117,7 @@ fn foo() {
 fn foo() {
     let mut m = core::iter::repeat(())
         .filter_map(|()| Some(92));
-    let n = m.next();
+    let _n = m.next();
 }
 "#,
         );
@@ -148,22 +148,22 @@ fn foo() {
 
 fn foo() {
     #[allow(clippy::filter_map_next)]
-    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+    let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
 }
 
 #[deny(clippy::filter_map_next)]
 fn foo() {
-    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
-}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
+    let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
 
 fn foo() {
-    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
-}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
+    let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
 
 #[warn(clippy::filter_map_next)]
 fn foo() {
-    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
-}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
+    let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
 
 "#,
         );
diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index 15bd28c00df..1f400bb42dd 100644
--- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -205,7 +205,7 @@ fn main() {
     test(123);
        //^^^ 💡 error: expected &i32, found i32
 }
-fn test(arg: &i32) {}
+fn test(_arg: &i32) {}
 "#,
         );
     }
@@ -217,13 +217,13 @@ fn test(arg: &i32) {}
 fn main() {
     test(123$0);
 }
-fn test(arg: &i32) {}
+fn test(_arg: &i32) {}
             "#,
             r#"
 fn main() {
     test(&123);
 }
-fn test(arg: &i32) {}
+fn test(_arg: &i32) {}
             "#,
         );
     }
@@ -235,13 +235,13 @@ fn test(arg: &i32) {}
 fn main() {
     test($0123);
 }
-fn test(arg: &mut i32) {}
+fn test(_arg: &mut i32) {}
             "#,
             r#"
 fn main() {
     test(&mut 123);
 }
-fn test(arg: &mut i32) {}
+fn test(_arg: &mut i32) {}
             "#,
         );
     }
@@ -254,13 +254,13 @@ fn test(arg: &mut i32) {}
 fn main() {
     test($0[1, 2, 3]);
 }
-fn test(arg: &[i32]) {}
+fn test(_arg: &[i32]) {}
             "#,
             r#"
 fn main() {
     test(&[1, 2, 3]);
 }
-fn test(arg: &[i32]) {}
+fn test(_arg: &[i32]) {}
             "#,
         );
     }
@@ -279,7 +279,7 @@ impl core::ops::Deref for Foo {
 fn main() {
     test($0Foo);
 }
-fn test(arg: &Bar) {}
+fn test(_arg: &Bar) {}
             "#,
             r#"
 struct Foo;
@@ -291,7 +291,7 @@ impl core::ops::Deref for Foo {
 fn main() {
     test(&Foo);
 }
-fn test(arg: &Bar) {}
+fn test(_arg: &Bar) {}
             "#,
         );
     }
@@ -305,7 +305,7 @@ fn main() {
 }
 struct Test;
 impl Test {
-    fn call_by_ref(&self, arg: &i32) {}
+    fn call_by_ref(&self, _arg: &i32) {}
 }
             "#,
             r#"
@@ -314,7 +314,7 @@ fn main() {
 }
 struct Test;
 impl Test {
-    fn call_by_ref(&self, arg: &i32) {}
+    fn call_by_ref(&self, _arg: &i32) {}
 }
             "#,
         );
@@ -345,7 +345,7 @@ macro_rules! thousand {
         1000_u64
     };
 }
-fn test(foo: &u64) {}
+fn test(_foo: &u64) {}
 fn main() {
     test($0thousand!());
 }
@@ -356,7 +356,7 @@ macro_rules! thousand {
         1000_u64
     };
 }
-fn test(foo: &u64) {}
+fn test(_foo: &u64) {}
 fn main() {
     test(&thousand!());
 }
@@ -369,12 +369,12 @@ fn main() {
         check_fix(
             r#"
 fn main() {
-    let test: &mut i32 = $0123;
+    let _test: &mut i32 = $0123;
 }
             "#,
             r#"
 fn main() {
-    let test: &mut i32 = &mut 123;
+    let _test: &mut i32 = &mut 123;
 }
             "#,
         );
@@ -411,7 +411,7 @@ fn div(x: i32, y: i32) -> Option<i32> {
             fn f<const N: u64>() -> Rate<N> { // FIXME: add some error
                 loop {}
             }
-            fn run(t: Rate<5>) {
+            fn run(_t: Rate<5>) {
             }
             fn main() {
                 run(f()) // FIXME: remove this error
@@ -426,7 +426,7 @@ fn div(x: i32, y: i32) -> Option<i32> {
         check_diagnostics(
             r#"
             pub struct Rate<T, const NOM: u32, const DENOM: u32>(T);
-            fn run(t: Rate<u32, 1, 1>) {
+            fn run(_t: Rate<u32, 1, 1>) {
             }
             fn main() {
                 run(Rate::<_, _, _>(5));
@@ -650,7 +650,7 @@ fn h() {
             r#"
 struct X<T>(T);
 
-fn foo(x: X<Unknown>) {}
+fn foo(_x: X<Unknown>) {}
 fn test1() {
     // Unknown might be `i32`, so we should not emit type mismatch here.
     foo(X(42));
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index 4af67227115..4e215a89d79 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -142,8 +142,8 @@ fn t<T>() -> T { loop {} }
         check_diagnostics(
             r#"
 fn main() {
-    let x = [(); _];
-    let y: [(); 10] = [(); _];
+    let _x = [(); _];
+    let _y: [(); 10] = [(); _];
     _ = 0;
     (_,) = (1,);
 }
diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs
new file mode 100644
index 00000000000..2658f12f8ad
--- /dev/null
+++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs
@@ -0,0 +1,110 @@
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
+
+// Diagnostic: unused-variables
+//
+// This diagnostic is triggered when a local variable is not used.
+pub(crate) fn unused_variables(
+    ctx: &DiagnosticsContext<'_>,
+    d: &hir::UnusedVariable,
+) -> Diagnostic {
+    let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcLint("unused_variables"),
+        "unused variable",
+        ast,
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::check_diagnostics;
+
+    #[test]
+    fn unused_variables_simple() {
+        check_diagnostics(
+            r#"
+//- minicore: fn
+struct Foo { f1: i32, f2: i64 }
+
+fn f(kkk: i32) {}
+   //^^^ warn: unused variable
+fn main() {
+    let a = 2;
+      //^ warn: unused variable
+    let b = 5;
+    // note: `unused variable` implies `unused mut`, so we should not emit both at the same time.
+    let mut c = f(b);
+      //^^^^^ warn: unused variable
+    let (d, e) = (3, 5);
+       //^ warn: unused variable
+    let _ = e;
+    let f1 = 2;
+    let f2 = 5;
+    let f = Foo { f1, f2 };
+    match f {
+        Foo { f1, f2 } => {
+            //^^ warn: unused variable
+            _ = f2;
+        }
+    }
+    let g = false;
+    if g {}
+    let h: fn() -> i32 = || 2;
+    let i = h();
+      //^ warn: unused variable
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn unused_self() {
+        check_diagnostics(
+            r#"
+struct S {
+}
+impl S {
+    fn owned_self(self, u: i32) {}
+                      //^ warn: unused variable
+    fn ref_self(&self, u: i32) {}
+                     //^ warn: unused variable
+    fn ref_mut_self(&mut self, u: i32) {}
+                             //^ warn: unused variable
+    fn owned_mut_self(mut self) {}
+                    //^^^^^^^^ 💡 warn: variable does not need to be mutable
+
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn allow_unused_variables_for_identifiers_starting_with_underline() {
+        check_diagnostics(
+            r#"
+fn main() {
+    let _x = 2;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn respect_lint_attributes_for_unused_variables() {
+        check_diagnostics(
+            r#"
+fn main() {
+    #[allow(unused_variables)]
+    let x = 2;
+}
+
+#[deny(unused)]
+fn main2() {
+    let x = 2;
+      //^ error: unused variable
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index ebe197a6790..fe5567544e8 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -56,6 +56,7 @@ mod handlers {
     pub(crate) mod unresolved_proc_macro;
     pub(crate) mod undeclared_label;
     pub(crate) mod unreachable_label;
+    pub(crate) mod unused_variables;
 
     // The handlers below are unusual, the implement the diagnostics as well.
     pub(crate) mod field_shorthand;
@@ -368,6 +369,7 @@ pub fn diagnostics(
             AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d),
             AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled),
             AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
+            AnyDiagnostic::UnusedVariable(d) => handlers::unused_variables::unused_variables(&ctx, &d),
             AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
             AnyDiagnostic::MismatchedTupleStructPatArgCount(d) => handlers::mismatched_arg_count::mismatched_tuple_struct_pat_arg_count(&ctx, &d),
         };
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 12e46c0f883..4152e606755 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -484,7 +484,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 10739..10747,
+                                        range: 10752..10760,
                                     },
                                 ),
                                 tooltip: "",
@@ -497,7 +497,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 10771..10775,
+                                        range: 10784..10788,
                                     },
                                 ),
                                 tooltip: "",
@@ -522,7 +522,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 10739..10747,
+                                        range: 10752..10760,
                                     },
                                 ),
                                 tooltip: "",
@@ -535,7 +535,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 10771..10775,
+                                        range: 10784..10788,
                                     },
                                 ),
                                 tooltip: "",
@@ -560,7 +560,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 10739..10747,
+                                        range: 10752..10760,
                                     },
                                 ),
                                 tooltip: "",
@@ -573,7 +573,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 10771..10775,
+                                        range: 10784..10788,
                                     },
                                 ),
                                 tooltip: "",
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 573f56b003a..cc41d87f5d9 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -489,7 +489,7 @@ pub mod ops {
             I: SliceIndex<[T]>,
         {
             type Output = I::Output;
-            fn index(&self, index: I) -> &I::Output {
+            fn index(&self, _index: I) -> &I::Output {
                 loop {}
             }
         }
@@ -497,7 +497,7 @@ pub mod ops {
         where
             I: SliceIndex<[T]>,
         {
-            fn index_mut(&mut self, index: I) -> &mut I::Output {
+            fn index_mut(&mut self, _index: I) -> &mut I::Output {
                 loop {}
             }
         }
@@ -507,7 +507,7 @@ pub mod ops {
             I: SliceIndex<[T]>,
         {
             type Output = I::Output;
-            fn index(&self, index: I) -> &I::Output {
+            fn index(&self, _index: I) -> &I::Output {
                 loop {}
             }
         }
@@ -515,7 +515,7 @@ pub mod ops {
         where
             I: SliceIndex<[T]>,
         {
-            fn index_mut(&mut self, index: I) -> &mut I::Output {
+            fn index_mut(&mut self, _index: I) -> &mut I::Output {
                 loop {}
             }
         }
@@ -863,17 +863,17 @@ pub mod fmt {
     pub struct DebugTuple;
     pub struct DebugStruct;
     impl Formatter<'_> {
-        pub fn debug_tuple(&mut self, name: &str) -> DebugTuple {
+        pub fn debug_tuple(&mut self, _name: &str) -> DebugTuple {
             DebugTuple
         }
 
-        pub fn debug_struct(&mut self, name: &str) -> DebugStruct {
+        pub fn debug_struct(&mut self, _name: &str) -> DebugStruct {
             DebugStruct
         }
     }
 
     impl DebugTuple {
-        pub fn field(&mut self, value: &dyn Debug) -> &mut Self {
+        pub fn field(&mut self, _value: &dyn Debug) -> &mut Self {
             self
         }
 
@@ -883,7 +883,7 @@ pub mod fmt {
     }
 
     impl DebugStruct {
-        pub fn field(&mut self, name: &str, value: &dyn Debug) -> &mut Self {
+        pub fn field(&mut self, _name: &str, _value: &dyn Debug) -> &mut Self {
             self
         }
 
@@ -996,7 +996,7 @@ pub mod fmt {
         ($($t:ty)*) => {
             $(
                 impl const Debug for $t {
-                    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+                    fn fmt(&self, _f: &mut Formatter<'_>) -> Result {
                         Ok(())
                     }
                 }
@@ -1012,7 +1012,7 @@ pub mod fmt {
     }
 
     impl<T: Debug> Debug for [T] {
-        fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        fn fmt(&self, _f: &mut Formatter<'_>) -> Result {
             Ok(())
         }
     }
@@ -1062,7 +1062,7 @@ pub mod option {
             }
         }
 
-        pub fn and<U>(self, optb: Option<U>) -> Option<U> {
+        pub fn and<U>(self, _optb: Option<U>) -> Option<U> {
             loop {}
         }
         pub fn unwrap_or(self, default: T) -> T {
@@ -1080,25 +1080,25 @@ pub mod option {
         }
         // endregion:result
         // region:fn
-        pub fn and_then<U, F>(self, f: F) -> Option<U>
+        pub fn and_then<U, F>(self, _f: F) -> Option<U>
         where
             F: FnOnce(T) -> Option<U>,
         {
             loop {}
         }
-        pub fn unwrap_or_else<F>(self, f: F) -> T
+        pub fn unwrap_or_else<F>(self, _f: F) -> T
         where
             F: FnOnce() -> T,
         {
             loop {}
         }
-        pub fn map_or<U, F>(self, default: U, f: F) -> U
+        pub fn map_or<U, F>(self, _default: U, _f: F) -> U
         where
             F: FnOnce(T) -> U,
         {
             loop {}
         }
-        pub fn map_or_else<U, D, F>(self, default: D, f: F) -> U
+        pub fn map_or_else<U, D, F>(self, _default: D, _f: F) -> U
         where
             D: FnOnce() -> U,
             F: FnOnce(T) -> U,
@@ -1129,7 +1129,7 @@ pub mod pin {
         pointer: P,
     }
     impl<P> Pin<P> {
-        pub fn new(pointer: P) -> Pin<P> {
+        pub fn new(_pointer: P) -> Pin<P> {
             loop {}
         }
     }
@@ -1226,7 +1226,7 @@ pub mod iter {
 
     mod sources {
         mod repeat {
-            pub fn repeat<T>(elt: T) -> Repeat<T> {
+            pub fn repeat<T>(_elt: T) -> Repeat<T> {
                 loop {}
             }
 
@@ -1266,7 +1266,7 @@ pub mod iter {
                 fn take(self, n: usize) -> crate::iter::Take<Self> {
                     loop {}
                 }
-                fn filter_map<B, F>(self, f: F) -> crate::iter::FilterMap<Self, F>
+                fn filter_map<B, F>(self, _f: F) -> crate::iter::FilterMap<Self, F>
                 where
                     Self: Sized,
                     F: FnMut(Self::Item) -> Option<B>,
@@ -1337,7 +1337,7 @@ mod panic {
 
 mod panicking {
     #[lang = "panic_fmt"]
-    pub const fn panic_fmt(fmt: crate::fmt::Arguments<'_>) -> ! {
+    pub const fn panic_fmt(_fmt: crate::fmt::Arguments<'_>) -> ! {
         loop {}
     }
 }