about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-03-07 09:49:49 +0000
committerbors <bors@rust-lang.org>2023-03-07 09:49:49 +0000
commit44ff3c407a9db70f0c8f02b04d73ab1f883f37fe (patch)
tree7448ecc21d6428cd3f4bbf07859a5a44ad8703b2
parent31c12ec282de5e2d4e835f320f8858277b3ba133 (diff)
parentbcd7ecb242cb546c83a40c1a3341543f1ed71064 (diff)
downloadrust-44ff3c407a9db70f0c8f02b04d73ab1f883f37fe.tar.gz
rust-44ff3c407a9db70f0c8f02b04d73ab1f883f37fe.zip
Auto merge of #14232 - HKalbasi:mir, r=Veykril
MIR episode 2

This PR adds:
1. `need-mut` and `unused-mut` diagnostics
2. `View mir` command which shows MIR for the body under cursor, useful for debugging
3. MIR lowering for or-patterns and for-loops
-rw-r--r--crates/hir-def/src/body.rs25
-rw-r--r--crates/hir-def/src/body/lower.rs117
-rw-r--r--crates/hir-def/src/body/pretty.rs23
-rw-r--r--crates/hir-def/src/body/scope.rs39
-rw-r--r--crates/hir-def/src/expr.rs12
-rw-r--r--crates/hir-def/src/resolver.rs14
-rw-r--r--crates/hir-ty/src/consteval/tests.rs259
-rw-r--r--crates/hir-ty/src/db.rs5
-rw-r--r--crates/hir-ty/src/diagnostics/decl_check.rs4
-rw-r--r--crates/hir-ty/src/diagnostics/match_check.rs3
-rw-r--r--crates/hir-ty/src/display.rs1
-rw-r--r--crates/hir-ty/src/infer.rs29
-rw-r--r--crates/hir-ty/src/infer/coerce.rs2
-rw-r--r--crates/hir-ty/src/infer/expr.rs6
-rw-r--r--crates/hir-ty/src/infer/pat.rs30
-rw-r--r--crates/hir-ty/src/infer/path.rs2
-rw-r--r--crates/hir-ty/src/layout/tests.rs12
-rw-r--r--crates/hir-ty/src/method_resolution.rs4
-rw-r--r--crates/hir-ty/src/mir.rs75
-rw-r--r--crates/hir-ty/src/mir/borrowck.rs223
-rw-r--r--crates/hir-ty/src/mir/eval.rs36
-rw-r--r--crates/hir-ty/src/mir/lower.rs1206
-rw-r--r--crates/hir-ty/src/mir/lower/as_place.rs237
-rw-r--r--crates/hir-ty/src/mir/pretty.rs348
-rw-r--r--crates/hir-ty/src/tests/coercion.rs3
-rw-r--r--crates/hir-ty/src/tests/method_resolution.rs3
-rw-r--r--crates/hir/src/diagnostics.rs15
-rw-r--r--crates/hir/src/from_id.rs8
-rw-r--r--crates/hir/src/has_source.rs13
-rw-r--r--crates/hir/src/lib.rs157
-rw-r--r--crates/hir/src/semantics.rs9
-rw-r--r--crates/hir/src/semantics/source_to_def.rs21
-rw-r--r--crates/hir/src/source_analyzer.rs8
-rw-r--r--crates/ide-assists/src/handlers/convert_match_to_let_else.rs2
-rw-r--r--crates/ide-assists/src/handlers/extract_function.rs25
-rw-r--r--crates/ide-assists/src/handlers/inline_local_variable.rs11
-rw-r--r--crates/ide-db/src/rename.rs104
-rw-r--r--crates/ide-db/src/search.rs8
-rw-r--r--crates/ide-diagnostics/src/handlers/mutability_errors.rs592
-rw-r--r--crates/ide-diagnostics/src/lib.rs4
-rw-r--r--crates/ide/src/highlight_related.rs57
-rw-r--r--crates/ide/src/hover/render.rs6
-rw-r--r--crates/ide/src/lib.rs5
-rw-r--r--crates/ide/src/navigation_target.rs20
-rw-r--r--crates/ide/src/rename.rs22
-rw-r--r--crates/ide/src/view_mir.rs29
-rw-r--r--crates/rust-analyzer/src/handlers.rs10
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/syntax/src/ast/traits.rs2
-rw-r--r--crates/test-utils/src/minicore.rs14
-rw-r--r--docs/dev/lsp-extensions.md13
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/commands.ts27
-rw-r--r--editors/code/src/lsp_ext.ts3
-rw-r--r--editors/code/src/main.ts1
-rw-r--r--lib/la-arena/src/map.rs6
57 files changed, 3164 insertions, 760 deletions
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index 8fd9255b8b1..545d2bebf5f 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -24,7 +24,7 @@ use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr};
 use crate::{
     attr::Attrs,
     db::DefDatabase,
-    expr::{dummy_expr_id, Expr, ExprId, Label, LabelId, Pat, PatId},
+    expr::{dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId},
     item_scope::BuiltinShadowMode,
     macro_id_to_def_id,
     nameres::DefMap,
@@ -270,6 +270,7 @@ pub struct Mark {
 pub struct Body {
     pub exprs: Arena<Expr>,
     pub pats: Arena<Pat>,
+    pub bindings: Arena<Binding>,
     pub or_pats: FxHashMap<PatId, Arc<[PatId]>>,
     pub labels: Arena<Label>,
     /// The patterns for the function's parameters. While the parameter types are
@@ -435,13 +436,24 @@ impl Body {
     }
 
     fn shrink_to_fit(&mut self) {
-        let Self { _c: _, body_expr: _, block_scopes, or_pats, exprs, labels, params, pats } = self;
+        let Self {
+            _c: _,
+            body_expr: _,
+            block_scopes,
+            or_pats,
+            exprs,
+            labels,
+            params,
+            pats,
+            bindings,
+        } = self;
         block_scopes.shrink_to_fit();
         or_pats.shrink_to_fit();
         exprs.shrink_to_fit();
         labels.shrink_to_fit();
         params.shrink_to_fit();
         pats.shrink_to_fit();
+        bindings.shrink_to_fit();
     }
 }
 
@@ -451,6 +463,7 @@ impl Default for Body {
             body_expr: dummy_expr_id(),
             exprs: Default::default(),
             pats: Default::default(),
+            bindings: Default::default(),
             or_pats: Default::default(),
             labels: Default::default(),
             params: Default::default(),
@@ -484,6 +497,14 @@ impl Index<LabelId> for Body {
     }
 }
 
+impl Index<BindingId> for Body {
+    type Output = Binding;
+
+    fn index(&self, b: BindingId) -> &Binding {
+        &self.bindings[b]
+    }
+}
+
 // FIXME: Change `node_` prefix to something more reasonable.
 // Perhaps `expr_syntax` and `expr_id`?
 impl BodySourceMap {
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 3164a5f4c29..b7458bfb8a7 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -15,6 +15,7 @@ use la_arena::Arena;
 use once_cell::unsync::OnceCell;
 use profile::Count;
 use rustc_hash::FxHashMap;
+use smallvec::SmallVec;
 use syntax::{
     ast::{
         self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
@@ -30,9 +31,9 @@ use crate::{
     builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
     db::DefDatabase,
     expr::{
-        dummy_expr_id, Array, BindingAnnotation, ClosureKind, Expr, ExprId, FloatTypeWrapper,
-        Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat, RecordLitField,
-        Statement,
+        dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, ClosureKind, Expr, ExprId,
+        FloatTypeWrapper, Label, LabelId, Literal, MatchArm, Movability, Pat, PatId,
+        RecordFieldPat, RecordLitField, Statement,
     },
     item_scope::BuiltinShadowMode,
     path::{GenericArgs, Path},
@@ -87,6 +88,7 @@ pub(super) fn lower(
         body: Body {
             exprs: Arena::default(),
             pats: Arena::default(),
+            bindings: Arena::default(),
             labels: Arena::default(),
             params: Vec::new(),
             body_expr: dummy_expr_id(),
@@ -116,6 +118,22 @@ struct ExprCollector<'a> {
     is_lowering_generator: bool,
 }
 
+#[derive(Debug, Default)]
+struct BindingList {
+    map: FxHashMap<Name, BindingId>,
+}
+
+impl BindingList {
+    fn find(
+        &mut self,
+        ec: &mut ExprCollector<'_>,
+        name: Name,
+        mode: BindingAnnotation,
+    ) -> BindingId {
+        *self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode))
+    }
+}
+
 impl ExprCollector<'_> {
     fn collect(
         mut self,
@@ -127,17 +145,16 @@ impl ExprCollector<'_> {
                 param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false))
             {
                 let ptr = AstPtr::new(&self_param);
-                let param_pat = self.alloc_pat(
-                    Pat::Bind {
-                        name: name![self],
-                        mode: BindingAnnotation::new(
-                            self_param.mut_token().is_some() && self_param.amp_token().is_none(),
-                            false,
-                        ),
-                        subpat: None,
-                    },
-                    Either::Right(ptr),
+                let binding_id = self.alloc_binding(
+                    name![self],
+                    BindingAnnotation::new(
+                        self_param.mut_token().is_some() && self_param.amp_token().is_none(),
+                        false,
+                    ),
                 );
+                let param_pat =
+                    self.alloc_pat(Pat::Bind { id: binding_id, subpat: None }, Either::Right(ptr));
+                self.add_definition_to_binding(binding_id, param_pat);
                 self.body.params.push(param_pat);
             }
 
@@ -179,6 +196,9 @@ impl ExprCollector<'_> {
         id
     }
 
+    fn alloc_binding(&mut self, name: Name, mode: BindingAnnotation) -> BindingId {
+        self.body.bindings.alloc(Binding { name, mode, definitions: SmallVec::new() })
+    }
     fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
         let src = self.expander.to_source(ptr);
         let id = self.make_pat(pat, src.clone());
@@ -804,7 +824,7 @@ impl ExprCollector<'_> {
     }
 
     fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
-        let pat_id = self.collect_pat_(pat);
+        let pat_id = self.collect_pat_(pat, &mut BindingList::default());
         for (_, pats) in self.name_to_pat_grouping.drain() {
             let pats = Arc::<[_]>::from(pats);
             self.body.or_pats.extend(pats.iter().map(|&pat| (pat, pats.clone())));
@@ -820,7 +840,7 @@ impl ExprCollector<'_> {
         }
     }
 
-    fn collect_pat_(&mut self, pat: ast::Pat) -> PatId {
+    fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatId {
         let pattern = match &pat {
             ast::Pat::IdentPat(bp) => {
                 let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
@@ -828,8 +848,10 @@ impl ExprCollector<'_> {
                 let key = self.is_lowering_inside_or_pat.then(|| name.clone());
                 let annotation =
                     BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
-                let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat));
-                let pattern = if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
+                let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat, binding_list));
+                let (binding, pattern) = if annotation == BindingAnnotation::Unannotated
+                    && subpat.is_none()
+                {
                     // This could also be a single-segment path pattern. To
                     // decide that, we need to try resolving the name.
                     let (resolved, _) = self.expander.def_map.resolve_path(
@@ -839,12 +861,12 @@ impl ExprCollector<'_> {
                         BuiltinShadowMode::Other,
                     );
                     match resolved.take_values() {
-                        Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()),
+                        Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())),
                         Some(ModuleDefId::EnumVariantId(_)) => {
                             // this is only really valid for unit variants, but
                             // shadowing other enum variants with a pattern is
                             // an error anyway
-                            Pat::Path(name.into())
+                            (None, Pat::Path(name.into()))
                         }
                         Some(ModuleDefId::AdtId(AdtId::StructId(s)))
                             if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
@@ -852,17 +874,24 @@ impl ExprCollector<'_> {
                             // Funnily enough, record structs *can* be shadowed
                             // by pattern bindings (but unit or tuple structs
                             // can't).
-                            Pat::Path(name.into())
+                            (None, Pat::Path(name.into()))
                         }
                         // shadowing statics is an error as well, so we just ignore that case here
-                        _ => Pat::Bind { name, mode: annotation, subpat },
+                        _ => {
+                            let id = binding_list.find(self, name, annotation);
+                            (Some(id), Pat::Bind { id, subpat })
+                        }
                     }
                 } else {
-                    Pat::Bind { name, mode: annotation, subpat }
+                    let id = binding_list.find(self, name, annotation);
+                    (Some(id), Pat::Bind { id, subpat })
                 };
 
                 let ptr = AstPtr::new(&pat);
                 let pat = self.alloc_pat(pattern, Either::Left(ptr));
+                if let Some(binding_id) = binding {
+                    self.add_definition_to_binding(binding_id, pat);
+                }
                 if let Some(key) = key {
                     self.name_to_pat_grouping.entry(key).or_default().push(pat);
                 }
@@ -871,11 +900,11 @@ impl ExprCollector<'_> {
             ast::Pat::TupleStructPat(p) => {
                 let path =
                     p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
-                let (args, ellipsis) = self.collect_tuple_pat(p.fields());
+                let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list);
                 Pat::TupleStruct { path, args, ellipsis }
             }
             ast::Pat::RefPat(p) => {
-                let pat = self.collect_pat_opt(p.pat());
+                let pat = self.collect_pat_opt_(p.pat(), binding_list);
                 let mutability = Mutability::from_mutable(p.mut_token().is_some());
                 Pat::Ref { pat, mutability }
             }
@@ -886,12 +915,12 @@ impl ExprCollector<'_> {
             }
             ast::Pat::OrPat(p) => {
                 self.is_lowering_inside_or_pat = true;
-                let pats = p.pats().map(|p| self.collect_pat_(p)).collect();
+                let pats = p.pats().map(|p| self.collect_pat_(p, binding_list)).collect();
                 Pat::Or(pats)
             }
-            ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat()),
+            ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat(), binding_list),
             ast::Pat::TuplePat(p) => {
-                let (args, ellipsis) = self.collect_tuple_pat(p.fields());
+                let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list);
                 Pat::Tuple { args, ellipsis }
             }
             ast::Pat::WildcardPat(_) => Pat::Wild,
@@ -904,7 +933,7 @@ impl ExprCollector<'_> {
                     .fields()
                     .filter_map(|f| {
                         let ast_pat = f.pat()?;
-                        let pat = self.collect_pat_(ast_pat);
+                        let pat = self.collect_pat_(ast_pat, binding_list);
                         let name = f.field_name()?.as_name();
                         Some(RecordFieldPat { name, pat })
                     })
@@ -923,9 +952,15 @@ impl ExprCollector<'_> {
 
                 // FIXME properly handle `RestPat`
                 Pat::Slice {
-                    prefix: prefix.into_iter().map(|p| self.collect_pat_(p)).collect(),
-                    slice: slice.map(|p| self.collect_pat_(p)),
-                    suffix: suffix.into_iter().map(|p| self.collect_pat_(p)).collect(),
+                    prefix: prefix
+                        .into_iter()
+                        .map(|p| self.collect_pat_(p, binding_list))
+                        .collect(),
+                    slice: slice.map(|p| self.collect_pat_(p, binding_list)),
+                    suffix: suffix
+                        .into_iter()
+                        .map(|p| self.collect_pat_(p, binding_list))
+                        .collect(),
                 }
             }
             ast::Pat::LiteralPat(lit) => {
@@ -948,7 +983,7 @@ impl ExprCollector<'_> {
                 Pat::Missing
             }
             ast::Pat::BoxPat(boxpat) => {
-                let inner = self.collect_pat_opt_(boxpat.pat());
+                let inner = self.collect_pat_opt_(boxpat.pat(), binding_list);
                 Pat::Box { inner }
             }
             ast::Pat::ConstBlockPat(const_block_pat) => {
@@ -965,7 +1000,7 @@ impl ExprCollector<'_> {
                     let src = self.expander.to_source(Either::Left(AstPtr::new(&pat)));
                     let pat =
                         self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
-                            this.collect_pat_opt_(expanded_pat)
+                            this.collect_pat_opt_(expanded_pat, binding_list)
                         });
                     self.source_map.pat_map.insert(src, pat);
                     return pat;
@@ -979,21 +1014,25 @@ impl ExprCollector<'_> {
         self.alloc_pat(pattern, Either::Left(ptr))
     }
 
-    fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>) -> PatId {
+    fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>, binding_list: &mut BindingList) -> PatId {
         match pat {
-            Some(pat) => self.collect_pat_(pat),
+            Some(pat) => self.collect_pat_(pat, binding_list),
             None => self.missing_pat(),
         }
     }
 
-    fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Box<[PatId]>, Option<usize>) {
+    fn collect_tuple_pat(
+        &mut self,
+        args: AstChildren<ast::Pat>,
+        binding_list: &mut BindingList,
+    ) -> (Box<[PatId]>, Option<usize>) {
         // Find the location of the `..`, if there is one. Note that we do not
         // consider the possibility of there being multiple `..` here.
         let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
         // We want to skip the `..` pattern here, since we account for it above.
         let args = args
             .filter(|p| !matches!(p, ast::Pat::RestPat(_)))
-            .map(|p| self.collect_pat_(p))
+            .map(|p| self.collect_pat_(p, binding_list))
             .collect();
 
         (args, ellipsis)
@@ -1022,6 +1061,10 @@ impl ExprCollector<'_> {
             None => Some(()),
         }
     }
