about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer.rs3
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs15
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs3
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs49
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs404
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs13
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs246
8 files changed, 637 insertions, 102 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
index 8bc3c50725d..88334b492d5 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
@@ -57,7 +57,7 @@ use crate::{
     db::HirDatabase,
     fold_tys,
     generics::Generics,
-    infer::{coerce::CoerceMany, unify::InferenceTable},
+    infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable},
     lower::ImplTraitLoweringMode,
     mir::MirSpan,
     to_assoc_type_id,
@@ -1154,6 +1154,7 @@ impl<'a> InferenceContext<'a> {
                 _ = self.infer_expr_coerce(
                     self.body.body_expr,
                     &Expectation::has_type(self.return_ty.clone()),
+                    ExprIsRead::Yes,
                 )
             }
         }
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs
index 5d5bff08644..21d0be6ed5f 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs
@@ -5,8 +5,9 @@ use hir_def::{hir::ExprId, AdtId};
 use stdx::never;
 
 use crate::{
-    infer::unify::InferenceTable, Adjustment, Binders, DynTy, InferenceDiagnostic, Interner,
-    PlaceholderIndex, QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause,
+    infer::{coerce::CoerceNever, unify::InferenceTable},
+    Adjustment, Binders, DynTy, InferenceDiagnostic, Interner, PlaceholderIndex,
+    QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause,
 };
 
 #[derive(Debug)]
@@ -128,7 +129,7 @@ impl CastCheck {
             return Ok(());
         }
 
-        if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty) {
+        if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty, CoerceNever::Yes) {
             apply_adjustments(self.source_expr, adj);
             set_coercion_cast(self.source_expr);
             return Ok(());
@@ -154,7 +155,8 @@ impl CastCheck {
                         let sig = self.expr_ty.callable_sig(table.db).expect("FnDef had no sig");
                         let sig = table.normalize_associated_types_in(sig);
                         let fn_ptr = TyKind::Function(sig.to_fn_ptr()).intern(Interner);
-                        if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr) {
+                        if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr, CoerceNever::Yes)
+                        {
                             apply_adjustments(self.source_expr, adj);
                         } else {
                             return Err(CastError::IllegalCast);
@@ -241,7 +243,8 @@ impl CastCheck {
             if let TyKind::Array(ety, _) = t_expr.kind(Interner) {
                 // Coerce to a raw pointer so that we generate RawPtr in MIR.
                 let array_ptr_type = TyKind::Raw(m_expr, t_expr.clone()).intern(Interner);
-                if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type) {
+                if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type, CoerceNever::Yes)
+                {
                     apply_adjustments(self.source_expr, adj);
                 } else {
                     never!(
@@ -253,7 +256,7 @@ impl CastCheck {
 
                 // This is a less strict condition than rustc's `demand_eqtype`,
                 // but false negative is better than false positive
-                if table.coerce(ety, t_cast).is_ok() {
+                if table.coerce(ety, t_cast, CoerceNever::Yes).is_ok() {
                     return Ok(());
                 }
             }
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
index 5cad08b9395..e9825cf0998 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
@@ -29,6 +29,7 @@ use crate::{
     db::{HirDatabase, InternedClosure},
     error_lifetime, from_chalk_trait_id, from_placeholder_idx,
     generics::Generics,
+    infer::coerce::CoerceNever,
     make_binders,
     mir::{BorrowKind, MirSpan, MutBorrowKind, ProjectionElem},
     to_chalk_trait_id,
@@ -65,7 +66,7 @@ impl InferenceContext<'_> {
         }
 
         // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here.
-        let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty);
+        let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty, CoerceNever::Yes);
 
         // Coroutines are not Fn* so return early.
         if matches!(closure_ty.kind(Interner), TyKind::Coroutine(..)) {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs
index 7e758c0b517..366c3cb0f17 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs
@@ -139,8 +139,8 @@ impl CoerceMany {
         };
         if let Some(sig) = sig {
             let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner);
-            let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty);
-            let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty);
+            let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty, CoerceNever::Yes);
+            let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty, CoerceNever::Yes);
             if let (Ok(result1), Ok(result2)) = (result1, result2) {
                 ctx.table.register_infer_ok(InferOk { value: (), goals: result1.goals });
                 for &e in &self.expressions {
@@ -159,9 +159,9 @@ impl CoerceMany {
         // type is a type variable and the new one is `!`, trying it the other
         // way around first would mean we make the type variable `!`, instead of
         // just marking it as possibly diverging.
-        if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty()) {
+        if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) {
             self.final_ty = Some(res);
-        } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) {
+        } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty, CoerceNever::Yes) {
             self.final_ty = Some(res);
         } else {
             match cause {
@@ -197,7 +197,7 @@ pub(crate) fn coerce(
     let vars = table.fresh_subst(tys.binders.as_slice(Interner));
     let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner);
     let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner);
-    let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars)?;
+    let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars, CoerceNever::Yes)?;
     // default any type vars that weren't unified back to their original bound vars
     // (kind of hacky)
     let find_var = |iv| {
@@ -219,6 +219,12 @@ pub(crate) fn coerce(
     Ok((adjustments, table.resolve_with_fallback(ty, &fallback)))
 }
 
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub(crate) enum CoerceNever {
+    Yes,
+    No,
+}
+
 impl InferenceContext<'_> {
     /// Unify two types, but may coerce the first one to the second one
     /// using "implicit coercion rules" if needed.
@@ -227,10 +233,16 @@ impl InferenceContext<'_> {
         expr: Option<ExprId>,
         from_ty: &Ty,
         to_ty: &Ty,
+        // [Comment from rustc](https://github.com/rust-lang/rust/blob/4cc494bbfe9911d24f3ee521f98d5c6bb7e3ffe8/compiler/rustc_hir_typeck/src/coercion.rs#L85-L89)
+        // Whether we allow `NeverToAny` coercions. This is unsound if we're
+        // coercing a place expression without it counting as a read in the MIR.
+        // This is a side-effect of HIR not really having a great distinction
+        // between places and values.
+        coerce_never: CoerceNever,
     ) -> Result<Ty, TypeError> {
         let from_ty = self.resolve_ty_shallow(from_ty);
         let to_ty = self.resolve_ty_shallow(to_ty);
-        let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty)?;
+        let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty, coerce_never)?;
         if let Some(expr) = expr {
             self.write_expr_adj(expr, adjustments);
         }
