diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index fec6efdc0f71b..acc87b746719b 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -237,9 +237,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"), } - // Any expression that produces a value of type `!` must have diverged + // Any expression that produces a value of type `!` must have diverged, + // unless it's the place of a raw ref expr, or a scrutinee of a match. if ty.is_never() { - self.diverges.set(self.diverges.get() | Diverges::always(expr.span)); + if matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _)) { + match self.tcx.parent_hir_node(expr.hir_id) { + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::AddrOf(hir::BorrowKind::Raw, ..), + .. + }) => {} + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Let(hir::LetExpr { init: target, .. }), + .. + }) + | hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Match(target, _, _), .. + }) + | hir::Node::LetStmt(hir::LetStmt { init: Some(target), .. }) + if expr.hir_id == target.hir_id => {} + _ => { + self.diverges.set(self.diverges.get() | Diverges::always(expr.span)); + } + } + } else { + self.diverges.set(self.diverges.get() | Diverges::always(expr.span)); + } } // Record the type, which applies it effects. diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index c4f74adb4207b..8fdf0a51c5568 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -27,6 +27,7 @@ use rustc_trait_selection::traits::{ObligationCause, ObligationCauseCode}; use ty::VariantDef; use super::report_unexpected_variant_res; +use crate::diverges::Diverges; use crate::gather_locals::DeclOrigin; use crate::{errors, FnCtxt, LoweredTy}; @@ -276,6 +277,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; + // All other patterns constitute a read, which causes us to diverge + // if the type is never. + if !matches!(pat.kind, PatKind::Wild | PatKind::Never) { + if ty.is_never() { + self.diverges.set(self.diverges.get() | Diverges::always(pat.span)); + } + } + self.write_ty(pat.hir_id, ty); // (note_1): In most of the cases where (note_1) is referenced diff --git a/tests/ui/never_type/diverging-place-match.rs b/tests/ui/never_type/diverging-place-match.rs new file mode 100644 index 0000000000000..7bc00773c0ba5 --- /dev/null +++ b/tests/ui/never_type/diverging-place-match.rs @@ -0,0 +1,13 @@ +#![feature(never_type)] + +fn make_up_a_value() -> T { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + let _: ! = *x; + // Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this + // is unsound since we act as if it diverges but it doesn't. + } +} + +fn main() {} diff --git a/tests/ui/never_type/diverging-place-match.stderr b/tests/ui/never_type/diverging-place-match.stderr new file mode 100644 index 0000000000000..e86c634d59186 --- /dev/null +++ b/tests/ui/never_type/diverging-place-match.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:4:5 + | +LL | fn make_up_a_value() -> T { + | - expected this type parameter +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | let _: ! = *x; +LL | | // Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this +LL | | // is unsound since we act as if it diverges but it doesn't. +LL | | } + | |_____^ expected type parameter `T`, found `()` + | + = note: expected type parameter `T` + found unit type `()` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/raw-ref-op/never-place-isnt-diverging.rs b/tests/ui/raw-ref-op/never-place-isnt-diverging.rs new file mode 100644 index 0000000000000..52f4158dbc988 --- /dev/null +++ b/tests/ui/raw-ref-op/never-place-isnt-diverging.rs @@ -0,0 +1,13 @@ +#![feature(never_type)] + +fn make_up_a_value() -> T { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + &raw const *x; + // Since `*x` is `!`, HIR typeck used to think that it diverges + // and allowed the block to coerce to any value, leading to UB. + } +} + +fn main() {} diff --git a/tests/ui/raw-ref-op/never-place-isnt-diverging.stderr b/tests/ui/raw-ref-op/never-place-isnt-diverging.stderr new file mode 100644 index 0000000000000..9eba57dde8fa7 --- /dev/null +++ b/tests/ui/raw-ref-op/never-place-isnt-diverging.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/never-place-isnt-diverging.rs:4:5 + | +LL | fn make_up_a_value() -> T { + | - expected this type parameter +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | &raw const *x; +LL | | // Since `*x` is `!`, HIR typeck used to think that it diverges +LL | | // and allowed the block to coerce to any value, leading to UB. +LL | | } + | |_____^ expected type parameter `T`, found `()` + | + = note: expected type parameter `T` + found unit type `()` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`.