Be far more strict about what we consider to be a read of never
This commit is contained in:
parent
d2bd018dad
commit
e8d5eb2a2b
@ -1066,7 +1066,12 @@ pub(crate) fn coerce(
|
|||||||
|
|
||||||
let cause =
|
let cause =
|
||||||
cause.unwrap_or_else(|| self.cause(expr.span, ObligationCauseCode::ExprAssignable));
|
cause.unwrap_or_else(|| self.cause(expr.span, ObligationCauseCode::ExprAssignable));
|
||||||
let coerce = Coerce::new(self, cause, allow_two_phase, self.expr_constitutes_read(expr));
|
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 ok = self.commit_if_ok(|_| coerce.coerce(source, target))?;
|
||||||
|
|
||||||
let (adjustments, _) = self.register_infer_ok_obligations(ok);
|
let (adjustments, _) = self.register_infer_ok_obligations(ok);
|
||||||
@ -1268,6 +1273,9 @@ fn try_find_coercion_lub<E>(
|
|||||||
// probably aren't processing function arguments here and even if we were,
|
// 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
|
// they're going to get autorefed again anyway and we can apply 2-phase borrows
|
||||||
// at that time.
|
// at that time.
|
||||||
|
//
|
||||||
|
// 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);
|
let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No, true);
|
||||||
coerce.use_lub = true;
|
coerce.use_lub = true;
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ pub(crate) fn check_expr_has_type_or_error(
|
|||||||
|
|
||||||
// While we don't allow *arbitrary* coercions here, we *do* allow
|
// While we don't allow *arbitrary* coercions here, we *do* allow
|
||||||
// coercions from ! to `expected`.
|
// coercions from ! to `expected`.
|
||||||
if ty.is_never() && self.expr_constitutes_read(expr) {
|
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) {
|
if let Some(_) = self.typeck_results.borrow().adjustments().get(expr.hir_id) {
|
||||||
let reported = self.dcx().span_delayed_bug(
|
let reported = self.dcx().span_delayed_bug(
|
||||||
expr.span,
|
expr.span,
|
||||||
@ -245,7 +245,7 @@ pub(super) fn check_expr_with_expectation_and_args(
|
|||||||
// unless it's a place expression that isn't being read from, in which case
|
// 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 `!`.
|
// diverging would be unsound since we may never actually read the `!`.
|
||||||
// e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
|
// e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
|
||||||
if ty.is_never() && self.expr_constitutes_read(expr) {
|
if ty.is_never() && self.expr_guaranteed_to_constitute_read_for_never(expr) {
|
||||||
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
|
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,10 +274,10 @@ pub(super) fn check_expr_with_expectation_and_args(
|
|||||||
/// expression and the *parent* expression is the scrutinee of a match or
|
/// expression and the *parent* expression is the scrutinee of a match or
|
||||||
/// the pointee of an `&` addr-of expression, since both of those parent
|
/// the pointee of an `&` addr-of expression, since both of those parent
|
||||||
/// expressions take a *place* and not a value.
|
/// expressions take a *place* and not a value.
|
||||||
///
|
pub(super) fn expr_guaranteed_to_constitute_read_for_never(
|
||||||
/// This function is unfortunately a bit heuristical, though it is certainly
|
&self,
|
||||||
/// far sounder than the prior status quo: <https://github.com/rust-lang/rust/issues/117288>.
|
expr: &'tcx hir::Expr<'tcx>,
|
||||||
pub(super) fn expr_constitutes_read(&self, expr: &'tcx hir::Expr<'tcx>) -> bool {
|
) -> bool {
|
||||||
// We only care about place exprs. Anything else returns an immediate
|
// We only care about place exprs. Anything else returns an immediate
|
||||||
// which would constitute a read. We don't care about distinguishing
|
// which would constitute a read. We don't care about distinguishing
|
||||||
// "syntactic" place exprs since if the base of a field projection is
|
// "syntactic" place exprs since if the base of a field projection is
|
||||||
@ -300,15 +300,15 @@ pub(super) fn expr_constitutes_read(&self, expr: &'tcx hir::Expr<'tcx>) -> bool
|
|||||||
expr.hir_id != lhs.hir_id
|
expr.hir_id != lhs.hir_id
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a subpattern that performs a read, we want to consider this
|
// See note on `PatKind::Or` below for why this is `all`.
|
||||||
// to diverge for compatibility to support something like `let x: () = *never_ptr;`.
|
|
||||||
ExprKind::Match(scrutinee, arms, _) => {
|
ExprKind::Match(scrutinee, arms, _) => {
|
||||||
assert_eq!(scrutinee.hir_id, expr.hir_id);
|
assert_eq!(scrutinee.hir_id, expr.hir_id);
|
||||||
arms.iter().any(|arm| self.pat_constitutes_read(arm.pat))
|
arms.iter()
|
||||||
|
.all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat))
|
||||||
}
|
}
|
||||||
ExprKind::Let(hir::LetExpr { init, pat, .. }) => {
|
ExprKind::Let(hir::LetExpr { init, pat, .. }) => {
|
||||||
assert_eq!(init.hir_id, expr.hir_id);
|
assert_eq!(init.hir_id, expr.hir_id);
|
||||||
self.pat_constitutes_read(*pat)
|
self.pat_guaranteed_to_constitute_read_for_never(*pat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any expression child of these expressions constitute reads.
|
// Any expression child of these expressions constitute reads.
|
||||||
@ -349,7 +349,7 @@ pub(super) fn expr_constitutes_read(&self, expr: &'tcx hir::Expr<'tcx>) -> bool
|
|||||||
// to diverge for compatibility to support something like `let x: () = *never_ptr;`.
|
// to diverge for compatibility to support something like `let x: () = *never_ptr;`.
|
||||||
hir::Node::LetStmt(hir::LetStmt { init: Some(target), pat, .. }) => {
|
hir::Node::LetStmt(hir::LetStmt { init: Some(target), pat, .. }) => {
|
||||||
assert_eq!(target.hir_id, expr.hir_id);
|
assert_eq!(target.hir_id, expr.hir_id);
|
||||||
self.pat_constitutes_read(*pat)
|
self.pat_guaranteed_to_constitute_read_for_never(*pat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// These nodes (if they have a sub-expr) do constitute a read.
|
// These nodes (if they have a sub-expr) do constitute a read.
|
||||||
@ -401,17 +401,32 @@ pub(super) fn expr_constitutes_read(&self, expr: &'tcx hir::Expr<'tcx>) -> bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this pattern constitutes a read of value of the scrutinee that
|
/// Whether this pattern constitutes a read of value of the scrutinee that
|
||||||
/// it is matching against.
|
/// 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.
|
/// See above for the nuances of what happens when this returns true.
|
||||||
pub(super) fn pat_constitutes_read(&self, pat: &hir::Pat<'_>) -> bool {
|
pub(super) fn pat_guaranteed_to_constitute_read_for_never(&self, pat: &hir::Pat<'_>) -> bool {
|
||||||
let mut does_read = false;
|
|
||||||
pat.walk(|pat| {
|
|
||||||
match pat.kind {
|
match pat.kind {
|
||||||
hir::PatKind::Wild | hir::PatKind::Never | hir::PatKind::Or(_) => {
|
// Does not constitute a read.
|
||||||
// Recurse
|
hir::PatKind::Wild => false,
|
||||||
true
|
|
||||||
|
// 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::Binding(_, _, _, _)
|
||||||
| hir::PatKind::Struct(_, _, _)
|
| hir::PatKind::Struct(_, _, _)
|
||||||
| hir::PatKind::TupleStruct(_, _, _)
|
| hir::PatKind::TupleStruct(_, _, _)
|
||||||
@ -423,15 +438,9 @@ pub(super) fn pat_constitutes_read(&self, pat: &hir::Pat<'_>) -> bool {
|
|||||||
| hir::PatKind::Lit(_)
|
| hir::PatKind::Lit(_)
|
||||||
| hir::PatKind::Range(_, _, _)
|
| hir::PatKind::Range(_, _, _)
|
||||||
| hir::PatKind::Slice(_, _, _)
|
| hir::PatKind::Slice(_, _, _)
|
||||||
| hir::PatKind::Err(_) => {
|
| hir::PatKind::Err(_) => true,
|
||||||
does_read = true;
|
|
||||||
// No need to continue.
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
does_read
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self, expr), level = "debug")]
|
#[instrument(skip(self, expr), level = "debug")]
|
||||||
fn check_expr_kind(
|
fn check_expr_kind(
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
use ty::VariantDef;
|
use ty::VariantDef;
|
||||||
|
|
||||||
use super::report_unexpected_variant_res;
|
use super::report_unexpected_variant_res;
|
||||||
use crate::diverges::Diverges;
|
|
||||||
use crate::gather_locals::DeclOrigin;
|
use crate::gather_locals::DeclOrigin;
|
||||||
use crate::{FnCtxt, LoweredTy, errors};
|
use crate::{FnCtxt, LoweredTy, errors};
|
||||||
|
|
||||||
@ -277,12 +276,6 @@ fn check_pat(&self, pat: &'tcx Pat<'tcx>, expected: Ty<'tcx>, pat_info: PatInfo<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// All other patterns constitute a read, which causes us to diverge
|
|
||||||
// if the type is never.
|
|
||||||
if ty.is_never() && self.pat_constitutes_read(pat) {
|
|
||||||
self.diverges.set(self.diverges.get() | Diverges::always(pat.span));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_ty(pat.hir_id, ty);
|
self.write_ty(pat.hir_id, ty);
|
||||||
|
|
||||||
// (note_1): In most of the cases where (note_1) is referenced
|
// (note_1): In most of the cases where (note_1) is referenced
|
||||||
|
@ -4,10 +4,10 @@ fn process_never(_1: *const !) -> () {
|
|||||||
debug input => _1;
|
debug input => _1;
|
||||||
let mut _0: ();
|
let mut _0: ();
|
||||||
scope 1 {
|
scope 1 {
|
||||||
debug _input => _1;
|
debug _input => const ();
|
||||||
}
|
}
|
||||||
|
|
||||||
bb0: {
|
bb0: {
|
||||||
return;
|
unreachable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ fn process_void(_1: *const Void) -> () {
|
|||||||
debug input => _1;
|
debug input => _1;
|
||||||
let mut _0: ();
|
let mut _0: ();
|
||||||
scope 1 {
|
scope 1 {
|
||||||
debug _input => _1;
|
debug _input => const ZeroSized: Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
bb0: {
|
bb0: {
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
// skip-filecheck
|
// skip-filecheck
|
||||||
#![feature(never_type)]
|
#![feature(never_type)]
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub enum Void {}
|
pub enum Void {}
|
||||||
|
|
||||||
// EMIT_MIR uninhabited_enum.process_never.SimplifyLocals-final.after.mir
|
// EMIT_MIR uninhabited_enum.process_never.SimplifyLocals-final.after.mir
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn process_never(input: *const !) {
|
pub fn process_never(input: *const !) {
|
||||||
let _input = unsafe { &*input };
|
let _input = unsafe { *input };
|
||||||
}
|
}
|
||||||
|
|
||||||
// EMIT_MIR uninhabited_enum.process_void.SimplifyLocals-final.after.mir
|
// EMIT_MIR uninhabited_enum.process_void.SimplifyLocals-final.after.mir
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn process_void(input: *const Void) {
|
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 `!`.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -44,4 +44,31 @@ fn field_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() {}
|
fn main() {}
|
||||||
|
@ -78,6 +78,65 @@ LL | | }
|
|||||||
= note: expected type `!`
|
= note: expected type `!`
|
||||||
found unit type `()`
|
found unit type `()`
|
||||||
|
|
||||||
error: aborting due to 6 previous errors
|
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`.
|
For more information about this error, try `rustc --explain E0308`.
|
||||||
|
@ -10,4 +10,13 @@ fn make_up_a_value<T>() -> T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn make_up_a_pointer<T>() -> *const T {
|
||||||
|
unsafe {
|
||||||
|
let x: *const ! = 0 as _;
|
||||||
|
&raw const *x
|
||||||
|
//~^ ERROR mismatched types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -15,6 +15,20 @@ LL | | }
|
|||||||
= note: expected type parameter `T`
|
= note: expected type parameter `T`
|
||||||
found unit type `()`
|
found unit type `()`
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error[E0308]: mismatched types
|
||||||
|
--> $DIR/never-place-isnt-diverging.rs:17:9
|
||||||
|
|
|
||||||
|
LL | fn make_up_a_pointer<T>() -> *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`.
|
For more information about this error, try `rustc --explain E0308`.
|
||||||
|
@ -2,7 +2,7 @@ error: unreachable expression
|
|||||||
--> $DIR/unwarned-match-on-never.rs:10:5
|
--> $DIR/unwarned-match-on-never.rs:10:5
|
||||||
|
|
|
|
||||||
LL | match x {}
|
LL | match x {}
|
||||||
| ---------- any code following this expression is unreachable
|
| - any code following this expression is unreachable
|
||||||
LL | // But matches in unreachable code are warned.
|
LL | // But matches in unreachable code are warned.
|
||||||
LL | match x {}
|
LL | match x {}
|
||||||
| ^^^^^^^^^^ unreachable expression
|
| ^^^^^^^^^^ unreachable expression
|
||||||
|
Loading…
Reference in New Issue
Block a user