@@ -245,10 +257,11 @@ impl InferenceTable<'_> {
         &mut self,
         from_ty: &Ty,
         to_ty: &Ty,
+        coerce_never: CoerceNever,
     ) -> Result<(Vec<Adjustment>, Ty), TypeError> {
         let from_ty = self.resolve_ty_shallow(from_ty);
         let to_ty = self.resolve_ty_shallow(to_ty);
-        match self.coerce_inner(from_ty, &to_ty) {
+        match self.coerce_inner(from_ty, &to_ty, coerce_never) {
             Ok(InferOk { value: (adjustments, ty), goals }) => {
                 self.register_infer_ok(InferOk { value: (), goals });
                 Ok((adjustments, ty))
@@ -260,19 +273,23 @@ impl InferenceTable<'_> {
         }
     }
 
-    fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty) -> CoerceResult {
+    fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty, coerce_never: CoerceNever) -> CoerceResult {
         if from_ty.is_never() {
-            // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound
-            // type variable, we want `?T` to fallback to `!` if not
-            // otherwise constrained. An example where this arises:
-            //
-            //     let _: Option<?T> = Some({ return; });
-            //
-            // here, we would coerce from `!` to `?T`.
             if let TyKind::InferenceVar(tv, TyVariableKind::General) = to_ty.kind(Interner) {
                 self.set_diverging(*tv, true);
             }
-            return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]);
+            if coerce_never == CoerceNever::Yes {
+                // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound
+                // type variable, we want `?T` to fallback to `!` if not
+                // otherwise constrained. An example where this arises:
+                //
+                //     let _: Option<?T> = Some({ return; });
+                //
+                // here, we would coerce from `!` to `?T`.
+                return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]);
+            } else {
+                return self.unify_and(&from_ty, to_ty, identity);
+            }
         }
 
         // If we are coercing into a TAIT, coerce into its proxy inference var, instead.
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
index a04e7b17ae6..657e4d77966 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
@@ -10,10 +10,11 @@ use either::Either;
 use hir_def::{
     hir::{
         ArithOp, Array, AsmOperand, AsmOptions, BinaryOp, ClosureKind, Expr, ExprId, LabelId,
-        Literal, Statement, UnaryOp,
+        Literal, Pat, PatId, Statement, UnaryOp,
     },
     lang_item::{LangItem, LangItemTarget},
     path::{GenericArg, GenericArgs, Path},
+    resolver::ValueNs,
     BlockId, FieldId, GenericDefId, GenericParamId, ItemContainerId, Lookup, TupleFieldId, TupleId,
 };
 use hir_expand::name::Name;
