diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index fb462eec1b9aa..642db3f36b586 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -82,6 +82,11 @@ struct Coerce<'a, 'tcx> { /// See #47489 and #48598 /// See docs on the "AllowTwoPhase" type for a more detailed discussion allow_two_phase: AllowTwoPhase, + /// 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: bool, } impl<'a, 'tcx> Deref for Coerce<'a, 'tcx> { @@ -125,8 +130,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { fcx: &'f FnCtxt<'f, 'tcx>, cause: ObligationCause<'tcx>, allow_two_phase: AllowTwoPhase, + coerce_never: bool, ) -> Self { - Coerce { fcx, cause, allow_two_phase, use_lub: false } + Coerce { fcx, cause, allow_two_phase, use_lub: false, coerce_never } } fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> InferResult<'tcx, Ty<'tcx>> { @@ -177,7 +183,12 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // Coercing from `!` to any type is allowed: if a.is_never() { - return success(simple(Adjust::NeverToAny)(b), b, vec![]); + if self.coerce_never { + return success(simple(Adjust::NeverToAny)(b), b, vec![]); + } else { + // Otherwise the only coercion we can do is unification. + return self.unify_and(a, b, identity); + } } // Coercing *from* an unresolved inference variable means that @@ -1038,7 +1049,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// The expressions *must not* have any preexisting adjustments. pub(crate) fn coerce( &self, - expr: &hir::Expr<'_>, + expr: &'tcx hir::Expr<'tcx>, expr_ty: Ty<'tcx>, mut target: Ty<'tcx>, allow_two_phase: AllowTwoPhase, @@ -1055,7 +1066,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let cause = cause.unwrap_or_else(|| self.cause(expr.span, ObligationCauseCode::ExprAssignable)); - let coerce = Coerce::new(self, cause, allow_two_phase); + let coerce = Coerce::new( + self, + cause, + allow_two_phase, + self.expr_guaranteed_to_constitute_read_for_never(expr), + ); let ok = self.commit_if_ok(|_| coerce.coerce(source, target))?; let (adjustments, _) = self.register_infer_ok_obligations(ok); @@ -1077,8 +1093,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!("coercion::can_with_predicates({:?} -> {:?})", source, target); let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable); - // We don't ever need two-phase here since we throw out the result of the coercion - let coerce = Coerce::new(self, cause, AllowTwoPhase::No); + // We don't ever need two-phase here since we throw out the result of the coercion. + // We also just always set `coerce_never` to true, since this is a heuristic. + let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); self.probe(|_| { let Ok(ok) = coerce.coerce(source, target) else { return false; @@ -1090,12 +1107,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// Given a type and a target type, this function will calculate and return - /// how many dereference steps needed to achieve `expr_ty <: target`. If + /// how many dereference steps needed to coerce `expr_ty` to `target`. If /// it's not possible, return `None`. - pub(crate) fn deref_steps(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> Option { + pub(crate) fn deref_steps_for_suggestion( + &self, + expr_ty: Ty<'tcx>, + target: Ty<'tcx>, + ) -> Option { let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable); - // We don't ever need two-phase here since we throw out the result of the coercion - let coerce = Coerce::new(self, cause, AllowTwoPhase::No); + // We don't ever need two-phase here since we throw out the result of the coercion. + let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); coerce .autoderef(DUMMY_SP, expr_ty) .find_map(|(ty, steps)| self.probe(|_| coerce.unify(ty, target)).ok().map(|_| steps)) @@ -1252,7 +1273,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // probably aren't processing function arguments here and even if we were, // they're going to get autorefed again anyway and we can apply 2-phase borrows // at that time. - let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No); + // + // NOTE: we set `coerce_never` to `true` here because coercion LUBs only + // operate on values and not places, so a never coercion is valid. + let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No, true); coerce.use_lub = true; // First try to coerce the new expression to the type of the previous ones, diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index e3c7dded0ca6e..2d71829537496 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1,3 +1,6 @@ +// ignore-tidy-filelength +// FIXME: we should move the field error reporting code somewhere else. + //! Type checking expressions. //! //! See [`rustc_hir_analysis::check`] for more context on type checking in general. @@ -62,7 +65,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // While we don't allow *arbitrary* coercions here, we *do* allow // coercions from ! to `expected`. - if ty.is_never() { + if ty.is_never() && self.expr_guaranteed_to_constitute_read_for_never(expr) { if let Some(_) = self.typeck_results.borrow().adjustments().get(expr.hir_id) { let reported = self.dcx().span_delayed_bug( expr.span, @@ -238,8 +241,11 @@ 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 - if ty.is_never() { + // 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 !`. + if ty.is_never() && self.expr_guaranteed_to_constitute_read_for_never(expr) { self.diverges.set(self.diverges.get() | Diverges::always(expr.span)); } @@ -257,6 +263,185 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty } + /// 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( + &self, + expr: &'tcx hir::Expr<'tcx>, + ) -> bool { + // 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 !expr.is_syntactic_place_expr() { + return true; + } + + let parent_node = self.tcx.parent_hir_node(expr.hir_id); + match parent_node { + hir::Node::Expr(parent_expr) => { + match parent_expr.kind { + // Addr-of, field projections, and LHS of assignment don't constitute reads. + // Assignment does call `drop_in_place`, though, but its safety + // requirements are not the same. + ExprKind::AddrOf(..) | hir::ExprKind::Field(..) => false, + ExprKind::Assign(lhs, _, _) => { + // Only the LHS does not constitute a read + expr.hir_id != lhs.hir_id + } + + // See note on `PatKind::Or` below for why this is `all`. + ExprKind::Match(scrutinee, arms, _) => { + assert_eq!(scrutinee.hir_id, expr.hir_id); + arms.iter() + .all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat)) + } + ExprKind::Let(hir::LetExpr { init, pat, .. }) => { + assert_eq!(init.hir_id, expr.hir_id); + self.pat_guaranteed_to_constitute_read_for_never(*pat) + } + + // Any expression child of these expressions constitute reads. + ExprKind::Array(_) + | ExprKind::Call(_, _) + | ExprKind::MethodCall(_, _, _, _) + | ExprKind::Tup(_) + | ExprKind::Binary(_, _, _) + | ExprKind::Unary(_, _) + | ExprKind::Cast(_, _) + | ExprKind::Type(_, _) + | ExprKind::DropTemps(_) + | ExprKind::If(_, _, _) + | ExprKind::Closure(_) + | ExprKind::Block(_, _) + | ExprKind::AssignOp(_, _, _) + | ExprKind::Index(_, _, _) + | ExprKind::Break(_, _) + | ExprKind::Ret(_) + | ExprKind::Become(_) + | ExprKind::InlineAsm(_) + | ExprKind::Struct(_, _, _) + | ExprKind::Repeat(_, _) + | ExprKind::Yield(_, _) => true, + + // These expressions have no (direct) sub-exprs. + ExprKind::ConstBlock(_) + | ExprKind::Loop(_, _, _, _) + | ExprKind::Lit(_) + | ExprKind::Path(_) + | ExprKind::Continue(_) + | ExprKind::OffsetOf(_, _) + | ExprKind::Err(_) => unreachable!("no sub-expr expected for {:?}", expr.kind), + } + } + + // 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;`. + hir::Node::LetStmt(hir::LetStmt { init: Some(target), pat, .. }) => { + assert_eq!(target.hir_id, expr.hir_id); + self.pat_guaranteed_to_constitute_read_for_never(*pat) + } + + // These nodes (if they have a sub-expr) do constitute a read. + hir::Node::Block(_) + | hir::Node::Arm(_) + | hir::Node::ExprField(_) + | hir::Node::AnonConst(_) + | hir::Node::ConstBlock(_) + | hir::Node::ConstArg(_) + | hir::Node::Stmt(_) + | hir::Node::Item(hir::Item { + kind: hir::ItemKind::Const(..) | hir::ItemKind::Static(..), + .. + }) + | hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Const(..), .. + }) + | hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Const(..), .. }) => true, + + // These nodes do not have direct sub-exprs. + hir::Node::Param(_) + | hir::Node::Item(_) + | hir::Node::ForeignItem(_) + | hir::Node::TraitItem(_) + | hir::Node::ImplItem(_) + | hir::Node::Variant(_) + | hir::Node::Field(_) + | hir::Node::PathSegment(_) + | hir::Node::Ty(_) + | hir::Node::AssocItemConstraint(_) + | hir::Node::TraitRef(_) + | hir::Node::Pat(_) + | hir::Node::PatField(_) + | hir::Node::LetStmt(_) + | hir::Node::Synthetic + | hir::Node::Err(_) + | hir::Node::Ctor(_) + | hir::Node::Lifetime(_) + | hir::Node::GenericParam(_) + | hir::Node::Crate(_) + | hir::Node::Infer(_) + | hir::Node::WhereBoundPredicate(_) + | hir::Node::ArrayLenInfer(_) + | hir::Node::PreciseCapturingNonLifetimeArg(_) + | hir::Node::OpaqueTy(_) => { + unreachable!("no sub-expr expected for {parent_node:?}") + } + } + } + + /// 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. + /// + /// See above for the nuances of what happens when this returns true. + pub(super) fn pat_guaranteed_to_constitute_read_for_never(&self, pat: &hir::Pat<'_>) -> bool { + match pat.kind { + // Does not constitute a read. + hir::PatKind::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. + hir::PatKind::Or(subpats) => { + subpats.iter().all(|pat| self.pat_guaranteed_to_constitute_read_for_never(pat)) + } + + // Does constitute a read, since it is equivalent to a discriminant read. + hir::PatKind::Never => true, + + // All of these constitute a read, or match on something that isn't `!`, + // which would require a `NeverToAny` coercion. + hir::PatKind::Binding(_, _, _, _) + | hir::PatKind::Struct(_, _, _) + | hir::PatKind::TupleStruct(_, _, _) + | hir::PatKind::Path(_) + | hir::PatKind::Tuple(_, _) + | hir::PatKind::Box(_) + | hir::PatKind::Ref(_, _) + | hir::PatKind::Deref(_) + | hir::PatKind::Lit(_) + | hir::PatKind::Range(_, _, _) + | hir::PatKind::Slice(_, _, _) + | hir::PatKind::Err(_) => true, + } + } + #[instrument(skip(self, expr), level = "debug")] fn check_expr_kind( &self, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 435b7d0f39a37..1df4d32f3cbac 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2608,7 +2608,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if let hir::ExprKind::Unary(hir::UnOp::Deref, inner) = expr.kind - && let Some(1) = self.deref_steps(expected, checked_ty) + && let Some(1) = self.deref_steps_for_suggestion(expected, checked_ty) { // We have `*&T`, check if what was expected was `&T`. // If so, we may want to suggest removing a `*`. @@ -2738,7 +2738,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } (_, &ty::RawPtr(ty_b, mutbl_b), &ty::Ref(_, ty_a, mutbl_a)) => { - if let Some(steps) = self.deref_steps(ty_a, ty_b) + if let Some(steps) = self.deref_steps_for_suggestion(ty_a, ty_b) // Only suggest valid if dereferencing needed. && steps > 0 // The pointer type implements `Copy` trait so the suggestion is always valid. @@ -2782,7 +2782,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } _ if sp == expr.span => { - if let Some(mut steps) = self.deref_steps(checked_ty, expected) { + if let Some(mut steps) = self.deref_steps_for_suggestion(checked_ty, expected) { let mut expr = expr.peel_blocks(); let mut prefix_span = expr.span.shrink_to_lo(); let mut remove = String::new(); diff --git a/src/tools/miri/tests/pass/underscore_pattern.rs b/src/tools/miri/tests/pass/underscore_pattern.rs index f0afe5589546e..f59bb9f5c82d5 100644 --- a/src/tools/miri/tests/pass/underscore_pattern.rs +++ b/src/tools/miri/tests/pass/underscore_pattern.rs @@ -1,5 +1,7 @@ // Various tests ensuring that underscore patterns really just construct the place, but don't check its contents. #![feature(strict_provenance)] +#![feature(never_type)] + use std::ptr; fn main() { @@ -9,6 +11,7 @@ fn main() { invalid_let(); dangling_let_type_annotation(); invalid_let_type_annotation(); + never(); } fn dangling_match() { @@ -34,6 +37,13 @@ fn invalid_match() { _ => {} } } + + unsafe { + let x: Uninit = Uninit { uninit: () }; + match x.value { + _ => {} + } + } } fn dangling_let() { @@ -41,6 +51,11 @@ fn dangling_let() { let ptr = ptr::without_provenance::(0x40); let _ = *ptr; } + + unsafe { + let ptr = ptr::without_provenance::(0x40); + let _ = *ptr; + } } fn invalid_let() { @@ -49,6 +64,12 @@ fn invalid_let() { let ptr = ptr::addr_of!(val).cast::(); let _ = *ptr; } + + unsafe { + let val = 3u8; + let ptr = ptr::addr_of!(val).cast::(); + let _ = *ptr; + } } // Adding a type annotation used to change how MIR is generated, make sure we cover both cases. @@ -57,6 +78,11 @@ fn dangling_let_type_annotation() { let ptr = ptr::without_provenance::(0x40); let _: bool = *ptr; } + + unsafe { + let ptr = ptr::without_provenance::(0x40); + let _: ! = *ptr; + } } fn invalid_let_type_annotation() { @@ -65,7 +91,28 @@ fn invalid_let_type_annotation() { let ptr = ptr::addr_of!(val).cast::(); let _: bool = *ptr; } + + unsafe { + let val = 3u8; + let ptr = ptr::addr_of!(val).cast::(); + let _: ! = *ptr; + } } -// FIXME: we should also test `!`, not just `bool` -- but that s currently buggy: -// https://github.com/rust-lang/rust/issues/117288 +// Regression test from . +fn never() { + unsafe { + let x = 3u8; + let x: *const ! = &x as *const u8 as *const _; + let _: ! = *x; + } + + // Without a type annotation, make sure we don't implicitly coerce `!` to `()` + // when we do the noop `*x` (as that would require a `!` *value*, creating + // which is UB). + unsafe { + let x = 3u8; + let x: *const ! = &x as *const u8 as *const _; + let _ = *x; + } +} diff --git a/tests/mir-opt/uninhabited_enum.process_never.SimplifyLocals-final.after.mir b/tests/mir-opt/uninhabited_enum.process_never.SimplifyLocals-final.after.mir index 240f409817d77..02e1f4be15e2e 100644 --- a/tests/mir-opt/uninhabited_enum.process_never.SimplifyLocals-final.after.mir +++ b/tests/mir-opt/uninhabited_enum.process_never.SimplifyLocals-final.after.mir @@ -3,9 +3,8 @@ fn process_never(_1: *const !) -> () { debug input => _1; let mut _0: (); - let _2: &!; scope 1 { - debug _input => _2; + debug _input => const (); } bb0: { diff --git a/tests/mir-opt/uninhabited_enum.process_void.SimplifyLocals-final.after.mir b/tests/mir-opt/uninhabited_enum.process_void.SimplifyLocals-final.after.mir index 51514ba5e5d90..64711755f7394 100644 --- a/tests/mir-opt/uninhabited_enum.process_void.SimplifyLocals-final.after.mir +++ b/tests/mir-opt/uninhabited_enum.process_void.SimplifyLocals-final.after.mir @@ -4,7 +4,7 @@ fn process_void(_1: *const Void) -> () { debug input => _1; let mut _0: (); scope 1 { - debug _input => _1; + debug _input => const ZeroSized: Void; } bb0: { diff --git a/tests/mir-opt/uninhabited_enum.rs b/tests/mir-opt/uninhabited_enum.rs index 859535852cf0d..90b5353f29143 100644 --- a/tests/mir-opt/uninhabited_enum.rs +++ b/tests/mir-opt/uninhabited_enum.rs @@ -1,18 +1,19 @@ // skip-filecheck #![feature(never_type)] +#[derive(Copy, Clone)] pub enum Void {} // EMIT_MIR uninhabited_enum.process_never.SimplifyLocals-final.after.mir #[no_mangle] pub fn process_never(input: *const !) { - let _input = unsafe { &*input }; + let _input = unsafe { *input }; } // EMIT_MIR uninhabited_enum.process_void.SimplifyLocals-final.after.mir #[no_mangle] pub fn process_void(input: *const Void) { - let _input = unsafe { &*input }; + let _input = unsafe { *input }; // In the future, this should end with `unreachable`, but we currently only do // unreachability analysis for `!`. } diff --git a/tests/mir-opt/uninhabited_not_read.main.SimplifyLocals-final.after.mir b/tests/mir-opt/uninhabited_not_read.main.SimplifyLocals-final.after.mir new file mode 100644 index 0000000000000..6bf4be652bef3 --- /dev/null +++ b/tests/mir-opt/uninhabited_not_read.main.SimplifyLocals-final.after.mir @@ -0,0 +1,49 @@ +// MIR for `main` after SimplifyLocals-final + +fn main() -> () { + let mut _0: (); + let _1: u8; + let mut _2: *const !; + let mut _3: *const u8; + let _4: u8; + let mut _5: *const !; + let mut _6: *const u8; + scope 1 { + debug x => _1; + scope 2 { + debug x => _2; + scope 3 { + } + } + } + scope 4 { + debug x => _4; + scope 5 { + debug x => _5; + scope 6 { + } + } + } + + bb0: { + StorageLive(_1); + _1 = const 3_u8; + StorageLive(_2); + StorageLive(_3); + _3 = &raw const _1; + _2 = move _3 as *const ! (PtrToPtr); + StorageDead(_3); + StorageDead(_2); + StorageDead(_1); + StorageLive(_4); + _4 = const 3_u8; + StorageLive(_5); + StorageLive(_6); + _6 = &raw const _4; + _5 = move _6 as *const ! (PtrToPtr); + StorageDead(_6); + StorageDead(_5); + StorageDead(_4); + return; + } +} diff --git a/tests/mir-opt/uninhabited_not_read.rs b/tests/mir-opt/uninhabited_not_read.rs new file mode 100644 index 0000000000000..15769cdd75b3f --- /dev/null +++ b/tests/mir-opt/uninhabited_not_read.rs @@ -0,0 +1,26 @@ +// skip-filecheck + +//@ edition: 2021 +// In ed 2021 and below, we don't fallback `!` to `()`. +// This would introduce a `! -> ()` coercion which would +// be UB if we didn't disallow this explicitly. + +#![feature(never_type)] + +// EMIT_MIR uninhabited_not_read.main.SimplifyLocals-final.after.mir +fn main() { + // With a type annotation + unsafe { + let x = 3u8; + let x: *const ! = &x as *const u8 as *const _; + let _: ! = *x; + } + + // Without a type annotation, make sure we don't implicitly coerce `!` to `()` + // when we do the noop `*x`. + unsafe { + let x = 3u8; + let x: *const ! = &x as *const u8 as *const _; + let _ = *x; + } +} diff --git a/tests/ui/closures/2229_closure_analysis/match/pattern-matching-should-fail.stderr b/tests/ui/closures/2229_closure_analysis/match/pattern-matching-should-fail.stderr index f8ed792e3c675..e0d900a1eb564 100644 --- a/tests/ui/closures/2229_closure_analysis/match/pattern-matching-should-fail.stderr +++ b/tests/ui/closures/2229_closure_analysis/match/pattern-matching-should-fail.stderr @@ -7,14 +7,12 @@ LL | let c1 = || match x { }; | ^ `x` used here but it isn't initialized error[E0381]: used binding `x` isn't initialized - --> $DIR/pattern-matching-should-fail.rs:15:14 + --> $DIR/pattern-matching-should-fail.rs:15:23 | LL | let x: !; | - binding declared here but left uninitialized LL | let c2 = || match x { _ => () }; - | ^^ - borrow occurs due to use in closure - | | - | `x` used here but it isn't initialized + | ^ `x` used here but it isn't initialized error[E0381]: used binding `variant` isn't initialized --> $DIR/pattern-matching-should-fail.rs:27:13 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..b9bc29a218c4a --- /dev/null +++ b/tests/ui/never_type/diverging-place-match.rs @@ -0,0 +1,74 @@ +#![feature(never_type)] + +fn not_a_read() -> ! { + 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 not_a_read_implicit() -> ! { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + let _ = *x; + } +} + +fn not_a_read_guide_coercion() -> ! { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + let _: () = *x; + //~^ ERROR mismatched types + } +} + +fn empty_match() -> ! { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + match *x { _ => {} }; + } +} + +fn field_projection() -> ! { + unsafe { + //~^ ERROR mismatched types + let x: *const (!, ()) = 0 as _; + let _ = (*x).0; + // ^ I think this is still UB, but because of the inbounds projection. + } +} + +fn covered_arm() -> ! { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + let (_ | 1i32) = *x; + //~^ ERROR mismatched types + } +} + +// FIXME: This *could* be considered a read of `!`, but we're not that sophisticated.. +fn uncovered_arm() -> ! { + unsafe { + //~^ ERROR mismatched types + let x: *const ! = 0 as _; + let (1i32 | _) = *x; + //~^ ERROR mismatched types + } +} + +fn coerce_ref_binding() -> ! { + unsafe { + let x: *const ! = 0 as _; + let ref _x: () = *x; + //~^ ERROR mismatched types + } +} + +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..74e1bfa0a6b64 --- /dev/null +++ b/tests/ui/never_type/diverging-place-match.stderr @@ -0,0 +1,142 @@ +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:4:5 + | +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 `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:14:5 + | +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | let _ = *x; +LL | | } + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:25:21 + | +LL | let _: () = *x; + | -- ^^ expected `()`, found `!` + | | + | expected due to this + | + = note: expected unit type `()` + found type `!` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:22:5 + | +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | let _: () = *x; +LL | | +LL | | } + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:31:5 + | +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | match *x { _ => {} }; +LL | | } + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:39:5 + | +LL | / unsafe { +LL | | +LL | | let x: *const (!, ()) = 0 as _; +LL | | let _ = (*x).0; +LL | | // ^ I think this is still UB, but because of the inbounds projection. +LL | | } + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:51:18 + | +LL | let (_ | 1i32) = *x; + | ^^^^ -- this expression has type `!` + | | + | expected `!`, found `i32` + | + = note: expected type `!` + found type `i32` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:48:5 + | +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | let (_ | 1i32) = *x; +LL | | +LL | | } + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:61:14 + | +LL | let (1i32 | _) = *x; + | ^^^^ -- this expression has type `!` + | | + | expected `!`, found `i32` + | + = note: expected type `!` + found type `i32` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:58:5 + | +LL | / unsafe { +LL | | +LL | | let x: *const ! = 0 as _; +LL | | let (1i32 | _) = *x; +LL | | +LL | | } + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/diverging-place-match.rs:69:26 + | +LL | let ref _x: () = *x; + | ^^ expected `()`, found `!` + | + = note: expected unit type `()` + found type `!` + +error: aborting due to 11 previous errors + +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..80d441729f746 --- /dev/null +++ b/tests/ui/raw-ref-op/never-place-isnt-diverging.rs @@ -0,0 +1,22 @@ +#![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 make_up_a_pointer() -> *const T { + unsafe { + let x: *const ! = 0 as _; + &raw const *x + //~^ ERROR mismatched types + } +} + +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..af9e788982179 --- /dev/null +++ b/tests/ui/raw-ref-op/never-place-isnt-diverging.stderr @@ -0,0 +1,34 @@ +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[E0308]: mismatched types + --> $DIR/never-place-isnt-diverging.rs:17:9 + | +LL | fn make_up_a_pointer() -> *const T { + | - -------- expected `*const T` because of return type + | | + | expected this type parameter +... +LL | &raw const *x + | ^^^^^^^^^^^^^ expected `*const T`, found `*const !` + | + = note: expected raw pointer `*const T` + found raw pointer `*const !` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/reachable/expr_assign.stderr b/tests/ui/reachable/expr_assign.stderr index c51156b3f40cf..cfbbe04db769d 100644 --- a/tests/ui/reachable/expr_assign.stderr +++ b/tests/ui/reachable/expr_assign.stderr @@ -14,12 +14,13 @@ LL | #![deny(unreachable_code)] | ^^^^^^^^^^^^^^^^ error: unreachable expression - --> $DIR/expr_assign.rs:20:14 + --> $DIR/expr_assign.rs:20:9 | LL | *p = return; - | -- ^^^^^^ unreachable expression - | | - | any code following this expression is unreachable + | ^^^^^------ + | | | + | | any code following this expression is unreachable + | unreachable expression error: unreachable expression --> $DIR/expr_assign.rs:26:15