Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not consider match/let/ref of place that evaluates to ! to diverge, disallow coercions from them too #129392

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Be far more strict about what we consider to be a read of never
  • Loading branch information
compiler-errors committed Oct 5, 2024
commit e8d5eb2a2b6cdddd6725501e1b54a33c278d9697
10 changes: 9 additions & 1 deletion compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,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, 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 (adjustments, _) = self.register_infer_ok_obligations(ok);
Expand Down Expand Up @@ -1268,6 +1273,9 @@ 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.
//
// 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;

Expand Down
87 changes: 48 additions & 39 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,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() && 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) {
let reported = self.dcx().span_delayed_bug(
expr.span,
Expand Down Expand Up @@ -245,7 +245,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// 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_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));
}

Expand Down Expand Up @@ -274,10 +274,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// 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.
///
/// This function is unfortunately a bit heuristical, though it is certainly
/// far sounder than the prior status quo: <https://github.com/rust-lang/rust/issues/117288>.
pub(super) fn expr_constitutes_read(&self, expr: &'tcx hir::Expr<'tcx>) -> bool {
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
Expand All @@ -300,15 +300,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr.hir_id != lhs.hir_id
}

// 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;`.
// 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().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, .. }) => {
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.
Expand Down Expand Up @@ -349,7 +349,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// 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_constitutes_read(*pat)
self.pat_guaranteed_to_constitute_read_for_never(*pat)
}

// These nodes (if they have a sub-expr) do constitute a read.
Expand Down Expand Up @@ -401,36 +401,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}

/// 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
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
/// perform `NeverToAny` coercions.
///
/// See above for the nuances of what happens when this returns true.
pub(super) fn pat_constitutes_read(&self, pat: &hir::Pat<'_>) -> bool {
let mut does_read = false;
pat.walk(|pat| {
match pat.kind {
hir::PatKind::Wild | hir::PatKind::Never | hir::PatKind::Or(_) => {
// Recurse
true
}
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(_) => {
does_read = true;
// No need to continue.
false
}
}
});
does_read
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.
Comment on lines +428 to +429
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that right?

A binding pattern let ref q = *ref_to_never; does not read from ref_to_never. I don't think this needs any FCP changes or another crater run.

All the other ones look good to me. I am unsure whether it's possible to write a test for that rn as I think we don't coerce there rn?

I guess please extend the tests/ui/never_type/diverging-place-match.rs to also test let ref x: () = *ptr_to_never;`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, and I can add it to the issue I opened too.

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")]
Expand Down
7 changes: 0 additions & 7 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use tracing::{debug, instrument, trace};
use ty::VariantDef;

use super::report_unexpected_variant_res;
use crate::diverges::Diverges;
use crate::gather_locals::DeclOrigin;
use crate::{FnCtxt, LoweredTy, errors};

Expand Down Expand Up @@ -277,12 +276,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
};

// 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);

// (note_1): In most of the cases where (note_1) is referenced
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ fn process_never(_1: *const !) -> () {
debug input => _1;
let mut _0: ();
scope 1 {
debug _input => _1;
debug _input => const ();
}

bb0: {
return;
unreachable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 5 additions & 2 deletions tests/mir-opt/uninhabited_enum.rs
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// 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 `!`.
}

fn main() {}
27 changes: 27 additions & 0 deletions tests/ui/never_type/diverging-place-match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
61 changes: 60 additions & 1 deletion tests/ui/never_type/diverging-place-match.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,65 @@ LL | | }
= note: expected 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`.
9 changes: 9 additions & 0 deletions tests/ui/raw-ref-op/never-place-isnt-diverging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ fn make_up_a_value<T>() -> T {
}
}

compiler-errors marked this conversation as resolved.
Show resolved Hide resolved

fn make_up_a_pointer<T>() -> *const T {
unsafe {
let x: *const ! = 0 as _;
&raw const *x
//~^ ERROR mismatched types
}
}

fn main() {}
16 changes: 15 additions & 1 deletion tests/ui/raw-ref-op/never-place-isnt-diverging.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ LL | | }
= note: expected type parameter `T`
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`.
2 changes: 1 addition & 1 deletion tests/ui/reachable/unwarned-match-on-never.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error: unreachable expression
--> $DIR/unwarned-match-on-never.rs:10:5
|
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 | match x {}
| ^^^^^^^^^^ unreachable expression
Expand Down
Loading