@@ -28,7 +29,7 @@ use crate::{
     error_lifetime,
     generics::{generics, Generics},
     infer::{
-        coerce::{CoerceMany, CoercionCause},
+        coerce::{CoerceMany, CoerceNever, CoercionCause},
         find_continuable,
         pat::contains_explicit_ref_binding,
         BreakableKind,
@@ -52,9 +53,20 @@ use super::{
     Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch,
 };
 
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub(crate) enum ExprIsRead {
+    Yes,
+    No,
+}
+
 impl InferenceContext<'_> {
-    pub(crate) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
-        let ty = self.infer_expr_inner(tgt_expr, expected);
+    pub(crate) fn infer_expr(
+        &mut self,
+        tgt_expr: ExprId,
+        expected: &Expectation,
+        is_read: ExprIsRead,
+    ) -> Ty {
+        let ty = self.infer_expr_inner(tgt_expr, expected, is_read);
         if let Some(expected_ty) = expected.only_has_type(&mut self.table) {
             let could_unify = self.unify(&ty, &expected_ty);
             if !could_unify {
@@ -67,16 +79,26 @@ impl InferenceContext<'_> {
         ty
     }
 
-    pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId) -> Ty {
-        self.infer_expr_inner(tgt_expr, &Expectation::None)
+    pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId, is_read: ExprIsRead) -> Ty {
+        self.infer_expr_inner(tgt_expr, &Expectation::None, is_read)
     }
 
     /// Infer type of expression with possibly implicit coerce to the expected type.
     /// Return the type after possible coercion.
-    pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty {
-        let ty = self.infer_expr_inner(expr, expected);
+    pub(super) fn infer_expr_coerce(
+        &mut self,
+        expr: ExprId,
+        expected: &Expectation,
+        is_read: ExprIsRead,
+    ) -> Ty {
+        let ty = self.infer_expr_inner(expr, expected, is_read);
         if let Some(target) = expected.only_has_type(&mut self.table) {
-            match self.coerce(Some(expr), &ty, &target) {
+            let coerce_never = if self.expr_guaranteed_to_constitute_read_for_never(expr, is_read) {
+                CoerceNever::Yes
+            } else {
+                CoerceNever::No
+            };
+            match self.coerce(Some(expr), &ty, &target, coerce_never) {
                 Ok(res) => res,
                 Err(_) => {
                     self.result.type_mismatches.insert(
@@ -91,8 +113,137 @@ impl InferenceContext<'_> {
         }
     }
 
-    fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty {
-        let ty = self.infer_expr_inner(expr, expected);
+    /// Whether this expression constitutes a read of value of the type that
+    /// it evaluates to.
+    ///
+    /// This is used to determine if we should consider the block to diverge
+    /// if the expression evaluates to `!`, and if we should insert a `NeverToAny`
+    /// coercion for values of type `!`.
+    ///
+    /// This function generally returns `false` if the expression is a place
+    /// expression and the *parent* expression is the scrutinee of a match or
+    /// the pointee of an `&` addr-of expression, since both of those parent
+    /// expressions take a *place* and not a value.
+    pub(super) fn expr_guaranteed_to_constitute_read_for_never(
+        &mut self,
+        expr: ExprId,
+        is_read: ExprIsRead,
+    ) -> bool {
+        // rustc does the place expr check first, but since we are feeding
+        // readness of the `expr` as a given value, we just can short-circuit
+        // the place expr check if it's true(see codes and comments below)
+        if is_read == ExprIsRead::Yes {
+            return true;
+        }
+
+        // We only care about place exprs. Anything else returns an immediate
+        // which would constitute a read. We don't care about distinguishing
+        // "syntactic" place exprs since if the base of a field projection is
+        // not a place then it would've been UB to read from it anyways since
+        // that constitutes a read.
+        if !self.is_syntactic_place_expr(expr) {
+            return true;
+        }
+
+        // rustc queries parent hir node of `expr` here and determine whether
+        // the current `expr` is read of value per its parent.
+        // But since we don't have hir node, we cannot follow such "bottom-up"
+        // method.
+        // So, we pass down such readness from the parent expression through the
+        // recursive `infer_expr*` calls in a "top-down" manner.
+        is_read == ExprIsRead::Yes
+    }
+
+    /// Whether this pattern constitutes a read of value of the scrutinee that
+    /// it is matching against. This is used to determine whether we should
+    /// perform `NeverToAny` coercions.
+    fn pat_guaranteed_to_constitute_read_for_never(&self, pat: PatId) -> bool {
+        match &self.body[pat] {
+            // Does not constitute a read.
+            Pat::Wild => false,
+
+            // This is unnecessarily restrictive when the pattern that doesn't
+            // constitute a read is unreachable.
+            //
+            // For example `match *never_ptr { value => {}, _ => {} }` or
+            // `match *never_ptr { _ if false => {}, value => {} }`.
+            //
+            // It is however fine to be restrictive here; only returning `true`
+            // can lead to unsoundness.
+            Pat::Or(subpats) => {
+                subpats.iter().all(|pat| self.pat_guaranteed_to_constitute_read_for_never(*pat))
+            }
+
+            // All of these constitute a read, or match on something that isn't `!`,
+            // which would require a `NeverToAny` coercion.
+            Pat::Bind { .. }
+            | Pat::TupleStruct { .. }
+            | Pat::Path(_)
+            | Pat::Tuple { .. }
+            | Pat::Box { .. }
+            | Pat::Ref { .. }
+            | Pat::Lit(_)
+            | Pat::Range { .. }
+            | Pat::Slice { .. }
+            | Pat::ConstBlock(_)
+            | Pat::Record { .. }
+            | Pat::Missing => true,
+        }
+    }
+
+    fn is_syntactic_place_expr(&self, expr: ExprId) -> bool {
+        match &self.body[expr] {
+            // Lang item paths cannot currently be local variables or statics.
+            Expr::Path(Path::LangItem(_, _)) => false,
+            Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false,
+            Expr::Path(path) => self
+                .resolver
+                .resolve_path_in_value_ns_fully(self.db.upcast(), path)
+                .map_or(true, |res| matches!(res, ValueNs::LocalBinding(_) | ValueNs::StaticId(_))),
+            Expr::Underscore => true,
+            Expr::UnaryOp { op: UnaryOp::Deref, .. } => true,
+            Expr::Field { .. } | Expr::Index { .. } => true,
+            Expr::Call { .. }
+            | Expr::MethodCall { .. }
+            | Expr::Tuple { .. }
+            | Expr::If { .. }
+            | Expr::Match { .. }
+            | Expr::Closure { .. }
+            | Expr::Block { .. }
+            | Expr::Array(..)
+            | Expr::Break { .. }
+            | Expr::Continue { .. }
+            | Expr::Return { .. }
+            | Expr::Become { .. }
+            | Expr::Let { .. }
+            | Expr::Loop { .. }
+            | Expr::InlineAsm(..)
+            | Expr::OffsetOf(..)
+            | Expr::Literal(..)
+            | Expr::Const(..)
+            | Expr::UnaryOp { .. }
+            | Expr::BinaryOp { .. }
+            | Expr::Yield { .. }
+            | Expr::Cast { .. }
+            | Expr::Async { .. }
+            | Expr::Unsafe { .. }
+            | Expr::Await { .. }
+            | Expr::Ref { .. }
+            | Expr::Range { .. }
+            | Expr::Box { .. }
+            | Expr::RecordLit { .. }
+            | Expr::Yeet { .. }
+            | Expr::Missing => false,
+        }
+    }
+
+    fn infer_expr_coerce_never(
+        &mut self,
+        expr: ExprId,
+        expected: &Expectation,
+        is_read: ExprIsRead,
+    ) -> Ty {
+        let ty = self.infer_expr_inner(expr, expected, is_read);
         // While we don't allow *arbitrary* coercions here, we *do* allow
         // coercions from `!` to `expected`.
         if ty.is_never() {
@@ -105,7 +256,7 @@ impl InferenceContext<'_> {
             }
 
             if let Some(target) = expected.only_has_type(&mut self.table) {
-                self.coerce(Some(expr), &ty, &target)
+                self.coerce(Some(expr), &ty, &target, CoerceNever::Yes)
                     .expect("never-to-any coercion should always succeed")
             } else {
                 ty
@@ -124,7 +275,12 @@ impl InferenceContext<'_> {
         }
     }
 
-    fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
+    fn infer_expr_inner(
+        &mut self,
+        tgt_expr: ExprId,
+        expected: &Expectation,
+        is_read: ExprIsRead,
+    ) -> Ty {
         self.db.unwind_if_cancelled();
 
         let ty = match &self.body[tgt_expr] {
@@ -134,17 +290,18 @@ impl InferenceContext<'_> {
                 self.infer_expr_coerce_never(
                     condition,
                     &Expectation::HasType(self.result.standard_types.bool_.clone()),
+                    ExprIsRead::Yes,
                 );
 
                 let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
 
-                let then_ty = self.infer_expr_inner(then_branch, expected);
+                let then_ty = self.infer_expr_inner(then_branch, expected, ExprIsRead::Yes);
                 let then_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
                 let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table));
                 coerce.coerce(self, Some(then_branch), &then_ty, CoercionCause::Expr(then_branch));
                 match else_branch {
                     Some(else_branch) => {
-                        let else_ty = self.infer_expr_inner(else_branch, expected);
+                        let else_ty = self.infer_expr_inner(else_branch, expected, ExprIsRead::Yes);
                         let else_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
                         coerce.coerce(
                             self,
@@ -163,7 +320,12 @@ impl InferenceContext<'_> {
                 coerce.complete(self)
             }
             &Expr::Let { pat, expr } => {
-                let input_ty = self.infer_expr(expr, &Expectation::none());
+                let child_is_read = if self.pat_guaranteed_to_constitute_read_for_never(pat) {
+                    ExprIsRead::Yes
+                } else {
+                    ExprIsRead::No
+                };
+                let input_ty = self.infer_expr(expr, &Expectation::none(), child_is_read);
                 self.infer_top_pat(pat, &input_ty);
                 self.result.standard_types.bool_.clone()
             }
@@ -176,7 +338,7 @@ impl InferenceContext<'_> {
             Expr::Const(id) => {
                 self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
                     let loc = this.db.lookup_intern_anonymous_const(*id);
-                    this.infer_expr(loc.root, expected)
+                    this.infer_expr(loc.root, expected, ExprIsRead::Yes)
                 })
                 .1
             }
@@ -189,7 +351,11 @@ impl InferenceContext<'_> {
                 let ty = self.table.new_type_var();
                 let (breaks, ()) =
                     self.with_breakable_ctx(BreakableKind::Loop, Some(ty), label, |this| {
-                        this.infer_expr(body, &Expectation::HasType(TyBuilder::unit()));
+                        this.infer_expr(
+                            body,
+                            &Expectation::HasType(TyBuilder::unit()),
+                            ExprIsRead::Yes,
+                        );
                     });
 
                 match breaks {
@@ -312,7 +478,7 @@ impl InferenceContext<'_> {
                 ty
             }
             Expr::Call { callee, args, .. } => {
-                let callee_ty = self.infer_expr(*callee, &Expectation::none());
+                let callee_ty = self.infer_expr(*callee, &Expectation::none(), ExprIsRead::Yes);
                 let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false);
                 let (res, derefed_callee) = loop {
                     let Some((callee_deref_ty, _)) = derefs.next() else {
@@ -393,7 +559,12 @@ impl InferenceContext<'_> {
                     expected,
                 ),
             Expr::Match { expr, arms } => {
-                let input_ty = self.infer_expr(*expr, &Expectation::none());
+                let scrutinee_is_read = arms
+                    .iter()
+                    .all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat));
+                let scrutinee_is_read =
+                    if scrutinee_is_read { ExprIsRead::Yes } else { ExprIsRead::No };
+                let input_ty = self.infer_expr(*expr, &Expectation::none(), scrutinee_is_read);
 
                 if arms.is_empty() {
                     self.diverges = Diverges::Always;
@@ -423,11 +594,12 @@ impl InferenceContext<'_> {
                             self.infer_expr_coerce_never(
                                 guard_expr,
                                 &Expectation::HasType(self.result.standard_types.bool_.clone()),
+                                ExprIsRead::Yes,
                             );
                         }
                         self.diverges = Diverges::Maybe;
 
-                        let arm_ty = self.infer_expr_inner(arm.expr, &expected);
+                        let arm_ty = self.infer_expr_inner(arm.expr, &expected, ExprIsRead::Yes);
                         all_arms_diverge &= self.diverges;
                         coerce.coerce(self, Some(arm.expr), &arm_ty, CoercionCause::Expr(arm.expr));
                     }
@@ -480,7 +652,11 @@ impl InferenceContext<'_> {
                         },
                         None => self.err_ty(),
                     };
-                    self.infer_expr_inner(expr, &Expectation::HasType(opt_coerce_to))
+                    self.infer_expr_inner(
+                        expr,
+                        &Expectation::HasType(opt_coerce_to),
+                        ExprIsRead::Yes,
+                    )
                 } else {
                     TyBuilder::unit()
                 };
@@ -517,10 +693,14 @@ impl InferenceContext<'_> {
             Expr::Yield { expr } => {
                 if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() {
                     if let Some(expr) = expr {
-                        self.infer_expr_coerce(*expr, &Expectation::has_type(yield_ty));
+                        self.infer_expr_coerce(
+                            *expr,
+                            &Expectation::has_type(yield_ty),
+                            ExprIsRead::Yes,
+                        );
                     } else {
                         let unit = self.result.standard_types.unit.clone();
-                        let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty);
+                        let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty, CoerceNever::Yes);
                     }
                     resume_ty
                 } else {
@@ -530,7 +710,7 @@ impl InferenceContext<'_> {
             }
             Expr::Yeet { expr } => {
                 if let &Some(expr) = expr {
-                    self.infer_expr_no_expect(expr);
+                    self.infer_expr_no_expect(expr, ExprIsRead::Yes);
                 }
                 self.result.standard_types.never.clone()
             }
@@ -589,28 +769,37 @@ impl InferenceContext<'_> {
                             // Field type might have some unknown types
                             // FIXME: we may want to emit a single type variable for all instance of type fields?
                             let field_ty = self.insert_type_vars(field_ty);
-                            self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty));
+                            self.infer_expr_coerce(
+                                field.expr,
+                                &Expectation::has_type(field_ty),
+                                ExprIsRead::Yes,
+                            );
                         }
                     }
                     None => {
                         for field in fields.iter() {
-                            self.infer_expr_coerce(field.expr, &Expectation::None);
+                            // Field projections don't constitute reads.
+                            self.infer_expr_coerce(field.expr, &Expectation::None, ExprIsRead::No);
                         }
                     }
                 }
                 if let Some(expr) = spread {
-                    self.infer_expr(*expr, &Expectation::has_type(ty.clone()));
+                    self.infer_expr(*expr, &Expectation::has_type(ty.clone()), ExprIsRead::Yes);
                 }
                 ty
             }
             Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name, expected),
             Expr::Await { expr } => {
-                let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
+                let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes);
                 self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
             }
             Expr::Cast { expr, type_ref } => {
                 let cast_ty = self.make_ty(type_ref);
-                let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone()));
+                let expr_ty = self.infer_expr(
+                    *expr,
+                    &Expectation::Castable(cast_ty.clone()),
+                    ExprIsRead::Yes,
+                );
                 self.deferred_cast_checks.push(CastCheck::new(
                     tgt_expr,
                     *expr,
@@ -638,7 +827,7 @@ impl InferenceContext<'_> {
                 } else {
                     Expectation::none()
                 };
-                let inner_ty = self.infer_expr_inner(*expr, &expectation);
+                let inner_ty = self.infer_expr_inner(*expr, &expectation, ExprIsRead::Yes);
                 match rawness {
                     Rawness::RawPtr => TyKind::Raw(mutability, inner_ty),
                     Rawness::Ref => {
@@ -650,7 +839,7 @@ impl InferenceContext<'_> {
             }
             &Expr::Box { expr } => self.infer_expr_box(expr, expected),
             Expr::UnaryOp { expr, op } => {
-                let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
+                let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes);
                 let inner_ty = self.resolve_ty_shallow(&inner_ty);
                 // FIXME: Note down method resolution her
                 match op {
@@ -720,19 +909,32 @@ impl InferenceContext<'_> {
                     // cannot happen in destructuring assignments because of how
                     // they are desugared.
                     if is_ordinary {
-                        let lhs_ty = self.infer_expr(lhs, &Expectation::none());
-                        self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty));
+                        // LHS of assignment doesn't constitute reads.
+                        let lhs_ty = self.infer_expr(lhs, &Expectation::none(), ExprIsRead::No);
+                        self.infer_expr_coerce(
+                            *rhs,
+                            &Expectation::has_type(lhs_ty),
+                            ExprIsRead::No,
+                        );
                     } else {
-                        let rhs_ty = self.infer_expr(*rhs, &Expectation::none());
+                        let rhs_ty = self.infer_expr(*rhs, &Expectation::none(), ExprIsRead::Yes);
                         self.infer_assignee_expr(lhs, &rhs_ty);
                     }
                     self.result.standard_types.unit.clone()
                 }
                 Some(BinaryOp::LogicOp(_)) => {
                     let bool_ty = self.result.standard_types.bool_.clone();
-                    self.infer_expr_coerce(*lhs, &Expectation::HasType(bool_ty.clone()));
+                    self.infer_expr_coerce(
+                        *lhs,
+                        &Expectation::HasType(bool_ty.clone()),
+                        ExprIsRead::Yes,
+                    );
                     let lhs_diverges = self.diverges;
-                    self.infer_expr_coerce(*rhs, &Expectation::HasType(bool_ty.clone()));
+                    self.infer_expr_coerce(
+                        *rhs,
+                        &Expectation::HasType(bool_ty.clone()),
+                        ExprIsRead::Yes,
+                    );
                     // Depending on the LHS' value, the RHS can never execute.
                     self.diverges = lhs_diverges;
                     bool_ty
@@ -741,11 +943,12 @@ impl InferenceContext<'_> {
                 _ => self.err_ty(),
             },
             Expr::Range { lhs, rhs, range_type } => {
-                let lhs_ty = lhs.map(|e| self.infer_expr_inner(e, &Expectation::none()));
+                let lhs_ty =
+                    lhs.map(|e| self.infer_expr_inner(e, &Expectation::none(), ExprIsRead::Yes));
                 let rhs_expect = lhs_ty
                     .as_ref()
                     .map_or_else(Expectation::none, |ty| Expectation::has_type(ty.clone()));
-                let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect));
+                let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect, ExprIsRead::Yes));
                 match (range_type, lhs_ty, rhs_ty) {
                     (RangeOp::Exclusive, None, None) => match self.resolve_range_full() {
                         Some(adt) => TyBuilder::adt(self.db, adt).build(),
@@ -779,8 +982,8 @@ impl InferenceContext<'_> {
                 }
             }
             Expr::Index { base, index, is_assignee_expr } => {
-                let base_ty = self.infer_expr_inner(*base, &Expectation::none());
-                let index_ty = self.infer_expr(*index, &Expectation::none());
+                let base_ty = self.infer_expr_inner(*base, &Expectation::none(), ExprIsRead::Yes);
+                let index_ty = self.infer_expr(*index, &Expectation::none(), ExprIsRead::Yes);
 
                 if let Some(index_trait) = self.resolve_lang_trait(LangItem::Index) {
                     let canonicalized = self.canonicalize(base_ty.clone());
@@ -851,7 +1054,11 @@ impl InferenceContext<'_> {
                 };
 
                 for (expr, ty) in exprs.iter().zip(tys.iter_mut()) {
-                    *ty = self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone()));
+                    *ty = self.infer_expr_coerce(
+                        *expr,
+                        &Expectation::has_type(ty.clone()),
+                        ExprIsRead::Yes,
+                    );
                 }
 
                 TyKind::Tuple(tys.len(), Substitution::from_iter(Interner, tys)).intern(Interner)
