about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-03-04 19:01:47 +0000
committerbors <bors@rust-lang.org>2023-03-04 19:01:47 +0000
commit94dc7a3eb092b5ea641a4d344fae5f3ad4158853 (patch)
tree8ccb40f4bce95305216083e84c05edf1c49f3c4c
parent5efcfe5ca983afea02b81c3e413d9064b013c514 (diff)
parentb85e2af898546f9c7a7b58b02b43ba0ae0c948c9 (diff)
downloadrust-94dc7a3eb092b5ea641a4d344fae5f3ad4158853.tar.gz
rust-94dc7a3eb092b5ea641a4d344fae5f3ad4158853.zip
Auto merge of #14251 - Veykril:ty-expr-stmt, r=Veykril
internal: Set expectation for no-semi expression statements to unit
-rw-r--r--crates/hir-def/src/body/lower.rs67
-rw-r--r--crates/hir-def/src/body/pretty.rs70
-rw-r--r--crates/hir-def/src/body/scope.rs10
-rw-r--r--crates/hir-def/src/expr.rs47
-rw-r--r--crates/hir-def/src/resolver.rs4
-rw-r--r--crates/hir-ty/src/diagnostics/unsafe_check.rs6
-rw-r--r--crates/hir-ty/src/infer/expr.rs295
-rw-r--r--crates/hir-ty/src/infer/path.rs25
-rw-r--r--crates/hir-ty/src/mir/lower.rs110
-rw-r--r--crates/hir-ty/src/tests.rs5
-rw-r--r--crates/hir-ty/src/tests/diagnostics.rs21
-rw-r--r--crates/hir-ty/src/tests/method_resolution.rs1
-rw-r--r--crates/hir-ty/src/tests/patterns.rs2
-rw-r--r--crates/hir-ty/src/tests/regression.rs8
-rw-r--r--crates/hir-ty/src/tests/simple.rs9
-rw-r--r--crates/ide-assists/src/handlers/extract_variable.rs2
16 files changed, 394 insertions, 288 deletions
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 04b1c4f01e2..3164a5f4c29 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -37,7 +37,7 @@ use crate::{
     item_scope::BuiltinShadowMode,
     path::{GenericArgs, Path},
     type_ref::{Mutability, Rawness, TypeRef},
-    AdtId, BlockLoc, ModuleDefId, UnresolvedMacro,
+    AdtId, BlockId, BlockLoc, ModuleDefId, UnresolvedMacro,
 };
 
 pub struct LowerCtx<'a> {
@@ -238,33 +238,32 @@ impl ExprCollector<'_> {
             }
             ast::Expr::BlockExpr(e) => match e.modifier() {
                 Some(ast::BlockModifier::Try(_)) => {
-                    let body = self.collect_block(e);
-                    self.alloc_expr(Expr::TryBlock { body }, syntax_ptr)
+                    self.collect_block_(e, |id, statements, tail| Expr::TryBlock {
+                        id,
+                        statements,
+                        tail,
+                    })
                 }
                 Some(ast::BlockModifier::Unsafe(_)) => {
-                    let body = self.collect_block(e);
-                    self.alloc_expr(Expr::Unsafe { body }, syntax_ptr)
+                    self.collect_block_(e, |id, statements, tail| Expr::Unsafe {
+                        id,
+                        statements,
+                        tail,
+                    })
                 }
-                // FIXME: we need to record these effects somewhere...
                 Some(ast::BlockModifier::Label(label)) => {
                     let label = self.collect_label(label);
-                    let res = self.collect_block(e);
-                    match &mut self.body.exprs[res] {
-                        Expr::Block { label: block_label, .. } => {
-                            *block_label = Some(label);
-                        }
-                        _ => unreachable!(),
-                    }
-                    res
-                }
-                Some(ast::BlockModifier::Async(_)) => {
-                    let body = self.collect_block(e);
-                    self.alloc_expr(Expr::Async { body }, syntax_ptr)
-                }
-                Some(ast::BlockModifier::Const(_)) => {
-                    let body = self.collect_block(e);
-                    self.alloc_expr(Expr::Const { body }, syntax_ptr)
+                    self.collect_block_(e, |id, statements, tail| Expr::Block {
+                        id,
+                        statements,
+                        tail,
+                        label: Some(label),
+                    })
                 }
+                Some(ast::BlockModifier::Async(_)) => self
+                    .collect_block_(e, |id, statements, tail| Expr::Async { id, statements, tail }),
+                Some(ast::BlockModifier::Const(_)) => self
+                    .collect_block_(e, |id, statements, tail| Expr::Const { id, statements, tail }),
                 None => self.collect_block(e),
             },
             ast::Expr::LoopExpr(e) => {
@@ -737,6 +736,19 @@ impl ExprCollector<'_> {
     }
 
     fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
