From dbc1f074bc0958df74bda0f7b8d0f185c2070dea Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 Jan 2024 17:43:27 +0100 Subject: [PATCH 1/5] Tweak --- compiler/rustc_hir_typeck/src/expr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index aca17ec77a4bb..4167c417d8332 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -208,10 +208,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // without the final expr (e.g. `try { return; }`). We don't want to generate an // unreachable_code lint for it since warnings for autogenerated code are confusing. let is_try_block_generated_unit_expr = match expr.kind { - ExprKind::Call(_, args) if expr.span.is_desugaring(DesugaringKind::TryBlock) => { - args.len() == 1 && args[0].span.is_desugaring(DesugaringKind::TryBlock) + ExprKind::Call(_, [arg]) => { + expr.span.is_desugaring(DesugaringKind::TryBlock) + && arg.span.is_desugaring(DesugaringKind::TryBlock) } - _ => false, }; From a9ea07d17c329f5a36e01826f704c16093d02f14 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 Jan 2024 16:16:30 +0100 Subject: [PATCH 2/5] Never pattern in function arguments diverges --- compiler/rustc_hir_typeck/src/check.rs | 10 +++- compiler/rustc_hir_typeck/src/expr.rs | 9 ++- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 9 +++ .../diverge-causes-unreachable-code.rs | 35 ++++++++++++ .../diverge-causes-unreachable-code.stderr | 38 +++++++++++++ .../rfc-0000-never_patterns/diverges-not.rs | 56 +++++++++++++++++++ .../diverges-not.stderr | 55 ++++++++++++++++++ .../rfcs/rfc-0000-never_patterns/diverges.rs | 29 ++++++++++ 8 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.rs create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.stderr create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs index c887368b2a233..4d9f5b831c161 100644 --- a/compiler/rustc_hir_typeck/src/check.rs +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -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; @@ -76,6 +75,12 @@ 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.function_diverges_because_of_empty_arguments.set(Diverges::Always { + span: param.pat.span, + custom_note: Some("any code following a never pattern is unreachable"), + }); + } // Check that argument is Sized. if !params_can_be_unsized { @@ -105,6 +110,7 @@ pub(super) fn check_fn<'a, 'tcx>( hir::FnRetTy::Return(ty) => ty.span, }; fcx.require_type_is_sized(declared_ret_ty, return_or_body_span, traits::SizedReturnType); + fcx.is_whole_body.set(true); fcx.check_return_expr(body.value, false); // Finalize the return check by taking the LUB of the return types diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 4167c417d8332..3be37d1a3be13 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -220,9 +220,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.warn_if_unreachable(expr.hir_id, expr.span, "expression"); } - // Hide the outer diverging and has_errors flags. + // Whether a past expression diverges doesn't affect typechecking of this expression, so we + // reset `diverges` while checking `expr`. let old_diverges = self.diverges.replace(Diverges::Maybe); + if self.is_whole_body.replace(false) { + // If this expression is the whole body and the function diverges because of its + // arguments, we check this here to ensure the body is considered to diverge. + self.diverges.set(self.function_diverges_because_of_empty_arguments.get()) + }; + let ty = ensure_sufficient_stack(|| match &expr.kind { hir::ExprKind::Path( qpath @ (hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)), diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index e6c2091d85add..f65e9b698ab2a 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -103,6 +103,13 @@ pub struct FnCtxt<'a, 'tcx> { /// the diverges flag is set to something other than `Maybe`. pub(super) diverges: Cell, + /// If one of the function arguments is a never pattern, this counts as diverging code. This + /// affect typechecking of the function body. + pub(super) function_diverges_because_of_empty_arguments: Cell, + + /// Whether the currently checked node is the whole body of the function. + pub(super) is_whole_body: Cell, + pub(super) enclosing_breakables: RefCell>, pub(super) inh: &'a Inherited<'tcx>, @@ -124,6 +131,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ret_coercion_span: Cell::new(None), coroutine_types: None, diverges: Cell::new(Diverges::Maybe), + function_diverges_because_of_empty_arguments: Cell::new(Diverges::Maybe), + is_whole_body: Cell::new(false), enclosing_breakables: RefCell::new(EnclosingBreakables { stack: Vec::new(), by_id: Default::default(), diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs new file mode 100644 index 0000000000000..5c852c8adcccf --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs @@ -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 +} diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr new file mode 100644 index 0000000000000..c9cd3f53944e4 --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr @@ -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 + diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.rs new file mode 100644 index 0000000000000..6b85ada3aadee --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.rs @@ -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 +} diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.stderr new file mode 100644 index 0000000000000..08a1bbe9bffac --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.stderr @@ -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`. diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs new file mode 100644 index 0000000000000..40aa2bd29e594 --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs @@ -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. +} From d1f1075931983be0a2ae91905fe8a6da50149d3d Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 Jan 2024 18:22:08 +0100 Subject: [PATCH 3/5] Never pattern in `let` statement diverges --- compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs | 6 ++++++ .../diverge-causes-unreachable-code.rs | 15 ++++++++------- .../diverge-causes-unreachable-code.stderr | 15 +++++++++++++-- tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs | 12 ++++++------ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index ddb4224b60db1..136ed1a709e94 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1471,6 +1471,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Type check a `let` statement. pub fn check_decl_local(&self, local: &'tcx hir::Local<'tcx>) { self.check_decl(local.into()); + if local.pat.is_never_pattern() { + self.diverges.set(Diverges::Always { + span: local.pat.span, + custom_note: Some("any code following a never pattern is unreachable"), + }); + } } pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>) { diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs index 5c852c8adcccf..f7e4007b920d9 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.rs @@ -17,13 +17,14 @@ fn ref_never_arg(&!: &Void) -> u32 { //~^ ERROR unreachable statement } -//fn never_let() -> u32 { -// let ptr: *const Void = std::ptr::null(); -// unsafe { -// let ! = *ptr; -// } -// println!(); -//} +fn never_let() -> u32 { + let ptr: *const Void = std::ptr::null(); + unsafe { + let ! = *ptr; + } + println!(); + //~^ ERROR unreachable statement +} fn never_match() -> u32 { let ptr: *const Void = std::ptr::null(); diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr index c9cd3f53944e4..c33a5855d5068 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverge-causes-unreachable-code.stderr @@ -24,7 +24,18 @@ LL | println!(); = 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 + --> $DIR/diverge-causes-unreachable-code.rs:25:5 + | +LL | let ! = *ptr; + | - any code following a never pattern is unreachable +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: unreachable statement + --> $DIR/diverge-causes-unreachable-code.rs:34:5 | LL | match *ptr { ! }; | ---------------- any code following this `match` expression is unreachable, as all arms diverge @@ -34,5 +45,5 @@ LL | println!(); | = 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 +error: aborting due to 4 previous errors diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs index 40aa2bd29e594..6f4b81b9b25b0 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs @@ -13,12 +13,12 @@ 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_let() -> u32 { + let ptr: *const Void = std::ptr::null(); + unsafe { + let ! = *ptr; + } +} fn never_match() -> u32 { let ptr: *const Void = std::ptr::null(); From c5a4e074f052d0528451035df58c0f612ee45118 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 Jan 2024 16:14:17 +0100 Subject: [PATCH 4/5] Use `-> !` to test divergence --- tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs index 6f4b81b9b25b0..57151e246ff56 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs @@ -9,21 +9,24 @@ enum Void {} // A never pattern alone diverges. -fn never_arg(!: Void) -> u32 {} +fn never_arg(!: Void) -> ! {} -fn ref_never_arg(&!: &Void) -> u32 {} +fn never_arg_returns_anything(!: Void) -> T {} -fn never_let() -> u32 { +fn ref_never_arg(&!: &Void) -> ! {} + +fn never_let() -> ! { let ptr: *const Void = std::ptr::null(); unsafe { let ! = *ptr; } } -fn never_match() -> u32 { +fn never_match() -> ! { let ptr: *const Void = std::ptr::null(); unsafe { match *ptr { ! }; } - println!(); // Ensures this typechecks because of divergence. + // Ensures this typechecks because of divergence and not the type of the match expression. + println!(); } From 3ff10242fee5356cb4be91db6df2eee6b9a34089 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 Jan 2024 21:14:16 +0100 Subject: [PATCH 5/5] Test async fn --- .../120240-async-fn-never-arg.rs | 16 ++++++++++++++++ .../120240-async-fn-never-arg.stderr | 12 ++++++++++++ .../ui/rfcs/rfc-0000-never_patterns/diverges.rs | 6 ++++++ 3 files changed, 34 insertions(+) create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.rs create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.stderr diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.rs b/tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.rs new file mode 100644 index 0000000000000..9150c831c8960 --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.rs @@ -0,0 +1,16 @@ +// edition: 2018 +// known-bug: #120240 +#![feature(never_patterns)] +#![allow(incomplete_features)] + +fn main() {} + +enum Void {} + +// Divergence is not detected. +async fn async_never(!: Void) -> ! {} // gives an error + +// Divergence is detected +async fn async_let(x: Void) -> ! { + let ! = x; +} diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.stderr new file mode 100644 index 0000000000000..fa71feee5f5aa --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/120240-async-fn-never-arg.stderr @@ -0,0 +1,12 @@ +error[E0308]: mismatched types + --> $DIR/120240-async-fn-never-arg.rs:11:36 + | +LL | async fn async_never(!: Void) -> ! {} // gives an error + | ^^ expected `!`, found `()` + | + = note: expected type `!` + found unit type `()` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs index 57151e246ff56..3783100b50282 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/diverges.rs @@ -1,4 +1,5 @@ // check-pass +// edition: 2018 #![feature(never_patterns)] #![allow(incomplete_features)] #![deny(unreachable_patterns)] @@ -30,3 +31,8 @@ fn never_match() -> ! { // Ensures this typechecks because of divergence and not the type of the match expression. println!(); } + +// Note: divergence is not detected for async fns when the `!` is in the argument (#120240). +async fn async_let(x: Void) -> ! { + let ! = x; +}