@@ -958,7 +1165,7 @@ impl InferenceContext<'_> {
             Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
             Expr::InlineAsm(asm) => {
                 let mut check_expr_asm_operand = |expr, is_input: bool| {
-                    let ty = self.infer_expr_no_expect(expr);
+                    let ty = self.infer_expr_no_expect(expr, ExprIsRead::Yes);
 
                     // If this is an input value, we require its type to be fully resolved
                     // at this point. This allows us to provide helpful coercions which help
@@ -975,11 +1182,11 @@ impl InferenceContext<'_> {
                                     CallableSig::from_def(self.db, *def, parameters).to_fn_ptr(),
                                 )
                                 .intern(Interner);
-                                _ = self.coerce(Some(expr), &ty, &fnptr_ty);
+                                _ = self.coerce(Some(expr), &ty, &fnptr_ty, CoerceNever::Yes);
                             }
                             TyKind::Ref(mutbl, _, base_ty) => {
                                 let ptr_ty = TyKind::Raw(*mutbl, base_ty.clone()).intern(Interner);
-                                _ = self.coerce(Some(expr), &ty, &ptr_ty);
+                                _ = self.coerce(Some(expr), &ty, &ptr_ty, CoerceNever::Yes);
                             }
                             _ => {}
                         }
@@ -1016,7 +1223,9 @@ impl InferenceContext<'_> {
         // use a new type variable if we got unknown here
         let ty = self.insert_type_vars_shallow(ty);
         self.write_expr_ty(tgt_expr, ty.clone());