+        self.collect_block_(block, |id, statements, tail| Expr::Block {
+            id,
+            statements,
+            tail,
+            label: None,
+        })
+    }
+
+    fn collect_block_(
+        &mut self,
+        block: ast::BlockExpr,
+        mk_block: impl FnOnce(BlockId, Box<[Statement]>, Option<ExprId>) -> Expr,
+    ) -> ExprId {
         let file_local_id = self.ast_id_map.ast_id(&block);
         let ast_id = AstId::new(self.expander.current_file_id, file_local_id);
         let block_loc =
@@ -769,15 +781,8 @@ impl ExprCollector<'_> {
         });
 
         let syntax_node_ptr = AstPtr::new(&block.into());
-        let expr_id = self.alloc_expr(
-            Expr::Block {
-                id: block_id,
-                statements: statements.into_boxed_slice(),
-                tail,
-                label: None,
-            },
-            syntax_node_ptr,
-        );
+        let expr_id = self
+            .alloc_expr(mk_block(block_id, statements.into_boxed_slice(), tail), syntax_node_ptr);
 
         self.expander.def_map = prev_def_map;
         self.expander.module = prev_local_module;
diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs
index 4b4664a1cf4..622756ee8a9 100644
--- a/crates/hir-def/src/body/pretty.rs
+++ b/crates/hir-def/src/body/pretty.rs
@@ -292,18 +292,6 @@ impl<'a> Printer<'a> {
                 self.print_expr(*expr);
                 w!(self, "?");
             }
-            Expr::TryBlock { body } => {
-                w!(self, "try ");
-                self.print_expr(*body);
-            }
-            Expr::Async { body } => {
-                w!(self, "async ");
-                self.print_expr(*body);
-            }
-            Expr::Const { body } => {
-                w!(self, "const ");
-                self.print_expr(*body);
-            }
             Expr::Cast { expr, type_ref } => {
                 self.print_expr(*expr);
                 w!(self, " as ");
@@ -402,10 +390,6 @@ impl<'a> Printer<'a> {
                 }
                 w!(self, ")");
             }