+
+    fn add_definition_to_binding(&mut self, binding_id: BindingId, pat_id: PatId) {
+        self.body.bindings[binding_id].definitions.push(pat_id);
+    }
 }
 
 impl From<ast::LiteralKind> for Literal {
diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs
index 622756ee8a9..f8b159797e4 100644
--- a/crates/hir-def/src/body/pretty.rs
+++ b/crates/hir-def/src/body/pretty.rs
@@ -5,7 +5,7 @@ use std::fmt::{self, Write};
 use syntax::ast::HasName;
 
 use crate::{
-    expr::{Array, BindingAnnotation, ClosureKind, Literal, Movability, Statement},
+    expr::{Array, BindingAnnotation, BindingId, ClosureKind, Literal, Movability, Statement},
     pretty::{print_generic_args, print_path, print_type_ref},
     type_ref::TypeRef,
 };
@@ -524,14 +524,8 @@ impl<'a> Printer<'a> {
             }
             Pat::Path(path) => self.print_path(path),
             Pat::Lit(expr) => self.print_expr(*expr),
-            Pat::Bind { mode, name, subpat } => {
-                let mode = match mode {
-                    BindingAnnotation::Unannotated => "",
-                    BindingAnnotation::Mutable => "mut ",
-                    BindingAnnotation::Ref => "ref ",
-                    BindingAnnotation::RefMut => "ref mut ",
-                };
-                w!(self, "{}{}", mode, name);
+            Pat::Bind { id, subpat } => {
+                self.print_binding(*id);
                 if let Some(pat) = subpat {
                     self.whitespace();
                     self.print_pat(*pat);
@@ -635,4 +629,15 @@ impl<'a> Printer<'a> {
     fn print_path(&mut self, path: &Path) {
         print_path(path, self).unwrap();
     }
+
+    fn print_binding(&mut self, id: BindingId) {
+        let Binding { name, mode, .. } = &self.body.bindings[id];
+        let mode = match mode {
+            BindingAnnotation::Unannotated => "",
+            BindingAnnotation::Mutable => "mut ",
+            BindingAnnotation::Ref => "ref ",
+            BindingAnnotation::RefMut => "ref mut ",
+        };
+        w!(self, "{}{}", mode, name);
+    }
 }
diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs
index cab657b807e..12fc1f116d7 100644
--- a/crates/hir-def/src/body/scope.rs
+++ b/crates/hir-def/src/body/scope.rs
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
 use crate::{
     body::Body,
     db::DefDatabase,
-    expr::{Expr, ExprId, LabelId, Pat, PatId, Statement},
+    expr::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
     BlockId, DefWithBodyId,
 };
 
@@ -23,7 +23,7 @@ pub struct ExprScopes {
 #[derive(Debug, PartialEq, Eq)]
 pub struct ScopeEntry {
     name: Name,
-    pat: PatId,
+    binding: BindingId,
 }
 
 impl ScopeEntry {
@@ -31,8 +31,8 @@ impl ScopeEntry {
         &self.name
     }
 
-    pub fn pat(&self) -> PatId {
-        self.pat
+    pub fn binding(&self) -> BindingId {
+        self.binding
     }
 }
 
@@ -126,18 +126,23 @@ impl ExprScopes {
         })
     }
 
-    fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
+    fn add_bindings(&mut self, body: &Body, scope: ScopeId, binding: BindingId) {
+        let Binding { name, .. } = &body.bindings[binding];
+        let entry = ScopeEntry { name: name.clone(), binding };
+        self.scopes[scope].entries.push(entry);
+    }
+
+    fn add_pat_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
         let pattern = &body[pat];
-        if let Pat::Bind { name, .. } = pattern {
-            let entry = ScopeEntry { name: name.clone(), pat };
-            self.scopes[scope].entries.push(entry);
+        if let Pat::Bind { id, .. } = pattern {
+            self.add_bindings(body, scope, *id);
         }
 
-        pattern.walk_child_pats(|pat| self.add_bindings(body, scope, pat));
+        pattern.walk_child_pats(|pat| self.add_pat_bindings(body, scope, pat));
     }
 
     fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) {
-        params.iter().for_each(|pat| self.add_bindings(body, scope, *pat));
+        params.iter().for_each(|pat| self.add_pat_bindings(body, scope, *pat));
     }
 
     fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
@@ -170,7 +175,7 @@ fn compute_block_scopes(
                 }
 
                 *scope = scopes.new_scope(*scope);
-                scopes.add_bindings(body, *scope, *pat);
+                scopes.add_pat_bindings(body, *scope, *pat);
             }
             Statement::Expr { expr, .. } => {
                 compute_expr_scopes(*expr, body, scopes, scope);
@@ -208,7 +213,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
         Expr::For { iterable, pat, body: body_expr, label } => {
             compute_expr_scopes(*iterable, body, scopes, scope);
             let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
-            scopes.add_bindings(body, scope, *pat);
+            scopes.add_pat_bindings(body, scope, *pat);
             compute_expr_scopes(*body_expr, body, scopes, &mut scope);
         }
         Expr::While { condition, body: body_expr, label } => {
@@ -229,7 +234,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
             compute_expr_scopes(*expr, body, scopes, scope);
             for arm in arms.iter() {
                 let mut scope = scopes.new_scope(*scope);
-                scopes.add_bindings(body, scope, arm.pat);
+                scopes.add_pat_bindings(body, scope, arm.pat);
                 if let Some(guard) = arm.guard {
                     scope = scopes.new_scope(scope);
                     compute_expr_scopes(guard, body, scopes, &mut scope);
@@ -248,7 +253,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
         &Expr::Let { pat, expr } => {
             compute_expr_scopes(expr, body, scopes, scope);
             *scope = scopes.new_scope(*scope);
-            scopes.add_bindings(body, *scope, pat);
+            scopes.add_pat_bindings(body, *scope, pat);
         }
         e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
     };
@@ -450,7 +455,7 @@ fn foo() {
         let function = find_function(&db, file_id);
 
         let scopes = db.expr_scopes(function.into());
-        let (_body, source_map) = db.body_with_source_map(function.into());
+        let (body, source_map) = db.body_with_source_map(function.into());
 
         let expr_scope = {
             let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
@@ -460,7 +465,9 @@ fn foo() {
         };
 
         let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
-        let pat_src = source_map.pat_syntax(resolved.pat()).unwrap();
+        let pat_src = source_map
+            .pat_syntax(*body.bindings[resolved.binding()].definitions.first().unwrap())
+            .unwrap();
 
         let local_name = pat_src.value.either(
             |it| it.syntax_node_ptr().to_node(file.syntax()),
diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs
index 78a2f861233..bbea608c55e 100644
--- a/crates/hir-def/src/expr.rs
+++ b/crates/hir-def/src/expr.rs
@@ -17,6 +17,7 @@ use std::fmt;
 use hir_expand::name::Name;
 use intern::Interned;
 use la_arena::{Idx, RawIdx};
+use smallvec::SmallVec;
 
 use crate::{
     builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
@@ -29,6 +30,8 @@ pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, Unar
 
 pub type ExprId = Idx<Expr>;
 
+pub type BindingId = Idx<Binding>;
+
 /// FIXME: this is a hacky function which should be removed
 pub(crate) fn dummy_expr_id() -> ExprId {
     ExprId::from_raw(RawIdx::from(u32::MAX))
@@ -434,6 +437,13 @@ impl BindingAnnotation {
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Binding {
+    pub name: Name,
+    pub mode: BindingAnnotation,
+    pub definitions: SmallVec<[PatId; 1]>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
 pub struct RecordFieldPat {
     pub name: Name,
     pub pat: PatId,
@@ -451,7 +461,7 @@ pub enum Pat {
     Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> },
     Path(Box<Path>),
     Lit(ExprId),
-    Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
+    Bind { id: BindingId, subpat: Option<PatId> },
     TupleStruct { path: Option<Box<Path>>, args: Box<[PatId]>, ellipsis: Option<usize> },
     Ref { pat: PatId, mutability: Mutability },
     Box { inner: PatId },
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index eea837ddd23..61e64fc1036 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -12,7 +12,7 @@ use crate::{
     body::scope::{ExprScopes, ScopeId},
     builtin_type::BuiltinType,
     db::DefDatabase,
-    expr::{ExprId, LabelId, PatId},
+    expr::{BindingId, ExprId, LabelId},
     generics::{GenericParams, TypeOrConstParamData},
     item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
     nameres::DefMap,
@@ -105,7 +105,7 @@ pub enum ResolveValueResult {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum ValueNs {
     ImplSelf(ImplId),
-    LocalBinding(PatId),
+    LocalBinding(BindingId),
     FunctionId(FunctionId),
     ConstId(ConstId),
     StaticId(StaticId),
@@ -267,7 +267,7 @@ impl Resolver {
 
                         if let Some(e) = entry {
                             return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(
-                                e.pat(),
+                                e.binding(),
                             )));
                         }
                     }
@@ -617,7 +617,7 @@ pub enum ScopeDef {
     ImplSelfType(ImplId),
     AdtSelfType(AdtId),
     GenericParam(GenericParamId),
-    Local(PatId),
+    Local(BindingId),
     Label(LabelId),
 }
 
@@ -669,7 +669,7 @@ impl Scope {
                     acc.add(&name, ScopeDef::Label(label))
                 }
                 scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| {
-                    acc.add_local(e.name(), e.pat());
+                    acc.add_local(e.name(), e.binding());
                 });
             }
         }
@@ -859,7 +859,7 @@ impl ScopeNames {
             self.add(name, ScopeDef::Unknown)
         }
     }
-    fn add_local(&mut self, name: &Name, pat: PatId) {
+    fn add_local(&mut self, name: &Name, binding: BindingId) {
         let set = self.map.entry(name.clone()).or_default();
         // XXX: hack, account for local (and only local) shadowing.
         //
@@ -870,7 +870,7 @@ impl ScopeNames {
             cov_mark::hit!(shadowing_shows_single_completion);
             return;
         }
-        set.push(ScopeDef::Local(pat))
+        set.push(ScopeDef::Local(binding))
     }
 }
 
diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs
index 19145b2d98e..f2e42d6e503 100644
--- a/crates/hir-ty/src/consteval/tests.rs
+++ b/crates/hir-ty/src/consteval/tests.rs
@@ -103,6 +103,22 @@ fn references() {
     "#,
         5,
     );
+    check_number(
+        r#"
+    struct Foo(i32);
+    impl Foo {
+        fn method(&mut self, x: i32) {
+            self.0 = 2 * self.0 + x;
+        }
+    }
+    const GOAL: i32 = {
+        let mut x = Foo(3);
+        x.method(5);
+        x.0
+    };
+    "#,
+        11,
+    );
 }
 
 #[test]
@@ -133,6 +149,60 @@ fn reference_autoderef() {
 }
 
 #[test]
+fn overloaded_deref() {
+    // FIXME: We should support this.
+    check_fail(
+        r#"
+    //- minicore: deref_mut
+    struct Foo;
+
+    impl core::ops::Deref for Foo {
+        type Target = i32;
+        fn deref(&self) -> &i32 {
+            &5
+        }
+    }
+
+    const GOAL: i32 = {
+        let x = Foo;
+        let y = &*x;
+        *y + *x
+    };
+    "#,
+        ConstEvalError::MirLowerError(MirLowerError::NotSupported(
+            "explicit overloaded deref".into(),
+        )),
+    );
+}
+
+#[test]
+fn overloaded_deref_autoref() {
+    check_number(
+        r#"
+    //- minicore: deref_mut
+    struct Foo;
+    struct Bar;
+
+    impl core::ops::Deref for Foo {
+        type Target = Bar;
+        fn deref(&self) -> &Bar {
+            &Bar
+        }
+    }
+
+    impl Bar {
+        fn method(&self) -> i32 {
+            5
+        }
+    }
+
+    const GOAL: i32 = Foo.method();
+    "#,
+        5,
+    );
+}
+
+#[test]
 fn function_call() {
     check_number(
         r#"
@@ -358,7 +428,7 @@ fn ifs() {
         if a < b { b } else { a }
     }
 
-    const GOAL: u8 = max(max(1, max(10, 3)), 0-122);
+    const GOAL: i32 = max(max(1, max(10, 3)), 0-122);
         "#,
         10,
     );
@@ -366,7 +436,7 @@ fn ifs() {
     check_number(
         r#"
     const fn max(a: &i32, b: &i32) -> &i32 {
-        if a < b { b } else { a }
+        if *a < *b { b } else { a }
     }
 
     const GOAL: i32 = *max(max(&1, max(&10, &3)), &5);
@@ -400,6 +470,43 @@ fn loops() {
 }
 
 #[test]
+fn for_loops() {
+    check_number(
+        r#"
+    //- minicore: iterator
+
+    struct Range {
+        start: u8,
+        end: u8,
+    }
+
+    impl Iterator for Range {
+        type Item = u8;
+        fn next(&mut self) -> Option<u8> {
+            if self.start >= self.end {
+                None
+            } else {
+                let r = self.start;
+                self.start = self.start + 1;
+                Some(r)
+            }
+        }
+    }
+
+    const GOAL: u8 = {
+        let mut sum = 0;
+        let ar = Range { start: 1, end: 11 };
+        for i in ar {
+            sum = sum + i;
+        }
+        sum
+    };
+        "#,
+        55,
+    );
+}
+
+#[test]
 fn recursion() {
     check_number(
         r#"
@@ -466,6 +573,16 @@ fn tuples() {
     );
     check_number(
         r#"
+    const GOAL: u8 = {
+        let mut a = (10, 20, 3, 15);
+        a.1 = 2;
+        a.0 + a.1 + a.2 + a.3
+    };
+        "#,
+        30,
+    );
+    check_number(
+        r#"
     struct TupleLike(i32, u8, i64, u16);
     const GOAL: u8 = {
         let a = TupleLike(10, 20, 3, 15);
@@ -493,6 +610,33 @@ fn tuples() {
 }
 
 #[test]
+fn path_pattern_matching() {
+    check_number(
+        r#"
+    enum Season {
+        Spring,
+        Summer,
+        Fall,
+        Winter,
+    }
+
+    use Season::*;
+
+    const fn f(x: Season) -> i32 {
+        match x {
+            Spring => 1,
+            Summer => 2,
+            Fall => 3,
+            Winter => 4,
+        }
+    }
+    const GOAL: i32 = f(Spring) + 10 * f(Summer) + 100 * f(Fall) + 1000 * f(Winter);
+        "#,
+        4321,
+    );
+}
+
+#[test]
 fn pattern_matching_ergonomics() {
     check_number(
         r#"
@@ -539,13 +683,56 @@ fn let_else() {
         let Some(x) = x else { return 10 };
         2 * x
     }
-    const GOAL: u8 = f(Some(1000)) + f(None);
+    const GOAL: i32 = f(Some(1000)) + f(None);
         "#,
         2010,
     );
 }
 
 #[test]
+fn function_param_patterns() {
+    check_number(
+        r#"
+    const fn f((a, b): &(u8, u8)) -> u8 {
+        *a + *b
+    }
+    const GOAL: u8 = f(&(2, 3));
+        "#,
+        5,
+    );
+    check_number(
+        r#"
+    const fn f(c @ (a, b): &(u8, u8)) -> u8 {
+        *a + *b + c.0 + (*c).1
+    }
+    const GOAL: u8 = f(&(2, 3));
+        "#,
+        10,
+    );
+    check_number(
+        r#"
+    const fn f(ref a: u8) -> u8 {
+        *a
+    }
+    const GOAL: u8 = f(2);
+        "#,
+        2,
+    );
+    check_number(
+        r#"
+    struct Foo(u8);
+    impl Foo {
+        const fn f(&self, (a, b): &(u8, u8)) -> u8 {
+            self.0 + *a + *b
+        }
+    }
+    const GOAL: u8 = Foo(4).f(&(2, 3));
+        "#,
+        9,
+    );
+}
+
+#[test]
 fn options() {
     check_number(
         r#"
@@ -572,7 +759,7 @@ fn options() {
             0
         }
     }
-    const GOAL: u8 = f(Some(Some(10))) + f(Some(None)) + f(None);
+    const GOAL: i32 = f(Some(Some(10))) + f(Some(None)) + f(None);
         "#,
         11,
     );
@@ -599,6 +786,44 @@ fn options() {
 }
 
 #[test]
+fn or_pattern() {
+    check_number(
+        r#"
+    const GOAL: u8 = {
+        let (a | a) = 2;
+        a
+    };
+        "#,
+        2,
+    );
+    check_number(
+        r#"
+    //- minicore: option
+    const fn f(x: Option<i32>) -> i32 {
+        let (Some(a) | Some(a)) = x else { return 2; };
+        a
+    }
+    const GOAL: i32 = f(Some(10)) + f(None);
+        "#,
+        12,
+    );
+    check_number(
+        r#"
+    //- minicore: option
+    const fn f(x: Option<i32>, y: Option<i32>) -> i32 {
+        match (x, y) {
+            (Some(x), Some(y)) => x * y,
+            (Some(a), _) | (_, Some(a)) => a,
+            _ => 10,
+        }
+    }
+    const GOAL: i32 = f(Some(10), Some(20)) + f(Some(30), None) + f(None, Some(40)) + f(None, None);
+        "#,
+        280,
+    );
+}
+
+#[test]
 fn array_and_index() {
     check_number(
         r#"
@@ -665,24 +890,24 @@ fn enums() {
         r#"
     enum E {
         F1 = 1,
-        F2 = 2 * E::F1 as u8,
-        F3 = 3 * E::F2 as u8,
+        F2 = 2 * E::F1 as isize, // Rustc expects an isize here
+        F3 = 3 * E::F2 as isize,
     }
-    const GOAL: i32 = E::F3 as u8;
+    const GOAL: u8 = E::F3 as u8;
     "#,
         6,
     );
     check_number(
         r#"
     enum E { F1 = 1, F2, }
-    const GOAL: i32 = E::F2 as u8;
+    const GOAL: u8 = E::F2 as u8;
     "#,
         2,
     );
     check_number(
         r#"
     enum E { F1, }
-    const GOAL: i32 = E::F1 as u8;
+    const GOAL: u8 = E::F1 as u8;
     "#,
         0,
     );
@@ -813,8 +1038,22 @@ fn exec_limits() {
         }
         sum
     }
-    const GOAL: usize = f(10000);
+    const GOAL: i32 = f(10000);
     "#,
         10000 * 10000,
     );
 }
+
+#[test]
+fn type_error() {
+    let e = eval_goal(
+        r#"
+    const GOAL: u8 = {
+        let x: u16 = 2;
+        let y: (u8, u8) = x;
+        y.0
+    };
+    "#,
+    );
+    assert!(matches!(e, Err(ConstEvalError::MirLowerError(MirLowerError::TypeMismatch(_)))));
+}
diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs
index 60e51b65f6b..304c78767f1 100644
--- a/crates/hir-ty/src/db.rs
+++ b/crates/hir-ty/src/db.rs
@@ -18,7 +18,7 @@ use crate::{
     chalk_db,
     consteval::ConstEvalError,
     method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
-    mir::{MirBody, MirLowerError},
+    mir::{BorrowckResult, MirBody, MirLowerError},
     Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner,
     PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId,
     ValueTyDefId,
@@ -38,6 +38,9 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::cycle(crate::mir::mir_body_recover)]
     fn mir_body(&self, def: DefWithBodyId) -> Result<Arc<MirBody>, MirLowerError>;
 
+    #[salsa::invoke(crate::mir::borrowck_query)]
+    fn borrowck(&self, def: DefWithBodyId) -> Result<Arc<BorrowckResult>, MirLowerError>;
+
     #[salsa::invoke(crate::lower::ty_query)]
     #[salsa::cycle(crate::lower::ty_recover)]
     fn ty(&self, def: TyDefId) -> Binders<Ty>;
diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs
index f4d1013ceb0..d36b93e3bdd 100644
--- a/crates/hir-ty/src/diagnostics/decl_check.rs
+++ b/crates/hir-ty/src/diagnostics/decl_check.rs
@@ -235,8 +235,8 @@ impl<'a> DeclValidator<'a> {
         let pats_replacements = body
             .pats
             .iter()
-            .filter_map(|(id, pat)| match pat {
-                Pat::Bind { name, .. } => Some((id, name)),
+            .filter_map(|(pat_id, pat)| match pat {
+                Pat::Bind { id, .. } => Some((pat_id, &body.bindings[*id].name)),
                 _ => None,
             })
             .filter_map(|(id, bind_name)| {
diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs
index 8b0f051b46b..859a37804ae 100644
--- a/crates/hir-ty/src/diagnostics/match_check.rs
+++ b/crates/hir-ty/src/diagnostics/match_check.rs
@@ -146,8 +146,9 @@ impl<'a> PatCtxt<'a> {
                 PatKind::Leaf { subpatterns }
             }
 
-            hir_def::expr::Pat::Bind { ref name, subpat, .. } => {
+            hir_def::expr::Pat::Bind { id, subpat, .. } => {
                 let bm = self.infer.pat_binding_modes[&pat];
+                let name = &self.body.bindings[id].name;
                 match (bm, ty.kind(Interner)) {
                     (BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty,
                     (BindingMode::Ref(_), _) => {
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 6dde4773602..bd3eccfe43d 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -531,6 +531,7 @@ fn render_const_scalar(
             hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name),
             hir_def::AdtId::EnumId(_) => f.write_str("<enum-not-supported>"),
         },
+        chalk_ir::TyKind::FnDef(..) => ty.hir_fmt(f),
         _ => f.write_str("<not-supported>"),
     }
 }
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 869b39ab37d..3a75f871211 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -22,7 +22,7 @@ use hir_def::{
     body::Body,
     builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
     data::{ConstData, StaticData},
-    expr::{BindingAnnotation, ExprId, ExprOrPatId, PatId},
+    expr::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, PatId},
     lang_item::{LangItem, LangItemTarget},
     layout::Integer,
     path::Path,
@@ -291,8 +291,10 @@ pub enum Adjust {
 /// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`.
 /// The target type is `U` in both cases, with the region and mutability
 /// being those shared by both the receiver and the returned reference.
+///
+/// Mutability is `None` when we are not sure.
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub struct OverloadedDeref(pub Mutability);
+pub struct OverloadedDeref(pub Option<Mutability>);
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum AutoBorrow {
@@ -352,7 +354,10 @@ pub struct InferenceResult {
     /// **Note**: When a pattern type is resolved it may still contain
     /// unresolved or missing subpatterns or subpatterns of mismatched types.
     pub type_of_pat: ArenaMap<PatId, Ty>,
+    pub type_of_binding: ArenaMap<BindingId, Ty>,
     pub type_of_rpit: ArenaMap<RpitId, Ty>,
+    /// Type of the result of `.into_iter()` on the for. `ExprId` is the one of the whole for loop.
+    pub type_of_for_iterator: FxHashMap<ExprId, Ty>,
     type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>,
     /// Interned common types to return references to.
     standard_types: InternedStandardTypes,
@@ -414,6 +419,14 @@ impl Index<PatId> for InferenceResult {
     }
 }
 
+impl Index<BindingId> for InferenceResult {
+    type Output = Ty;
+
+    fn index(&self, b: BindingId) -> &Ty {
+        self.type_of_binding.get(b).unwrap_or(&self.standard_types.unknown)
+    }
+}
+
 /// The inference context contains all information needed during type inference.
 #[derive(Clone, Debug)]
 pub(crate) struct InferenceContext<'a> {
@@ -534,7 +547,13 @@ impl<'a> InferenceContext<'a> {
         for ty in result.type_of_pat.values_mut() {
             *ty = table.resolve_completely(ty.clone());
         }
-        for ty in result.type_of_rpit.iter_mut().map(|x| x.1) {
+        for ty in result.type_of_binding.values_mut() {
+            *ty = table.resolve_completely(ty.clone());
+        }
+        for ty in result.type_of_rpit.values_mut() {
+            *ty = table.resolve_completely(ty.clone());
+        }
+        for ty in result.type_of_for_iterator.values_mut() {
             *ty = table.resolve_completely(ty.clone());
         }
         for mismatch in result.type_mismatches.values_mut() {
@@ -704,6 +723,10 @@ impl<'a> InferenceContext<'a> {
         self.result.type_of_pat.insert(pat, ty);
     }
 
+    fn write_binding_ty(&mut self, id: BindingId, ty: Ty) {
+        self.result.type_of_binding.insert(id, ty);
+    }
+
     fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) {
         self.result.diagnostics.push(diagnostic);
     }
diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index 8bce47d71cb..48c91530266 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -693,7 +693,7 @@ pub(super) fn auto_deref_adjust_steps(autoderef: &Autoderef<'_, '_>) -> Vec<Adju
         .iter()
         .map(|(kind, _source)| match kind {
             // We do not know what kind of deref we require at this point yet
-            AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
+            AutoderefKind::Overloaded => Some(OverloadedDeref(None)),
             AutoderefKind::Builtin => None,
         })
         .zip(targets)
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index cca84488c94..535189ff028 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -242,8 +242,10 @@ impl<'a> InferenceContext<'a> {
                 let iterable_ty = self.infer_expr(iterable, &Expectation::none());
                 let into_iter_ty =
                     self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
-                let pat_ty =
-                    self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item());
+                let pat_ty = self
+                    .resolve_associated_type(into_iter_ty.clone(), self.resolve_iterator_item());
+
+                self.result.type_of_for_iterator.insert(tgt_expr, into_iter_ty);
 
                 self.infer_top_pat(pat, &pat_ty);
                 self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| {
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index a7bd009e34b..0f49e837881 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -5,7 +5,10 @@ use std::iter::repeat_with;
 use chalk_ir::Mutability;
 use hir_def::{
     body::Body,
-    expr::{BindingAnnotation, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId, RecordFieldPat},
+    expr::{
+        Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId,
+        RecordFieldPat,
+    },
     path::Path,
 };
 use hir_expand::name::Name;
@@ -248,8 +251,8 @@ impl<'a> InferenceContext<'a> {
                 // FIXME update resolver for the surrounding expression
                 self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty())
             }
-            Pat::Bind { mode, name: _, subpat } => {
-                return self.infer_bind_pat(pat, *mode, default_bm, *subpat, &expected);
+            Pat::Bind { id, subpat } => {
+                return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected);
             }
             Pat::Slice { prefix, slice, suffix } => {
                 self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm)
@@ -320,11 +323,12 @@ impl<'a> InferenceContext<'a> {
     fn infer_bind_pat(
         &mut self,
         pat: PatId,
-        mode: BindingAnnotation,
+        binding: BindingId,
         default_bm: BindingMode,
         subpat: Option<PatId>,
         expected: &Ty,
     ) -> Ty {
+        let Binding { mode, .. } = self.body.bindings[binding];
         let mode = if mode == BindingAnnotation::Unannotated {
             default_bm
         } else {
@@ -344,7 +348,8 @@ impl<'a> InferenceContext<'a> {
             }
             BindingMode::Move => inner_ty.clone(),
         };
-        self.write_pat_ty(pat, bound_ty);
+        self.write_pat_ty(pat, bound_ty.clone());
+        self.write_binding_ty(binding, bound_ty);
         return inner_ty;
     }
 
@@ -420,11 +425,14 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
         Pat::Lit(expr) => {
             !matches!(body[*expr], Expr::Literal(Literal::String(..) | Literal::ByteString(..)))
         }
-        Pat::Bind {
-            mode: BindingAnnotation::Mutable | BindingAnnotation::Unannotated,
-            subpat: Some(subpat),
-            ..
-        } => is_non_ref_pat(body, *subpat),
+        Pat::Bind { id, subpat: Some(subpat), .. }
+            if matches!(
+                body.bindings[*id].mode,
+                BindingAnnotation::Mutable | BindingAnnotation::Unannotated
+            ) =>
+        {
+            is_non_ref_pat(body, *subpat)
+        }
         Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false,
     }
 }
@@ -432,7 +440,7 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
 pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool {
     let mut res = false;
     walk_pats(body, pat_id, &mut |pat| {
-        res |= matches!(pat, Pat::Bind { mode: BindingAnnotation::Ref, .. })
+        res |= matches!(pat, Pat::Bind { id, .. } if body.bindings[*id].mode == BindingAnnotation::Ref);
     });
     res
 }
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index 891e1fab2ed..2267fedaa8e 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -50,7 +50,7 @@ impl<'a> InferenceContext<'a> {
         };
 
         let typable: ValueTyDefId = match value {
-            ValueNs::LocalBinding(pat) => match self.result.type_of_pat.get(pat) {
+            ValueNs::LocalBinding(pat) => match self.result.type_of_binding.get(pat) {
                 Some(ty) => return Some(ty.clone()),
                 None => {
                     never!("uninferred pattern?");
diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs
index 546044fc13a..a8971fde3c2 100644
--- a/crates/hir-ty/src/layout/tests.rs
+++ b/crates/hir-ty/src/layout/tests.rs
@@ -65,17 +65,9 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
         })
         .unwrap();
     let hir_body = db.body(adt_id.into());
-    let pat = hir_body
-        .pats
-        .iter()
-        .find(|x| match x.1 {
-            hir_def::expr::Pat::Bind { name, .. } => name.to_smol_str() == "goal",
-            _ => false,
-        })
-        .unwrap()
-        .0;
+    let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0;
     let infer = db.infer(adt_id.into());
-    let goal_ty = infer.type_of_pat[pat].clone();
+    let goal_ty = infer.type_of_binding[b].clone();
     layout_of_ty(&db, &goal_ty, module_id.krate())
 }
 
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index 8dd34bc3882..92a17fc3a99 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -579,8 +579,8 @@ impl ReceiverAdjustments {
                     ty = new_ty.clone();
                     adjust.push(Adjustment {
                         kind: Adjust::Deref(match kind {
-                            // FIXME should we know the mutability here?
-                            AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
+                            // FIXME should we know the mutability here, when autoref is `None`?
+                            AutoderefKind::Overloaded => Some(OverloadedDeref(self.autoref)),
                             AutoderefKind::Builtin => None,
                         }),
                         target: new_ty,
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index 140caad5456..7c1cbbdf53d 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -1,23 +1,27 @@
 //! MIR definitions and implementation
 
-use std::iter;
+use std::{fmt::Display, iter};
 
 use crate::{
     infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty,
 };
 use chalk_ir::Mutability;
 use hir_def::{
-    expr::{Expr, Ordering},
+    expr::{BindingId, Expr, ExprId, Ordering, PatId},
     DefWithBodyId, FieldId, UnionId, VariantId,
 };
-use la_arena::{Arena, Idx, RawIdx};
+use la_arena::{Arena, ArenaMap, Idx, RawIdx};
 
 mod eval;
 mod lower;
+mod borrowck;
+mod pretty;
 
+pub use borrowck::{borrowck_query, BorrowckResult, MutabilityReason};
 pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError};
 pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError};
 use smallvec::{smallvec, SmallVec};
+use stdx::impl_from;
 
 use super::consteval::{intern_const_scalar, try_const_usize};
 
@@ -30,13 +34,7 @@ fn return_slot() -> LocalId {
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct Local {
-    pub mutability: Mutability,
-    //pub local_info: Option<Box<LocalInfo>>,
-    //pub internal: bool,
-    //pub is_block_tail: Option<BlockTailInfo>,
     pub ty: Ty,
-    //pub user_ty: Option<Box<UserTypeProjections>>,
-    //pub source_info: SourceInfo,
 }
 
 /// An operand in MIR represents a "value" in Rust, the definition of which is undecided and part of
@@ -85,6 +83,10 @@ impl Operand {
     fn from_bytes(data: Vec<u8>, ty: Ty) -> Self {
         Operand::from_concrete_const(data, MemoryMap::default(), ty)
     }
+
+    fn const_zst(ty: Ty) -> Operand {
+        Self::from_bytes(vec![], ty)
+    }
 }
 
 #[derive(Debug, PartialEq, Eq, Clone)]
@@ -181,6 +183,11 @@ impl SwitchTargets {
         iter::zip(&self.values, &self.targets).map(|(x, y)| (*x, *y))
     }
 
+    /// Returns a slice with all possible jump targets (including the fallback target).
+    pub fn all_targets(&self) -> &[BasicBlockId] {
+        &self.targets
+    }
+
     /// Finds the `BasicBlock` to which this `SwitchInt` will branch given the
     /// specific value. This cannot fail, as it'll return the `otherwise`
     /// branch if there's not a specific match for the value.
@@ -557,6 +564,30 @@ pub enum BinOp {
     Offset,
 }
 
+impl Display for BinOp {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            BinOp::Add => "+",
+            BinOp::Sub => "-",
+            BinOp::Mul => "*",
+            BinOp::Div => "/",
+            BinOp::Rem => "%",
+            BinOp::BitXor => "^",
+            BinOp::BitAnd => "&",
+            BinOp::BitOr => "|",
+            BinOp::Shl => "<<",
+            BinOp::Shr => ">>",
+            BinOp::Eq => "==",
+            BinOp::Lt => "<",
+            BinOp::Le => "<=",
+            BinOp::Ne => "!=",
+            BinOp::Ge => ">=",
+            BinOp::Gt => ">",
+            BinOp::Offset => "`offset`",
+        })
+    }
+}
+
 impl From<hir_def::expr::ArithOp> for BinOp {
     fn from(value: hir_def::expr::ArithOp) -> Self {
         match value {
@@ -758,7 +789,7 @@ pub enum Rvalue {
 }
 
 #[derive(Debug, PartialEq, Eq, Clone)]
-pub enum Statement {
+pub enum StatementKind {
     Assign(Place, Rvalue),
     //FakeRead(Box<(FakeReadCause, Place)>),
     //SetDiscriminant {
@@ -773,6 +804,17 @@ pub enum Statement {
     //Intrinsic(Box<NonDivergingIntrinsic>),
     Nop,
 }
+impl StatementKind {
+    fn with_span(self, span: MirSpan) -> Statement {
+        Statement { kind: self, span }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Statement {
+    pub kind: StatementKind,
+    pub span: MirSpan,
+}
 
 #[derive(Debug, Default, PartialEq, Eq)]
 pub struct BasicBlock {
@@ -803,10 +845,19 @@ pub struct MirBody {
     pub start_block: BasicBlockId,
     pub owner: DefWithBodyId,
     pub arg_count: usize,
+    pub binding_locals: ArenaMap<BindingId, LocalId>,
+    pub param_locals: Vec<LocalId>,
 }
 
-impl MirBody {}
-
 fn const_as_usize(c: &Const) -> usize {
     try_const_usize(c).unwrap() as usize
 }
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum MirSpan {
+    ExprId(ExprId),
+    PatId(PatId),
+    Unknown,
+}
+
+impl_from!(ExprId, PatId for MirSpan);
diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs
new file mode 100644
index 00000000000..c8729af86a9
--- /dev/null
+++ b/crates/hir-ty/src/mir/borrowck.rs
@@ -0,0 +1,223 @@
+//! MIR borrow checker, which is used in diagnostics like `unused_mut`
+
+// Currently it is an ad-hoc implementation, only useful for mutability analysis. Feel free to remove all of these
+// if needed for implementing a proper borrow checker.
+
+use std::sync::Arc;
+
+use hir_def::DefWithBodyId;
+use la_arena::ArenaMap;
+use stdx::never;
+
+use crate::db::HirDatabase;
+
+use super::{
+    BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, Place, ProjectionElem,
+    Rvalue, StatementKind, Terminator,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+/// Stores spans which implies that the local should be mutable.
+pub enum MutabilityReason {
+    Mut { spans: Vec<MirSpan> },
+    Not,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BorrowckResult {
+    pub mir_body: Arc<MirBody>,
+    pub mutability_of_locals: ArenaMap<LocalId, MutabilityReason>,
+}
+
+pub fn borrowck_query(
+    db: &dyn HirDatabase,
+    def: DefWithBodyId,
+) -> Result<Arc<BorrowckResult>, MirLowerError> {
+    let _p = profile::span("borrowck_query");
+    let body = db.mir_body(def)?;
+    let r = BorrowckResult { mutability_of_locals: mutability_of_locals(&body), mir_body: body };
+    Ok(Arc::new(r))
+}
+
+fn is_place_direct(lvalue: &Place) -> bool {
+    !lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref)
+}
+
+enum ProjectionCase {
+    /// Projection is a local
+    Direct,
+    /// Projection is some field or slice of a local
+    DirectPart,
+    /// Projection is deref of something
+    Indirect,
+}
+
+fn place_case(lvalue: &Place) -> ProjectionCase {
+    let mut is_part_of = false;
+    for proj in lvalue.projection.iter().rev() {
+        match proj {
+            ProjectionElem::Deref => return ProjectionCase::Indirect, // It's indirect
+            ProjectionElem::ConstantIndex { .. }
+            | ProjectionElem::Subslice { .. }
+            | ProjectionElem::Field(_)
+            | ProjectionElem::TupleField(_)
+            | ProjectionElem::Index(_) => {
+                is_part_of = true;
+            }
+            ProjectionElem::OpaqueCast(_) => (),
+        }
+    }
+    if is_part_of {
+        ProjectionCase::DirectPart
+    } else {
+        ProjectionCase::Direct
+    }
+}
+
+/// Returns a map from basic blocks to the set of locals that might be ever initialized before
+/// the start of the block. Only `StorageDead` can remove something from this map, and we ignore
+/// `Uninit` and `drop` and similars after initialization.
+fn ever_initialized_map(body: &MirBody) -> ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> {
+    let mut result: ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> =
+        body.basic_blocks.iter().map(|x| (x.0, ArenaMap::default())).collect();
+    fn dfs(
+        body: &MirBody,
+        b: BasicBlockId,
+        l: LocalId,
+        result: &mut ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>>,
+    ) {
+        let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs
+        let block = &body.basic_blocks[b];
+        for statement in &block.statements {
+            match &statement.kind {
+                StatementKind::Assign(p, _) => {
+                    if p.projection.len() == 0 && p.local == l {
+                        is_ever_initialized = true;
+                    }
+                }
+                StatementKind::StorageDead(p) => {
+                    if *p == l {
+                        is_ever_initialized = false;
+                    }
+                }
+                StatementKind::Deinit(_) | StatementKind::Nop | StatementKind::StorageLive(_) => (),
+            }
+        }
+        let Some(terminator) = &block.terminator else {
+            never!("Terminator should be none only in construction");
+            return;
+        };
+        let targets = match terminator {
+            Terminator::Goto { target } => vec![*target],
+            Terminator::SwitchInt { targets, .. } => targets.all_targets().to_vec(),
+            Terminator::Resume
+            | Terminator::Abort
+            | Terminator::Return
+            | Terminator::Unreachable => vec![],
+            Terminator::Call { target, cleanup, destination, .. } => {
+                if destination.projection.len() == 0 && destination.local == l {
+                    is_ever_initialized = true;
+                }
+                target.into_iter().chain(cleanup.into_iter()).copied().collect()
+            }
+            Terminator::Drop { .. }
+            | Terminator::DropAndReplace { .. }
+            | Terminator::Assert { .. }
+            | Terminator::Yield { .. }
+            | Terminator::GeneratorDrop
+            | Terminator::FalseEdge { .. }
+            | Terminator::FalseUnwind { .. } => {
+                never!("We don't emit these MIR terminators yet");
+                vec![]
+            }
+        };
+        for target in targets {
+            if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized {
+                result[target].insert(l, is_ever_initialized);
+                dfs(body, target, l, result);
+            }
+        }
+    }
+    for &l in &body.param_locals {
+        result[body.start_block].insert(l, true);
+        dfs(body, body.start_block, l, &mut result);
+    }
+    for l in body.locals.iter().map(|x| x.0) {
+        if !result[body.start_block].contains_idx(l) {
+            result[body.start_block].insert(l, false);
+            dfs(body, body.start_block, l, &mut result);
+        }
+    }
+    result
+}
+
+fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
+    let mut result: ArenaMap<LocalId, MutabilityReason> =
+        body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect();
+    let mut push_mut_span = |local, span| match &mut result[local] {
+        MutabilityReason::Mut { spans } => spans.push(span),
+        x @ MutabilityReason::Not => *x = MutabilityReason::Mut { spans: vec![span] },
+    };
+    let ever_init_maps = ever_initialized_map(body);
+    for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
+        let block = &body.basic_blocks[block_id];
+        for statement in &block.statements {
+            match &statement.kind {
+                StatementKind::Assign(place, value) => {
+                    match place_case(place) {
+                        ProjectionCase::Direct => {
+                            if ever_init_map.get(place.local).copied().unwrap_or_default() {
+                                push_mut_span(place.local, statement.span);
+                            } 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);
+                        }
+                        ProjectionCase::Indirect => (),
+                    }
+                    if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
+                        if is_place_direct(p) {
+                            push_mut_span(p.local, statement.span);
+                        }
+                    }
+                }
+                StatementKind::StorageDead(p) => {
+                    ever_init_map.insert(*p, false);
+                }
+                StatementKind::Deinit(_) | StatementKind::StorageLive(_) | StatementKind::Nop => (),
+            }
+        }
+        let Some(terminator) = &block.terminator else {
+            never!("Terminator should be none only in construction");
+            continue;
+        };
+        match terminator {
+            Terminator::Goto { .. }
+            | Terminator::Resume
+            | Terminator::Abort
+            | Terminator::Return
+            | Terminator::Unreachable
+            | Terminator::FalseEdge { .. }
+            | Terminator::FalseUnwind { .. }
+            | Terminator::GeneratorDrop
+            | Terminator::SwitchInt { .. }
+            | Terminator::Drop { .. }
+            | Terminator::DropAndReplace { .. }
+            | Terminator::Assert { .. }
+            | Terminator::Yield { .. } => (),
+            Terminator::Call { destination, .. } => {
+                if destination.projection.len() == 0 {
+                    if ever_init_map.get(destination.local).copied().unwrap_or_default() {
+                        push_mut_span(destination.local, MirSpan::Unknown);
+                    } else {
+                        ever_init_map.insert(destination.local, true);
+                    }
+                }
+            }
+        }
+    }
+    result
+}
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index 1ec32010a19..c5d843d9ebd 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -29,7 +29,7 @@ use crate::{
 
 use super::{
     const_as_usize, return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError,
-    Operand, Place, ProjectionElem, Rvalue, Statement, Terminator, UnOp,
+    Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, UnOp,
 };
 
 pub struct Evaluator<'a> {
@@ -263,12 +263,14 @@ impl Evaluator<'_> {
         for proj in &p.projection {
             match proj {
                 ProjectionElem::Deref => {
-                    match &ty.data(Interner).kind {
-                        TyKind::Ref(_, _, inner) => {
-                            ty = inner.clone();
+                    ty = match &ty.data(Interner).kind {
+                        TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(),
+                        _ => {
+                            return Err(MirEvalError::TypeError(
+                                "Overloaded deref in MIR is disallowed",
+                            ))
                         }
-                        _ => not_supported!("dereferencing smart pointers"),
-                    }
+                    };
                     let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?);
                     addr = Address::from_usize(x);
                 }
@@ -395,7 +397,8 @@ impl Evaluator<'_> {
                 .locals
                 .iter()
                 .map(|(id, x)| {
-                    let size = self.size_of_sized(&x.ty, &locals, "no unsized local")?;
+                    let size =
+                        self.size_of_sized(&x.ty, &locals, "no unsized local in extending stack")?;
                     let my_ptr = stack_ptr;
                     stack_ptr += size;
                     Ok((id, Stack(my_ptr)))
@@ -425,16 +428,16 @@ impl Evaluator<'_> {
                 return Err(MirEvalError::ExecutionLimitExceeded);
             }
             for statement in &current_block.statements {
-                match statement {
-                    Statement::Assign(l, r) => {
+                match &statement.kind {
+                    StatementKind::Assign(l, r) => {
                         let addr = self.place_addr(l, &locals)?;
                         let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?;
                         self.write_memory(addr, &result)?;
                     }
-                    Statement::Deinit(_) => not_supported!("de-init statement"),
-                    Statement::StorageLive(_) => not_supported!("storage-live statement"),
-                    Statement::StorageDead(_) => not_supported!("storage-dead statement"),
-                    Statement::Nop => (),
+                    StatementKind::Deinit(_) => not_supported!("de-init statement"),
+                    StatementKind::StorageLive(_)
+                    | StatementKind::StorageDead(_)
+                    | StatementKind::Nop => (),
                 }
             }
             let Some(terminator) = current_block.terminator.as_ref() else {
@@ -1121,7 +1124,12 @@ impl Evaluator<'_> {
     }
 
     fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> {
-        lang_attr(self.db.upcast(), def)
+        let candidate = lang_attr(self.db.upcast(), def)?;
+        // filter normal lang functions out
+        if [LangItem::IntoIterIntoIter, LangItem::IteratorNext].contains(&candidate) {
+            return None;
+        }
+        Some(candidate)
     }
 
     fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result<MemoryMap> {
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 936b56a0217..afa5275ac6b 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -6,31 +6,38 @@ use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind};
 use hir_def::{
     body::Body,
     expr::{
-        Array, BindingAnnotation, ExprId, LabelId, Literal, MatchArm, Pat, PatId, RecordLitField,
+        Array, BindingAnnotation, BindingId, ExprId, LabelId, Literal, MatchArm, Pat, PatId,
+        RecordLitField,
     },
+    lang_item::{LangItem, LangItemTarget},
     layout::LayoutError,
+    path::Path,
     resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
     DefWithBodyId, EnumVariantId, HasModule,
 };
+use hir_expand::name::Name;
 use la_arena::ArenaMap;
 
 use crate::{
-    consteval::ConstEvalError, db::HirDatabase, layout::layout_of_ty, mapping::ToChalk,
-    utils::generics, Adjust, AutoBorrow, CallableDefId, TyBuilder, TyExt,
+    consteval::ConstEvalError, db::HirDatabase, display::HirDisplay, infer::TypeMismatch,
+    inhabitedness::is_ty_uninhabited_from, layout::layout_of_ty, mapping::ToChalk, static_lifetime,
+    utils::generics, Adjust, Adjustment, AutoBorrow, CallableDefId, TyBuilder, TyExt,
 };
 
 use super::*;
 
+mod as_place;
+
 #[derive(Debug, Clone, Copy)]
 struct LoopBlocks {
     begin: BasicBlockId,
-    end: BasicBlockId,
+    /// `None` for loops that are not terminating
+    end: Option<BasicBlockId>,
 }
 
 struct MirLowerCtx<'a> {
     result: MirBody,
     owner: DefWithBodyId,
-    binding_locals: ArenaMap<PatId, LocalId>,
     current_loop_blocks: Option<LoopBlocks>,
     discr_temp: Option<Place>,
     db: &'a dyn HirDatabase,
@@ -43,13 +50,22 @@ pub enum MirLowerError {
     ConstEvalError(Box<ConstEvalError>),
     LayoutError(LayoutError),
     IncompleteExpr,
-    UnresolvedName,
+    UnresolvedName(String),
+    RecordLiteralWithoutPath,
+    UnresolvedMethod,
+    UnresolvedField,
     MissingFunctionDefinition,
+    TypeMismatch(TypeMismatch),
+    /// This should be never happen. Type mismatch should catch everything.
     TypeError(&'static str),
     NotSupported(String),
     ContinueWithoutLoop,
     BreakWithoutLoop,
     Loop,
+    /// Something that should never happen and is definitely a bug, but we don't want to panic if it happened
+    ImplementationError(&'static str),
+    LangItemNotFound(LangItem),
+    MutatingRvalue,
 }
 
 macro_rules! not_supported {
@@ -58,6 +74,13 @@ macro_rules! not_supported {
     };
 }
 
+macro_rules! implementation_error {
+    ($x: expr) => {{
+        ::stdx::never!("MIR lower implementation bug: {}", $x);
+        return Err(MirLowerError::ImplementationError($x));
+    }};
+}
+
 impl From<ConstEvalError> for MirLowerError {
     fn from(value: ConstEvalError) -> Self {
         match value {
@@ -73,93 +96,89 @@ impl From<LayoutError> for MirLowerError {
     }
 }
 
+impl MirLowerError {
+    fn unresolved_path(db: &dyn HirDatabase, p: &Path) -> Self {
+        Self::UnresolvedName(p.display(db).to_string())
+    }
+}
+
 type Result<T> = std::result::Result<T, MirLowerError>;
 
 impl MirLowerCtx<'_> {
     fn temp(&mut self, ty: Ty) -> Result<LocalId> {
         if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) {
-            not_supported!("unsized temporaries");
-        }
-        Ok(self.result.locals.alloc(Local { mutability: Mutability::Not, ty }))
-    }
-
-    fn lower_expr_as_place(&self, expr_id: ExprId) -> Option<Place> {
-        let adjustments = self.infer.expr_adjustments.get(&expr_id);
-        let mut r = self.lower_expr_as_place_without_adjust(expr_id)?;
-        for adjustment in adjustments.iter().flat_map(|x| x.iter()) {
-            match adjustment.kind {
-                Adjust::NeverToAny => return Some(r),
-                Adjust::Deref(None) => {
-                    r.projection.push(ProjectionElem::Deref);
-                }
-                Adjust::Deref(Some(_)) => return None,
-                Adjust::Borrow(_) => return None,
-                Adjust::Pointer(_) => return None,
-            }
-        }
-        Some(r)
-    }
-
-    fn lower_expr_as_place_without_adjust(&self, expr_id: ExprId) -> Option<Place> {
-        match &self.body.exprs[expr_id] {
-            Expr::Path(p) => {
-                let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
-                let pr = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path())?;
-                let pr = match pr {
-                    ResolveValueResult::ValueNs(v) => v,
-                    ResolveValueResult::Partial(..) => return None,
-                };
-                match pr {
-                    ValueNs::LocalBinding(pat_id) => Some(self.binding_locals[pat_id].into()),
-                    _ => None,
-                }
-            }
-            Expr::UnaryOp { expr, op } => match op {
-                hir_def::expr::UnaryOp::Deref => {
-                    let mut r = self.lower_expr_as_place(*expr)?;
-                    r.projection.push(ProjectionElem::Deref);
-                    Some(r)
-                }
-                _ => None,
-            },
-            _ => None,
+            implementation_error!("unsized temporaries");
         }
+        Ok(self.result.locals.alloc(Local { ty }))
     }
 
     fn lower_expr_to_some_operand(
         &mut self,
         expr_id: ExprId,
         current: BasicBlockId,
-    ) -> Result<(Operand, BasicBlockId)> {
+    ) -> Result<Option<(Operand, BasicBlockId)>> {
         if !self.has_adjustments(expr_id) {
             match &self.body.exprs[expr_id] {
                 Expr::Literal(l) => {
                     let ty = self.expr_ty(expr_id);
-                    return Ok((self.lower_literal_to_operand(ty, l)?, current));
+                    return Ok(Some((self.lower_literal_to_operand(ty, l)?, current)));
                 }
                 _ => (),
             }
         }
-        let (p, current) = self.lower_expr_to_some_place(expr_id, current)?;
-        Ok((Operand::Copy(p), current))
+        let Some((p, current)) = self.lower_expr_as_place(current, expr_id, true)? else {
+            return Ok(None);
+        };
+        Ok(Some((Operand::Copy(p), current)))
     }
 
-    fn lower_expr_to_some_place(
+    fn lower_expr_to_place_with_adjust(
         &mut self,
         expr_id: ExprId,
-        prev_block: BasicBlockId,
-    ) -> Result<(Place, BasicBlockId)> {
-        if let Some(p) = self.lower_expr_as_place(expr_id) {
-            return Ok((p, prev_block));
-        }
-        let mut ty = self.expr_ty(expr_id);
-        if let Some(x) = self.infer.expr_adjustments.get(&expr_id) {
-            if let Some(x) = x.last() {
-                ty = x.target.clone();
-            }
+        place: Place,
+        current: BasicBlockId,
+        adjustments: &[Adjustment],
+    ) -> Result<Option<BasicBlockId>> {
+        match adjustments.split_last() {
+            Some((last, rest)) => match &last.kind {
+                Adjust::NeverToAny => {
+                    let temp = self.temp(TyKind::Never.intern(Interner))?;
+                    self.lower_expr_to_place_with_adjust(expr_id, temp.into(), current, rest)
+                }
+                Adjust::Deref(_) => {
+                    let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, adjustments)? else {
+                            return Ok(None);
+                        };
+                    self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into());
+                    Ok(Some(current))
+                }
+                Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => {
+                    let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else {
+                            return Ok(None);
+                        };
+                    let bk = BorrowKind::from_chalk(*m);
+                    self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into());
+                    Ok(Some(current))
+                }
+                Adjust::Pointer(cast) => {
+                    let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else {
+                            return Ok(None);
+                        };
+                    self.push_assignment(
+                        current,
+                        place,
+                        Rvalue::Cast(
+                            CastKind::Pointer(cast.clone()),
+                            Operand::Copy(p).into(),
+                            last.target.clone(),
+                        ),
+                        expr_id.into(),
+                    );
+                    Ok(Some(current))
+                }
+            },
+            None => self.lower_expr_to_place_without_adjust(expr_id, place, current),
         }
-        let place = self.temp(ty)?;
-        Ok((place.into(), self.lower_expr_to_place(expr_id, place.into(), prev_block)?))
     }
 
     fn lower_expr_to_place(
@@ -167,48 +186,9 @@ impl MirLowerCtx<'_> {
         expr_id: ExprId,
         place: Place,
         prev_block: BasicBlockId,
-    ) -> Result<BasicBlockId> {
-        if let Some(x) = self.infer.expr_adjustments.get(&expr_id) {
-            if x.len() > 0 {
-                let tmp = self.temp(self.expr_ty(expr_id))?;
-                let current =
-                    self.lower_expr_to_place_without_adjust(expr_id, tmp.into(), prev_block)?;
-                let mut r = Place::from(tmp);
-                for adjustment in x {
-                    match &adjustment.kind {
-                        Adjust::NeverToAny => (),
-                        Adjust::Deref(None) => {
-                            r.projection.push(ProjectionElem::Deref);
-                        }
-                        Adjust::Deref(Some(_)) => not_supported!("overloaded dereference"),
-                        Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => {
-                            let tmp = self.temp(adjustment.target.clone())?;
-                            self.push_assignment(
-                                current,
-                                tmp.into(),
-                                Rvalue::Ref(BorrowKind::from_chalk(*m), r),
-                            );
-                            r = tmp.into();
-                        }
-                        Adjust::Pointer(cast) => {
-                            let target = &adjustment.target;
-                            let tmp = self.temp(target.clone())?;
-                            self.push_assignment(
-                                current,
-                                tmp.into(),
-                                Rvalue::Cast(
-                                    CastKind::Pointer(cast.clone()),
-                                    Operand::Copy(r).into(),
-                                    target.clone(),
-                                ),
-                            );
-                            r = tmp.into();
-                        }
-                    }
-                }
-                self.push_assignment(current, place, Operand::Copy(r).into());
-                return Ok(current);
-            }
+    ) -> Result<Option<BasicBlockId>> {
+        if let Some(adjustments) = self.infer.expr_adjustments.get(&expr_id) {
+            return self.lower_expr_to_place_with_adjust(expr_id, place, prev_block, adjustments);
         }
         self.lower_expr_to_place_without_adjust(expr_id, place, prev_block)
     }
@@ -218,27 +198,41 @@ impl MirLowerCtx<'_> {
         expr_id: ExprId,
         place: Place,
         mut current: BasicBlockId,
-    ) -> Result<BasicBlockId> {
+    ) -> Result<Option<BasicBlockId>> {
         match &self.body.exprs[expr_id] {
             Expr::Missing => Err(MirLowerError::IncompleteExpr),
             Expr::Path(p) => {
+                let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
                 let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
                 let pr = resolver
                     .resolve_path_in_value_ns(self.db.upcast(), p.mod_path())
-                    .ok_or(MirLowerError::UnresolvedName)?;
+                    .ok_or_else(unresolved_name)?;
                 let pr = match pr {
                     ResolveValueResult::ValueNs(v) => v,
                     ResolveValueResult::Partial(..) => {
-                        return match self
+                        if let Some(assoc) = self
                             .infer
                             .assoc_resolutions_for_expr(expr_id)
-                            .ok_or(MirLowerError::UnresolvedName)?
-                            .0
-                            //.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))?
                         {
-                            hir_def::AssocItemId::ConstId(c) => self.lower_const(c, current, place),
-                            _ => return Err(MirLowerError::UnresolvedName),
-                        };
+                            match assoc.0 {
+                                hir_def::AssocItemId::ConstId(c) => {
+                                    self.lower_const(c, current, place, expr_id.into())?;
+                                    return Ok(Some(current))
+                                },
+                                _ => not_supported!("associated functions and types"),
+                            }
+                        } else if let Some(variant) = self
+                            .infer
+                            .variant_resolution_for_expr(expr_id)
+                        {
+                            match variant {
+                                VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e),
+                                VariantId::StructId(s) => ValueNs::StructId(s),
+                                VariantId::UnionId(_) => implementation_error!("Union variant as path"),
+                            }
+                        } else {
+                            return Err(unresolved_name());
+                        }
                     }
                 };
                 match pr {
@@ -246,14 +240,26 @@ impl MirLowerCtx<'_> {
                         self.push_assignment(
                             current,
                             place,
-                            Operand::Copy(self.binding_locals[pat_id].into()).into(),
+                            Operand::Copy(self.result.binding_locals[pat_id].into()).into(),
+                            expr_id.into(),
                         );
-                        Ok(current)
+                        Ok(Some(current))
+                    }
+                    ValueNs::ConstId(const_id) => {
+                        self.lower_const(const_id, current, place, expr_id.into())?;
+                        Ok(Some(current))
                     }
-                    ValueNs::ConstId(const_id) => self.lower_const(const_id, current, place),
                     ValueNs::EnumVariantId(variant_id) => {
                         let ty = self.infer.type_of_expr[expr_id].clone();
-                        self.lower_enum_variant(variant_id, current, place, ty, vec![])
+                        let current = self.lower_enum_variant(
+                            variant_id,
+                            current,
+                            place,
+                            ty,
+                            vec![],
+                            expr_id.into(),
+                        )?;
+                        Ok(Some(current))
                     }
                     ValueNs::GenericParam(p) => {
                         let Some(def) = self.owner.as_generic_def_id() else {
@@ -277,12 +283,13 @@ impl MirLowerCtx<'_> {
                                 .intern(Interner),
                             )
                             .into(),
+                            expr_id.into(),
                         );
-                        Ok(current)
+                        Ok(Some(current))
                     }
                     ValueNs::StructId(_) => {
                         // It's probably a unit struct or a zero sized function, so no action is needed.
-                        Ok(current)
+                        Ok(Some(current))
                     }
                     x => {
                         not_supported!("unknown name {x:?} in value name space");
@@ -290,19 +297,18 @@ impl MirLowerCtx<'_> {
                 }
             }
             Expr::If { condition, then_branch, else_branch } => {
-                let (discr, current) = self.lower_expr_to_some_operand(*condition, current)?;
+                let Some((discr, current)) = self.lower_expr_to_some_operand(*condition, current)? else {
+                    return Ok(None);
+                };
                 let start_of_then = self.new_basic_block();
-                let end = self.new_basic_block();
                 let end_of_then =
                     self.lower_expr_to_place(*then_branch, place.clone(), start_of_then)?;
-                self.set_goto(end_of_then, end);
-                let mut start_of_else = end;
-                if let Some(else_branch) = else_branch {
-                    start_of_else = self.new_basic_block();
-                    let end_of_else =
-                        self.lower_expr_to_place(*else_branch, place, start_of_else)?;
-                    self.set_goto(end_of_else, end);
-                }
+                let start_of_else = self.new_basic_block();
+                let end_of_else = if let Some(else_branch) = else_branch {
+                    self.lower_expr_to_place(*else_branch, place, start_of_else)?
+                } else {
+                    Some(start_of_else)
+                };
                 self.set_terminator(
                     current,
                     Terminator::SwitchInt {
@@ -310,26 +316,37 @@ impl MirLowerCtx<'_> {
                         targets: SwitchTargets::static_if(1, start_of_then, start_of_else),
                     },
                 );
-                Ok(end)
+                Ok(self.merge_blocks(end_of_then, end_of_else))
             }
             Expr::Let { pat, expr } => {
-                let (cond_place, current) = self.lower_expr_to_some_place(*expr, current)?;
-                let result = self.new_basic_block();
+                let Some((cond_place, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+                    return Ok(None);
+                };
                 let (then_target, else_target) = self.pattern_match(
                     current,
                     None,
                     cond_place,
-                    self.expr_ty(*expr),
+                    self.expr_ty_after_adjustments(*expr),
                     *pat,
                     BindingAnnotation::Unannotated,
                 )?;
-                self.write_bytes_to_place(then_target, place.clone(), vec![1], TyBuilder::bool())?;
-                self.set_goto(then_target, result);
+                self.write_bytes_to_place(
+                    then_target,
+                    place.clone(),
+                    vec![1],
+                    TyBuilder::bool(),
+                    MirSpan::Unknown,
+                )?;
                 if let Some(else_target) = else_target {
-                    self.write_bytes_to_place(else_target, place, vec![0], TyBuilder::bool())?;
-                    self.set_goto(else_target, result);
+                    self.write_bytes_to_place(
+                        else_target,
+                        place,
+                        vec![0],
+                        TyBuilder::bool(),
+                        MirSpan::Unknown,
+                    )?;
                 }
-                Ok(result)
+                Ok(self.merge_blocks(Some(then_target), else_target))
             }
             Expr::Unsafe { id: _, statements, tail } => {
                 self.lower_block_to_place(None, statements, current, *tail, place)
@@ -337,14 +354,18 @@ impl MirLowerCtx<'_> {
             Expr::Block { id: _, statements, tail, label } => {
                 self.lower_block_to_place(*label, statements, current, *tail, place)
             }
-            Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin, _| {
-                let (_, block) = this.lower_expr_to_some_place(*body, begin)?;
-                this.set_goto(block, begin);
+            Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin| {
+                if let Some((_, block)) = this.lower_expr_as_place(begin, *body, true)? {
+                    this.set_goto(block, begin);
+                }
                 Ok(())
             }),
             Expr::While { condition, body, label } => {
-                self.lower_loop(current, *label, |this, begin, end| {
-                    let (discr, to_switch) = this.lower_expr_to_some_operand(*condition, begin)?;
+                self.lower_loop(current, *label, |this, begin| {
+                    let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else {
+                        return Ok(());
+                    };
+                    let end = this.current_loop_end()?;
                     let after_cond = this.new_basic_block();
                     this.set_terminator(
                         to_switch,
@@ -353,18 +374,71 @@ impl MirLowerCtx<'_> {
                             targets: SwitchTargets::static_if(1, after_cond, end),
                         },
                     );
-                    let (_, block) = this.lower_expr_to_some_place(*body, after_cond)?;
-                    this.set_goto(block, begin);
+                    if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? {
+                        this.set_goto(block, begin);
+                    }
                     Ok(())
                 })
             }
-            Expr::For { .. } => not_supported!("for loop"),
+            &Expr::For { iterable, pat, body, label } => {
+                let into_iter_fn = self.resolve_lang_item(LangItem::IntoIterIntoIter)?
+                    .as_function().ok_or(MirLowerError::LangItemNotFound(LangItem::IntoIterIntoIter))?;
+                let iter_next_fn = self.resolve_lang_item(LangItem::IteratorNext)?
+                    .as_function().ok_or(MirLowerError::LangItemNotFound(LangItem::IteratorNext))?;
+                let option_some = self.resolve_lang_item(LangItem::OptionSome)?
+                    .as_enum_variant().ok_or(MirLowerError::LangItemNotFound(LangItem::OptionSome))?;
+                let option = option_some.parent;
+                let into_iter_fn_op = Operand::const_zst(
+                    TyKind::FnDef(
+                        self.db.intern_callable_def(CallableDefId::FunctionId(into_iter_fn)).into(),
+                        Substitution::from1(Interner, self.expr_ty(iterable))
+                    ).intern(Interner));
+                let iter_next_fn_op = Operand::const_zst(
+                    TyKind::FnDef(
+                        self.db.intern_callable_def(CallableDefId::FunctionId(iter_next_fn)).into(),
+                        Substitution::from1(Interner, self.expr_ty(iterable))
+                    ).intern(Interner));
+                let &Some(iterator_ty) = &self.infer.type_of_for_iterator.get(&expr_id) else {
+                    return Err(MirLowerError::TypeError("unknown for loop iterator type"));
+                };
+                let ref_mut_iterator_ty = TyKind::Ref(Mutability::Mut, static_lifetime(), iterator_ty.clone()).intern(Interner);
+                let item_ty = &self.infer.type_of_pat[pat];
+                let option_item_ty = TyKind::Adt(chalk_ir::AdtId(option.into()), Substitution::from1(Interner, item_ty.clone())).intern(Interner);
+                let iterator_place: Place = self.temp(iterator_ty.clone())?.into();
+                let option_item_place: Place = self.temp(option_item_ty.clone())?.into();
+                let ref_mut_iterator_place: Place = self.temp(ref_mut_iterator_ty)?.into();
+                let Some(current) = self.lower_call_and_args(into_iter_fn_op, Some(iterable).into_iter(), iterator_place.clone(), current, false)?
+                else {
+                    return Ok(None);
+                };
+                self.push_assignment(current, ref_mut_iterator_place.clone(), Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, iterator_place), expr_id.into());
+                self.lower_loop(current, label, |this, begin| {
+                    let Some(current) = this.lower_call(iter_next_fn_op, vec![Operand::Copy(ref_mut_iterator_place)], option_item_place.clone(), begin, false)?
+                    else {
+                        return Ok(());
+                    };
+                    let end = this.current_loop_end()?;
+                    let (current, _) = this.pattern_matching_variant(
+                        option_item_ty.clone(),
+                        BindingAnnotation::Unannotated,
+                        option_item_place.into(),
+                        option_some.into(),
+                        current,
+                        pat.into(),
+                        Some(end),
+                        &[pat], &None)?;
+                    if let Some((_, block)) = this.lower_expr_as_place(current, body, true)? {
+                        this.set_goto(block, begin);
+                    }
+                    Ok(())
+                })
+            },
             Expr::Call { callee, args, .. } => {
-                let callee_ty = self.expr_ty(*callee);
+                let callee_ty = self.expr_ty_after_adjustments(*callee);
                 match &callee_ty.data(Interner).kind {
                     chalk_ir::TyKind::FnDef(..) => {
                         let func = Operand::from_bytes(vec![], callee_ty.clone());
-                        self.lower_call(func, args.iter().copied(), place, current)
+                        self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id))
                     }
                     TyKind::Scalar(_)
                     | TyKind::Tuple(_, _)
@@ -394,24 +468,28 @@ impl MirLowerCtx<'_> {
             }
             Expr::MethodCall { receiver, args, .. } => {
                 let (func_id, generic_args) =
-                    self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedName)?;
+                    self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedMethod)?;
                 let ty = chalk_ir::TyKind::FnDef(
                     CallableDefId::FunctionId(func_id).to_chalk(self.db),
                     generic_args,
                 )
                 .intern(Interner);
                 let func = Operand::from_bytes(vec![], ty);
-                self.lower_call(
+                self.lower_call_and_args(
                     func,
                     iter::once(*receiver).chain(args.iter().copied()),
                     place,
                     current,
+                    self.is_uninhabited(expr_id),
                 )
             }
             Expr::Match { expr, arms } => {
-                let (cond_place, mut current) = self.lower_expr_to_some_place(*expr, current)?;
-                let cond_ty = self.expr_ty(*expr);
-                let end = self.new_basic_block();
+                let Some((cond_place, mut current)) = self.lower_expr_as_place(current, *expr, true)?
+                else {
+                    return Ok(None);
+                };
+                let cond_ty = self.expr_ty_after_adjustments(*expr);
+                let mut end = None;
                 for MatchArm { pat, guard, expr } in arms.iter() {
                     if guard.is_some() {
                         not_supported!("pattern matching with guard");
@@ -424,8 +502,10 @@ impl MirLowerCtx<'_> {
                         *pat,
                         BindingAnnotation::Unannotated,
                     )?;
-                    let block = self.lower_expr_to_place(*expr, place.clone(), then)?;
-                    self.set_goto(block, end);
+                    if let Some(block) = self.lower_expr_to_place(*expr, place.clone(), then)? {
+                        let r = end.get_or_insert_with(|| self.new_basic_block());
+                        self.set_goto(block, *r);
+                    }
                     match otherwise {
                         Some(o) => current = o,
                         None => {
@@ -446,8 +526,7 @@ impl MirLowerCtx<'_> {
                     let loop_data =
                         self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?;
                     self.set_goto(current, loop_data.begin);
-                    let otherwise = self.new_basic_block();
-                    Ok(otherwise)
+                    Ok(None)
                 }
             },
             Expr::Break { expr, label } => {
@@ -457,26 +536,33 @@ impl MirLowerCtx<'_> {
                 match label {
                     Some(_) => not_supported!("break with label"),
                     None => {
-                        let loop_data =
-                            self.current_loop_blocks.ok_or(MirLowerError::BreakWithoutLoop)?;
-                        self.set_goto(current, loop_data.end);
-                        Ok(self.new_basic_block())
+                        let end =
+                            self.current_loop_end()?;
+                        self.set_goto(current, end);
+                        Ok(None)
                     }
                 }
             }
             Expr::Return { expr } => {
                 if let Some(expr) = expr {
-                    current = self.lower_expr_to_place(*expr, return_slot().into(), current)?;
+                    if let Some(c) = self.lower_expr_to_place(*expr, return_slot().into(), current)? {
+                        current = c;
+                    } else {
+                        return Ok(None);
+                    }
                 }
                 self.set_terminator(current, Terminator::Return);
-                Ok(self.new_basic_block())
+                Ok(None)
             }
             Expr::Yield { .. } => not_supported!("yield"),
-            Expr::RecordLit { fields, .. } => {
+            Expr::RecordLit { fields, path, .. } => {
                 let variant_id = self
                     .infer
                     .variant_resolution_for_expr(expr_id)
-                    .ok_or(MirLowerError::UnresolvedName)?;
+                    .ok_or_else(|| match path {
+                        Some(p) => MirLowerError::UnresolvedName(p.display(self.db).to_string()),
+                        None => MirLowerError::RecordLiteralWithoutPath,
+                    })?;
                 let subst = match self.expr_ty(expr_id).kind(Interner) {
                     TyKind::Adt(_, s) => s.clone(),
                     _ => not_supported!("Non ADT record literal"),
@@ -487,9 +573,11 @@ impl MirLowerCtx<'_> {
                         let mut operands = vec![None; variant_data.fields().len()];
                         for RecordLitField { name, expr } in fields.iter() {
                             let field_id =
-                                variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?;
-                            let op;
-                            (op, current) = self.lower_expr_to_some_operand(*expr, current)?;
+                                variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?;
+                            let Some((op, c)) = self.lower_expr_to_some_operand(*expr, current)? else {
+                                return Ok(None);
+                            };
+                            current = c;
                             operands[u32::from(field_id.into_raw()) as usize] = Some(op);
                         }
                         self.push_assignment(
@@ -501,15 +589,16 @@ impl MirLowerCtx<'_> {
                                     MirLowerError::TypeError("missing field in record literal"),
                                 )?,
                             ),
+                            expr_id.into(),
                         );
-                        Ok(current)
+                        Ok(Some(current))
                     }
                     VariantId::UnionId(union_id) => {
                         let [RecordLitField { name, expr }] = fields.as_ref() else {
                             not_supported!("Union record literal with more than one field");
                         };
                         let local_id =
-                            variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?;
+                            variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?;
                         let mut place = place;
                         place
                             .projection
@@ -518,23 +607,6 @@ impl MirLowerCtx<'_> {
                     }
                 }
             }
-            Expr::Field { expr, name } => {
-                let (mut current_place, current) = self.lower_expr_to_some_place(*expr, current)?;
-                if let TyKind::Tuple(..) = self.expr_ty(*expr).kind(Interner) {
-                    let index = name
-                        .as_tuple_index()
-                        .ok_or(MirLowerError::TypeError("named field on tuple"))?;
-                    current_place.projection.push(ProjectionElem::TupleField(index))
-                } else {
-                    let field = self
-                        .infer
-                        .field_resolution(expr_id)
-                        .ok_or(MirLowerError::UnresolvedName)?;
-                    current_place.projection.push(ProjectionElem::Field(field));
-                }
-                self.push_assignment(current, place, Operand::Copy(current_place).into());
-                Ok(current)
-            }
             Expr::Await { .. } => not_supported!("await"),
             Expr::Try { .. } => not_supported!("? operator"),
             Expr::Yeet { .. } => not_supported!("yeet"),
@@ -542,41 +614,51 @@ impl MirLowerCtx<'_> {
             Expr::Async { .. } => not_supported!("async block"),
             Expr::Const { .. } => not_supported!("anonymous const block"),
             Expr::Cast { expr, type_ref: _ } => {
-                let (x, current) = self.lower_expr_to_some_operand(*expr, current)?;
+                let Some((x, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
+                    return Ok(None);
+                };
                 let source_ty = self.infer[*expr].clone();
                 let target_ty = self.infer[expr_id].clone();
                 self.push_assignment(
                     current,
                     place,
                     Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, x, target_ty),
+                    expr_id.into(),
                 );
-                Ok(current)
+                Ok(Some(current))
             }
             Expr::Ref { expr, rawness: _, mutability } => {
-                let p;
-                (p, current) = self.lower_expr_to_some_place(*expr, current)?;
+                let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+                    return Ok(None);
+                };
                 let bk = BorrowKind::from_hir(*mutability);
-                self.push_assignment(current, place, Rvalue::Ref(bk, p));
-                Ok(current)
+                self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into());
+                Ok(Some(current))
             }
             Expr::Box { .. } => not_supported!("box expression"),
-            Expr::UnaryOp { expr, op } => match op {
-                hir_def::expr::UnaryOp::Deref => {
-                    let (mut tmp, current) = self.lower_expr_to_some_place(*expr, current)?;
-                    tmp.projection.push(ProjectionElem::Deref);
-                    self.push_assignment(current, place, Operand::Copy(tmp).into());
-                    Ok(current)
-                }
-                hir_def::expr::UnaryOp::Not => {
-                    let (op, current) = self.lower_expr_to_some_operand(*expr, current)?;
-                    self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Not, op));
-                    Ok(current)
-                }
-                hir_def::expr::UnaryOp::Neg => {
-                    let (op, current) = self.lower_expr_to_some_operand(*expr, current)?;
-                    self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Neg, op));
-                    Ok(current)
-                }
+            Expr::Field { .. } | Expr::Index { .. } | Expr::UnaryOp { op: hir_def::expr::UnaryOp::Deref, .. } => {
+                let Some((p, current)) = self.lower_expr_as_place(current, expr_id, true)? else {
+                    return Ok(None);
+                };
+                self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into());
+                Ok(Some(current))
+            }
+            Expr::UnaryOp { expr, op: op @ (hir_def::expr::UnaryOp::Not | hir_def::expr::UnaryOp::Neg) } => {
+                let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
+                    return Ok(None);
+                };
+                let operation = match op {
+                    hir_def::expr::UnaryOp::Not => UnOp::Not,
+                    hir_def::expr::UnaryOp::Neg => UnOp::Neg,
+                    _ => unreachable!(),
+                };
+                self.push_assignment(
+                    current,
+                    place,
+                    Rvalue::UnaryOp(operation, operand),
+                    expr_id.into(),
+                );
+                Ok(Some(current))
             },
             Expr::BinaryOp { lhs, rhs, op } => {
                 let op = op.ok_or(MirLowerError::IncompleteExpr)?;
@@ -584,18 +666,23 @@ impl MirLowerCtx<'_> {
                     if op.is_some() {
                         not_supported!("assignment with arith op (like +=)");
                     }
-                    let Some(lhs_place) = self.lower_expr_as_place(*lhs) else {
-                        not_supported!("assignment to complex place");
+                    let Some((lhs_place, current)) =
+                        self.lower_expr_as_place(current, *lhs, false)?
+                    else {
+                        return Ok(None);
                     };
-                    let rhs_op;
-                    (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?;
-                    self.push_assignment(current, lhs_place, rhs_op.into());
-                    return Ok(current);
+                    let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else {
+                        return Ok(None);
+                    };
+                    self.push_assignment(current, lhs_place, rhs_op.into(), expr_id.into());
+                    return Ok(Some(current));
                 }
-                let lhs_op;
-                (lhs_op, current) = self.lower_expr_to_some_operand(*lhs, current)?;
-                let rhs_op;
-                (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?;
+                let Some((lhs_op, current)) = self.lower_expr_to_some_operand(*lhs, current)? else {
+                    return Ok(None);
+                };
+                let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else {
+                    return Ok(None);
+                };
                 self.push_assignment(
                     current,
                     place,
@@ -612,34 +699,32 @@ impl MirLowerCtx<'_> {
                         lhs_op,
                         rhs_op,
                     ),
+                    expr_id.into(),
                 );
-                Ok(current)
+                Ok(Some(current))
             }
             Expr::Range { .. } => not_supported!("range"),
-            Expr::Index { base, index } => {
-                let mut p_base;
-                (p_base, current) = self.lower_expr_to_some_place(*base, current)?;
-                let l_index = self.temp(self.expr_ty(*index))?;
-                current = self.lower_expr_to_place(*index, l_index.into(), current)?;
-                p_base.projection.push(ProjectionElem::Index(l_index));
-                self.push_assignment(current, place, Operand::Copy(p_base).into());
-                Ok(current)
-            }
             Expr::Closure { .. } => not_supported!("closure"),
             Expr::Tuple { exprs, is_assignee_expr: _ } => {
-                let r = Rvalue::Aggregate(
-                    AggregateKind::Tuple(self.expr_ty(expr_id)),
-                    exprs
+                let Some(values) = exprs
                         .iter()
                         .map(|x| {
-                            let o;
-                            (o, current) = self.lower_expr_to_some_operand(*x, current)?;
-                            Ok(o)
+                            let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else {
+                                return Ok(None);
+                            };
+                            current = c;
+                            Ok(Some(o))
                         })
-                        .collect::<Result<_>>()?,
+                        .collect::<Result<Option<_>>>()?
+                else {
+                    return Ok(None);
+                };
+                let r = Rvalue::Aggregate(
+                    AggregateKind::Tuple(self.expr_ty(expr_id)),
+                    values,
                 );
-                self.push_assignment(current, place, r);
-                Ok(current)
+                self.push_assignment(current, place, r, expr_id.into());
+                Ok(Some(current))
             }
             Expr::Array(l) => match l {
                 Array::ElementList { elements, .. } => {
@@ -651,86 +736,54 @@ impl MirLowerCtx<'_> {
                             ))
                         }
                     };
-                    let r = Rvalue::Aggregate(
-                        AggregateKind::Array(elem_ty),
-                        elements
+                    let Some(values) = elements
                             .iter()
                             .map(|x| {
-                                let o;
-                                (o, current) = self.lower_expr_to_some_operand(*x, current)?;
-                                Ok(o)
+                                let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else {
+                                    return Ok(None);
+                                };
+                                current = c;
+                                Ok(Some(o))
                             })
-                            .collect::<Result<_>>()?,
+                            .collect::<Result<Option<_>>>()?
+                    else {
+                        return Ok(None);
+                    };
+                    let r = Rvalue::Aggregate(
+                        AggregateKind::Array(elem_ty),
+                        values,
                     );
-                    self.push_assignment(current, place, r);
-                    Ok(current)
+                    self.push_assignment(current, place, r, expr_id.into());
+                    Ok(Some(current))
                 }
                 Array::Repeat { .. } => not_supported!("array repeat"),
             },
             Expr::Literal(l) => {
                 let ty = self.expr_ty(expr_id);
                 let op = self.lower_literal_to_operand(ty, l)?;
-                self.push_assignment(current, place, op.into());
-                Ok(current)
+                self.push_assignment(current, place, op.into(), expr_id.into());
+                Ok(Some(current))
             }
             Expr::Underscore => not_supported!("underscore"),
         }
     }
 
-    fn lower_block_to_place(
-        &mut self,
-        label: Option<LabelId>,
-        statements: &[hir_def::expr::Statement],
-        mut current: BasicBlockId,
-        tail: Option<ExprId>,
-        place: Place,
-    ) -> Result<BasicBlockId> {
-        if label.is_some() {
-            not_supported!("block with label");
-        }
-        for statement in statements.iter() {
-            match statement {
-                hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => {
-                    match initializer {
-                        Some(expr_id) => {
-                            let else_block;
-                            let init_place;
-                            (init_place, current) =
-                                self.lower_expr_to_some_place(*expr_id, current)?;
-                            (current, else_block) = self.pattern_match(
-                                current,
-                                None,
-                                init_place,
-                                self.expr_ty(*expr_id),
-                                *pat,
-                                BindingAnnotation::Unannotated,
-                            )?;
-                            match (else_block, else_branch) {
-                                (None, _) => (),
-                                (Some(else_block), None) => {
-                                    self.set_terminator(else_block, Terminator::Unreachable);
-                                }
-                                (Some(else_block), Some(else_branch)) => {
-                                    let (_, b) =
-                                        self.lower_expr_to_some_place(*else_branch, else_block)?;
-                                    self.set_terminator(b, Terminator::Unreachable);
-                                }
-                            }
-                        }
-                        None => continue,
-                    }
-                }
-                hir_def::expr::Statement::Expr { expr, has_semi: _ } => {
-                    let ty = self.expr_ty(*expr);
-                    let temp = self.temp(ty)?;
-                    current = self.lower_expr_to_place(*expr, temp.into(), current)?;
-                }
+    fn push_field_projection(&self, place: &mut Place, expr_id: ExprId) -> Result<()> {
+        if let Expr::Field { expr, name } = &self.body[expr_id] {
+            if let TyKind::Tuple(..) = self.expr_ty_after_adjustments(*expr).kind(Interner) {
+                let index = name
+                    .as_tuple_index()
+                    .ok_or(MirLowerError::TypeError("named field on tuple"))?;
+                place.projection.push(ProjectionElem::TupleField(index))
+            } else {
+                let field =
+                    self.infer.field_resolution(expr_id).ok_or(MirLowerError::UnresolvedField)?;
+                place.projection.push(ProjectionElem::Field(field));
             }
+        } else {
+            not_supported!("")
         }
-        match tail {
-            Some(tail) => self.lower_expr_to_place(tail, place, current),
-            None => Ok(current),
-        }
+        Ok(())
     }
 
     fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> {
@@ -779,9 +832,10 @@ impl MirLowerCtx<'_> {
         const_id: hir_def::ConstId,
         prev_block: BasicBlockId,
         place: Place,
-    ) -> Result<BasicBlockId> {
+        span: MirSpan,
+    ) -> Result<()> {
         let c = self.db.const_eval(const_id)?;
-        self.write_const_to_place(c, prev_block, place)
+        self.write_const_to_place(c, prev_block, place, span)
     }
 
     fn write_const_to_place(
@@ -789,9 +843,10 @@ impl MirLowerCtx<'_> {
         c: Const,
         prev_block: BasicBlockId,
         place: Place,
-    ) -> Result<BasicBlockId> {
-        self.push_assignment(prev_block, place, Operand::Constant(c).into());
-        Ok(prev_block)
+        span: MirSpan,
+    ) -> Result<()> {
+        self.push_assignment(prev_block, place, Operand::Constant(c).into(), span);
+        Ok(())
     }
 
     fn write_bytes_to_place(
@@ -800,9 +855,10 @@ impl MirLowerCtx<'_> {
         place: Place,
         cv: Vec<u8>,
         ty: Ty,
-    ) -> Result<BasicBlockId> {
-        self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into());
-        Ok(prev_block)
+        span: MirSpan,
+    ) -> Result<()> {
+        self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into(), span);
+        Ok(())
     }
 
     fn lower_enum_variant(
@@ -812,6 +868,7 @@ impl MirLowerCtx<'_> {
         place: Place,
         ty: Ty,
         fields: Vec<Operand>,
+        span: MirSpan,
     ) -> Result<BasicBlockId> {
         let subst = match ty.kind(Interner) {
             TyKind::Adt(_, subst) => subst.clone(),
@@ -821,36 +878,51 @@ impl MirLowerCtx<'_> {
             prev_block,
             place,
             Rvalue::Aggregate(AggregateKind::Adt(variant_id.into(), subst), fields),
+            span,
         );
         Ok(prev_block)
     }
 
-    fn lower_call(
+    fn lower_call_and_args(
         &mut self,
         func: Operand,
         args: impl Iterator<Item = ExprId>,
         place: Place,
         mut current: BasicBlockId,
-    ) -> Result<BasicBlockId> {
-        let args = args
+        is_uninhabited: bool,
+    ) -> Result<Option<BasicBlockId>> {
+        let Some(args) = args
             .map(|arg| {
-                let temp;
-                (temp, current) = self.lower_expr_to_some_operand(arg, current)?;
-                Ok(temp)
+                if let Some((temp, c)) = self.lower_expr_to_some_operand(arg, current)? {
+                    current = c;
+                    Ok(Some(temp))
+                } else {
+                    Ok(None)
+                }
             })
-            .collect::<Result<Vec<_>>>()?;
-        let b = self.result.basic_blocks.alloc(BasicBlock {
-            statements: vec![],
-            terminator: None,
-            is_cleanup: false,
-        });
+            .collect::<Result<Option<Vec<_>>>>()?
+        else {
+            return Ok(None);
+        };
+        self.lower_call(func, args, place, current, is_uninhabited)
+    }
+
+    fn lower_call(
+        &mut self,
+        func: Operand,
+        args: Vec<Operand>,
+        place: Place,
+        current: BasicBlockId,
+        is_uninhabited: bool,
+    ) -> Result<Option<BasicBlockId>> {
+        let b = if is_uninhabited { None } else { Some(self.new_basic_block()) };
         self.set_terminator(
             current,
             Terminator::Call {
                 func,
                 args,
                 destination: place,
-                target: Some(b),
+                target: b,
                 cleanup: None,
                 from_hir_call: true,
             },
@@ -874,8 +946,28 @@ impl MirLowerCtx<'_> {
         self.infer[e].clone()
     }
 
-    fn push_assignment(&mut self, block: BasicBlockId, place: Place, rvalue: Rvalue) {
-        self.result.basic_blocks[block].statements.push(Statement::Assign(place, rvalue));
+    fn expr_ty_after_adjustments(&self, e: ExprId) -> Ty {
+        let mut ty = None;
+        if let Some(x) = self.infer.expr_adjustments.get(&e) {
+            if let Some(x) = x.last() {
+                ty = Some(x.target.clone());
+            }
+        }
+        ty.unwrap_or_else(|| self.expr_ty(e))
+    }
+
+    fn push_statement(&mut self, block: BasicBlockId, statement: Statement) {
+        self.result.basic_blocks[block].statements.push(statement);
+    }
+
+    fn push_assignment(
+        &mut self,
+        block: BasicBlockId,
+        place: Place,
+        rvalue: Rvalue,
+        span: MirSpan,
+    ) {
+        self.push_statement(block, StatementKind::Assign(place, rvalue).with_span(span));
     }
 
     /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if
@@ -924,11 +1016,50 @@ impl MirLowerCtx<'_> {
                     binding_mode,
                 )?
             }
-            Pat::Or(_) => not_supported!("or pattern"),
+            Pat::Or(pats) => {
+                let then_target = self.new_basic_block();
+                let mut finished = false;
+                for pat in &**pats {
+                    let (next, next_else) = self.pattern_match(
+                        current,
+                        None,
+                        cond_place.clone(),
+                        cond_ty.clone(),
+                        *pat,
+                        binding_mode,
+                    )?;
+                    self.set_goto(next, then_target);
+                    match next_else {
+                        Some(t) => {
+                            current = t;
+                        }
+                        None => {
+                            finished = true;
+                            break;
+                        }
+                    }
+                }
+                (then_target, (!finished).then_some(current))
+            }
             Pat::Record { .. } => not_supported!("record pattern"),
             Pat::Range { .. } => not_supported!("range pattern"),
             Pat::Slice { .. } => not_supported!("slice pattern"),
-            Pat::Path(_) => not_supported!("path pattern"),
+            Pat::Path(_) => {
+                let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else {
+                    not_supported!("unresolved variant");
+                };
+                self.pattern_matching_variant(
+                    cond_ty,
+                    binding_mode,
+                    cond_place,
+                    variant,
+                    current,
+                    pattern.into(),
+                    current_else,
+                    &[],
+                    &None,
+                )?
+            }
             Pat::Lit(l) => {
                 let then_target = self.new_basic_block();
                 let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
@@ -962,8 +1093,9 @@ impl MirLowerCtx<'_> {
                 }
                 (then_target, Some(else_target))
             }
-            Pat::Bind { mode, name: _, subpat } => {
-                let target_place = self.binding_locals[pattern];
+            Pat::Bind { id, subpat } => {
+                let target_place = self.result.binding_locals[*id];
+                let mode = self.body.bindings[*id].mode;
                 if let Some(subpat) = subpat {
                     (current, current_else) = self.pattern_match(
                         current,
@@ -975,8 +1107,9 @@ impl MirLowerCtx<'_> {
                     )?
                 }
                 if matches!(mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) {
-                    binding_mode = *mode;
+                    binding_mode = mode;
                 }
+                self.push_storage_live(*id, current)?;
                 self.push_assignment(
                     current,
                     target_place.into(),
@@ -990,6 +1123,7 @@ impl MirLowerCtx<'_> {
                             cond_place,
                         ),
                     },
+                    pattern.into(),
                 );
                 (current, current_else)
             }
@@ -997,74 +1131,17 @@ impl MirLowerCtx<'_> {
                 let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else {
                     not_supported!("unresolved variant");
                 };
-                pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place);
-                let subst = match cond_ty.kind(Interner) {
-                    TyKind::Adt(_, s) => s,
-                    _ => {
-                        return Err(MirLowerError::TypeError(
-                            "non adt type matched with tuple struct",
-                        ))
-                    }
-                };
-                let fields_type = self.db.field_types(variant);
-                match variant {
-                    VariantId::EnumVariantId(v) => {
-                        let e = self.db.const_eval_discriminant(v)? as u128;
-                        let next = self.new_basic_block();
-                        let tmp = self.discr_temp_place();
-                        self.push_assignment(
-                            current,
-                            tmp.clone(),
-                            Rvalue::Discriminant(cond_place.clone()),
-                        );
-                        let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
-                        self.set_terminator(
-                            current,
-                            Terminator::SwitchInt {
-                                discr: Operand::Copy(tmp),
-                                targets: SwitchTargets::static_if(e, next, else_target),
-                            },
-                        );
-                        let enum_data = self.db.enum_data(v.parent);
-                        let fields =
-                            enum_data.variants[v.local_id].variant_data.fields().iter().map(
-                                |(x, _)| {
-                                    (
-                                        PlaceElem::Field(FieldId { parent: v.into(), local_id: x }),
-                                        fields_type[x].clone().substitute(Interner, subst),
-                                    )
-                                },
-                            );
-                        self.pattern_match_tuple_like(
-                            next,
-                            Some(else_target),
-                            args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)),
-                            *ellipsis,
-                            &cond_place,
-                            binding_mode,
-                        )?
-                    }
-                    VariantId::StructId(s) => {
-                        let struct_data = self.db.struct_data(s);
-                        let fields = struct_data.variant_data.fields().iter().map(|(x, _)| {
-                            (
-                                PlaceElem::Field(FieldId { parent: s.into(), local_id: x }),
-                                fields_type[x].clone().substitute(Interner, subst),
-                            )
-                        });
-                        self.pattern_match_tuple_like(
-                            current,
-                            current_else,
-                            args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)),
-                            *ellipsis,
-                            &cond_place,
-                            binding_mode,
-                        )?
-                    }
-                    VariantId::UnionId(_) => {
-                        return Err(MirLowerError::TypeError("pattern matching on union"))
-                    }
-                }
+                self.pattern_matching_variant(
+                    cond_ty,
+                    binding_mode,
+                    cond_place,
+                    variant,
+                    current,
+                    pattern.into(),
+                    current_else,
+                    args,
+                    ellipsis,
+                )?
             }
             Pat::Ref { .. } => not_supported!("& pattern"),
             Pat::Box { .. } => not_supported!("box pattern"),
@@ -1072,6 +1149,83 @@ impl MirLowerCtx<'_> {
         })
     }
 
+    fn pattern_matching_variant(
+        &mut self,
+        mut cond_ty: Ty,
+        mut binding_mode: BindingAnnotation,
+        mut cond_place: Place,
+        variant: VariantId,
+        current: BasicBlockId,
+        span: MirSpan,
+        current_else: Option<BasicBlockId>,
+        args: &[PatId],
+        ellipsis: &Option<usize>,
+    ) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
+        pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place);
+        let subst = match cond_ty.kind(Interner) {
+            TyKind::Adt(_, s) => s,
+            _ => return Err(MirLowerError::TypeError("non adt type matched with tuple struct")),
+        };
+        let fields_type = self.db.field_types(variant);
+        Ok(match variant {
+            VariantId::EnumVariantId(v) => {
+                let e = self.db.const_eval_discriminant(v)? as u128;
+                let next = self.new_basic_block();
+                let tmp = self.discr_temp_place();
+                self.push_assignment(
+                    current,
+                    tmp.clone(),
+                    Rvalue::Discriminant(cond_place.clone()),
+                    span,
+                );
+                let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
+                self.set_terminator(
+                    current,
+                    Terminator::SwitchInt {
+                        discr: Operand::Copy(tmp),
+                        targets: SwitchTargets::static_if(e, next, else_target),
+                    },
+                );
+                let enum_data = self.db.enum_data(v.parent);
+                let fields =
+                    enum_data.variants[v.local_id].variant_data.fields().iter().map(|(x, _)| {
+                        (
+                            PlaceElem::Field(FieldId { parent: v.into(), local_id: x }),
+                            fields_type[x].clone().substitute(Interner, subst),
+                        )
+                    });
+                self.pattern_match_tuple_like(
+                    next,
+                    Some(else_target),
+                    args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)),
+                    *ellipsis,
+                    &cond_place,
+                    binding_mode,
+                )?
+            }
+            VariantId::StructId(s) => {
+                let struct_data = self.db.struct_data(s);
+                let fields = struct_data.variant_data.fields().iter().map(|(x, _)| {
+                    (
+                        PlaceElem::Field(FieldId { parent: s.into(), local_id: x }),
+                        fields_type[x].clone().substitute(Interner, subst),
+                    )
+                });
+                self.pattern_match_tuple_like(
+                    current,
+                    current_else,
+                    args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)),
+                    *ellipsis,
+                    &cond_place,
+                    binding_mode,
+                )?
+            }
+            VariantId::UnionId(_) => {
+                return Err(MirLowerError::TypeError("pattern matching on union"))
+            }
+        })
+    }
+
     fn pattern_match_tuple_like(
         &mut self,
         mut current: BasicBlockId,
@@ -1109,23 +1263,161 @@ impl MirLowerCtx<'_> {
         &mut self,
         prev_block: BasicBlockId,
         label: Option<LabelId>,
-        f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId, BasicBlockId) -> Result<()>,
-    ) -> Result<BasicBlockId> {
+        f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId) -> Result<()>,
+    ) -> Result<Option<BasicBlockId>> {
         if label.is_some() {
             not_supported!("loop with label");
         }
         let begin = self.new_basic_block();
-        let end = self.new_basic_block();
-        let prev = mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end }));
+        let prev =
+            mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end: None }));
         self.set_goto(prev_block, begin);
-        f(self, begin, end)?;
-        self.current_loop_blocks = prev;
-        Ok(end)
+        f(self, begin)?;
+        let my = mem::replace(&mut self.current_loop_blocks, prev)
+            .ok_or(MirLowerError::ImplementationError("current_loop_blocks is corrupt"))?;
+        Ok(my.end)
     }
 
     fn has_adjustments(&self, expr_id: ExprId) -> bool {
         !self.infer.expr_adjustments.get(&expr_id).map(|x| x.is_empty()).unwrap_or(true)
     }
+
+    fn merge_blocks(
+        &mut self,
+        b1: Option<BasicBlockId>,
+        b2: Option<BasicBlockId>,
+    ) -> Option<BasicBlockId> {
+        match (b1, b2) {
+            (None, None) => None,
+            (None, Some(b)) | (Some(b), None) => Some(b),
+            (Some(b1), Some(b2)) => {
+                let bm = self.new_basic_block();
+                self.set_goto(b1, bm);
+                self.set_goto(b2, bm);
+                Some(bm)
+            }
+        }
+    }
+
+    fn current_loop_end(&mut self) -> Result<BasicBlockId> {
+        let r = match self
+            .current_loop_blocks
+            .as_mut()
+            .ok_or(MirLowerError::ImplementationError("Current loop access out of loop"))?
+            .end
+        {
+            Some(x) => x,
+            None => {
+                let s = self.new_basic_block();
+                self.current_loop_blocks
+                    .as_mut()
+                    .ok_or(MirLowerError::ImplementationError("Current loop access out of loop"))?
+                    .end = Some(s);
+                s
+            }
+        };
+        Ok(r)
+    }
+
+    fn is_uninhabited(&self, expr_id: ExprId) -> bool {
+        is_ty_uninhabited_from(&self.infer[expr_id], self.owner.module(self.db.upcast()), self.db)
+    }
+
+    /// This function push `StorageLive` statements for each binding in the pattern.
+    fn push_storage_live(&mut self, b: BindingId, current: BasicBlockId) -> Result<()> {
+        // Current implementation is wrong. It adds no `StorageDead` at the end of scope, and before each break
+        // and continue. It just add a `StorageDead` before the `StorageLive`, which is not wrong, but unneeeded in
+        // the proper implementation. Due this limitation, implementing a borrow checker on top of this mir will falsely
+        // allow this:
+        //
+        // ```
+        // let x;
+        // loop {
+        //     let y = 2;
+        //     x = &y;
+        //     if some_condition {
+        //         break; // we need to add a StorageDead(y) above this to kill the x borrow
+        //     }
+        // }
+        // use(x)
+        // ```
+        // But I think this approach work for mutability analysis, as user can't write code which mutates a binding
+        // after StorageDead, except loops, which are handled by this hack.
+        let span = self.body.bindings[b]
+            .definitions
+            .first()
+            .copied()
+            .map(MirSpan::PatId)
+            .unwrap_or(MirSpan::Unknown);
+        let l = self.result.binding_locals[b];
+        self.push_statement(current, StatementKind::StorageDead(l).with_span(span));
+        self.push_statement(current, StatementKind::StorageLive(l).with_span(span));
+        Ok(())
+    }
+
+    fn resolve_lang_item(&self, item: LangItem) -> Result<LangItemTarget> {
+        let crate_id = self.owner.module(self.db.upcast()).krate();
+        self.db.lang_item(crate_id, item).ok_or(MirLowerError::LangItemNotFound(item))
+    }
+
+    fn lower_block_to_place(
+        &mut self,
+        label: Option<LabelId>,
+        statements: &[hir_def::expr::Statement],
+        mut current: BasicBlockId,
+        tail: Option<ExprId>,
+        place: Place,
+    ) -> Result<Option<Idx<BasicBlock>>> {
+        if label.is_some() {
+            not_supported!("block with label");
+        }
+        for statement in statements.iter() {
+            match statement {
+                hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => {
+                    if let Some(expr_id) = initializer {
+                        let else_block;
+                        let Some((init_place, c)) =
+                        self.lower_expr_as_place(current, *expr_id, true)?
+                    else {
+                        return Ok(None);
+                    };
+                        current = c;
+                        (current, else_block) = self.pattern_match(
+                            current,
+                            None,
+                            init_place,
+                            self.expr_ty_after_adjustments(*expr_id),
+                            *pat,
+                            BindingAnnotation::Unannotated,
+                        )?;
+                        match (else_block, else_branch) {
+                            (None, _) => (),
+                            (Some(else_block), None) => {
+                                self.set_terminator(else_block, Terminator::Unreachable);
+                            }
+                            (Some(else_block), Some(else_branch)) => {
+                                if let Some((_, b)) =
+                                    self.lower_expr_as_place(else_block, *else_branch, true)?
+                                {
+                                    self.set_terminator(b, Terminator::Unreachable);
+                                }
+                            }
+                        }
+                    }
+                }
+                hir_def::expr::Statement::Expr { expr, has_semi: _ } => {
+                    let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else {
+                        return Ok(None);
+                    };
+                    current = c;
+                }
+            }
+        }
+        match tail {
+            Some(tail) => self.lower_expr_to_place(tail, place, current),
+            None => Ok(Some(current)),
+        }
+    }
 }
 
 fn pattern_matching_dereference(
@@ -1161,9 +1453,20 @@ fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> {
 }
 
 pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result<Arc<MirBody>> {
+    let _p = profile::span("mir_body_query").detail(|| match def {
+        DefWithBodyId::FunctionId(it) => db.function_data(it).name.to_string(),
+        DefWithBodyId::StaticId(it) => db.static_data(it).name.clone().to_string(),
+        DefWithBodyId::ConstId(it) => {
+            db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string()
+        }
+        DefWithBodyId::VariantId(it) => {
+            db.enum_data(it.parent).variants[it.local_id].name.to_string()
+        }
+    });
     let body = db.body(def);
     let infer = db.infer(def);
-    Ok(Arc::new(lower_to_mir(db, def, &body, &infer, body.body_expr)?))
+    let result = lower_to_mir(db, def, &body, &infer, body.body_expr)?;
+    Ok(Arc::new(result))
 }
 
 pub fn mir_body_recover(
@@ -1183,37 +1486,88 @@ pub fn lower_to_mir(
     // need to take this input explicitly.
     root_expr: ExprId,
 ) -> Result<MirBody> {
+    if let Some((_, x)) = infer.type_mismatches().next() {
+        return Err(MirLowerError::TypeMismatch(x.clone()));
+    }
     let mut basic_blocks = Arena::new();
     let start_block =
         basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false });
     let mut locals = Arena::new();
     // 0 is return local
-    locals.alloc(Local { mutability: Mutability::Mut, ty: infer[root_expr].clone() });
-    let mut create_local_of_path = |p: PatId| {
-        // FIXME: mutablity is broken
-        locals.alloc(Local { mutability: Mutability::Not, ty: infer[p].clone() })
-    };
+    locals.alloc(Local { ty: infer[root_expr].clone() });
+    let mut binding_locals: ArenaMap<BindingId, LocalId> = ArenaMap::new();
     // 1 to param_len is for params
-    let mut binding_locals: ArenaMap<PatId, LocalId> =
-        body.params.iter().map(|&x| (x, create_local_of_path(x))).collect();
+    let param_locals: Vec<LocalId> = if let DefWithBodyId::FunctionId(fid) = owner {
+        let substs = TyBuilder::placeholder_subst(db, fid);
+        let callable_sig = db.callable_item_signature(fid.into()).substitute(Interner, &substs);
+        body.params
+            .iter()
+            .zip(callable_sig.params().iter())
+            .map(|(&x, ty)| {
+                let local_id = locals.alloc(Local { ty: ty.clone() });
+                if let Pat::Bind { id, subpat: None } = body[x] {
+                    if matches!(
+                        body.bindings[id].mode,
+                        BindingAnnotation::Unannotated | BindingAnnotation::Mutable
+                    ) {
+                        binding_locals.insert(id, local_id);
+                    }
+                }
+                local_id
+            })
+            .collect()
+    } else {
+        if !body.params.is_empty() {
+            return Err(MirLowerError::TypeError("Unexpected parameter for non function body"));
+        }
+        vec![]
+    };
     // and then rest of bindings
-    for (pat_id, _) in body.pats.iter() {
-        if !binding_locals.contains_idx(pat_id) {
-            binding_locals.insert(pat_id, create_local_of_path(pat_id));
+    for (id, _) in body.bindings.iter() {
+        if !binding_locals.contains_idx(id) {
+            binding_locals.insert(id, locals.alloc(Local { ty: infer[id].clone() }));
         }
     }
-    let mir = MirBody { basic_blocks, locals, start_block, owner, arg_count: body.params.len() };
+    let mir = MirBody {
+        basic_blocks,
+        locals,
+        start_block,
+        binding_locals,
+        param_locals,
+        owner,
+        arg_count: body.params.len(),
+    };
     let mut ctx = MirLowerCtx {
         result: mir,
         db,
         infer,
         body,
-        binding_locals,
         owner,
         current_loop_blocks: None,
         discr_temp: None,
     };
-    let b = ctx.lower_expr_to_place(root_expr, return_slot().into(), start_block)?;
-    ctx.result.basic_blocks[b].terminator = Some(Terminator::Return);
+    let mut current = start_block;
+    for (&param, local) in body.params.iter().zip(ctx.result.param_locals.clone().into_iter()) {
+        if let Pat::Bind { id, .. } = body[param] {
+            if local == ctx.result.binding_locals[id] {
+                continue;
+            }
+        }
+        let r = ctx.pattern_match(
+            current,
+            None,
+            local.into(),
+            ctx.result.locals[local].ty.clone(),
+            param,
+            BindingAnnotation::Unannotated,
+        )?;
+        if let Some(b) = r.1 {
+            ctx.set_terminator(b, Terminator::Unreachable);
+        }
+        current = r.0;
+    }
+    if let Some(b) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? {
+        ctx.result.basic_blocks[b].terminator = Some(Terminator::Return);
+    }
     Ok(ctx.result)
 }
diff --git a/crates/hir-ty/src/mir/lower/as_place.rs b/crates/hir-ty/src/mir/lower/as_place.rs
new file mode 100644
index 00000000000..09bcdd93be0
--- /dev/null
+++ b/crates/hir-ty/src/mir/lower/as_place.rs
@@ -0,0 +1,237 @@
+//! MIR lowering for places
+
+use super::*;
+use hir_expand::name;
+
+macro_rules! not_supported {
+    ($x: expr) => {
+        return Err(MirLowerError::NotSupported(format!($x)))
+    };
+}
+
+impl MirLowerCtx<'_> {
+    fn lower_expr_to_some_place_without_adjust(
+        &mut self,
+        expr_id: ExprId,
+        prev_block: BasicBlockId,
+    ) -> Result<Option<(Place, BasicBlockId)>> {
+        let ty = self.expr_ty(expr_id);
+        let place = self.temp(ty)?;
+        let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)? else {
+            return Ok(None);
+        };
+        Ok(Some((place.into(), current)))
+    }
+
+    fn lower_expr_to_some_place_with_adjust(
+        &mut self,
+        expr_id: ExprId,
+        prev_block: BasicBlockId,
+        adjustments: &[Adjustment],
+    ) -> Result<Option<(Place, BasicBlockId)>> {
+        let ty =
+            adjustments.last().map(|x| x.target.clone()).unwrap_or_else(|| self.expr_ty(expr_id));
+        let place = self.temp(ty)?;
+        let Some(current) = self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)? else {
+            return Ok(None);
+        };
+        Ok(Some((place.into(), current)))
+    }
+
+    pub(super) fn lower_expr_as_place_with_adjust(
+        &mut self,
+        current: BasicBlockId,
+        expr_id: ExprId,
+        upgrade_rvalue: bool,
+        adjustments: &[Adjustment],
+    ) -> Result<Option<(Place, BasicBlockId)>> {
+        let try_rvalue = |this: &mut MirLowerCtx<'_>| {
+            if !upgrade_rvalue {
+                return Err(MirLowerError::MutatingRvalue);
+            }
+            this.lower_expr_to_some_place_with_adjust(expr_id, current, adjustments)
+        };
+        if let Some((last, rest)) = adjustments.split_last() {
+            match last.kind {
+                Adjust::Deref(None) => {
+                    let Some(mut x) = self.lower_expr_as_place_with_adjust(
+                        current,
+                        expr_id,
+                        upgrade_rvalue,
+                        rest,
+                    )? else {
+                        return Ok(None);
+                    };
+                    x.0.projection.push(ProjectionElem::Deref);
+                    Ok(Some(x))
+                }
+                Adjust::Deref(Some(od)) => {
+                    let Some((r, current)) = self.lower_expr_as_place_with_adjust(
+                        current,
+                        expr_id,
+                        upgrade_rvalue,
+                        rest,
+                    )? else {
+                        return Ok(None);
+                    };
+                    self.lower_overloaded_deref(
+                        current,
+                        r,
+                        rest.last()
+                            .map(|x| x.target.clone())
+                            .unwrap_or_else(|| self.expr_ty(expr_id)),
+                        last.target.clone(),
+                        expr_id.into(),
+                        match od.0 {
+                            Some(Mutability::Mut) => true,
+                            Some(Mutability::Not) => false,
+                            None => {
+                                not_supported!("implicit overloaded deref with unknown mutability")
+                            }
+                        },
+                    )
+                }
+                Adjust::NeverToAny | Adjust::Borrow(_) | Adjust::Pointer(_) => try_rvalue(self),
+            }
+        } else {
+            self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue)
+        }
+    }
+
+    pub(super) fn lower_expr_as_place(
+        &mut self,
+        current: BasicBlockId,
+        expr_id: ExprId,
+        upgrade_rvalue: bool,
+    ) -> Result<Option<(Place, BasicBlockId)>> {
+        match self.infer.expr_adjustments.get(&expr_id) {
+            Some(a) => self.lower_expr_as_place_with_adjust(current, expr_id, upgrade_rvalue, a),
+            None => self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue),
+        }
+    }
+
+    fn lower_expr_as_place_without_adjust(
+        &mut self,
+        current: BasicBlockId,
+        expr_id: ExprId,
+        upgrade_rvalue: bool,
+    ) -> Result<Option<(Place, BasicBlockId)>> {
+        let try_rvalue = |this: &mut MirLowerCtx<'_>| {
+            if !upgrade_rvalue {
+                return Err(MirLowerError::MutatingRvalue);
+            }
+            this.lower_expr_to_some_place_without_adjust(expr_id, current)
+        };
+        match &self.body.exprs[expr_id] {
+            Expr::Path(p) => {
+                let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
+                let Some(pr) = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) else {
+                    return Err(MirLowerError::unresolved_path(self.db, p));
+                };
+                let pr = match pr {
+                    ResolveValueResult::ValueNs(v) => v,
+                    ResolveValueResult::Partial(..) => return try_rvalue(self),
+                };
+                match pr {
+                    ValueNs::LocalBinding(pat_id) => {
+                        Ok(Some((self.result.binding_locals[pat_id].into(), current)))
+                    }
+                    _ => try_rvalue(self),
+                }
+            }
+            Expr::UnaryOp { expr, op } => match op {
+                hir_def::expr::UnaryOp::Deref => {
+                    if !matches!(
+                        self.expr_ty(*expr).kind(Interner),
+                        TyKind::Ref(..) | TyKind::Raw(..)
+                    ) {
+                        let Some(_) = self.lower_expr_as_place(current, *expr, true)? else {
+                            return Ok(None);
+                        };
+                        not_supported!("explicit overloaded deref");
+                    }
+                    let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+                        return Ok(None);
+                    };
+                    r.projection.push(ProjectionElem::Deref);
+                    Ok(Some((r, current)))
+                }
+                _ => try_rvalue(self),
+            },
+            Expr::Field { expr, .. } => {
+                let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+                    return Ok(None);
+                };
+                self.push_field_projection(&mut r, expr_id)?;
+                Ok(Some((r, current)))
+            }
+            Expr::Index { base, index } => {
+                let base_ty = self.expr_ty_after_adjustments(*base);
+                let index_ty = self.expr_ty_after_adjustments(*index);
+                if index_ty != TyBuilder::usize()
+                    || !matches!(base_ty.kind(Interner), TyKind::Array(..) | TyKind::Slice(..))
+                {
+                    not_supported!("overloaded index");
+                }
+                let Some((mut p_base, current)) =
+                    self.lower_expr_as_place(current, *base, true)? else {
+                    return Ok(None);
+                };
+                let l_index = self.temp(self.expr_ty_after_adjustments(*index))?;
+                let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)? else {
+                    return Ok(None);
+                };
+                p_base.projection.push(ProjectionElem::Index(l_index));
+                Ok(Some((p_base, current)))
+            }
+            _ => try_rvalue(self),
+        }
+    }
+
+    fn lower_overloaded_deref(
+        &mut self,
+        current: BasicBlockId,
+        place: Place,
+        source_ty: Ty,
+        target_ty: Ty,
+        span: MirSpan,
+        mutability: bool,
+    ) -> Result<Option<(Place, BasicBlockId)>> {
+        let (chalk_mut, trait_lang_item, trait_method_name, borrow_kind) = if !mutability {
+            (Mutability::Not, LangItem::Deref, name![deref], BorrowKind::Shared)
+        } else {
+            (
+                Mutability::Mut,
+                LangItem::DerefMut,
+                name![deref_mut],
+                BorrowKind::Mut { allow_two_phase_borrow: false },
+            )
+        };
+        let ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), source_ty.clone()).intern(Interner);
+        let target_ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), target_ty).intern(Interner);
+        let ref_place: Place = self.temp(ty_ref)?.into();
+        self.push_assignment(current, ref_place.clone(), Rvalue::Ref(borrow_kind, place), span);
+        let deref_trait = self
+            .resolve_lang_item(trait_lang_item)?
+            .as_trait()
+            .ok_or(MirLowerError::LangItemNotFound(trait_lang_item))?;
+        let deref_fn = self
+            .db
+            .trait_data(deref_trait)
+            .method_by_name(&trait_method_name)
+            .ok_or(MirLowerError::LangItemNotFound(trait_lang_item))?;
+        let deref_fn_op = Operand::const_zst(
+            TyKind::FnDef(
+                self.db.intern_callable_def(CallableDefId::FunctionId(deref_fn)).into(),
+                Substitution::from1(Interner, source_ty),
+            )
+            .intern(Interner),
+        );
+        let mut result: Place = self.temp(target_ty_ref)?.into();
+        let Some(current) = self.lower_call(deref_fn_op, vec![Operand::Copy(ref_place)], result.clone(), current, false)? else {
+            return Ok(None);
+        };
+        result.projection.push(ProjectionElem::Deref);
+        Ok(Some((result, current)))
+    }
+}
diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs
new file mode 100644
index 00000000000..ffc08b7e346
--- /dev/null
+++ b/crates/hir-ty/src/mir/pretty.rs
@@ -0,0 +1,348 @@
+//! A pretty-printer for MIR.
+
+use std::fmt::{Display, Write};
+
+use hir_def::{body::Body, expr::BindingId};
+use hir_expand::name::Name;
+use la_arena::ArenaMap;
+
+use crate::{
+    db::HirDatabase,
+    display::HirDisplay,
+    mir::{PlaceElem, ProjectionElem, StatementKind, Terminator},
+};
+
+use super::{
+    AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, Operand, Place, Rvalue, UnOp,
+};
+
+impl MirBody {
+    pub fn pretty_print(&self, db: &dyn HirDatabase) -> String {
+        let hir_body = db.body(self.owner);
+        let mut ctx = MirPrettyCtx::new(self, &hir_body, db);
+        ctx.for_body();
+        ctx.result
+    }
+}
+
+struct MirPrettyCtx<'a> {
+    body: &'a MirBody,
+    hir_body: &'a Body,
+    db: &'a dyn HirDatabase,
+    result: String,
+    ident: String,
+    local_to_binding: ArenaMap<LocalId, BindingId>,
+}
+
+macro_rules! w {
+    ($dst:expr, $($arg:tt)*) => {
+        { let _ = write!($dst, $($arg)*); }
+    };
+}
+
+macro_rules! wln {
+    ($dst:expr) => {
+        { let _ = writeln!($dst); }
+    };
+    ($dst:expr, $($arg:tt)*) => {
+        { let _ = writeln!($dst, $($arg)*); }
+    };
+}
+
+impl Write for MirPrettyCtx<'_> {
+    fn write_str(&mut self, s: &str) -> std::fmt::Result {
+        let mut it = s.split('\n'); // note: `.lines()` is wrong here
+        self.write(it.next().unwrap_or_default());
+        for line in it {
+            self.write_line();
+            self.write(line);
+        }
+        Ok(())
+    }
+}
+
+enum LocalName {
+    Unknown(LocalId),
+    Binding(Name, LocalId),
+}
+
+impl Display for LocalName {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())),
+            LocalName::Binding(n, l) => write!(f, "{n}_{}", u32::from(l.into_raw())),
+        }
+    }
+}
+
+impl<'a> MirPrettyCtx<'a> {
+    fn for_body(&mut self) {
+        self.with_block(|this| {
+            this.locals();
+            wln!(this);
+            this.blocks();
+        });
+    }
+
+    fn with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_>)) {
+        self.ident += "    ";
+        wln!(self, "{{");
+        f(self);
+        for _ in 0..4 {
+            self.result.pop();
+            self.ident.pop();
+        }
+        wln!(self, "}}");
+    }
+
+    fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self {
+        let local_to_binding = body.binding_locals.iter().map(|(x, y)| (*y, x)).collect();
+        MirPrettyCtx {
+            body,
+            db,
+            result: String::new(),
+            ident: String::new(),
+            local_to_binding,
+            hir_body,
+        }
+    }
+
+    fn write_line(&mut self) {
+        self.result.push('\n');
+        self.result += &self.ident;
+    }
+
+    fn write(&mut self, line: &str) {
+        self.result += line;
+    }
+
+    fn locals(&mut self) {
+        for (id, local) in self.body.locals.iter() {
+            wln!(self, "let {}: {};", self.local_name(id), local.ty.display(self.db));
+        }
+    }
+
+    fn local_name(&self, local: LocalId) -> LocalName {
+        match self.local_to_binding.get(local) {
+            Some(b) => LocalName::Binding(self.hir_body.bindings[*b].name.clone(), local),
+            None => LocalName::Unknown(local),
+        }
+    }
+
+    fn basic_block_id(&self, basic_block_id: BasicBlockId) -> String {
+        format!("'bb{}", u32::from(basic_block_id.into_raw()))
+    }
+
+    fn blocks(&mut self) {
+        for (id, block) in self.body.basic_blocks.iter() {
+            wln!(self);
+            w!(self, "{}: ", self.basic_block_id(id));
+            self.with_block(|this| {
+                for statement in &block.statements {
+                    match &statement.kind {
+                        StatementKind::Assign(l, r) => {
+                            this.place(l);
+                            w!(this, " = ");
+                            this.rvalue(r);
+                            wln!(this, ";");
+                        }
+                        StatementKind::StorageDead(p) => {
+                            wln!(this, "StorageDead({})", this.local_name(*p));
+                        }
+                        StatementKind::StorageLive(p) => {
+                            wln!(this, "StorageLive({})", this.local_name(*p));
+                        }
+                        StatementKind::Deinit(p) => {
+                            w!(this, "Deinit(");
+                            this.place(p);
+                            wln!(this, ");");
+                        }
+                        StatementKind::Nop => wln!(this, "Nop;"),
+                    }
+                }
+                match &block.terminator {
+                    Some(terminator) => match terminator {
+                        Terminator::Goto { target } => {
+                            wln!(this, "goto 'bb{};", u32::from(target.into_raw()))
+                        }
+                        Terminator::SwitchInt { discr, targets } => {
+                            w!(this, "switch ");
+                            this.operand(discr);
+                            w!(this, " ");
+                            this.with_block(|this| {
+                                for (c, b) in targets.iter() {
+                                    wln!(this, "{c} => {},", this.basic_block_id(b));
+                                }
+                                wln!(this, "_ => {},", this.basic_block_id(targets.otherwise()));
+                            });
+                        }
+                        Terminator::Call { func, args, destination, target, .. } => {
+                            w!(this, "Call ");
+                            this.with_block(|this| {
+                                w!(this, "func: ");
+                                this.operand(func);
+                                wln!(this, ",");
+                                w!(this, "args: [");
+                                this.operand_list(args);
+                                wln!(this, "],");
+                                w!(this, "destination: ");
+                                this.place(destination);
+                                wln!(this, ",");
+                                w!(this, "target: ");
+                                match target {
+                                    Some(t) => w!(this, "{}", this.basic_block_id(*t)),
+                                    None => w!(this, "<unreachable>"),
+                                }
+                                wln!(this, ",");
+                            });
+                        }
+                        _ => wln!(this, "{:?};", terminator),
+                    },
+                    None => wln!(this, "<no-terminator>;"),
+                }
+            })
+        }
+    }
+
+    fn place(&mut self, p: &Place) {
+        fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) {
+            let Some((last, head)) = projections.split_last() else {
+                // no projection
+                w!(this, "{}", this.local_name(local));
+                return;
+            };
+            match last {
+                ProjectionElem::Deref => {
+                    w!(this, "(*");
+                    f(this, local, head);
+                    w!(this, ")");
+                }
+                ProjectionElem::Field(field) => {
+                    let variant_data = field.parent.variant_data(this.db.upcast());
+                    let name = &variant_data.fields()[field.local_id].name;
+                    match field.parent {
+                        hir_def::VariantId::EnumVariantId(e) => {
+                            w!(this, "(");
+                            f(this, local, head);
+                            let variant_name =
+                                &this.db.enum_data(e.parent).variants[e.local_id].name;
+                            w!(this, " as {}).{}", variant_name, name);
+                        }
+                        hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => {
+                            f(this, local, head);
+                            w!(this, ".{name}");
+                        }
+                    }
+                }
+                ProjectionElem::TupleField(x) => {
+                    f(this, local, head);
+                    w!(this, ".{}", x);
+                }
+                ProjectionElem::Index(l) => {
+                    f(this, local, head);
+                    w!(this, "[{}]", this.local_name(*l));
+                }
+                x => {
+                    f(this, local, head);
+                    w!(this, ".{:?}", x);
+                }
+            }
+        }
+        f(self, p.local, &p.projection);
+    }
+
+    fn operand(&mut self, r: &Operand) {
+        match r {
+            Operand::Copy(p) | Operand::Move(p) => {
+                // MIR at the time of writing doesn't have difference between move and copy, so we show them
+                // equally. Feel free to change it.
+                self.place(p);
+            }
+            Operand::Constant(c) => w!(self, "Const({})", c.display(self.db)),
+        }
+    }
+
+    fn rvalue(&mut self, r: &Rvalue) {
+        match r {
+            Rvalue::Use(op) => self.operand(op),
+            Rvalue::Ref(r, p) => {
+                match r {
+                    BorrowKind::Shared => w!(self, "&"),
+                    BorrowKind::Shallow => w!(self, "&shallow "),
+                    BorrowKind::Unique => w!(self, "&uniq "),
+                    BorrowKind::Mut { .. } => w!(self, "&mut "),
+                }
+                self.place(p);
+            }
+            Rvalue::Aggregate(AggregateKind::Tuple(_), x) => {
+                w!(self, "(");
+                self.operand_list(x);
+                w!(self, ")");
+            }
+            Rvalue::Aggregate(AggregateKind::Array(_), x) => {
+                w!(self, "[");
+                self.operand_list(x);
+                w!(self, "]");
+            }
+            Rvalue::Aggregate(AggregateKind::Adt(_, _), x) => {
+                w!(self, "Adt(");
+                self.operand_list(x);
+                w!(self, ")");
+            }
+            Rvalue::Aggregate(AggregateKind::Union(_, _), x) => {
+                w!(self, "Union(");
+                self.operand_list(x);
+                w!(self, ")");
+            }
+            Rvalue::Len(p) => {
+                w!(self, "Len(");
+                self.place(p);
+                w!(self, ")");
+            }
+            Rvalue::Cast(ck, op, ty) => {
+                w!(self, "Discriminant({ck:?}");
+                self.operand(op);
+                w!(self, "{})", ty.display(self.db));
+            }
+            Rvalue::CheckedBinaryOp(b, o1, o2) => {
+                self.operand(o1);
+                w!(self, " {b} ");
+                self.operand(o2);
+            }
+            Rvalue::UnaryOp(u, o) => {
+                let u = match u {
+                    UnOp::Not => "!",
+                    UnOp::Neg => "-",
+                };
+                w!(self, "{u} ");
+                self.operand(o);
+            }
+            Rvalue::Discriminant(p) => {
+                w!(self, "Discriminant(");
+                self.place(p);
+                w!(self, ")");
+            }
+            Rvalue::ShallowInitBox(op, _) => {
+                w!(self, "ShallowInitBox(");
+                self.operand(op);
+                w!(self, ")");
+            }
+            Rvalue::CopyForDeref(p) => {
+                w!(self, "CopyForDeref(");
+                self.place(p);
+                w!(self, ")");
+            }
+        }
+    }
+
+    fn operand_list(&mut self, x: &[Operand]) {
+        let mut it = x.iter();
+        if let Some(first) = it.next() {
+            self.operand(first);
+            for op in it {
+                w!(self, ", ");
+                self.operand(op);
+            }
+        }
+    }
+}
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 3e110abaf4b..b524922b6cf 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -258,6 +258,7 @@ fn test() {
 
 #[test]
 fn coerce_autoderef_block() {
+    // FIXME: We should know mutability in overloaded deref
     check_no_mismatches(
         r#"
 //- minicore: deref
@@ -267,7 +268,7 @@ fn takes_ref_str(x: &str) {}
 fn returns_string() -> String { loop {} }
 fn test() {
     takes_ref_str(&{ returns_string() });
-               // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Not))), Borrow(Ref(Not))
+               // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(None))), Borrow(Ref(Not))
 }
 "#,
     );
diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs
index 4b671449e15..e568e7013fa 100644
--- a/crates/hir-ty/src/tests/method_resolution.rs
+++ b/crates/hir-ty/src/tests/method_resolution.rs
@@ -1252,6 +1252,7 @@ fn foo<T: Trait>(a: &T) {
 
 #[test]
 fn autoderef_visibility_field() {
+    // FIXME: We should know mutability in overloaded deref
     check(
         r#"
 //- minicore: deref
@@ -1273,7 +1274,7 @@ mod a {
 mod b {
     fn foo() {
         let x = super::a::Bar::new().0;
-             // ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Not)))
+             // ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(None)))
              // ^^^^^^^^^^^^^^^^^^^^^^ type: char
     }
 }
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index b30c664e24f..c257ee2ae3a 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -10,7 +10,7 @@ use hir_def::path::ModPath;
 use hir_expand::{name::Name, HirFileId, InFile};
 use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
 
-use crate::{AssocItem, Field, MacroKind, Type};
+use crate::{AssocItem, Field, Local, MacroKind, Type};
 
 macro_rules! diagnostics {
     ($($diag:ident,)*) => {
@@ -41,6 +41,7 @@ diagnostics![
     MissingFields,
     MissingMatchArms,
     MissingUnsafe,
+    NeedMut,
     NoSuchField,
     PrivateAssocItem,
     PrivateField,
@@ -54,6 +55,7 @@ diagnostics![
     UnresolvedMethodCall,
     UnresolvedModule,
     UnresolvedProcMacro,
+    UnusedMut,
 ];
 
 #[derive(Debug)]
@@ -209,4 +211,15 @@ pub struct TypeMismatch {
     pub actual: Type,
 }
 
+#[derive(Debug)]
+pub struct NeedMut {
+    pub local: Local,
+    pub span: InFile<SyntaxNodePtr>,
+}
+
+#[derive(Debug)]
+pub struct UnusedMut {
+    pub local: Local,
+}
+
 pub use hir_ty::diagnostics::IncorrectCase;
diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs
index 43276919567..aaaa7abf386 100644
--- a/crates/hir/src/from_id.rs
+++ b/crates/hir/src/from_id.rs
@@ -4,7 +4,7 @@
 //! are splitting the hir.
 
 use hir_def::{
-    expr::{LabelId, PatId},
+    expr::{BindingId, LabelId},
     AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, GenericParamId,
     ModuleDefId, VariantId,
 };
@@ -251,9 +251,9 @@ impl From<AssocItem> for GenericDefId {
     }
 }
 
-impl From<(DefWithBodyId, PatId)> for Local {
-    fn from((parent, pat_id): (DefWithBodyId, PatId)) -> Self {
-        Local { parent, pat_id }
+impl From<(DefWithBodyId, BindingId)> for Local {
+    fn from((parent, binding_id): (DefWithBodyId, BindingId)) -> Self {
+        Local { parent, binding_id }
     }
 }
 
diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs
index a28b2d577aa..9f6b5c0a9fc 100644
--- a/crates/hir/src/has_source.rs
+++ b/crates/hir/src/has_source.rs
@@ -10,8 +10,9 @@ use hir_expand::InFile;
 use syntax::ast;
 
 use crate::{
-    db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, Macro,
-    Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant,
+    db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam,
+    LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam,
+    Union, Variant,
 };
 
 pub trait HasSource {
@@ -178,3 +179,11 @@ impl HasSource for LifetimeParam {
         Some(child_source.map(|it| it[self.id.local_id].clone()))
     }
 }
+
+impl HasSource for LocalSource {
+    type Ast = Either<ast::IdentPat, ast::SelfParam>;
+
+    fn source(self, _: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
+        Some(self.source)
+    }
+}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index a5df94885f6..92b31031ca1 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -41,7 +41,7 @@ use either::Either;
 use hir_def::{
     adt::VariantData,
     body::{BodyDiagnostic, SyntheticSyntax},
-    expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId},
+    expr::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat},
     generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
     item_tree::ItemTreeNode,
     lang_item::{LangItem, LangItemTarget},
@@ -63,7 +63,7 @@ use hir_ty::{
     display::HexifiedConst,
     layout::layout_of_ty,
     method_resolution::{self, TyFingerprint},
-    mir::interpret_mir,
+    mir::{self, interpret_mir},
     primitive::UintTy,
     traits::FnTrait,
     AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
@@ -77,7 +77,7 @@ use rustc_hash::FxHashSet;
 use stdx::{impl_from, never};
 use syntax::{
     ast::{self, HasAttrs as _, HasDocComments, HasName},
-    AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T,
+    AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, T,
 };
 
 use crate::db::{DefDatabase, HirDatabase};
@@ -87,10 +87,10 @@ pub use crate::{
     diagnostics::{
         AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase,
         InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
-        MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
+        MissingMatchArms, MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
         ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
         UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
-        UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
+        UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut,
     },
     has_source::HasSource,
     semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@@ -1327,6 +1327,15 @@ impl DefWithBody {
         body.pretty_print(db.upcast(), self.id())
     }
 
+    /// A textual representation of the MIR of this def's body for debugging purposes.
+    pub fn debug_mir(self, db: &dyn HirDatabase) -> String {
+        let body = db.mir_body(self.id());
+        match body {
+            Ok(body) => body.pretty_print(db),
+            Err(e) => format!("error:\n{e:?}"),
+        }
+    }
+
     pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
         let krate = self.module(db).id.krate();
 
@@ -1500,6 +1509,41 @@ impl DefWithBody {
             }
         }
 
+        let hir_body = db.body(self.into());
+
+        if let Ok(borrowck_result) = db.borrowck(self.into()) {
+            let mir_body = &borrowck_result.mir_body;
+            let mol = &borrowck_result.mutability_of_locals;
+            for (binding_id, _) in hir_body.bindings.iter() {
+                let need_mut = &mol[mir_body.binding_locals[binding_id]];
+                let local = Local { parent: self.into(), binding_id };
+                match (need_mut, local.is_mut(db)) {
+                    (mir::MutabilityReason::Mut { .. }, true)
+                    | (mir::MutabilityReason::Not, false) => (),
+                    (mir::MutabilityReason::Mut { spans }, false) => {
+                        for span in spans {
+                            let span: InFile<SyntaxNodePtr> = match span {
+                                mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
+                                    Ok(s) => s.map(|x| x.into()),
+                                    Err(_) => continue,
+                                },
+                                mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
+                                    Ok(s) => s.map(|x| match x {
+                                        Either::Left(e) => e.into(),
+                                        Either::Right(e) => e.into(),
+                                    }),
+                                    Err(_) => continue,
+                                },
+                                mir::MirSpan::Unknown => continue,
+                            };
+                            acc.push(NeedMut { local, span }.into());
+                        }
+                    }
+                    (mir::MutabilityReason::Not, true) => acc.push(UnusedMut { local }.into()),
+                }
+            }
+        }
+
         for diagnostic in BodyValidationDiagnostic::collect(db, self.into()) {
             match diagnostic {
                 BodyValidationDiagnostic::RecordMissingFields {
@@ -1786,8 +1830,8 @@ impl Param {
         let parent = DefWithBodyId::FunctionId(self.func.into());
         let body = db.body(parent);
         let pat_id = body.params[self.idx];
-        if let Pat::Bind { .. } = &body[pat_id] {
-            Some(Local { parent, pat_id: body.params[self.idx] })
+        if let Pat::Bind { id, .. } = &body[pat_id] {
+            Some(Local { parent, binding_id: *id })
         } else {
             None
         }
@@ -2464,13 +2508,50 @@ impl GenericDef {
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub struct Local {
     pub(crate) parent: DefWithBodyId,
-    pub(crate) pat_id: PatId,
+    pub(crate) binding_id: BindingId,
+}
+
+pub struct LocalSource {
+    pub local: Local,
+    pub source: InFile<Either<ast::IdentPat, ast::SelfParam>>,
+}
+
+impl LocalSource {
+    pub fn as_ident_pat(&self) -> Option<&ast::IdentPat> {
+        match &self.source.value {
+            Either::Left(x) => Some(x),
+            Either::Right(_) => None,
+        }
+    }
+
+    pub fn into_ident_pat(self) -> Option<ast::IdentPat> {
+        match self.source.value {
+            Either::Left(x) => Some(x),
+            Either::Right(_) => None,
+        }
+    }
+
+    pub fn original_file(&self, db: &dyn HirDatabase) -> FileId {
+        self.source.file_id.original_file(db.upcast())
+    }
+
+    pub fn name(&self) -> Option<ast::Name> {
+        self.source.value.name()
+    }
+
+    pub fn syntax(&self) -> &SyntaxNode {
+        self.source.value.syntax()
+    }
+
+    pub fn syntax_ptr(self) -> InFile<SyntaxNodePtr> {
+        self.source.map(|x| SyntaxNodePtr::new(x.syntax()))
+    }
 }
 
 impl Local {
     pub fn is_param(self, db: &dyn HirDatabase) -> bool {
-        let src = self.source(db);
-        match src.value {
+        let src = self.primary_source(db);
+        match src.source.value {
             Either::Left(pat) => pat
                 .syntax()
                 .ancestors()
@@ -2490,13 +2571,7 @@ impl Local {
 
     pub fn name(self, db: &dyn HirDatabase) -> Name {
         let body = db.body(self.parent);
-        match &body[self.pat_id] {
-            Pat::Bind { name, .. } => name.clone(),
-            _ => {
-                stdx::never!("hir::Local is missing a name!");
-                Name::missing()
-            }
-        }
+        body[self.binding_id].name.clone()
     }
 
     pub fn is_self(self, db: &dyn HirDatabase) -> bool {
@@ -2505,15 +2580,12 @@ impl Local {
 
     pub fn is_mut(self, db: &dyn HirDatabase) -> bool {
         let body = db.body(self.parent);
-        matches!(&body[self.pat_id], Pat::Bind { mode: BindingAnnotation::Mutable, .. })
+        body[self.binding_id].mode == BindingAnnotation::Mutable
     }
 
     pub fn is_ref(self, db: &dyn HirDatabase) -> bool {
         let body = db.body(self.parent);
-        matches!(
-            &body[self.pat_id],
-            Pat::Bind { mode: BindingAnnotation::Ref | BindingAnnotation::RefMut, .. }
-        )
+        matches!(body[self.binding_id].mode, BindingAnnotation::Ref | BindingAnnotation::RefMut)
     }
 
     pub fn parent(self, _db: &dyn HirDatabase) -> DefWithBody {
@@ -2527,34 +2599,33 @@ impl Local {
     pub fn ty(self, db: &dyn HirDatabase) -> Type {
         let def = self.parent;
         let infer = db.infer(def);
-        let ty = infer[self.pat_id].clone();
+        let ty = infer[self.binding_id].clone();
         Type::new(db, def, ty)
     }
 
-    pub fn associated_locals(self, db: &dyn HirDatabase) -> Box<[Local]> {
-        let body = db.body(self.parent);
-        body.ident_patterns_for(&self.pat_id)
+    /// All definitions for this local. Example: `let (a$0, _) | (_, a$0) = x;`
+    pub fn sources(self, db: &dyn HirDatabase) -> Vec<LocalSource> {
+        let (body, source_map) = db.body_with_source_map(self.parent);
+        body[self.binding_id]
+            .definitions
             .iter()
-            .map(|&pat_id| Local { parent: self.parent, pat_id })
+            .map(|&definition| {
+                let src = source_map.pat_syntax(definition).unwrap(); // Hmm...
+                let root = src.file_syntax(db.upcast());
+                src.map(|ast| match ast {
+                    // Suspicious unwrap
+                    Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
+                    Either::Right(it) => Either::Right(it.to_node(&root)),
+                })
+            })
+            .map(|source| LocalSource { local: self, source })
             .collect()
     }
 
-    /// If this local is part of a multi-local, retrieve the representative local.
-    /// That is the local that references are being resolved to.
-    pub fn representative(self, db: &dyn HirDatabase) -> Local {
-        let body = db.body(self.parent);
-        Local { pat_id: body.pattern_representative(self.pat_id), ..self }
-    }
-
-    pub fn source(self, db: &dyn HirDatabase) -> InFile<Either<ast::IdentPat, ast::SelfParam>> {
-        let (_body, source_map) = db.body_with_source_map(self.parent);
-        let src = source_map.pat_syntax(self.pat_id).unwrap(); // Hmm...
-        let root = src.file_syntax(db.upcast());
-        src.map(|ast| match ast {
-            // Suspicious unwrap
-            Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
-            Either::Right(it) => Either::Right(it.to_node(&root)),
-        })
+    /// The leftmost definition for this local. Example: `let (a$0, _) | (_, a) = x;`
+    pub fn primary_source(self, db: &dyn HirDatabase) -> LocalSource {
+        let all_sources = self.sources(db);
+        all_sources.into_iter().next().unwrap()
     }
 }
 
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 697f4b43bc7..2a0077cf505 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1103,7 +1103,10 @@ impl<'db> SemanticsImpl<'db> {
                     let kind = match adjust.kind {
                         hir_ty::Adjust::NeverToAny => Adjust::NeverToAny,
                         hir_ty::Adjust::Deref(Some(hir_ty::OverloadedDeref(m))) => {
-                            Adjust::Deref(Some(OverloadedDeref(mutability(m))))
+                            // FIXME: Should we handle unknown mutability better?
+                            Adjust::Deref(Some(OverloadedDeref(
+                                m.map(mutability).unwrap_or(Mutability::Shared),
+                            )))
                         }
                         hir_ty::Adjust::Deref(None) => Adjust::Deref(None),
                         hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => {
@@ -1654,8 +1657,8 @@ impl<'a> SemanticsScope<'a> {
                     resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
                     resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
                     resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()),
-                    resolver::ScopeDef::Local(pat_id) => match self.resolver.body_owner() {
-                        Some(parent) => ScopeDef::Local(Local { parent, pat_id }),
+                    resolver::ScopeDef::Local(binding_id) => match self.resolver.body_owner() {
+                        Some(parent) => ScopeDef::Local(Local { parent, binding_id }),
                         None => continue,
                     },
                     resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() {
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index ddfec20e3f9..f6f8c9a250f 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -89,7 +89,7 @@ use base_db::FileId;
 use hir_def::{
     child_by_source::ChildBySource,
     dyn_map::DynMap,
-    expr::{LabelId, PatId},
+    expr::{BindingId, LabelId},
     keys::{self, Key},
     AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId,
     GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId,
@@ -98,7 +98,7 @@ use hir_def::{
 use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId};
 use rustc_hash::FxHashMap;
 use smallvec::SmallVec;
-use stdx::impl_from;
+use stdx::{impl_from, never};
 use syntax::{
     ast::{self, HasName},
     AstNode, SyntaxNode,
@@ -216,14 +216,14 @@ impl SourceToDefCtx<'_, '_> {
     pub(super) fn bind_pat_to_def(
         &mut self,
         src: InFile<ast::IdentPat>,
-    ) -> Option<(DefWithBodyId, PatId)> {
+    ) -> Option<(DefWithBodyId, BindingId)> {
         let container = self.find_pat_or_label_container(src.syntax())?;
         let (body, source_map) = self.db.body_with_source_map(container);
         let src = src.map(ast::Pat::from);
         let pat_id = source_map.node_pat(src.as_ref())?;
         // the pattern could resolve to a constant, verify that that is not the case
-        if let crate::Pat::Bind { .. } = body[pat_id] {
-            Some((container, pat_id))
+        if let crate::Pat::Bind { id, .. } = body[pat_id] {
+            Some((container, id))
         } else {
             None
         }
@@ -231,11 +231,16 @@ impl SourceToDefCtx<'_, '_> {
     pub(super) fn self_param_to_def(
         &mut self,
         src: InFile<ast::SelfParam>,
-    ) -> Option<(DefWithBodyId, PatId)> {
+    ) -> Option<(DefWithBodyId, BindingId)> {
         let container = self.find_pat_or_label_container(src.syntax())?;
-        let (_body, source_map) = self.db.body_with_source_map(container);
+        let (body, source_map) = self.db.body_with_source_map(container);
         let pat_id = source_map.node_self_param(src.as_ref())?;
-        Some((container, pat_id))
+        if let crate::Pat::Bind { id, .. } = body[pat_id] {
+            Some((container, id))
+        } else {
+            never!();
+            None
+        }
     }
     pub(super) fn label_to_def(
         &mut self,
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 118a7f8ea86..133fa810d66 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -422,8 +422,8 @@ impl SourceAnalyzer {
             // Shorthand syntax, resolve to the local
             let path = ModPath::from_segments(PathKind::Plain, once(local_name.clone()));
             match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
-                Some(ValueNs::LocalBinding(pat_id)) => {
-                    Some(Local { pat_id, parent: self.resolver.body_owner()? })
+                Some(ValueNs::LocalBinding(binding_id)) => {
+                    Some(Local { binding_id, parent: self.resolver.body_owner()? })
                 }
                 _ => None,
             }
@@ -1018,8 +1018,8 @@ fn resolve_hir_path_(
     let values = || {
         resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| {
             let res = match val {
-                ValueNs::LocalBinding(pat_id) => {
-                    let var = Local { parent: body_owner?, pat_id };
+                ValueNs::LocalBinding(binding_id) => {
+                    let var = Local { parent: body_owner?, binding_id };
                     PathResolution::Local(var)
                 }
                 ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
diff --git a/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/crates/ide-assists/src/handlers/convert_match_to_let_else.rs
index 65c2479e9f2..745a870ab6b 100644
--- a/crates/ide-assists/src/handlers/convert_match_to_let_else.rs
+++ b/crates/ide-assists/src/handlers/convert_match_to_let_else.rs
@@ -101,7 +101,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti
             let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?;
             match NameRefClass::classify(&ctx.sema, &name_ref)? {
                 NameRefClass::Definition(Definition::Local(local)) => {
-                    let source = local.source(ctx.db()).value.left()?;
+                    let source = local.primary_source(ctx.db()).into_ident_pat()?;
                     Some(source.name()?)
                 }
                 _ => None,
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index e04a1dabb2c..0b90c9ba34f 100644
--- a/crates/ide-assists/src/handlers/extract_function.rs
+++ b/crates/ide-assists/src/handlers/extract_function.rs
@@ -3,7 +3,8 @@ use std::iter;
 use ast::make;
 use either::Either;
 use hir::{
-    HasSource, HirDisplay, InFile, Local, ModuleDef, PathResolution, Semantics, TypeInfo, TypeParam,
+    HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
+    TypeInfo, TypeParam,
 };
 use ide_db::{
     defs::{Definition, NameRefClass},
@@ -710,7 +711,7 @@ impl FunctionBody {
                     ) => local_ref,
                     _ => return,
                 };
-            let InFile { file_id, value } = local_ref.source(sema.db);
+            let InFile { file_id, value } = local_ref.primary_source(sema.db).source;
             // locals defined inside macros are not relevant to us
             if !file_id.is_macro() {
                 match value {
@@ -972,11 +973,11 @@ impl FunctionBody {
         locals: impl Iterator<Item = Local>,
     ) -> Vec<Param> {
         locals
-            .map(|local| (local, local.source(ctx.db())))
+            .map(|local| (local, local.primary_source(ctx.db())))
             .filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
-            .filter_map(|(local, src)| match src.value {
-                Either::Left(src) => Some((local, src)),
-                Either::Right(_) => {
+            .filter_map(|(local, src)| match src.into_ident_pat() {
+                Some(src) => Some((local, src)),
+                None => {
                     stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
                     None
                 }
@@ -1238,17 +1239,9 @@ fn local_outlives_body(
 fn is_defined_outside_of_body(
     ctx: &AssistContext<'_>,
     body: &FunctionBody,
-    src: &hir::InFile<Either<ast::IdentPat, ast::SelfParam>>,
+    src: &LocalSource,
 ) -> bool {
-    src.file_id.original_file(ctx.db()) == ctx.file_id()
-        && !body.contains_node(either_syntax(&src.value))
-}
-
-fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
-    match value {
-        Either::Left(pat) => pat.syntax(),
-        Either::Right(it) => it.syntax(),
-    }
+    src.original_file(ctx.db()) == ctx.file_id() && !body.contains_node(src.syntax())
 }
 
 /// find where to put extracted function definition
diff --git a/crates/ide-assists/src/handlers/inline_local_variable.rs b/crates/ide-assists/src/handlers/inline_local_variable.rs
index ce44100e34b..e69d1a29677 100644
--- a/crates/ide-assists/src/handlers/inline_local_variable.rs
+++ b/crates/ide-assists/src/handlers/inline_local_variable.rs
@@ -1,4 +1,3 @@
-use either::Either;
 use hir::{PathResolution, Semantics};
 use ide_db::{
     base_db::FileId,
@@ -205,12 +204,14 @@ fn inline_usage(
         return None;
     }
 
-    // FIXME: Handle multiple local definitions
-    let bind_pat = match local.source(sema.db).value {
-        Either::Left(ident) => ident,
-        _ => return None,
+    let sources = local.sources(sema.db);
+    let [source] = sources.as_slice() else {
+        // Not applicable with locals with multiple definitions (i.e. or patterns)
+        return None;
     };
 
+    let bind_pat = source.as_ident_pat()?;
+
     let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
 
     let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs
index 4179f1bd4f3..f710211c8cb 100644
--- a/crates/ide-db/src/rename.rs
+++ b/crates/ide-db/src/rename.rs
@@ -121,14 +121,7 @@ impl Definition {
             Definition::Trait(it) => name_range(it, sema),
             Definition::TraitAlias(it) => name_range(it, sema),
             Definition::TypeAlias(it) => name_range(it, sema),
-            Definition::Local(local) => {
-                let src = local.source(sema.db);
-                let name = match &src.value {
-                    Either::Left(bind_pat) => bind_pat.name()?,
-                    Either::Right(_) => return None,
-                };
-                src.with_value(name.syntax()).original_file_range_opt(sema.db)
-            }
+            Definition::Local(it) => name_range(it.primary_source(sema.db), sema),
             Definition::GenericParam(generic_param) => match generic_param {
                 hir::GenericParam::LifetimeParam(lifetime_param) => {
                     let src = lifetime_param.source(sema.db)?;
@@ -302,13 +295,7 @@ fn rename_reference(
         source_change.insert_source_edit(file_id, edit);
         Ok(())
     };
-    match def {
-        Definition::Local(l) => l
-            .associated_locals(sema.db)
-            .iter()
-            .try_for_each(|&local| insert_def_edit(Definition::Local(local))),
-        def => insert_def_edit(def),
-    }?;
+    insert_def_edit(def)?;
     Ok(source_change)
 }
 
@@ -471,59 +458,64 @@ fn source_edit_from_def(
     def: Definition,
     new_name: &str,
 ) -> Result<(FileId, TextEdit)> {
-    let FileRange { file_id, range } = def
-        .range_for_rename(sema)
-        .ok_or_else(|| format_err!("No identifier available to rename"))?;
-
     let mut edit = TextEdit::builder();
     if let Definition::Local(local) = def {
-        if let Either::Left(pat) = local.source(sema.db).value {
-            // special cases required for renaming fields/locals in Record patterns
-            if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
+        let mut file_id = None;
+        for source in local.sources(sema.db) {
+            let source = source.source;
+            file_id = source.file_id.file_id();
+            if let Either::Left(pat) = source.value {
                 let name_range = pat.name().unwrap().syntax().text_range();
-                if let Some(name_ref) = pat_field.name_ref() {
-                    if new_name == name_ref.text() && pat.at_token().is_none() {
-                        // Foo { field: ref mut local } -> Foo { ref mut field }
-                        //       ^^^^^^ delete this
-                        //                      ^^^^^ replace this with `field`
-                        cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
-                        edit.delete(
-                            name_ref
-                                .syntax()
-                                .text_range()
-                                .cover_offset(pat.syntax().text_range().start()),
-                        );
-                        edit.replace(name_range, name_ref.text().to_string());
+                // special cases required for renaming fields/locals in Record patterns
+                if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
+                    if let Some(name_ref) = pat_field.name_ref() {
+                        if new_name == name_ref.text() && pat.at_token().is_none() {
+                            // Foo { field: ref mut local } -> Foo { ref mut field }
+                            //       ^^^^^^ delete this
+                            //                      ^^^^^ replace this with `field`
+                            cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
+                            edit.delete(
+                                name_ref
+                                    .syntax()
+                                    .text_range()
+                                    .cover_offset(pat.syntax().text_range().start()),
+                            );
+                            edit.replace(name_range, name_ref.text().to_string());
+                        } else {
+                            // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
+                            // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
+                            //                      ^^^^^ replace this with `new_name`
+                            edit.replace(name_range, new_name.to_string());
+                        }
                     } else {
-                        // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
-                        // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
-                        //                      ^^^^^ replace this with `new_name`
+                        // Foo { ref mut field } -> Foo { field: ref mut new_name }
+                        //      ^ insert `field: `
+                        //               ^^^^^ replace this with `new_name`
+                        edit.insert(
+                            pat.syntax().text_range().start(),
+                            format!("{}: ", pat_field.field_name().unwrap()),
+                        );
                         edit.replace(name_range, new_name.to_string());
                     }
                 } else {
-                    // Foo { ref mut field } -> Foo { field: ref mut new_name }
-                    //      ^ insert `field: `
-                    //               ^^^^^ replace this with `new_name`
-                    edit.insert(
-                        pat.syntax().text_range().start(),
-                        format!("{}: ", pat_field.field_name().unwrap()),
-                    );
                     edit.replace(name_range, new_name.to_string());
                 }
             }
         }
+        let Some(file_id) = file_id else { bail!("No file available to rename") };
+        return Ok((file_id, edit.finish()));
     }
-    if edit.is_empty() {
-        let (range, new_name) = match def {
-            Definition::GenericParam(hir::GenericParam::LifetimeParam(_))
-            | Definition::Label(_) => (
-                TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
-                new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
-            ),
-            _ => (range, new_name.to_owned()),
-        };
-        edit.replace(range, new_name);
-    }
+    let FileRange { file_id, range } = def
+        .range_for_rename(sema)
+        .ok_or_else(|| format_err!("No identifier available to rename"))?;
+    let (range, new_name) = match def {
+        Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) => (
+            TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
+            new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
+        ),
+        _ => (range, new_name.to_owned()),
+    };
+    edit.replace(range, new_name);
     Ok((file_id, edit.finish()))
 }
 
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index bcdaac4cf82..6298ea1927d 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -320,7 +320,7 @@ impl Definition {
             scope: None,
             include_self_kw_refs: None,
             local_repr: match self {
-                Definition::Local(local) => Some(local.representative(sema.db)),
+                Definition::Local(local) => Some(local),
                 _ => None,
             },
             search_self_mod: false,
@@ -646,7 +646,7 @@ impl<'a> FindUsages<'a> {
         match NameRefClass::classify(self.sema, name_ref) {
             Some(NameRefClass::Definition(def @ Definition::Local(local)))
                 if matches!(
-                    self.local_repr, Some(repr) if repr == local.representative(self.sema.db)
+                    self.local_repr, Some(repr) if repr == local
                 ) =>
             {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
@@ -707,7 +707,7 @@ impl<'a> FindUsages<'a> {
                     Definition::Field(_) if field == self.def => {
                         ReferenceCategory::new(&field, name_ref)
                     }
-                    Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local.representative(self.sema.db)) => {
+                    Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local) => {
                         ReferenceCategory::new(&Definition::Local(local), name_ref)
                     }
                     _ => return false,
@@ -755,7 +755,7 @@ impl<'a> FindUsages<'a> {
             Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => {
                 if matches!(
                     self.local_repr,
-                    Some(repr) if local.representative(self.sema.db) == repr
+                    Some(repr) if local == repr
                 ) {
                     let FileRange { file_id, range } = self.sema.original_range(name.syntax());
                     let reference = FileReference {
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
new file mode 100644
index 00000000000..9c79ceba01e
--- /dev/null
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -0,0 +1,592 @@
+use ide_db::source_change::SourceChange;
+use syntax::{AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T};
+use text_edit::TextEdit;
+
+use crate::{fix, Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: need-mut
+//
+// This diagnostic is triggered on mutating an immutable variable.
+pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagnostic {
+    let fixes = (|| {
+        if d.local.is_ref(ctx.sema.db) {
+            // There is no simple way to add `mut` to `ref x` and `ref mut x`
+            return None;
+        }
+        let file_id = d.span.file_id.file_id()?;
+        let mut edit_builder = TextEdit::builder();
+        let use_range = d.span.value.text_range();
+        for source in d.local.sources(ctx.sema.db) {
+            let Some(ast) = source.name() else { continue };
+            edit_builder.insert(ast.syntax().text_range().start(), "mut ".to_string());
+        }
+        let edit = edit_builder.finish();
+        Some(vec![fix(
+            "add_mut",
+            "Change it to be mutable",
+            SourceChange::from_text_edit(file_id, edit),
+            use_range,
+        )])
+    })();
+    Diagnostic::new(
+        "need-mut",
+        format!("cannot mutate immutable variable `{}`", d.local.name(ctx.sema.db)),
+        ctx.sema.diagnostics_display_range(d.span.clone()).range,
+    )
+    .with_fixes(fixes)
+}
+
+// Diagnostic: unused-mut
+//
+// This diagnostic is triggered when a mutable variable isn't actually mutated.
+pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Diagnostic {
+    let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
+    let fixes = (|| {
+        let file_id = ast.file_id.file_id()?;
+        let mut edit_builder = TextEdit::builder();
+        let use_range = ast.value.text_range();
+        for source in d.local.sources(ctx.sema.db) {
+            let ast = source.syntax();
+            let Some(mut_token) = token(ast, T![mut]) else { continue };
+            edit_builder.delete(mut_token.text_range());
+            if let Some(token) = mut_token.next_token() {
+                if token.kind() == SyntaxKind::WHITESPACE {
+                    edit_builder.delete(token.text_range());
+                }
+            }
+        }
+        let edit = edit_builder.finish();
+        Some(vec![fix(
+            "remove_mut",
+            "Remove unnecessary `mut`",
+            SourceChange::from_text_edit(file_id, edit),
+            use_range,
+        )])
+    })();
+    let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
+    Diagnostic::new(
+        "unused-mut",
+        "variable does not need to be mutable",
+        ctx.sema.diagnostics_display_range(ast).range,
+    )
+    .severity(Severity::WeakWarning)
+    .experimental() // Not supporting `#[allow(unused_mut)]` leads to false positive.
+    .with_fixes(fixes)
+}
+
+pub(super) fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
+    parent.children_with_tokens().filter_map(|it| it.into_token()).find(|it| it.kind() == kind)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_diagnostics, check_fix};
+
+    #[test]
+    fn unused_mut_simple() {
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mut x = 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    f(x);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn no_false_positive_simple() {
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let x = 2;
+    f(x);
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mut x = 2;
+    x = 5;
+    f(x);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn multiple_errors_for_single_variable() {
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let x = 2;
+    x = 10;
+  //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+    x = 5;
+  //^^^^^ 💡 error: cannot mutate immutable variable `x`
+    &mut x;
+  //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+    f(x);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn unused_mut_fix() {
+        check_fix(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mu$0t x = 2;
+    f(x);
+}
+"#,
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let x = 2;
+    f(x);
+}
+"#,
+        );
+        check_fix(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let ((mu$0t x, _) | (_, mut x)) = (2, 3);
+    f(x);
+}
+"#,
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let ((x, _) | (_, x)) = (2, 3);
+    f(x);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn need_mut_fix() {
+        check_fix(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let x = 2;
+    x$0 = 5;
+    f(x);
+}
+"#,
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mut x = 2;
+    x = 5;
+    f(x);
+}
+"#,
+        );
+        check_fix(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let ((x, _) | (_, x)) = (2, 3);
+    x =$0 4;
+    f(x);
+}
+"#,
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let ((mut x, _) | (_, mut x)) = (2, 3);
+    x = 4;
+    f(x);
+}
+"#,
+        );
+
+        check_fix(
+            r#"
+struct Foo(i32);
+
+impl Foo {
+    fn foo(self) {
+        self = Fo$0o(5);
+    }
+}
+"#,
+            r#"
+struct Foo(i32);
+
+impl Foo {
+    fn foo(mut self) {
+        self = Foo(5);
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn need_mut_fix_not_applicable_on_ref() {
+        check_diagnostics(
+            r#"
+fn main() {
+    let ref x = 2;
+    x = &5;
+  //^^^^^^ error: cannot mutate immutable variable `x`
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn main() {
+    let ref mut x = 2;
+    x = &mut 5;
+  //^^^^^^^^^^ error: cannot mutate immutable variable `x`
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn field_mutate() {
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mut x = (2, 7);
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    f(x.1);
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mut x = (2, 7);
+    x.0 = 5;
+    f(x.1);
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let x = (2, 7);
+    x.0 = 5;
+  //^^^^^^^ 💡 error: cannot mutate immutable variable `x`
+    f(x.1);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn mutable_reference() {
+        check_diagnostics(
+            r#"
+fn main() {
+    let mut x = &mut 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    *x = 5;
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn main() {
+    let x = 2;
+    &mut x;
+  //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn main() {
+    let x_own = 2;
+    let ref mut x_ref = x_own;
+      //^^^^^^^^^^^^^ 💡 error: cannot mutate immutable variable `x_own`
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+struct Foo;
+impl Foo {
+    fn method(&mut self, x: i32) {}
+}
+fn main() {
+    let x = Foo;
+    x.method(2);
+  //^ 💡 error: cannot mutate immutable variable `x`
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn match_bindings() {
+        check_diagnostics(
+            r#"
+fn main() {
+    match (2, 3) {
+        (x, mut y) => {
+          //^^^^^ 💡 weak: variable does not need to be mutable
+            x = 7;
+          //^^^^^ 💡 error: cannot mutate immutable variable `x`
+        }
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn mutation_in_dead_code() {
+        // This one is interesting. Dead code is not represented at all in the MIR, so
+        // there would be no mutablility 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.
+        check_diagnostics(
+            r#"
+fn main() {
+    return;
+    let mut x = 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    &mut x;
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn main() {
+    loop {}
+    let mut x = 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    &mut x;
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+enum X {}
+fn g() -> X {
+    loop {}
+}
+fn f() -> ! {
+    loop {}
+}
+fn main(b: bool) {
+    if b {
+        f();
+    } else {
+        g();
+    }
+    let mut x = 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    &mut x;
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn main(b: bool) {
+    if b {
+        loop {}
+    } else {
+        return;
+    }
+    let mut x = 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    &mut x;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn initialization_is_not_mutation() {
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let mut x;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    x = 5;
+    f(x);
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main(b: bool) {
+    let mut x;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    if b {
+        x = 1;
+    } else {
+        x = 3;
+    }
+    f(x);
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main(b: bool) {
+    let x;
+    if b {
+        x = 1;
+    }
+    x = 3;
+  //^^^^^ 💡 error: cannot mutate immutable variable `x`
+    f(x);
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    let x;
+    loop {
+        x = 1;
+      //^^^^^ 💡 error: cannot mutate immutable variable `x`
+        f(x);
+    }
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    loop {
+        let mut x = 1;
+          //^^^^^ 💡 weak: variable does not need to be mutable
+        f(x);
+        if let mut y = 2 {
+             //^^^^^ 💡 weak: variable does not need to be mutable
+            f(y);
+        }
+        match 3 {
+            mut z => f(z),
+          //^^^^^ 💡 weak: variable does not need to be mutable
+        }
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn function_arguments_are_initialized() {
+        check_diagnostics(
+            r#"
+fn f(mut x: i32) {
+   //^^^^^ 💡 weak: variable does not need to be mutable
+}
+"#,
+        );
+        check_diagnostics(
+            r#"
+fn f(x: i32) {
+   x = 5;
+ //^^^^^ 💡 error: cannot mutate immutable variable `x`
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn for_loop() {
+        check_diagnostics(
+            r#"
+//- minicore: iterators
+fn f(x: [(i32, u8); 10]) {
+    for (a, mut b) in x {
+          //^^^^^ 💡 weak: variable does not need to be mutable
+        a = 2;
+      //^^^^^ 💡 error: cannot mutate immutable variable `a`
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn overloaded_deref() {
+        // FIXME: check for false negative
+        check_diagnostics(
+            r#"
+//- minicore: deref_mut
+use core::ops::{Deref, DerefMut};
+
+struct Foo;
+impl Deref for Foo {
+    type Target = i32;
+    fn deref(&self) -> &i32 {
+        &5
+    }
+}
+impl DerefMut for Foo {
+    fn deref_mut(&mut self) -> &mut i32 {
+        &mut 5
+    }
+}
+fn f() {
+    let x = Foo;
+    let y = &*x;
+    let x = Foo;
+    let mut x = Foo;
+    let y: &mut i32 = &mut x;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn or_pattern() {
+        check_diagnostics(
+            r#"
+//- minicore: option
+fn f(_: i32) {}
+fn main() {
+    let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
+             //^^^^^ 💡 weak: variable does not need to be mutable
+    f(x);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn respect_allow_unused_mut() {
+        // FIXME: respect
+        check_diagnostics(
+            r#"
+fn f(_: i32) {}
+fn main() {
+    #[allow(unused_mut)]
+    let mut x = 2;
+      //^^^^^ 💡 weak: variable does not need to be mutable
+    f(x);
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index c8635ff8011..f6c9b79c30c 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -37,6 +37,7 @@ mod handlers {
     pub(crate) mod missing_fields;
     pub(crate) mod missing_match_arms;
     pub(crate) mod missing_unsafe;
+    pub(crate) mod mutability_errors;
     pub(crate) mod no_such_field;
     pub(crate) mod private_assoc_item;
     pub(crate) mod private_field;
@@ -273,7 +274,8 @@ pub fn diagnostics(
             AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
             AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d),
             AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d),
-
+            AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
+            AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
             AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
                 Some(it) => it,
                 None => continue,
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index c889eb930f3..d88ffd25c40 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -14,7 +14,7 @@ use syntax::{
     SyntaxNode, SyntaxToken, TextRange, T,
 };
 
-use crate::{references, NavigationTarget, TryToNav};
+use crate::{navigation_target::ToNav, references, NavigationTarget, TryToNav};
 
 #[derive(PartialEq, Eq, Hash)]
 pub struct HighlightedRange {
@@ -98,32 +98,39 @@ fn highlight_references(
             category: access,
         });
     let mut res = FxHashSet::default();
-
-    let mut def_to_hl_range = |def| {
-        let hl_range = match def {
-            Definition::Module(module) => {
-                Some(NavigationTarget::from_module_to_decl(sema.db, module))
-            }
-            def => def.try_to_nav(sema.db),
-        }
-        .filter(|decl| decl.file_id == file_id)
-        .and_then(|decl| decl.focus_range)
-        .map(|range| {
-            let category =
-                references::decl_mutability(&def, node, range).then_some(ReferenceCategory::Write);
-            HighlightedRange { range, category }
-        });
-        if let Some(hl_range) = hl_range {
-            res.insert(hl_range);
-        }
-    };
     for &def in &defs {
         match def {
-            Definition::Local(local) => local
-                .associated_locals(sema.db)
-                .iter()
-                .for_each(|&local| def_to_hl_range(Definition::Local(local))),
-            def => def_to_hl_range(def),
+            Definition::Local(local) => {
+                let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
+                local
+                    .sources(sema.db)
+                    .into_iter()
+                    .map(|x| x.to_nav(sema.db))
+                    .filter(|decl| decl.file_id == file_id)
+                    .filter_map(|decl| decl.focus_range)
+                    .map(|range| HighlightedRange { range, category })
+                    .for_each(|x| {
+                        res.insert(x);
+                    });
+            }
+            def => {
+                let hl_range = match def {
+                    Definition::Module(module) => {
+                        Some(NavigationTarget::from_module_to_decl(sema.db, module))
+                    }
+                    def => def.try_to_nav(sema.db),
+                }
+                .filter(|decl| decl.file_id == file_id)
+                .and_then(|decl| decl.focus_range)
+                .map(|range| {
+                    let category = references::decl_mutability(&def, node, range)
+                        .then_some(ReferenceCategory::Write);
+                    HighlightedRange { range, category }
+                });
+                if let Some(hl_range) = hl_range {
+                    res.insert(hl_range);
+                }
+            }
         }
     }
 
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 6a29ddf59e1..da725ce502b 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -635,8 +635,8 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
     let ty = it.ty(db);
     let ty = ty.display_truncated(db, None);
     let is_mut = if it.is_mut(db) { "mut " } else { "" };
-    let desc = match it.source(db).value {
-        Either::Left(ident) => {
+    let desc = match it.primary_source(db).into_ident_pat() {
+        Some(ident) => {
             let name = it.name(db);
             let let_kw = if ident
                 .syntax()
@@ -649,7 +649,7 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
             };
             format!("{let_kw}{is_mut}{name}: {ty}")
         }
-        Either::Right(_) => format!("{is_mut}self: {ty}"),
+        None => format!("{is_mut}self: {ty}"),
     };
     markup(None, desc, None)
 }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index f2b535bdc7e..078b66dd395 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -55,6 +55,7 @@ mod syntax_tree;
 mod typing;
 mod view_crate_graph;
 mod view_hir;
+mod view_mir;
 mod view_item_tree;
 mod shuffle_crate_graph;
 
@@ -308,6 +309,10 @@ impl Analysis {
         self.with_db(|db| view_hir::view_hir(db, position))
     }
 
+    pub fn view_mir(&self, position: FilePosition) -> Cancellable<String> {
+        self.with_db(|db| view_mir::view_mir(db, position))
+    }
+
     pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> {
         self.with_db(|db| view_item_tree::view_item_tree(db, file_id))
     }
diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs
index 11d10d2b854..6aae82f9816 100644
--- a/crates/ide/src/navigation_target.rs
+++ b/crates/ide/src/navigation_target.rs
@@ -5,7 +5,7 @@ use std::fmt;
 use either::Either;
 use hir::{
     symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasSource, HirDisplay,
-    InFile, ModuleSource, Semantics,
+    InFile, LocalSource, ModuleSource, Semantics,
 };
 use ide_db::{
     base_db::{FileId, FileRange},
@@ -387,9 +387,11 @@ impl TryToNav for hir::GenericParam {
     }
 }
 
-impl ToNav for hir::Local {
+impl ToNav for LocalSource {
     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
-        let InFile { file_id, value } = self.source(db);
+        let InFile { file_id, value } = &self.source;
+        let file_id = *file_id;
+        let local = self.local;
         let (node, name) = match &value {
             Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
             Either::Right(it) => (it.syntax(), it.name()),
@@ -398,10 +400,10 @@ impl ToNav for hir::Local {
         let FileRange { file_id, range: full_range } =
             InFile::new(file_id, node).original_file_range(db);
 
-        let name = self.name(db).to_smol_str();
-        let kind = if self.is_self(db) {
+        let name = local.name(db).to_smol_str();
+        let kind = if local.is_self(db) {
             SymbolKind::SelfParam
-        } else if self.is_param(db) {
+        } else if local.is_param(db) {
             SymbolKind::ValueParam
         } else {
             SymbolKind::Local
@@ -419,6 +421,12 @@ impl ToNav for hir::Local {
     }
 }
 
+impl ToNav for hir::Local {
+    fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
+        self.primary_source(db).to_nav(db)
+    }
+}
+
 impl ToNav for hir::Label {
     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
         let InFile { file_id, value } = self.source(db);
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index c0237e1edd0..e10c4638102 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -353,6 +353,11 @@ mod tests {
     fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
         let ra_fixture_after = &trim_indent(ra_fixture_after);
         let (analysis, position) = fixture::position(ra_fixture_before);
+        if !ra_fixture_after.starts_with("error: ") {
+            if let Err(err) = analysis.prepare_rename(position).unwrap() {
+                panic!("Prepare rename to '{new_name}' was failed: {err}")
+            }
+        }
         let rename_result = analysis
             .rename(position, new_name)
             .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
@@ -1710,6 +1715,23 @@ fn foo(bar: i32) -> Foo {
     }
 
     #[test]
+    fn test_rename_local_simple() {
+        check(
+            "i",
+            r#"
+fn foo(bar$0: i32) -> i32 {
+    bar
+}
+"#,
+            r#"
+fn foo(i: i32) -> i32 {
+    i
+}
+"#,
+        );
+    }
+
+    #[test]
     fn test_rename_local_put_init_shorthand() {
         cov_mark::check!(test_rename_local_put_init_shorthand);
         check(
diff --git a/crates/ide/src/view_mir.rs b/crates/ide/src/view_mir.rs
new file mode 100644
index 00000000000..a36aba58bc0
--- /dev/null
+++ b/crates/ide/src/view_mir.rs
@@ -0,0 +1,29 @@
+use hir::{DefWithBody, Semantics};
+use ide_db::base_db::FilePosition;
+use ide_db::RootDatabase;
+use syntax::{algo::find_node_at_offset, ast, AstNode};
+
+// Feature: View Mir
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **rust-analyzer: View Mir**
+// |===
+pub(crate) fn view_mir(db: &RootDatabase, position: FilePosition) -> String {
+    body_mir(db, position).unwrap_or_else(|| "Not inside a function body".to_string())
+}
+
+fn body_mir(db: &RootDatabase, position: FilePosition) -> Option<String> {
+    let sema = Semantics::new(db);
+    let source_file = sema.parse(position.file_id);
+
+    let item = find_node_at_offset::<ast::Item>(source_file.syntax(), position.offset)?;
+    let def: DefWithBody = match item {
+        ast::Item::Fn(it) => sema.to_def(&it)?.into(),
+        ast::Item::Const(it) => sema.to_def(&it)?.into(),
+        ast::Item::Static(it) => sema.to_def(&it)?.into(),
+        _ => return None,
+    };
+    Some(def.debug_mir(db))
+}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 4e08bd0a724..32ac9a42dec 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -134,6 +134,16 @@ pub(crate) fn handle_view_hir(
     Ok(res)
 }
 
+pub(crate) fn handle_view_mir(
+    snap: GlobalStateSnapshot,
+    params: lsp_types::TextDocumentPositionParams,
+) -> Result<String> {
+    let _p = profile::span("handle_view_mir");
+    let position = from_proto::file_position(&snap, params)?;
+    let res = snap.analysis.view_mir(position)?;
+    Ok(res)
+}
+
 pub(crate) fn handle_view_file_text(
     snap: GlobalStateSnapshot,
     params: lsp_types::TextDocumentIdentifier,
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index e33589cc536..c7b513db981 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -74,6 +74,14 @@ impl Request for ViewHir {
     const METHOD: &'static str = "rust-analyzer/viewHir";
 }
 
+pub enum ViewMir {}
+
+impl Request for ViewMir {
+    type Params = lsp_types::TextDocumentPositionParams;
+    type Result = String;
+    const METHOD: &'static str = "rust-analyzer/viewMir";
+}
+
 pub enum ViewFileText {}
 
 impl Request for ViewFileText {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index d1e38b33c7d..d2797690669 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -634,6 +634,7 @@ impl GlobalState {
             .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
             .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
             .on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
+            .on::<lsp_ext::ViewMir>(handlers::handle_view_mir)
             .on::<lsp_ext::ViewFileText>(handlers::handle_view_file_text)
             .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
             .on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs
index bc7b5582318..3e43df2d0d5 100644
--- a/crates/syntax/src/ast/traits.rs
+++ b/crates/syntax/src/ast/traits.rs
@@ -134,3 +134,5 @@ impl Iterator for AttrDocCommentIter {
         })
     }
 }
+
+impl<A: HasName, B: HasName> HasName for Either<A, B> {}
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index b6336e2216f..93ff76a040c 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -762,6 +762,20 @@ pub mod iter {
                     self
                 }
             }
+            pub struct IntoIter<T, const N: usize>([T; N]);
+            impl<T, const N: usize> IntoIterator for [T; N] {
+                type Item = T;
+                type IntoIter = IntoIter<T, N>;
+                fn into_iter(self) -> I {
+                    IntoIter(self)
+                }
+            }
+            impl<T, const N: usize> Iterator for IntoIter<T, N> {
+                type Item = T;
+                fn next(&mut self) -> Option<T> {
+                    loop {}
+                }
+            }
         }
         pub use self::collect::IntoIterator;
     }
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index c3623a5cc46..de142203208 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: d87477896dfe41d4
+lsp_ext.rs hash: 37f31ae648632897
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
@@ -527,6 +527,17 @@ Primarily for debugging, but very useful for all people working on rust-analyzer
 Returns a textual representation of the HIR of the function containing the cursor.
 For debugging or when working on rust-analyzer itself.
 
+## View Mir
+
+**Method:** `rust-analyzer/viewMir`
+
+**Request:** `TextDocumentPositionParams`
+
+**Response:** `string`
+
+Returns a textual representation of the MIR of the function containing the cursor.
+For debugging or when working on rust-analyzer itself.
+
 ## View File Text
 
 **Method:** `rust-analyzer/viewFileText`
diff --git a/editors/code/package.json b/editors/code/package.json
index effe4e279c7..90f7b9074c8 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -115,6 +115,11 @@
                 "category": "rust-analyzer (debug command)"
             },
             {
+                "command": "rust-analyzer.viewMir",
+                "title": "View Mir",
+                "category": "rust-analyzer (debug command)"
+            },
+            {
                 "command": "rust-analyzer.viewFileText",
                 "title": "View File Text (as seen by the server)",
                 "category": "rust-analyzer (debug command)"
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 49a8ca4edba..70b91fe7dc8 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -405,12 +405,11 @@ export function syntaxTree(ctx: CtxInit): Cmd {
     };
 }
 
-// Opens the virtual file that will show the HIR of the function containing the cursor position
-//
-// The contents of the file come from the `TextDocumentContentProvider`
-export function viewHir(ctx: CtxInit): Cmd {
+function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
+    const viewXir = xir === "hir" ? "viewHir" : "viewMir";
+    const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
     const tdcp = new (class implements vscode.TextDocumentContentProvider {
-        readonly uri = vscode.Uri.parse("rust-analyzer-hir://viewHir/hir.rs");
+        readonly uri = vscode.Uri.parse(`rust-analyzer-${xir}://${viewXir}/${xir}.rs`);
         readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
         constructor() {
             vscode.workspace.onDidChangeTextDocument(
@@ -452,7 +451,7 @@ export function viewHir(ctx: CtxInit): Cmd {
                 ),
                 position: client.code2ProtocolConverter.asPosition(rustEditor.selection.active),
             };
-            return client.sendRequest(ra.viewHir, params, ct);
+            return client.sendRequest(requestType, params, ct);
         }
 
         get onDidChange(): vscode.Event<vscode.Uri> {
@@ -461,7 +460,7 @@ export function viewHir(ctx: CtxInit): Cmd {
     })();
 
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-hir", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider(`rust-analyzer-${xir}`, tdcp)
     );
 
     return async () => {
@@ -474,6 +473,20 @@ export function viewHir(ctx: CtxInit): Cmd {
     };
 }
 
+// Opens the virtual file that will show the HIR of the function containing the cursor position
+//
+// The contents of the file come from the `TextDocumentContentProvider`
+export function viewHir(ctx: CtxInit): Cmd {
+    return viewHirOrMir(ctx, "hir");
+}
+
+// Opens the virtual file that will show the MIR of the function containing the cursor position
+//
+// The contents of the file come from the `TextDocumentContentProvider`
+export function viewMir(ctx: CtxInit): Cmd {
+    return viewHirOrMir(ctx, "mir");
+}
+
 export function viewFileText(ctx: CtxInit): Cmd {
     const tdcp = new (class implements vscode.TextDocumentContentProvider {
         readonly uri = vscode.Uri.parse("rust-analyzer-file-text://viewFileText/file.rs");
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index f6f5124dc41..400cd207d41 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -59,6 +59,9 @@ export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string
 export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
     "rust-analyzer/viewHir"
 );
+export const viewMir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
+    "rust-analyzer/viewMir"
+);
 export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>(
     "rust-analyzer/viewItemTree"
 );
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 5987368e6e0..1eb01f30c1e 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -158,6 +158,7 @@ function createCommands(): Record<string, CommandFactory> {
         parentModule: { enabled: commands.parentModule },
         syntaxTree: { enabled: commands.syntaxTree },
         viewHir: { enabled: commands.viewHir },
+        viewMir: { enabled: commands.viewMir },
         viewFileText: { enabled: commands.viewFileText },
         viewItemTree: { enabled: commands.viewItemTree },
         viewCrateGraph: { enabled: commands.viewCrateGraph },
diff --git a/lib/la-arena/src/map.rs b/lib/la-arena/src/map.rs
index b9d491da3c0..7fff2b09c97 100644
--- a/lib/la-arena/src/map.rs
+++ b/lib/la-arena/src/map.rs
@@ -94,6 +94,12 @@ impl<T, V> ArenaMap<Idx<T>, V> {
             .filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_mut()?)))
     }
 
+    /// Returns an iterator over the arena indexes and values in the map.
+    // FIXME: Implement `IntoIterator` trait.
+    pub fn into_iter(self) -> impl Iterator<Item = (Idx<T>, V)> {
+        self.v.into_iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o?)))
+    }
+
     /// Gets the given key's corresponding entry in the map for in-place manipulation.
     pub fn entry(&mut self, idx: Idx<T>) -> Entry<'_, Idx<T>, V> {
         let idx = Self::to_idx(idx);