-        if self.resolve_ty_shallow(&ty).is_never() {
+        if self.resolve_ty_shallow(&ty).is_never()
+            && self.expr_guaranteed_to_constitute_read_for_never(tgt_expr, is_read)
+        {
             // Any expression that produces a value of type `!` must have diverged
             self.diverges = Diverges::Always;
         }
@@ -1041,7 +1250,7 @@ impl InferenceContext<'_> {
         let (_, inner_ty) = self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
             let ty = this.infer_block(tgt_expr, *id, statements, *tail, None, expected);
             if let Some(target) = expected.only_has_type(&mut this.table) {
-                match this.coerce(Some(tgt_expr), &ty, &target) {
+                match this.coerce(Some(tgt_expr), &ty, &target, CoerceNever::Yes) {
                     Ok(res) => res,
                     Err(_) => {
                         this.result.type_mismatches.insert(
@@ -1153,7 +1362,7 @@ impl InferenceContext<'_> {
             Array::ElementList { elements, .. } => {
                 let mut coerce = CoerceMany::new(elem_ty);
                 for &expr in elements.iter() {
-                    let cur_elem_ty = self.infer_expr_inner(expr, &expected);
+                    let cur_elem_ty = self.infer_expr_inner(expr, &expected, ExprIsRead::Yes);
                     coerce.coerce(self, Some(expr), &cur_elem_ty, CoercionCause::Expr(expr));
                 }
                 (
@@ -1162,13 +1371,17 @@ impl InferenceContext<'_> {
                 )
             }
             &Array::Repeat { initializer, repeat } => {
-                self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone()));
+                self.infer_expr_coerce(
+                    initializer,
+                    &Expectation::has_type(elem_ty.clone()),
+                    ExprIsRead::Yes,
+                );
                 let usize = TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner);
                 match self.body[repeat] {
                     Expr::Underscore => {
                         self.write_expr_ty(repeat, usize);
                     }
-                    _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize)),
+                    _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize), ExprIsRead::Yes),
                 }
 
                 (
@@ -1193,7 +1406,8 @@ impl InferenceContext<'_> {
             .as_mut()
             .expect("infer_return called outside function body")
             .expected_ty();
-        let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty));
+        let return_expr_ty =
+            self.infer_expr_inner(expr, &Expectation::HasType(ret_ty), ExprIsRead::Yes);
         let mut coerce_many = self.return_coercion.take().unwrap();
         coerce_many.coerce(self, Some(expr), &return_expr_ty, CoercionCause::Expr(expr));
         self.return_coercion = Some(coerce_many);