-            Expr::Unsafe { body } => {
-                w!(self, "unsafe ");
-                self.print_expr(*body);
-            }
             Expr::Array(arr) => {
                 w!(self, "[");
                 if !matches!(arr, Array::ElementList { elements, .. } if elements.is_empty()) {
@@ -428,25 +412,47 @@ impl<'a> Printer<'a> {
             }
             Expr::Literal(lit) => self.print_literal(lit),
             Expr::Block { id: _, statements, tail, label } => {
-                self.whitespace();
-                if let Some(lbl) = label {
-                    w!(self, "{}: ", self.body[*lbl].name);
+                let label = label.map(|lbl| format!("{}: ", self.body[lbl].name));
+                self.print_block(label.as_deref(), statements, tail);
+            }
+            Expr::Unsafe { id: _, statements, tail } => {
+                self.print_block(Some("unsafe "), statements, tail);
+            }
+            Expr::TryBlock { id: _, statements, tail } => {
+                self.print_block(Some("try "), statements, tail);
+            }
+            Expr::Async { id: _, statements, tail } => {
+                self.print_block(Some("async "), statements, tail);
+            }
+            Expr::Const { id: _, statements, tail } => {
+                self.print_block(Some("const "), statements, tail);
+            }
+        }
+    }
+
+    fn print_block(
+        &mut self,
+        label: Option<&str>,
+        statements: &Box<[Statement]>,
+        tail: &Option<la_arena::Idx<Expr>>,
+    ) {
+        self.whitespace();
+        if let Some(lbl) = label {
+            w!(self, "{}", lbl);
+        }
+        w!(self, "{{");
+        if !statements.is_empty() || tail.is_some() {
+            self.indented(|p| {
+                for stmt in &**statements {
+                    p.print_stmt(stmt);
                 }
-                w!(self, "{{");
-                if !statements.is_empty() || tail.is_some() {
-                    self.indented(|p| {
-                        for stmt in &**statements {
-                            p.print_stmt(stmt);
-                        }
-                        if let Some(tail) = tail {
-                            p.print_expr(*tail);
-                        }
-                        p.newline();
-                    });
+                if let Some(tail) = tail {
+                    p.print_expr(*tail);
                 }
-                w!(self, "}}");
-            }
+                p.newline();
+            });
         }
+        w!(self, "}}");
     }
 
     fn print_pat(&mut self, pat: PatId) {
diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs
index 2617d4288a3..e7078b7953b 100644
--- a/crates/hir-def/src/body/scope.rs
+++ b/crates/hir-def/src/body/scope.rs
@@ -194,6 +194,16 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
             scopes.set_scope(expr, scope);
             compute_block_scopes(statements, *tail, body, scopes, &mut scope);
         }
+        Expr::Unsafe { id, statements, tail }
+        | Expr::Async { id, statements, tail }
+        | Expr::Const { id, statements, tail }
+        | Expr::TryBlock { id, statements, tail } => {
+            let mut scope = scopes.new_block_scope(*scope, *id, None);
+            // Overwrite the old scope for the block expr, so that every block scope can be found
+            // via the block itself (important for blocks that only contain items, no expressions).
+            scopes.set_scope(expr, scope);
+            compute_block_scopes(statements, *tail, body, scopes, &mut 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));
diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs
index 8d6f0be2648..78a2f861233 100644
--- a/crates/hir-def/src/expr.rs
+++ b/crates/hir-def/src/expr.rs
@@ -109,6 +109,26 @@ pub enum Expr {
         tail: Option<ExprId>,
         label: Option<LabelId>,
     },
+    TryBlock {
+        id: BlockId,
+        statements: Box<[Statement]>,
+        tail: Option<ExprId>,
+    },
+    Async {
+        id: BlockId,
+        statements: Box<[Statement]>,
+        tail: Option<ExprId>,
+    },
+    Const {
+        id: BlockId,
+        statements: Box<[Statement]>,
+        tail: Option<ExprId>,
+    },
+    Unsafe {
+        id: BlockId,
+        statements: Box<[Statement]>,
+        tail: Option<ExprId>,
+    },
     Loop {
         body: ExprId,
         label: Option<LabelId>,
@@ -172,15 +192,6 @@ pub enum Expr {
     Try {
         expr: ExprId,
     },
-    TryBlock {
-        body: ExprId,
-    },
-    Async {
-        body: ExprId,
-    },
-    Const {
-        body: ExprId,
-    },
     Cast {
         expr: ExprId,
         type_ref: Interned<TypeRef>,
@@ -222,9 +233,6 @@ pub enum Expr {
         exprs: Box<[ExprId]>,
         is_assignee_expr: bool,
     },
-    Unsafe {
-        body: ExprId,
-    },
     Array(Array),
     Literal(Literal),
     Underscore,
@@ -290,13 +298,20 @@ impl Expr {
             Expr::Let { expr, .. } => {
                 f(*expr);
             }
-            Expr::Block { statements, tail, .. } => {
+            Expr::Block { statements, tail, .. }
+            | Expr::TryBlock { statements, tail, .. }
+            | Expr::Unsafe { statements, tail, .. }
+            | Expr::Async { statements, tail, .. }
+            | Expr::Const { statements, tail, .. } => {
                 for stmt in statements.iter() {
                     match stmt {
-                        Statement::Let { initializer, .. } => {
+                        Statement::Let { initializer, else_branch, .. } => {
                             if let &Some(expr) = initializer {
                                 f(expr);
                             }
+                            if let &Some(expr) = else_branch {
+                                f(expr);
+                            }
                         }
                         Statement::Expr { expr: expression, .. } => f(*expression),
                     }
@@ -305,10 +320,6 @@ impl Expr {
                     f(expr);
                 }
             }
-            Expr::TryBlock { body }
-            | Expr::Unsafe { body }
-            | Expr::Async { body }
-            | Expr::Const { body } => f(*body),
             Expr::Loop { body, .. } => f(*body),
             Expr::While { condition, body, .. } => {
                 f(*condition);
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 0b9c136c7eb..664db292a7f 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -294,8 +294,8 @@ impl Resolver {
             }
         }
 
-        if let res @ Some(_) = self.module_scope.resolve_path_in_value_ns(db, path) {
-            return res;
+        if let Some(res) = self.module_scope.resolve_path_in_value_ns(db, path) {
+            return Some(res);
         }
 
         // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back
diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs
index 431ab949b46..d25c0ccf00d 100644
--- a/crates/hir-ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs
@@ -94,8 +94,10 @@ fn walk_unsafe(
                 unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
             }
         }
-        Expr::Unsafe { body: child } => {
-            return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb);
+        Expr::Unsafe { .. } => {
+            return expr.walk_child_exprs(|child| {
+                walk_unsafe(db, infer, def, body, child, true, unsafe_expr_cb);
+            });
         }
         _ => {}
     }
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 02024e1ea78..8895dc095f9 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -84,6 +84,30 @@ impl<'a> InferenceContext<'a> {
         }
     }
 
+    pub(super) fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty {
+        let ty = self.infer_expr_inner(expr, expected);
+        // While we don't allow *arbitrary* coercions here, we *do* allow
+        // coercions from ! to `expected`.
+        if ty.is_never() {
+            if let Some(adjustments) = self.result.expr_adjustments.get(&expr) {
+                return if let [Adjustment { kind: Adjust::NeverToAny, target }] = &**adjustments {
+                    target.clone()
+                } else {
+                    self.err_ty()
+                };
+            }
+
+            let adj_ty = self.table.new_type_var();
+            self.write_expr_adj(
+                expr,
+                vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty.clone() }],
+            );
+            adj_ty
+        } else {
+            ty
+        }
+    }
+
     fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
         self.db.unwind_if_cancelled();
 
@@ -91,7 +115,7 @@ impl<'a> InferenceContext<'a> {
             Expr::Missing => self.err_ty(),
             &Expr::If { condition, then_branch, else_branch } => {
                 let expected = &expected.adjust_for_branches(&mut self.table);
-                self.infer_expr(
+                self.infer_expr_coerce_never(
                     condition,
                     &Expectation::HasType(self.result.standard_types.bool_.clone()),
                 );
@@ -124,41 +148,18 @@ impl<'a> InferenceContext<'a> {
                 self.result.standard_types.bool_.clone()
             }
             Expr::Block { statements, tail, label, id: _ } => {
-                let old_resolver = mem::replace(
-                    &mut self.resolver,
-                    resolver_for_expr(self.db.upcast(), self.owner, tgt_expr),
-                );
-                let ty = match label {
-                    Some(_) => {
-                        let break_ty = self.table.new_type_var();
-                        let (breaks, ty) = self.with_breakable_ctx(
-                            BreakableKind::Block,
-                            Some(break_ty.clone()),
-                            *label,
-                            |this| {
-                                this.infer_block(
-                                    tgt_expr,
-                                    statements,
-                                    *tail,
-                                    &Expectation::has_type(break_ty),
-                                )
-                            },
-                        );
-                        breaks.unwrap_or(ty)
-                    }
-                    None => self.infer_block(tgt_expr, statements, *tail, expected),
-                };
-                self.resolver = old_resolver;
-                ty
+                self.infer_block(tgt_expr, statements, *tail, *label, expected)
+            }
+            Expr::Unsafe { id: _, statements, tail } => {
+                self.infer_block(tgt_expr, statements, *tail, None, expected)
             }
-            Expr::Unsafe { body } => self.infer_expr(*body, expected),
-            Expr::Const { body } => {
+            Expr::Const { id: _, statements, tail } => {
                 self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
-                    this.infer_expr(*body, expected)
+                    this.infer_block(tgt_expr, statements, *tail, None, expected)
                 })
                 .1
             }
-            Expr::TryBlock { body } => {
+            Expr::TryBlock { id: _, statements, tail } => {
                 // The type that is returned from the try block
                 let try_ty = self.table.new_type_var();
                 if let Some(ty) = expected.only_has_type(&mut self.table) {
@@ -169,13 +170,16 @@ impl<'a> InferenceContext<'a> {
                 let ok_ty =
                     self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output());
 
-                self.with_breakable_ctx(BreakableKind::Block, Some(ok_ty.clone()), None, |this| {
-                    this.infer_expr(*body, &Expectation::has_type(ok_ty));
-                });
-
+                self.infer_block(
+                    tgt_expr,
+                    statements,
+                    *tail,
+                    None,
+                    &Expectation::has_type(ok_ty.clone()),
+                );
                 try_ty
             }
-            Expr::Async { body } => {
+            Expr::Async { id: _, statements, tail } => {
                 let ret_ty = self.table.new_type_var();
                 let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
                 let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
@@ -184,7 +188,13 @@ impl<'a> InferenceContext<'a> {
 
                 let (_, inner_ty) =
                     self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
-                        this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty))
+                        this.infer_block(
+                            tgt_expr,
+                            statements,
+                            *tail,
+                            None,
+                            &Expectation::has_type(ret_ty),
+                        )
                     });
 
                 self.diverges = prev_diverges;
@@ -193,7 +203,8 @@ impl<'a> InferenceContext<'a> {
 
                 // Use the first type parameter as the output type of future.
                 // existential type AsyncBlockImplTrait<InnerType>: Future<Output = InnerType>
-                let impl_trait_id = crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body);
+                let impl_trait_id =
+                    crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, tgt_expr);
                 let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into();
                 TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty))
                     .intern(Interner)
@@ -403,37 +414,47 @@ impl<'a> InferenceContext<'a> {
             Expr::Match { expr, arms } => {
                 let input_ty = self.infer_expr(*expr, &Expectation::none());
 
-                let expected = expected.adjust_for_branches(&mut self.table);
-
-                let result_ty = if arms.is_empty() {
+                if arms.is_empty() {
+                    self.diverges = Diverges::Always;
                     self.result.standard_types.never.clone()
                 } else {
-                    expected.coercion_target_type(&mut self.table)
-                };
-                let mut coerce = CoerceMany::new(result_ty);
-
-                let matchee_diverges = self.diverges;
-                let mut all_arms_diverge = Diverges::Always;
-
-                for arm in arms.iter() {
-                    self.diverges = Diverges::Maybe;
-                    let input_ty = self.resolve_ty_shallow(&input_ty);
-                    self.infer_top_pat(arm.pat, &input_ty);
-                    if let Some(guard_expr) = arm.guard {
-                        self.infer_expr(
-                            guard_expr,
-                            &Expectation::HasType(self.result.standard_types.bool_.clone()),
-                        );
+                    let matchee_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
+                    let mut all_arms_diverge = Diverges::Always;
+                    for arm in arms.iter() {
+                        let input_ty = self.resolve_ty_shallow(&input_ty);
+                        self.infer_top_pat(arm.pat, &input_ty);
                     }
 
-                    let arm_ty = self.infer_expr_inner(arm.expr, &expected);
-                    all_arms_diverge &= self.diverges;
-                    coerce.coerce(self, Some(arm.expr), &arm_ty);
-                }
+                    let expected = expected.adjust_for_branches(&mut self.table);
+                    let result_ty = match &expected {
+                        // We don't coerce to `()` so that if the match expression is a
+                        // statement it's branches can have any consistent type.
+                        Expectation::HasType(ty) if *ty != self.result.standard_types.unit => {
+                            ty.clone()
+                        }
+                        _ => self.table.new_type_var(),
+                    };
+                    let mut coerce = CoerceMany::new(result_ty);
+
+                    for arm in arms.iter() {
+                        if let Some(guard_expr) = arm.guard {
+                            self.diverges = Diverges::Maybe;
+                            self.infer_expr_coerce_never(
+                                guard_expr,
+                                &Expectation::HasType(self.result.standard_types.bool_.clone()),
+                            );
+                        }
+                        self.diverges = Diverges::Maybe;
 
-                self.diverges = matchee_diverges | all_arms_diverge;
+                        let arm_ty = self.infer_expr_inner(arm.expr, &expected);
+                        all_arms_diverge &= self.diverges;
+                        coerce.coerce(self, Some(arm.expr), &arm_ty);
+                    }
 
-                coerce.complete(self)
+                    self.diverges = matchee_diverges | all_arms_diverge;
+
+                    coerce.complete(self)
+                }
             }
             Expr::Path(p) => {
                 // FIXME this could be more efficient...
@@ -1143,73 +1164,101 @@ impl<'a> InferenceContext<'a> {
         expr: ExprId,
         statements: &[Statement],
         tail: Option<ExprId>,
+        label: Option<LabelId>,
         expected: &Expectation,
     ) -> Ty {
-        for stmt in statements {
-            match stmt {
-                Statement::Let { pat, type_ref, initializer, else_branch } => {
-                    let decl_ty = type_ref
-                        .as_ref()
-                        .map(|tr| self.make_ty(tr))
-                        .unwrap_or_else(|| self.table.new_type_var());
-
-                    let ty = if let Some(expr) = initializer {
-                        let ty = if contains_explicit_ref_binding(&self.body, *pat) {
-                            self.infer_expr(*expr, &Expectation::has_type(decl_ty.clone()))
-                        } else {
-                            self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone()))
-                        };
-                        if type_ref.is_some() {
-                            decl_ty
-                        } else {
-                            ty
+        let coerce_ty = expected.coercion_target_type(&mut self.table);
+        let old_resolver =
+            mem::replace(&mut self.resolver, resolver_for_expr(self.db.upcast(), self.owner, expr));
+        let (break_ty, ty) =
+            self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty.clone()), label, |this| {
+                for stmt in statements {
+                    match stmt {
+                        Statement::Let { pat, type_ref, initializer, else_branch } => {
+                            let decl_ty = type_ref
+                                .as_ref()
+                                .map(|tr| this.make_ty(tr))
+                                .unwrap_or_else(|| this.table.new_type_var());
+
+                            let ty = if let Some(expr) = initializer {
+                                let ty = if contains_explicit_ref_binding(&this.body, *pat) {
+                                    this.infer_expr(*expr, &Expectation::has_type(decl_ty.clone()))
+                                } else {
+                                    this.infer_expr_coerce(
+                                        *expr,
+                                        &Expectation::has_type(decl_ty.clone()),
+                                    )
+                                };
+                                if type_ref.is_some() {
+                                    decl_ty
+                                } else {
+                                    ty
+                                }
+                            } else {
+                                decl_ty
+                            };
+
+                            this.infer_top_pat(*pat, &ty);
+
+                            if let Some(expr) = else_branch {
+                                let previous_diverges =
+                                    mem::replace(&mut this.diverges, Diverges::Maybe);
+                                this.infer_expr_coerce(
+                                    *expr,
+                                    &Expectation::HasType(this.result.standard_types.never.clone()),
+                                );
+                                this.diverges = previous_diverges;
+                            }
+                        }
+                        &Statement::Expr { expr, has_semi } => {
+                            if has_semi {
+                                this.infer_expr(expr, &Expectation::none());
+                            } else {
+                                this.infer_expr_coerce(
+                                    expr,
+                                    &Expectation::HasType(this.result.standard_types.unit.clone()),
+                                );
+                            }
                         }
-                    } else {
-                        decl_ty
-                    };
-
-                    self.infer_top_pat(*pat, &ty);
-
-                    if let Some(expr) = else_branch {
-                        let previous_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
-                        self.infer_expr_coerce(
-                            *expr,
-                            &Expectation::HasType(self.result.standard_types.never.clone()),
-                        );
-                        self.diverges = previous_diverges;
                     }
                 }
-                Statement::Expr { expr, .. } => {
-                    self.infer_expr(*expr, &Expectation::none());
-                }
-            }
-        }
 
-        if let Some(expr) = tail {
-            self.infer_expr_coerce(expr, expected)
-        } else {
-            // Citing rustc: if there is no explicit tail expression,
-            // that is typically equivalent to a tail expression
-            // of `()` -- except if the block diverges. In that
-            // case, there is no value supplied from the tail
-            // expression (assuming there are no other breaks,
-            // this implies that the type of the block will be
-            // `!`).
-            if self.diverges.is_always() {
-                // we don't even make an attempt at coercion
-                self.table.new_maybe_never_var()
-            } else if let Some(t) = expected.only_has_type(&mut self.table) {
-                if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() {
-                    self.result.type_mismatches.insert(
-                        expr.into(),
-                        TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() },
-                    );
+                // FIXME: This should make use of the breakable CoerceMany
+                if let Some(expr) = tail {
+                    this.infer_expr_coerce(expr, expected)
+                } else {
+                    // Citing rustc: if there is no explicit tail expression,
+                    // that is typically equivalent to a tail expression
+                    // of `()` -- except if the block diverges. In that
+                    // case, there is no value supplied from the tail
+                    // expression (assuming there are no other breaks,
+                    // this implies that the type of the block will be
+                    // `!`).
+                    if this.diverges.is_always() {
+                        // we don't even make an attempt at coercion
+                        this.table.new_maybe_never_var()
+                    } else if let Some(t) = expected.only_has_type(&mut this.table) {
+                        if this
+                            .coerce(Some(expr), &this.result.standard_types.unit.clone(), &t)
+                            .is_err()
+                        {
+                            this.result.type_mismatches.insert(
+                                expr.into(),
+                                TypeMismatch {
+                                    expected: t.clone(),
+                                    actual: this.result.standard_types.unit.clone(),
+                                },
+                            );
+                        }
+                        t
+                    } else {
+                        this.result.standard_types.unit.clone()
+                    }
                 }
-                t
-            } else {
-                TyBuilder::unit()
-            }
-        }
+            });
+        self.resolver = old_resolver;
+
+        break_ty.unwrap_or(ty)
     }
 
     fn lookup_field(
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index 0a8527afbd0..b3867623f37 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -40,20 +40,14 @@ impl<'a> InferenceContext<'a> {
         id: ExprOrPatId,
     ) -> Option<Ty> {
         let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
-            if path.segments().is_empty() {
-                // This can't actually happen syntax-wise
-                return None;
-            }
+            let Some(last) = path.segments().last() else { return None };
             let ty = self.make_ty(type_ref);
             let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
             let ctx = crate::lower::TyLoweringContext::new(self.db, resolver);
             let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty);
-            self.resolve_ty_assoc_item(
-                ty,
-                path.segments().last().expect("path had at least one segment").name,
-                id,
-            )?
+            self.resolve_ty_assoc_item(ty, last.name, id)?
         } else {
+            // FIXME: report error, unresolved first path segment
             let value_or_partial =
                 resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?;
 
@@ -66,10 +60,13 @@ impl<'a> InferenceContext<'a> {
         };
 
         let typable: ValueTyDefId = match value {
-            ValueNs::LocalBinding(pat) => {
-                let ty = self.result.type_of_pat.get(pat)?.clone();
-                return Some(ty);
-            }
+            ValueNs::LocalBinding(pat) => match self.result.type_of_pat.get(pat) {
+                Some(ty) => return Some(ty.clone()),
+                None => {
+                    never!("uninferred pattern?");
+                    return None;
+                }
+            },
             ValueNs::FunctionId(it) => it.into(),
             ValueNs::ConstId(it) => it.into(),
             ValueNs::StaticId(it) => it.into(),
@@ -91,7 +88,7 @@ impl<'a> InferenceContext<'a> {
                     let ty = self.db.value_ty(struct_id.into()).substitute(Interner, &substs);
                     return Some(ty);
                 } else {
-                    // FIXME: diagnostic, invalid Self reference
+                    // FIXME: report error, invalid Self reference
                     return None;
                 }
             }
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 1fa21e230c4..936b56a0217 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -331,56 +331,11 @@ impl MirLowerCtx<'_> {
                 }
                 Ok(result)
             }
+            Expr::Unsafe { id: _, statements, tail } => {
+                self.lower_block_to_place(None, statements, current, *tail, place)
+            }
             Expr::Block { id: _, statements, tail, label } => {
-                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)?;
-                        }
-                    }
-                }
-                match tail {
-                    Some(tail) => self.lower_expr_to_place(*tail, place, current),
-                    None => Ok(current),
-                }
+                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)?;
@@ -686,7 +641,6 @@ impl MirLowerCtx<'_> {
                 self.push_assignment(current, place, r);
                 Ok(current)
             }
