@@ -238,8 +238,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
238238 _ => self . warn_if_unreachable ( expr. hir_id , expr. span , "expression" ) ,
239239 }
240240
241- // Any expression that produces a value of type `!` must have diverged
242- if ty. is_never ( ) {
241+ // Any expression that produces a value of type `!` must have diverged,
242+ // unless it's a place expression that isn't being read from, in which case
243+ // diverging would be unsound since we may never actually read the `!`.
244+ // e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
245+ if ty. is_never ( ) && self . expr_constitutes_read ( expr) {
243246 self . diverges . set ( self . diverges . get ( ) | Diverges :: always ( expr. span ) ) ;
244247 }
245248
@@ -257,6 +260,66 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
257260 ty
258261 }
259262
263+ pub ( super ) fn expr_constitutes_read ( & self , expr : & ' tcx hir:: Expr < ' tcx > ) -> bool {
264+ // We only care about place exprs. Anything else returns an immediate
265+ // which would constitute a read. We don't care about distinguishing
266+ // "syntactic" place exprs since if the base of a field projection is
267+ // not a place then it would've been UB to read from it anyways since
268+ // that constitutes a read.
269+ if !expr. is_syntactic_place_expr ( ) {
270+ return true ;
271+ }
272+
273+ // If this expression has any adjustments applied after the place expression,
274+ // they may constitute reads.
275+ if !self . typeck_results . borrow ( ) . expr_adjustments ( expr) . is_empty ( ) {
276+ return true ;
277+ }
278+
279+ fn pat_does_read ( pat : & hir:: Pat < ' _ > ) -> bool {
280+ let mut does_read = false ;
281+ pat. walk ( |pat| {
282+ if matches ! (
283+ pat. kind,
284+ hir:: PatKind :: Wild | hir:: PatKind :: Never | hir:: PatKind :: Or ( _)
285+ ) {
286+ true
287+ } else {
288+ does_read = true ;
289+ // No need to continue.
290+ false
291+ }
292+ } ) ;
293+ does_read
294+ }
295+
296+ match self . tcx . parent_hir_node ( expr. hir_id ) {
297+ // Addr-of, field projections, and LHS of assignment don't constitute reads.
298+ // Assignment does call `drop_in_place`, though, but its safety
299+ // requirements are not the same.
300+ hir:: Node :: Expr ( hir:: Expr { kind : hir:: ExprKind :: AddrOf ( ..) , .. } ) => false ,
301+ hir:: Node :: Expr ( hir:: Expr {
302+ kind : hir:: ExprKind :: Assign ( target, _, _) | hir:: ExprKind :: Field ( target, _) ,
303+ ..
304+ } ) if expr. hir_id == target. hir_id => false ,
305+
306+ // If we have a subpattern that performs a read, we want to consider this
307+ // to diverge for compatibility to support something like `let x: () = *never_ptr;`.
308+ hir:: Node :: LetStmt ( hir:: LetStmt { init : Some ( target) , pat, .. } )
309+ | hir:: Node :: Expr ( hir:: Expr {
310+ kind : hir:: ExprKind :: Let ( hir:: LetExpr { init : target, pat, .. } ) ,
311+ ..
312+ } ) if expr. hir_id == target. hir_id && !pat_does_read ( * pat) => false ,
313+ hir:: Node :: Expr ( hir:: Expr { kind : hir:: ExprKind :: Match ( target, arms, _) , .. } )
314+ if expr. hir_id == target. hir_id
315+ && !arms. iter ( ) . any ( |arm| pat_does_read ( arm. pat ) ) =>
316+ {
317+ false
318+ }
319+ _ => true ,
320+ }
321+ }
322+
260323 #[ instrument( skip( self , expr) , level = "debug" ) ]
261324 fn check_expr_kind (
262325 & self ,
0 commit comments