From a947c4c2c34a5bc1f25d5e9fd5cc269b4ae0d330 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 5 Jan 2024 17:19:28 +0100 Subject: [PATCH 1/3] Add tests --- tests/ui/pattern/never_patterns.stderr | 17 ----- .../rfc-0000-never_patterns/typeck.rs} | 64 +++++++++++++++---- 2 files changed, 51 insertions(+), 30 deletions(-) delete mode 100644 tests/ui/pattern/never_patterns.stderr rename tests/ui/{pattern/never_patterns.rs => rfcs/rfc-0000-never_patterns/typeck.rs} (62%) diff --git a/tests/ui/pattern/never_patterns.stderr b/tests/ui/pattern/never_patterns.stderr deleted file mode 100644 index 20eeb01cf71..00000000000 --- a/tests/ui/pattern/never_patterns.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0005]: refutable pattern in local binding - --> $DIR/never_patterns.rs:10:9 - | -LL | let Ok(_x) = res; - | ^^^^^^ pattern `Err(_)` not covered - | - = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant - = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html - = note: the matched value is of type `Result` -help: you might want to use `let else` to handle the variant that isn't matched - | -LL | let Ok(_x) = res else { todo!() }; - | ++++++++++++++++ - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0005`. diff --git a/tests/ui/pattern/never_patterns.rs b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs similarity index 62% rename from tests/ui/pattern/never_patterns.rs rename to tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs index 8f44f8a6559..90ff34e2f02 100644 --- a/tests/ui/pattern/never_patterns.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs @@ -1,63 +1,84 @@ +// check-pass #![feature(never_patterns)] +#![feature(exhaustive_patterns)] #![allow(incomplete_features)] +#[derive(Copy, Clone)] enum Void {} fn main() {} // The classic use for empty types. -fn safe_unwrap_result(res: Result) { - let Ok(_x) = res; //~ ERROR refutable pattern in local binding +fn safe_unwrap_result(res: Result) { + let Ok(_x) = res; let (Ok(_x) | Err(!)) = &res; - let (Ok(_x) | Err(&!)) = res.as_ref(); + let (Ok(_x) | Err(!)) = res.as_ref(); } // Check we only accept `!` where we want to. -fn never_pattern_location(void: Void) { +fn never_pattern_typeck(void: Void) { // FIXME(never_patterns): Don't accept on a non-empty type. + match () { + !, + } + match (0, false) { + !, + } + match (0, false) { + (_, !), + } match Some(0) { None => {} Some(!), } + // FIXME(never_patterns): Don't accept on an arbitrary type, even if there are no more branches. match () { () => {} !, } + // FIXME(never_patterns): Don't accept even on an empty branch. match None:: { None => {} !, } + match (&[] as &[Void]) { + [] => {} + !, + } // FIXME(never_patterns): Let alone if the emptiness is behind a reference. match None::<&Void> { None => {} !, } + // Participate in match ergonomics. match &void { - ! + !, } match &&void { - ! + !, } match &&void { - &! + &!, } match &None:: { None => {} - Some(!) + Some(!), } match None::<&Void> { None => {} Some(!), } - // Accept on a composite empty type. - match None::<&(u32, Void)> { - None => {} - Some(&!), + + // Accept on a directly empty type. + match void { + !, + } + match &void { + &!, } - // Accept on an simple empty type. match None:: { None => {} Some(!), @@ -70,4 +91,21 @@ fn never_pattern_location(void: Void) { None => {} Some(&(_, !)), } + match (&[] as &[Void]) { + [] => {} + [!], + } + // Accept on a composite empty type. + match None::<&(u32, Void)> { + None => {} + Some(&!), + } + match None::<&(u32, Void)> { + None => {} + Some(!), + } + match None::<&Result> { + None => {} + Some(!), + } } From d8b72e796e4e2bc31701e2197c4bc6f324adbf64 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 5 Jan 2024 17:21:09 +0100 Subject: [PATCH 2/3] Typecheck never patterns --- compiler/rustc_hir_typeck/src/pat.rs | 3 +- compiler/rustc_mir_build/messages.ftl | 5 ++ compiler/rustc_mir_build/src/errors.rs | 10 +++ .../src/thir/pattern/check_match.rs | 16 +++++ .../ui/rfcs/rfc-0000-never_patterns/typeck.rs | 17 +++-- .../rfc-0000-never_patterns/typeck.stderr | 66 +++++++++++++++++++ 6 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index fe8c36dbe06..2060e01e1d6 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -178,8 +178,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty = match pat.kind { PatKind::Wild | PatKind::Err(_) => expected, - // FIXME(never_patterns): check the type is uninhabited. If that is not possible within - // typeck, do that in a later phase. + // We allow any type here; we ensure that the type is uninhabited during match checking. PatKind::Never => expected, PatKind::Lit(lt) => self.check_pat_lit(pat.span, lt, expected, ti), PatKind::Range(lhs, rhs, _) => self.check_pat_range(pat.span, lhs, rhs, expected, ti), diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index 615b553434f..2f11cb123ee 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -234,6 +234,11 @@ mir_build_mutation_of_layout_constrained_field_requires_unsafe_unsafe_op_in_unsa mir_build_non_const_path = runtime values cannot be referenced in patterns +mir_build_non_empty_never_pattern = + mismatched types + .label = a never pattern must be used on an uninhabited type + .note = the matched value is of type `{$ty}` + mir_build_non_exhaustive_match_all_arms_guarded = match arms with guards don't count towards exhaustivity diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 61ad99acf38..e3cc21cef11 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -788,6 +788,16 @@ pub struct FloatPattern; #[diag(mir_build_pointer_pattern)] pub struct PointerPattern; +#[derive(Diagnostic)] +#[diag(mir_build_non_empty_never_pattern)] +#[note] +pub struct NonEmptyNeverPattern<'tcx> { + #[primary_span] + #[label] + pub span: Span, + pub ty: Ty<'tcx>, +} + #[derive(LintDiagnostic)] #[diag(mir_build_indirect_structural_match)] #[note(mir_build_type_not_structural_tip)] diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index f6c5e4a5cd6..6b08f6eb7c5 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -276,10 +276,13 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { } else { // Check the pattern for some things unrelated to exhaustiveness. let refutable = if cx.refutable { Refutable } else { Irrefutable }; + let mut err = Ok(()); pat.walk_always(|pat| { check_borrow_conflicts_in_at_patterns(self, pat); check_for_bindings_named_same_as_variants(self, pat, refutable); + err = err.and(check_never_pattern(cx, pat)); }); + err?; Ok(cx.pattern_arena.alloc(cx.lower_pat(pat))) } } @@ -811,6 +814,19 @@ fn check_for_bindings_named_same_as_variants( } } +/// Check that never patterns are only used on inhabited types. +fn check_never_pattern<'tcx>( + cx: &MatchCheckCtxt<'_, 'tcx>, + pat: &Pat<'tcx>, +) -> Result<(), ErrorGuaranteed> { + if let PatKind::Never = pat.kind { + if !cx.is_uninhabited(pat.ty) { + return Err(cx.tcx.dcx().emit_err(NonEmptyNeverPattern { span: pat.span, ty: pat.ty })); + } + } + Ok(()) +} + fn report_irrefutable_let_patterns( tcx: TyCtxt<'_>, id: HirId, diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs index 90ff34e2f02..333108e92c0 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs @@ -1,4 +1,3 @@ -// check-pass #![feature(never_patterns)] #![feature(exhaustive_patterns)] #![allow(incomplete_features)] @@ -17,40 +16,48 @@ fn safe_unwrap_result(res: Result) { // Check we only accept `!` where we want to. fn never_pattern_typeck(void: Void) { - // FIXME(never_patterns): Don't accept on a non-empty type. + // Don't accept on a non-empty type. match () { !, + //~^ ERROR: mismatched types } match (0, false) { !, + //~^ ERROR: mismatched types } match (0, false) { (_, !), + //~^ ERROR: mismatched types } match Some(0) { None => {} Some(!), + //~^ ERROR: mismatched types } - // FIXME(never_patterns): Don't accept on an arbitrary type, even if there are no more branches. + // Don't accept on an arbitrary type, even if there are no more branches. match () { () => {} !, + //~^ ERROR: mismatched types } - // FIXME(never_patterns): Don't accept even on an empty branch. + // Don't accept even on an empty branch. match None:: { None => {} !, + //~^ ERROR: mismatched types } match (&[] as &[Void]) { [] => {} !, + //~^ ERROR: mismatched types } - // FIXME(never_patterns): Let alone if the emptiness is behind a reference. + // Let alone if the emptiness is behind a reference. match None::<&Void> { None => {} !, + //~^ ERROR: mismatched types } // Participate in match ergonomics. diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr new file mode 100644 index 00000000000..8c0475894de --- /dev/null +++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr @@ -0,0 +1,66 @@ +error: mismatched types + --> $DIR/typeck.rs:21:9 + | +LL | !, + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `()` + +error: mismatched types + --> $DIR/typeck.rs:25:9 + | +LL | !, + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `(i32, bool)` + +error: mismatched types + --> $DIR/typeck.rs:29:13 + | +LL | (_, !), + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `bool` + +error: mismatched types + --> $DIR/typeck.rs:34:14 + | +LL | Some(!), + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `i32` + +error: mismatched types + --> $DIR/typeck.rs:41:9 + | +LL | !, + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `()` + +error: mismatched types + --> $DIR/typeck.rs:48:9 + | +LL | !, + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `Option` + +error: mismatched types + --> $DIR/typeck.rs:53:9 + | +LL | !, + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `[Void]` + +error: mismatched types + --> $DIR/typeck.rs:59:9 + | +LL | !, + | ^ a never pattern must be used on an uninhabited type + | + = note: the matched value is of type `Option<&Void>` + +error: aborting due to 8 previous errors + From ff6fa67a9dd61cafe44446ff2910e14ad78584c2 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 16 Jan 2024 16:39:38 +0100 Subject: [PATCH 3/3] Split-off the passing tests to ensure they pass --- .../{typeck.stderr => typeck.fail.stderr} | 16 ++++++------ .../ui/rfcs/rfc-0000-never_patterns/typeck.rs | 25 ++++++++++++------- 2 files changed, 24 insertions(+), 17 deletions(-) rename tests/ui/rfcs/rfc-0000-never_patterns/{typeck.stderr => typeck.fail.stderr} (88%) diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr similarity index 88% rename from tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr rename to tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr index 8c0475894de..013a8b53a55 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.stderr +++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr @@ -1,5 +1,5 @@ error: mismatched types - --> $DIR/typeck.rs:21:9 + --> $DIR/typeck.rs:25:9 | LL | !, | ^ a never pattern must be used on an uninhabited type @@ -7,7 +7,7 @@ LL | !, = note: the matched value is of type `()` error: mismatched types - --> $DIR/typeck.rs:25:9 + --> $DIR/typeck.rs:29:9 | LL | !, | ^ a never pattern must be used on an uninhabited type @@ -15,7 +15,7 @@ LL | !, = note: the matched value is of type `(i32, bool)` error: mismatched types - --> $DIR/typeck.rs:29:13 + --> $DIR/typeck.rs:33:13 | LL | (_, !), | ^ a never pattern must be used on an uninhabited type @@ -23,7 +23,7 @@ LL | (_, !), = note: the matched value is of type `bool` error: mismatched types - --> $DIR/typeck.rs:34:14 + --> $DIR/typeck.rs:38:14 | LL | Some(!), | ^ a never pattern must be used on an uninhabited type @@ -31,7 +31,7 @@ LL | Some(!), = note: the matched value is of type `i32` error: mismatched types - --> $DIR/typeck.rs:41:9 + --> $DIR/typeck.rs:45:9 | LL | !, | ^ a never pattern must be used on an uninhabited type @@ -39,7 +39,7 @@ LL | !, = note: the matched value is of type `()` error: mismatched types - --> $DIR/typeck.rs:48:9 + --> $DIR/typeck.rs:52:9 | LL | !, | ^ a never pattern must be used on an uninhabited type @@ -47,7 +47,7 @@ LL | !, = note: the matched value is of type `Option` error: mismatched types - --> $DIR/typeck.rs:53:9 + --> $DIR/typeck.rs:57:9 | LL | !, | ^ a never pattern must be used on an uninhabited type @@ -55,7 +55,7 @@ LL | !, = note: the matched value is of type `[Void]` error: mismatched types - --> $DIR/typeck.rs:59:9 + --> $DIR/typeck.rs:63:9 | LL | !, | ^ a never pattern must be used on an uninhabited type diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs index 333108e92c0..31a23fa002c 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs +++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs @@ -1,3 +1,6 @@ +// revisions: pass fail +//[pass] check-pass +//[fail] check-fail #![feature(never_patterns)] #![feature(exhaustive_patterns)] #![allow(incomplete_features)] @@ -15,51 +18,55 @@ fn safe_unwrap_result(res: Result) { } // Check we only accept `!` where we want to. -fn never_pattern_typeck(void: Void) { +#[cfg(fail)] +fn never_pattern_typeck_fail(void: Void) { // Don't accept on a non-empty type. match () { !, - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } match (0, false) { !, - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } match (0, false) { (_, !), - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } match Some(0) { None => {} Some(!), - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } // Don't accept on an arbitrary type, even if there are no more branches. match () { () => {} !, - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } // Don't accept even on an empty branch. match None:: { None => {} !, - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } match (&[] as &[Void]) { [] => {} !, - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } // Let alone if the emptiness is behind a reference. match None::<&Void> { None => {} !, - //~^ ERROR: mismatched types + //[fail]~^ ERROR: mismatched types } +} +#[cfg(pass)] +fn never_pattern_typeck_pass(void: Void) { // Participate in match ergonomics. match &void { !,