@@ -238,8 +238,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
238
238
_ => self . warn_if_unreachable ( expr. hir_id , expr. span , "expression" ) ,
239
239
}
240
240
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) {
243
246
self . diverges . set ( self . diverges . get ( ) | Diverges :: always ( expr. span ) ) ;
244
247
}
245
248
@@ -257,6 +260,66 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
257
260
ty
258
261
}
259
262
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
+
260
323
#[ instrument( skip( self , expr) , level = "debug" ) ]
261
324
fn check_expr_kind (
262
325
& self ,
0 commit comments