@@ -1213,7 +1427,7 @@ impl InferenceContext<'_> {
             None => {
                 // FIXME: diagnose return outside of function
                 if let Some(expr) = expr {
-                    self.infer_expr_no_expect(expr);
+                    self.infer_expr_no_expect(expr, ExprIsRead::Yes);
                 }
             }
         }
@@ -1225,8 +1439,11 @@ impl InferenceContext<'_> {
             Some(return_coercion) => {
                 let ret_ty = return_coercion.expected_ty();
 
-                let call_expr_ty =
-                    self.infer_expr_inner(expr, &Expectation::HasType(ret_ty.clone()));
+                let call_expr_ty = self.infer_expr_inner(
+                    expr,
+                    &Expectation::HasType(ret_ty.clone()),
+                    ExprIsRead::Yes,
+                );
 
                 // NB: this should *not* coerce.
                 //     tail calls don't support any coercions except lifetimes ones (like `&'static u8 -> &'a u8`).
@@ -1234,7 +1451,7 @@ impl InferenceContext<'_> {
             }
             None => {
                 // FIXME: diagnose `become` outside of functions
-                self.infer_expr_no_expect(expr);
+                self.infer_expr_no_expect(expr, ExprIsRead::Yes);
             }
         }
 
@@ -1255,7 +1472,7 @@ impl InferenceContext<'_> {
                 })
                 .unwrap_or_else(Expectation::none);
 
-            let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp);
+            let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp, ExprIsRead::Yes);
             TyBuilder::adt(self.db, box_id)
                 .push(inner_ty)
                 .fill_with_defaults(self.db, || self.table.new_type_var())
@@ -1333,12 +1550,13 @@ impl InferenceContext<'_> {
             Expr::Underscore => rhs_ty.clone(),
             _ => {
                 // `lhs` is a place expression, a unit struct, or an enum variant.
-                let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none());
+                // LHS of assignment doesn't constitute reads.
+                let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none(), ExprIsRead::No);
 
                 // This is the only branch where this function may coerce any type.
                 // We are returning early to avoid the unifiability check below.
                 let lhs_ty = self.insert_type_vars_shallow(lhs_ty);
-                let ty = match self.coerce(None, &rhs_ty, &lhs_ty) {
+                let ty = match self.coerce(None, &rhs_ty, &lhs_ty, CoerceNever::Yes) {
                     Ok(ty) => ty,
                     Err(_) => {
                         self.result.type_mismatches.insert(
@@ -1373,7 +1591,12 @@ impl InferenceContext<'_> {
         tgt_expr: ExprId,
     ) -> Ty {
         let lhs_expectation = Expectation::none();
-        let lhs_ty = self.infer_expr(lhs, &lhs_expectation);
+        let is_read = if matches!(op, BinaryOp::Assignment { .. }) {
+            ExprIsRead::Yes
+        } else {
+            ExprIsRead::No
+        };
+        let lhs_ty = self.infer_expr(lhs, &lhs_expectation, is_read);
         let rhs_ty = self.table.new_type_var();
 
         let trait_func = lang_items_for_bin_op(op).and_then(|(name, lang_item)| {
@@ -1396,7 +1619,7 @@ impl InferenceContext<'_> {
                     self.err_ty()
                 };
 
-                self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty));
+                self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty), ExprIsRead::Yes);
 
                 return ret_ty;
             }
@@ -1415,7 +1638,7 @@ impl InferenceContext<'_> {
         let method_ty = self.db.value_ty(func.into()).unwrap().substitute(Interner, &subst);
         self.register_obligations_for_call(&method_ty);
 