-            Expr::Unsafe { body } => self.lower_expr_to_place(*body, place, current),
             Expr::Array(l) => match l {
                 Array::ElementList { elements, .. } => {
                     let elem_ty = match &self.expr_ty(expr_id).data(Interner).kind {
@@ -723,6 +677,62 @@ impl MirLowerCtx<'_> {
         }
     }
 
+    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)?;
+                }
+            }
+        }
+        match tail {
+            Some(tail) => self.lower_expr_to_place(tail, place, current),
+            None => Ok(current),
+        }
+    }
+
     fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> {
         let size = layout_of_ty(self.db, &ty, self.owner.module(self.db.upcast()).krate())?
             .size
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index ab848a18eb3..759878b10bb 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -61,22 +61,27 @@ fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
     Some(tracing::subscriber::set_default(subscriber))
 }
 
+#[track_caller]
 fn check_types(ra_fixture: &str) {
     check_impl(ra_fixture, false, true, false)
 }
 
+#[track_caller]
 fn check_types_source_code(ra_fixture: &str) {
     check_impl(ra_fixture, false, true, true)
 }
 
+#[track_caller]
 fn check_no_mismatches(ra_fixture: &str) {
     check_impl(ra_fixture, true, false, false)
 }
 
+#[track_caller]
 fn check(ra_fixture: &str) {
     check_impl(ra_fixture, false, false, false)
 }
 
