Skip to content

Commit 01ab12a

Browse files
authored
Rollup merge of rust-lang#120104 - Nadrieril:never-pat-diverges, r=compiler-errors
never_patterns: Count `!` bindings as diverging A binding that is a never pattern is not reachable, hence counts as diverging code. This allows in particular `fn foo(!: Void) -> SomeType {}` to typecheck. r? `@compiler-errors`
2 parents 06c2757 + 3ff1024 commit 01ab12a

11 files changed

+296
-6
lines changed

compiler/rustc_hir_typeck/src/check.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use std::cell::RefCell;
22

33
use crate::coercion::CoerceMany;
44
use crate::gather_locals::GatherLocalsVisitor;
5-
use crate::CoroutineTypes;
6-
use crate::FnCtxt;
5+
use crate::{CoroutineTypes, Diverges, FnCtxt};
76
use rustc_hir as hir;
87
use rustc_hir::def::DefKind;
98
use rustc_hir::intravisit::Visitor;
@@ -76,6 +75,12 @@ pub(super) fn check_fn<'a, 'tcx>(
7675
let ty: Option<&hir::Ty<'_>> = try { inputs_hir?.get(idx)? };
7776
let ty_span = ty.map(|ty| ty.span);
7877
fcx.check_pat_top(param.pat, param_ty, ty_span, None, None);
78+
if param.pat.is_never_pattern() {
79+
fcx.function_diverges_because_of_empty_arguments.set(Diverges::Always {
80+
span: param.pat.span,
81+
custom_note: Some("any code following a never pattern is unreachable"),
82+
});
83+
}
7984

8085
// Check that argument is Sized.
8186
if !params_can_be_unsized {
@@ -105,6 +110,7 @@ pub(super) fn check_fn<'a, 'tcx>(
105110
hir::FnRetTy::Return(ty) => ty.span,
106111
};
107112
fcx.require_type_is_sized(declared_ret_ty, return_or_body_span, traits::SizedReturnType);
113+
fcx.is_whole_body.set(true);
108114
fcx.check_return_expr(body.value, false);
109115

110116
// Finalize the return check by taking the LUB of the return types

compiler/rustc_hir_typeck/src/expr.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
208208
// without the final expr (e.g. `try { return; }`). We don't want to generate an
209209
// unreachable_code lint for it since warnings for autogenerated code are confusing.
210210
let is_try_block_generated_unit_expr = match expr.kind {
211-
ExprKind::Call(_, args) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {
212-
args.len() == 1 && args[0].span.is_desugaring(DesugaringKind::TryBlock)
211+
ExprKind::Call(_, [arg]) => {
212+
expr.span.is_desugaring(DesugaringKind::TryBlock)
213+
&& arg.span.is_desugaring(DesugaringKind::TryBlock)
213214
}
214-
215215
_ => false,
216216
};
217217

@@ -220,9 +220,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
220220
self.warn_if_unreachable(expr.hir_id, expr.span, "expression");
221221
}
222222

223-
// Hide the outer diverging and has_errors flags.
223+
// Whether a past expression diverges doesn't affect typechecking of this expression, so we
224+
// reset `diverges` while checking `expr`.
224225
let old_diverges = self.diverges.replace(Diverges::Maybe);
225226

227+
if self.is_whole_body.replace(false) {
228+
// If this expression is the whole body and the function diverges because of its
229+
// arguments, we check this here to ensure the body is considered to diverge.
230+
self.diverges.set(self.function_diverges_because_of_empty_arguments.get())
231+
};
232+
226233
let ty = ensure_sufficient_stack(|| match &expr.kind {
227234
hir::ExprKind::Path(
228235
qpath @ (hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)),

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14711471
/// Type check a `let` statement.
14721472
pub fn check_decl_local(&self, local: &'tcx hir::Local<'tcx>) {
14731473
self.check_decl(local.into());
1474+
if local.pat.is_never_pattern() {
1475+
self.diverges.set(Diverges::Always {
1476+
span: local.pat.span,
1477+
custom_note: Some("any code following a never pattern is unreachable"),
1478+
});
1479+
}
14741480
}
14751481

14761482
pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>) {

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ pub struct FnCtxt<'a, 'tcx> {
103103
/// the diverges flag is set to something other than `Maybe`.
104104
pub(super) diverges: Cell<Diverges>,
105105

106+
/// If one of the function arguments is a never pattern, this counts as diverging code. This
107+
/// affect typechecking of the function body.
108+
pub(super) function_diverges_because_of_empty_arguments: Cell<Diverges>,
109+
110+
/// Whether the currently checked node is the whole body of the function.
111+
pub(super) is_whole_body: Cell<bool>,
112+
106113
pub(super) enclosing_breakables: RefCell<EnclosingBreakables<'tcx>>,
107114

108115
pub(super) inh: &'a Inherited<'tcx>,
@@ -124,6 +131,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
124131
ret_coercion_span: Cell::new(None),
125132
coroutine_types: None,
126133
diverges: Cell::new(Diverges::Maybe),
134+
function_diverges_because_of_empty_arguments: Cell::new(Diverges::Maybe),
135+
is_whole_body: Cell::new(false),
127136
enclosing_breakables: RefCell::new(EnclosingBreakables {
128137
stack: Vec::new(),
129138
by_id: Default::default(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// edition: 2018
2+
// known-bug: #120240
3+
#![feature(never_patterns)]
4+
#![allow(incomplete_features)]
5+
6+
fn main() {}
7+
8+
enum Void {}
9+
10+
// Divergence is not detected.
11+
async fn async_never(!: Void) -> ! {} // gives an error
12+
13+
// Divergence is detected
14+
async fn async_let(x: Void) -> ! {
15+
let ! = x;
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/120240-async-fn-never-arg.rs:11:36
3+
|
4+
LL | async fn async_never(!: Void) -> ! {} // gives an error
5+
| ^^ expected `!`, found `()`
6+
|
7+
= note: expected type `!`
8+
found unit type `()`
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#![feature(never_patterns)]
2+
#![allow(incomplete_features)]
3+
#![deny(unreachable_patterns)]
4+
#![deny(unreachable_code)]
5+
6+
fn main() {}
7+
8+
enum Void {}
9+
10+
fn never_arg(!: Void) -> u32 {
11+
println!();
12+
//~^ ERROR unreachable statement
13+
}
14+
15+
fn ref_never_arg(&!: &Void) -> u32 {
16+
println!();
17+
//~^ ERROR unreachable statement
18+
}
19+
20+
fn never_let() -> u32 {
21+
let ptr: *const Void = std::ptr::null();
22+
unsafe {
23+
let ! = *ptr;
24+
}
25+
println!();
26+
//~^ ERROR unreachable statement
27+
}
28+
29+
fn never_match() -> u32 {
30+
let ptr: *const Void = std::ptr::null();
31+
unsafe {
32+
match *ptr { ! };
33+
}
34+
println!();
35+
//~^ ERROR unreachable statement
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
error: unreachable statement
2+
--> $DIR/diverge-causes-unreachable-code.rs:11:5
3+
|
4+
LL | fn never_arg(!: Void) -> u32 {
5+
| - any code following a never pattern is unreachable
6+
LL | println!();
7+
| ^^^^^^^^^^ unreachable statement
8+
|
9+
note: the lint level is defined here
10+
--> $DIR/diverge-causes-unreachable-code.rs:4:9
11+
|
12+
LL | #![deny(unreachable_code)]
13+
| ^^^^^^^^^^^^^^^^
14+
= 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)
15+
16+
error: unreachable statement
17+
--> $DIR/diverge-causes-unreachable-code.rs:16:5
18+
|
19+
LL | fn ref_never_arg(&!: &Void) -> u32 {
20+
| -- any code following a never pattern is unreachable
21+
LL | println!();
22+
| ^^^^^^^^^^ unreachable statement
23+
|
24+
= 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)
25+
26+
error: unreachable statement
27+
--> $DIR/diverge-causes-unreachable-code.rs:25:5
28+
|
29+
LL | let ! = *ptr;
30+
| - any code following a never pattern is unreachable
31+
LL | }
32+
LL | println!();
33+
| ^^^^^^^^^^ unreachable statement
34+
|
35+
= 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)
36+
37+
error: unreachable statement
38+
--> $DIR/diverge-causes-unreachable-code.rs:34:5
39+
|
40+
LL | match *ptr { ! };
41+
| ---------------- any code following this `match` expression is unreachable, as all arms diverge
42+
LL | }
43+
LL | println!();
44+
| ^^^^^^^^^^ unreachable statement
45+
|
46+
= 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)
47+
48+
error: aborting due to 4 previous errors
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![feature(never_patterns)]
2+
#![feature(let_chains)]
3+
#![allow(incomplete_features)]
4+
#![deny(unreachable_patterns)]
5+
6+
fn main() {}
7+
8+
enum Void {}
9+
10+
// Contrast with `./diverges.rs`: merely having an empty type around isn't enough to diverge.
11+
12+
fn wild_void(_: Void) -> u32 {}
13+
//~^ ERROR: mismatched types
14+
15+
fn wild_let() -> u32 {
16+
let ptr: *const Void = std::ptr::null();
17+
unsafe {
18+
//~^ ERROR: mismatched types
19+
let _ = *ptr;
20+
}
21+
}
22+
23+
fn wild_match() -> u32 {
24+
let ptr: *const Void = std::ptr::null();
25+
unsafe {
26+
match *ptr {
27+
_ => {} //~ ERROR: mismatched types
28+
}
29+
}
30+
}
31+
32+
fn binding_void(_x: Void) -> u32 {}
33+
//~^ ERROR: mismatched types
34+
35+
fn binding_let() -> u32 {
36+
let ptr: *const Void = std::ptr::null();
37+
unsafe {
38+
//~^ ERROR: mismatched types
39+
let _x = *ptr;
40+
}
41+
}
42+
43+
fn binding_match() -> u32 {
44+
let ptr: *const Void = std::ptr::null();
45+
unsafe {
46+
match *ptr {
47+
_x => {} //~ ERROR: mismatched types
48+
}
49+
}
50+
}
51+
52+
// Don't confuse this with a `let !` statement.
53+
fn let_chain(x: Void) -> u32 {
54+
if let true = true && let ! = x {}
55+
//~^ ERROR: mismatched types
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/diverges-not.rs:12:26
3+
|
4+
LL | fn wild_void(_: Void) -> u32 {}
5+
| --------- ^^^ expected `u32`, found `()`
6+
| |
7+
| implicitly returns `()` as its body has no tail or `return` expression
8+
9+
error[E0308]: mismatched types
10+
--> $DIR/diverges-not.rs:17:5
11+
|
12+
LL | / unsafe {
13+
LL | |
14+
LL | | let _ = *ptr;
15+
LL | | }
16+
| |_____^ expected `u32`, found `()`
17+
18+
error[E0308]: mismatched types
19+
--> $DIR/diverges-not.rs:27:18
20+
|
21+
LL | _ => {}
22+
| ^^ expected `u32`, found `()`
23+
24+
error[E0308]: mismatched types
25+
--> $DIR/diverges-not.rs:32:30
26+
|
27+
LL | fn binding_void(_x: Void) -> u32 {}
28+
| ------------ ^^^ expected `u32`, found `()`
29+
| |
30+
| implicitly returns `()` as its body has no tail or `return` expression
31+
32+
error[E0308]: mismatched types
33+
--> $DIR/diverges-not.rs:37:5
34+
|
35+
LL | / unsafe {
36+
LL | |
37+
LL | | let _x = *ptr;
38+
LL | | }
39+
| |_____^ expected `u32`, found `()`
40+
41+
error[E0308]: mismatched types
42+
--> $DIR/diverges-not.rs:47:19
43+
|
44+
LL | _x => {}
45+
| ^^ expected `u32`, found `()`
46+
47+
error[E0308]: mismatched types
48+
--> $DIR/diverges-not.rs:54:37
49+
|
50+
LL | if let true = true && let ! = x {}
51+
| ^^ expected `u32`, found `()`
52+
53+
error: aborting due to 7 previous errors
54+
55+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// check-pass
2+
// edition: 2018
3+
#![feature(never_patterns)]
4+
#![allow(incomplete_features)]
5+
#![deny(unreachable_patterns)]
6+
7+
fn main() {}
8+
9+
enum Void {}
10+
11+
// A never pattern alone diverges.
12+
13+
fn never_arg(!: Void) -> ! {}
14+
15+
fn never_arg_returns_anything<T>(!: Void) -> T {}
16+
17+
fn ref_never_arg(&!: &Void) -> ! {}
18+
19+
fn never_let() -> ! {
20+
let ptr: *const Void = std::ptr::null();
21+
unsafe {
22+
let ! = *ptr;
23+
}
24+
}
25+
26+
fn never_match() -> ! {
27+
let ptr: *const Void = std::ptr::null();
28+
unsafe {
29+
match *ptr { ! };
30+
}
31+
// Ensures this typechecks because of divergence and not the type of the match expression.
32+
println!();
33+
}
34+
35+
// Note: divergence is not detected for async fns when the `!` is in the argument (#120240).
36+
async fn async_let(x: Void) -> ! {
37+
let ! = x;
38+
}

0 commit comments

Comments
 (0)