-        self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone()));
+        self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone()), ExprIsRead::Yes);
 
         let ret_ty = match method_ty.callable_sig(self.db) {
             Some(sig) => {
@@ -1487,12 +1710,25 @@ impl InferenceContext<'_> {
                                 .unwrap_or_else(|| this.table.new_type_var());
 
                             let ty = if let Some(expr) = initializer {
+                                // If we have a subpattern that performs a read, we want to consider this
+                                // to diverge for compatibility to support something like `let x: () = *never_ptr;`.
+                                let target_is_read =
+                                    if this.pat_guaranteed_to_constitute_read_for_never(*pat) {
+                                        ExprIsRead::Yes
+                                    } else {
+                                        ExprIsRead::No
+                                    };
                                 let ty = if contains_explicit_ref_binding(this.body, *pat) {
-                                    this.infer_expr(*expr, &Expectation::has_type(decl_ty.clone()))
+                                    this.infer_expr(
+                                        *expr,
+                                        &Expectation::has_type(decl_ty.clone()),
+                                        target_is_read,
+                                    )
                                 } else {
                                     this.infer_expr_coerce(
                                         *expr,
                                         &Expectation::has_type(decl_ty.clone()),
+                                        target_is_read,
                                     )
                                 };
                                 if type_ref.is_some() {
@@ -1512,17 +1748,19 @@ impl InferenceContext<'_> {
                                 this.infer_expr_coerce(
                                     *expr,
                                     &Expectation::HasType(this.result.standard_types.never.clone()),
+                                    ExprIsRead::Yes,
                                 );
                                 this.diverges = previous_diverges;
                             }
                         }
                         &Statement::Expr { expr, has_semi } => {
                             if has_semi {
-                                this.infer_expr(expr, &Expectation::none());
+                                this.infer_expr(expr, &Expectation::none(), ExprIsRead::Yes);
                             } else {
                                 this.infer_expr_coerce(
                                     expr,
                                     &Expectation::HasType(this.result.standard_types.unit.clone()),
+                                    ExprIsRead::Yes,
                                 );
                             }
                         }
@@ -1532,7 +1770,7 @@ impl InferenceContext<'_> {
 
                 // FIXME: This should make use of the breakable CoerceMany
                 if let Some(expr) = tail {
-                    this.infer_expr_coerce(expr, expected)
+                    this.infer_expr_coerce(expr, expected, ExprIsRead::Yes)
                 } else {
                     // Citing rustc: if there is no explicit tail expression,
                     // that is typically equivalent to a tail expression
@@ -1545,8 +1783,20 @@ impl InferenceContext<'_> {
                         // 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) {
+                        let coerce_never = if this
+                            .expr_guaranteed_to_constitute_read_for_never(expr, ExprIsRead::Yes)
+                        {
+                            CoerceNever::Yes
+                        } else {
+                            CoerceNever::No
+                        };
                         if this
-                            .coerce(Some(expr), &this.result.standard_types.unit.clone(), &t)
+                            .coerce(
+                                Some(expr),
+                                &this.result.standard_types.unit.clone(),
+                                &t,
+                                coerce_never,
+                            )
                             .is_err()
                         {
                             this.result.type_mismatches.insert(
@@ -1658,7 +1908,8 @@ impl InferenceContext<'_> {
         name: &Name,
         expected: &Expectation,
     ) -> Ty {
-        let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none());
+        // Field projections don't constitute reads.
+        let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::No);
 
         if name.is_missing() {
             // Bail out early, don't even try to look up field. Also, we don't issue an unresolved
@@ -1730,7 +1981,7 @@ impl InferenceContext<'_> {
         generic_args: Option<&GenericArgs>,
         expected: &Expectation,
     ) -> Ty {
-        let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none());
+        let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::Yes);
         let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
 
         let resolved = method_resolution::lookup_method(
@@ -1917,7 +2168,7 @@ impl InferenceContext<'_> {
                 let expected_ty = self.normalize_associated_types_in(expected_ty);
                 let expected = Expectation::rvalue_hint(self, expected_ty);
                 // infer with the expected type we have...
-                let ty = self.infer_expr_inner(arg, &expected);
+                let ty = self.infer_expr_inner(arg, &expected, ExprIsRead::Yes);
 
                 // then coerce to either the expected type or just the formal parameter type
                 let coercion_target = if let Some(ty) = expected.only_has_type(&mut self.table) {
@@ -1931,7 +2182,20 @@ impl InferenceContext<'_> {
                 // The function signature may contain some unknown types, so we need to insert
                 // type vars here to avoid type mismatch false positive.
                 let coercion_target = self.insert_type_vars(coercion_target);
-                if self.coerce(Some(arg), &ty, &coercion_target).is_err() && !arg_count_mismatch {
+
+                // Any expression that produces a value of type `!` must have diverged,
+                // unless it's a place expression that isn't being read from, in which case
+                // diverging would be unsound since we may never actually read the `!`.
+                // e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
+                let coerce_never =
+                    if self.expr_guaranteed_to_constitute_read_for_never(arg, ExprIsRead::Yes) {
+                        CoerceNever::Yes
+                    } else {
+                        CoerceNever::No
+                    };
+                if self.coerce(Some(arg), &ty, &coercion_target, coerce_never).is_err()
+                    && !arg_count_mismatch
+                {
                     self.result.type_mismatches.insert(
                         arg.into(),
                         TypeMismatch { expected: coercion_target, actual: ty.clone() },
@@ -2106,7 +2370,7 @@ impl InferenceContext<'_> {
             }
             let _ty = arg.data(Interner).ty.clone();
             let expected = Expectation::none(); // FIXME use actual const ty, when that is lowered correctly
-            self.infer_expr(args[arg_idx as usize], &expected);
+            self.infer_expr(args[arg_idx as usize], &expected, ExprIsRead::Yes);
             // FIXME: evaluate and unify with the const
         }
         let mut indices = legacy_const_generics_indices.as_ref().clone();
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
index 8dcaa9c581e..6a0daee6ea9 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
@@ -12,12 +12,11 @@ use hir_expand::name::Name;
 use intern::sym;
 
 use crate::{
-    infer::Expectation, lower::lower_to_chalk_mutability, Adjust, Adjustment, AutoBorrow, Interner,
-    OverloadedDeref, TyBuilder, TyKind,
+    infer::{expr::ExprIsRead, Expectation, InferenceContext},
+    lower::lower_to_chalk_mutability,
+    Adjust, Adjustment, AutoBorrow, Interner, OverloadedDeref, TyBuilder, TyKind,
 };
 
-use super::InferenceContext;
-
 impl InferenceContext<'_> {
     pub(crate) fn infer_mut_body(&mut self) {
         self.infer_mut_expr(self.body.body_expr, Mutability::Not);
@@ -164,7 +163,11 @@ impl InferenceContext<'_> {
                                         if let Some(ty) = self.result.type_of_expr.get(index) {
                                             ty.clone()
                                         } else {
-                                            self.infer_expr(index, &Expectation::none())
+                                            self.infer_expr(
+                                                index,
+                                                &Expectation::none(),
+                                                ExprIsRead::Yes,
+                                            )
                                         };
                                     let trait_ref = TyBuilder::trait_ref(self.db, index_trait)
                                         .push(base_ty)
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs
index 50542b2acd4..fee6755408e 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs
@@ -12,7 +12,7 @@ use stdx::TupleExt;
 
 use crate::{
     consteval::{try_const_usize, usize_const},
-    infer::{BindingMode, Expectation, InferenceContext, TypeMismatch},
+    infer::{expr::ExprIsRead, BindingMode, Expectation, InferenceContext, TypeMismatch},
     lower::lower_to_chalk_mutability,
     primitive::UintTy,
     static_lifetime, InferenceDiagnostic, Interner, Mutability, Scalar, Substitution, Ty,
@@ -361,7 +361,7 @@ impl InferenceContext<'_> {
                 None => self.err_ty(),
             },
             Pat::ConstBlock(expr) => {
-                self.infer_expr(*expr, &Expectation::has_type(expected.clone()))
+                self.infer_expr(*expr, &Expectation::has_type(expected.clone()), ExprIsRead::Yes)
             }
             Pat::Missing => self.err_ty(),
         };
@@ -497,7 +497,7 @@ impl InferenceContext<'_> {
             }
         }
 
-        self.infer_expr(expr, &Expectation::has_type(expected.clone()))
+        self.infer_expr(expr, &Expectation::has_type(expected.clone()), ExprIsRead::Yes)
     }
 
     fn is_non_ref_pat(&mut self, body: &hir_def::body::Body, pat: PatId) -> bool {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs
index 5c63cd00f97..1ca4c9b2ad5 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs
@@ -539,3 +539,249 @@ fn test() {
 "#,
     );
 }
+
+#[test]
+fn diverging_place_match1() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn not_a_read() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        let _: ! = *x;
+    }
+}
+"#,
+        expect![[r#"
+            21..100 '{     ...   } }': !
+            27..98 'unsafe...     }': !
+            48..49 'x': *const !
+            62..63 '0': i32
+            62..68 '0 as _': *const !
+            82..83 '_': !
+            89..91 '*x': !
+            90..91 'x': *const !
+            27..98: expected !, got ()
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match2() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn not_a_read_implicit() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        let _ = *x;
+    }
+}
+"#,
+        expect![[r#"
+            30..106 '{     ...   } }': !
+            36..104 'unsafe...     }': !
+            57..58 'x': *const !
+            71..72 '0': i32
+            71..77 '0 as _': *const !
+            91..92 '_': !
+            95..97 '*x': !
+            96..97 'x': *const !
+            36..104: expected !, got ()
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match3() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn not_a_read_guide_coercion() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        let _: () = *x;
+    }
+}
+"#,
+        expect![[r#"
+            36..116 '{     ...   } }': !
+            42..114 'unsafe...     }': !
+            63..64 'x': *const !
+            77..78 '0': i32
+            77..83 '0 as _': *const !
+            97..98 '_': ()
+            105..107 '*x': !
+            106..107 'x': *const !
+            42..114: expected !, got ()
+            105..107: expected (), got !
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match4() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn empty_match() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        match *x { _ => {} };
+    }
+}
+"#,
+        expect![[r#"
+            22..108 '{     ...   } }': !
+            28..106 'unsafe...     }': !
+            49..50 'x': *const !
+            63..64 '0': i32
+            63..69 '0 as _': *const !
+            79..99 'match ...> {} }': ()
+            85..87 '*x': !
+            86..87 'x': *const !
+            90..91 '_': !
+            95..97 '{}': ()
+            28..106: expected !, got ()
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match5() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn field_projection() -> ! {
+    unsafe {
+        let x: *const (!, ()) = 0 as _;
+        let _ = (*x).0;
+    }
+}
+"#,
+        expect![[r#"
+            27..113 '{     ...   } }': !
+            33..111 'unsafe...     }': !
+            54..55 'x': *const (!, ())
+            74..75 '0': i32
+            74..80 '0 as _': *const (!, ())
+            94..95 '_': !
+            98..104 '(*x).0': !
+            99..101 '*x': (!, ())
+            100..101 'x': *const (!, ())
+            33..111: expected !, got ()
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match6() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn covered_arm() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        let (_ | 1i32) = *x;
+    }
+}
+"#,
+        expect![[r#"
+            22..107 '{     ...   } }': !
+            28..105 'unsafe...     }': !
+            49..50 'x': *const !
+            63..64 '0': i32
+            63..69 '0 as _': *const !
+            84..85 '_': !
+            84..92 '_ | 1i32': !
+            88..92 '1i32': i32
+            88..92 '1i32': i32
+            96..98 '*x': !
+            97..98 'x': *const !
+            28..105: expected !, got ()
+            88..92: expected !, got i32
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match7() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn uncovered_arm() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        let (1i32 | _) = *x;
+    }
+}
+"#,
+        expect![[r#"
+            24..109 '{     ...   } }': !
+            30..107 'unsafe...     }': !
+            51..52 'x': *const !
+            65..66 '0': i32
+            65..71 '0 as _': *const !
+            86..90 '1i32': i32
+            86..90 '1i32': i32
+            86..94 '1i32 | _': !
+            93..94 '_': !
+            98..100 '*x': !
+            99..100 'x': *const !
+            30..107: expected !, got ()
+            86..90: expected !, got i32
+        "#]],
+    )
+}
+
+#[test]
+fn diverging_place_match8() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn coerce_ref_binding() -> ! {
+    unsafe {
+        let x: *const ! = 0 as _;
+        let ref _x: () = *x;
+    }
+}
+"#,
+        expect![[r#"
+            29..114 '{     ...   } }': !
+            35..112 'unsafe...     }': !
+            56..57 'x': *const !
+            70..71 '0': i32
+            70..76 '0 as _': *const !
+            90..96 'ref _x': &'? ()
+            103..105 '*x': !
+            104..105 'x': *const !
+            103..105: expected (), got !
+        "#]],
+    )
+}
+
+#[test]
+fn never_place_isnt_diverging() {
+    check_infer_with_mismatches(
+        r#"
+//- minicore: sized
+fn make_up_a_pointer<T>() -> *const T {
+    unsafe {
+        let x: *const ! = 0 as _;
+        &raw const *x
+    }
+}
+"#,
+        expect![[r#"
+            38..116 '{     ...   } }': *const T
+            44..114 'unsafe...     }': *const T
+            65..66 'x': *const !
+            79..80 '0': i32
+            79..85 '0 as _': *const !
+            95..108 '&raw const *x': *const !
+            106..108 '*x': !
+            107..108 'x': *const !
+            95..108: expected *const T, got *const !
+        "#]],
+    )
+}