+#[track_caller]
 fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) {
     let _tracing = setup_tracing();
     let (db, files) = TestDB::with_many_files(ra_fixture);
diff --git a/crates/hir-ty/src/tests/diagnostics.rs b/crates/hir-ty/src/tests/diagnostics.rs
index f00fa972948..1876be303ad 100644
--- a/crates/hir-ty/src/tests/diagnostics.rs
+++ b/crates/hir-ty/src/tests/diagnostics.rs
@@ -73,3 +73,24 @@ fn test(x: bool) -> &'static str {
 "#,
     );
 }
+
+#[test]
+fn non_unit_block_expr_stmt_no_semi() {
+    check(
+        r#"
+fn test(x: bool) {
+    if x {
+        "notok"
+      //^^^^^^^ expected (), got &str
+    } else {
+        "ok"
+      //^^^^ expected (), got &str
+    }
+    match x { true => true, false => 0 }
+  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), got bool
+                                   //^ expected bool, got i32
+    ()
+}
+"#,
+    );
+}
diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs
index 41c53701df6..4b671449e15 100644
--- a/crates/hir-ty/src/tests/method_resolution.rs
+++ b/crates/hir-ty/src/tests/method_resolution.rs
@@ -1167,7 +1167,6 @@ fn test() {
             123..167 '{     ...o(); }': ()
             133..134 's': &S
             137..151 'unsafe { f() }': &S
-            137..151 'unsafe { f() }': &S
             146..147 'f': fn f() -> &S
             146..149 'f()': &S
             157..158 's': &S
diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs
index be67329fee4..74bcab6caa9 100644
--- a/crates/hir-ty/src/tests/patterns.rs
+++ b/crates/hir-ty/src/tests/patterns.rs
@@ -476,7 +476,7 @@ fn infer_adt_pattern() {
             183..184 'x': usize
             190..191 'x': usize
             201..205 'E::B': E
-            209..212 'foo': bool
+            209..212 'foo': {unknown}
             216..217 '1': usize
             227..231 'E::B': E
             235..237 '10': usize
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index de6ae7fff8f..2fa6234da1e 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -270,7 +270,7 @@ fn infer_std_crash_5() {
             61..320 '{     ...     }': ()
             75..79 'name': &{unknown}
             82..166 'if doe...     }': &{unknown}
-            85..98 'doesnt_matter': bool
+            85..98 'doesnt_matter': {unknown}
             99..128 '{     ...     }': &{unknown}
             113..118 'first': &{unknown}
             134..166 '{     ...     }': &{unknown}
@@ -279,7 +279,7 @@ fn infer_std_crash_5() {
             181..188 'content': &{unknown}
             191..313 'if ICE...     }': &{unknown}
             194..231 'ICE_RE..._VALUE': {unknown}
-            194..247 'ICE_RE...&name)': bool
+            194..247 'ICE_RE...&name)': {unknown}
             241..246 '&name': &&{unknown}
             242..246 'name': &{unknown}
             248..276 '{     ...     }': &{unknown}
@@ -1015,9 +1015,9 @@ fn cfg_tail() {
             20..31 '{ "first" }': ()
             22..29 '"first"': &str
             72..190 '{     ...] 13 }': ()
-            78..88 '{ "fake" }': &str
+            78..88 '{ "fake" }': ()
             80..86 '"fake"': &str
-            93..103 '{ "fake" }': &str
+            93..103 '{ "fake" }': ()
             95..101 '"fake"': &str
             108..120 '{ "second" }': ()
             110..118 '"second"': &str
diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs
index 2e5787b701c..1a07a2c51d8 100644
--- a/crates/hir-ty/src/tests/simple.rs
+++ b/crates/hir-ty/src/tests/simple.rs
@@ -352,7 +352,6 @@ unsafe fn baz(u: MyUnion) {
             71..89 'MyUnio...o: 0 }': MyUnion
             86..87 '0': u32
             95..113 'unsafe...(u); }': ()
-            95..113 'unsafe...(u); }': ()
             104..107 'baz': fn baz(MyUnion)
             104..110 'baz(u)': ()
             108..109 'u': MyUnion
@@ -360,7 +359,6 @@ unsafe fn baz(u: MyUnion) {
             126..146 'MyUnio... 0.0 }': MyUnion
             141..144 '0.0': f32
             152..170 'unsafe...(u); }': ()
-            152..170 'unsafe...(u); }': ()
             161..164 'baz': fn baz(MyUnion)
             161..167 'baz(u)': ()
             165..166 'u': MyUnion
@@ -2077,22 +2075,17 @@ async fn main() {
             16..193 '{     ...2 }; }': ()
             26..27 'x': i32
             30..43 'unsafe { 92 }': i32
-            30..43 'unsafe { 92 }': i32
             39..41 '92': i32
             53..54 'y': impl Future<Output = ()>
-            57..85 'async ...wait }': ()
             57..85 'async ...wait }': impl Future<Output = ()>
-            65..77 'async { () }': ()
             65..77 'async { () }': impl Future<Output = ()>
             65..83 'async ....await': ()
             73..75 '()': ()
             95..96 'z': ControlFlow<(), ()>
-            130..140 'try { () }': ()
             130..140 'try { () }': ControlFlow<(), ()>
             136..138 '()': ()
             150..151 'w': i32
             154..166 'const { 92 }': i32
-            154..166 'const { 92 }': i32
             162..164 '92': i32
             176..177 't': i32
             180..190 ''a: { 92 }': i32
@@ -2122,7 +2115,6 @@ fn main() {
             83..84 'f': F
             89..91 '{}': ()
             103..231 '{     ... }); }': ()
-            109..161 'async ...     }': Result<(), ()>
             109..161 'async ...     }': impl Future<Output = Result<(), ()>>
             125..139 'return Err(())': !
             132..135 'Err': Err<(), ()>(()) -> Result<(), ()>
@@ -2134,7 +2126,6 @@ fn main() {
             167..171 'test': fn test<(), (), || -> impl Future<Output = Result<(), ()>>, impl Future<Output = Result<(), ()>>>(|| -> impl Future<Output = Result<(), ()>>)
             167..228 'test(|...    })': ()
             172..227 '|| asy...     }': || -> impl Future<Output = Result<(), ()>>
-            175..227 'async ...     }': Result<(), ()>
             175..227 'async ...     }': impl Future<Output = Result<(), ()>>
             191..205 'return Err(())': !
             198..201 'Err': Err<(), ()>(()) -> Result<(), ()>
diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs
index a738deffb95..16356141288 100644
--- a/crates/ide-assists/src/handlers/extract_variable.rs
+++ b/crates/ide-assists/src/handlers/extract_variable.rs
@@ -287,7 +287,7 @@ fn foo() {
             extract_variable,
             r"
 fn foo() {
-    $0{ let x = 0; x }$0
+    $0{ let x = 0; x }$0;
     something_else();
 }",
             r"