Skip to content

Commit

Permalink
Never pattern in function arguments diverges
Browse files Browse the repository at this point in the history
  • Loading branch information
Nadrieril committed Jan 18, 2024
1 parent 25df572 commit be29ccb
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 8 deletions.
10 changes: 8 additions & 2 deletions compiler/rustc_hir_typeck/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use std::cell::RefCell;

use crate::coercion::CoerceMany;
use crate::gather_locals::GatherLocalsVisitor;
use crate::CoroutineTypes;
use crate::FnCtxt;
use crate::{CoroutineTypes, Diverges, FnCtxt};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::intravisit::Visitor;
Expand Down Expand Up @@ -76,6 +75,13 @@ pub(super) fn check_fn<'a, 'tcx>(
let ty: Option<&hir::Ty<'_>> = try { inputs_hir?.get(idx)? };
let ty_span = ty.map(|ty| ty.span);
fcx.check_pat_top(param.pat, param_ty, ty_span, None, None);
if param.pat.is_never_pattern() {
fcx.diverges.set(Diverges::Always {
span: param.pat.span,
custom_note: Some("any code following a never pattern is unreachable"),
});
fcx.is_whole_body_and_arguments_diverge.set(true);
}

// Check that argument is Sized.
if !params_can_be_unsized {
Expand Down
16 changes: 10 additions & 6 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => false,
};

// Warn for expressions after diverging siblings.
if !is_try_block_generated_unit_expr {
self.warn_if_unreachable(expr.hir_id, expr.span, "expression");
}
let old_diverges = if self.is_whole_body_and_arguments_diverge.replace(false) {
self.diverges.get()
} else {
// Warn for expressions after diverging siblings.
if !is_try_block_generated_unit_expr {
self.warn_if_unreachable(expr.hir_id, expr.span, "expression");
}

// Hide the outer diverging and has_errors flags.
let old_diverges = self.diverges.replace(Diverges::Maybe);
// Hide the outer diverging flag.
self.diverges.replace(Diverges::Maybe)
};

let ty = ensure_sufficient_stack(|| match &expr.kind {
hir::ExprKind::Path(
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ pub struct FnCtxt<'a, 'tcx> {
/// the diverges flag is set to something other than `Maybe`.
pub(super) diverges: Cell<Diverges>,

/// Whether the currently checked node is the whole body of the function, and one of the
/// function arguments is a never pattern. This makes the whole body of the function diverge.
pub(super) is_whole_body_and_arguments_diverge: Cell<bool>,

pub(super) enclosing_breakables: RefCell<EnclosingBreakables<'tcx>>,

pub(super) inh: &'a Inherited<'tcx>,
Expand All @@ -124,6 +128,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ret_coercion_span: Cell::new(None),
coroutine_types: None,
diverges: Cell::new(Diverges::Maybe),
is_whole_body_and_arguments_diverge: Cell::new(false),
enclosing_breakables: RefCell::new(EnclosingBreakables {
stack: Vec::new(),
by_id: Default::default(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![feature(never_patterns)]
#![allow(incomplete_features)]
#![deny(unreachable_patterns)]
#![deny(unreachable_code)]

fn main() {}

enum Void {}

fn never_arg(!: Void) -> u32 {
println!();
//~^ ERROR unreachable statement
}

fn ref_never_arg(&!: &Void) -> u32 {
println!();
//~^ ERROR unreachable statement
}

//fn never_let() -> u32 {
// let ptr: *const Void = std::ptr::null();
// unsafe {
// let ! = *ptr;
// }
// println!();
//}

fn never_match() -> u32 {
let ptr: *const Void = std::ptr::null();
unsafe {
match *ptr { ! };
}
println!();
//~^ ERROR unreachable statement
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
error: unreachable statement
--> $DIR/diverge-causes-unreachable-code.rs:11:5
|
LL | fn never_arg(!: Void) -> u32 {
| - any code following a never pattern is unreachable
LL | println!();
| ^^^^^^^^^^ unreachable statement
|
note: the lint level is defined here
--> $DIR/diverge-causes-unreachable-code.rs:4:9
|
LL | #![deny(unreachable_code)]
| ^^^^^^^^^^^^^^^^
= note: this error originates in the macro `$crate::print` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: unreachable statement
--> $DIR/diverge-causes-unreachable-code.rs:16:5
|
LL | fn ref_never_arg(&!: &Void) -> u32 {
| -- any code following a never pattern is unreachable
LL | println!();
| ^^^^^^^^^^ unreachable statement
|
= note: this error originates in the macro `$crate::print` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: unreachable statement
--> $DIR/diverge-causes-unreachable-code.rs:33:5
|
LL | match *ptr { ! };
| ---------------- any code following this `match` expression is unreachable, as all arms diverge
LL | }
LL | println!();
| ^^^^^^^^^^ unreachable statement
|
= note: this error originates in the macro `$crate::print` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors

56 changes: 56 additions & 0 deletions tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#![feature(never_patterns)]
#![feature(let_chains)]
#![allow(incomplete_features)]
#![deny(unreachable_patterns)]

fn main() {}

enum Void {}

// Contrast with `./diverges.rs`: merely having an empty type around isn't enough to diverge.

fn wild_void(_: Void) -> u32 {}
//~^ ERROR: mismatched types

fn wild_let() -> u32 {
let ptr: *const Void = std::ptr::null();
unsafe {
//~^ ERROR: mismatched types
let _ = *ptr;
}
}

fn wild_match() -> u32 {
let ptr: *const Void = std::ptr::null();
unsafe {
match *ptr {
_ => {} //~ ERROR: mismatched types
}
}
}

fn binding_void(_x: Void) -> u32 {}
//~^ ERROR: mismatched types

fn binding_let() -> u32 {
let ptr: *const Void = std::ptr::null();
unsafe {
//~^ ERROR: mismatched types
let _x = *ptr;
}
}

fn binding_match() -> u32 {
let ptr: *const Void = std::ptr::null();
unsafe {
match *ptr {
_x => {} //~ ERROR: mismatched types
}
}
}

// Don't confuse this with a `let !` statement.
fn let_chain(x: Void) -> u32 {
if let true = true && let ! = x {}
//~^ ERROR: mismatched types
}
55 changes: 55 additions & 0 deletions tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
error[E0308]: mismatched types
--> $DIR/diverges-not.rs:12:26
|
LL | fn wild_void(_: Void) -> u32 {}
| --------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> $DIR/diverges-not.rs:17:5
|
LL | / unsafe {
LL | |
LL | | let _ = *ptr;
LL | | }
| |_____^ expected `u32`, found `()`

error[E0308]: mismatched types
--> $DIR/diverges-not.rs:27:18
|
LL | _ => {}
| ^^ expected `u32`, found `()`

error[E0308]: mismatched types
--> $DIR/diverges-not.rs:32:30
|
LL | fn binding_void(_x: Void) -> u32 {}
| ------------ ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> $DIR/diverges-not.rs:37:5
|
LL | / unsafe {
LL | |
LL | | let _x = *ptr;
LL | | }
| |_____^ expected `u32`, found `()`

error[E0308]: mismatched types
--> $DIR/diverges-not.rs:47:19
|
LL | _x => {}
| ^^ expected `u32`, found `()`

error[E0308]: mismatched types
--> $DIR/diverges-not.rs:54:37
|
LL | if let true = true && let ! = x {}
| ^^ expected `u32`, found `()`

error: aborting due to 7 previous errors

For more information about this error, try `rustc --explain E0308`.
29 changes: 29 additions & 0 deletions tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// check-pass
#![feature(never_patterns)]
#![allow(incomplete_features)]
#![deny(unreachable_patterns)]

fn main() {}

enum Void {}

// A never pattern alone diverges.

fn never_arg(!: Void) -> u32 {}

fn ref_never_arg(&!: &Void) -> u32 {}

// fn never_let() -> u32 {
// let ptr: *const Void = std::ptr::null();
// unsafe {
// let ! = *ptr;
// }
// }

fn never_match() -> u32 {
let ptr: *const Void = std::ptr::null();
unsafe {
match *ptr { ! };
}
println!(); // Ensures this typechecks because of divergence.
}

0 comments on commit be29ccb

Please sign in to comment.