about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-01-19 19:27:02 +0100
committerGitHub <noreply@github.com>2024-01-19 19:27:02 +0100
commit5761c36c0a4b29c9b7896fdebb027159db72cb29 (patch)
treed492fafb78a177c12eb4f687996a219f5b5f7b48
parent455382d8df6e6faa169468a3af07aabf165f3559 (diff)
parentff6fa67a9dd61cafe44446ff2910e14ad78584c2 (diff)
downloadrust-5761c36c0a4b29c9b7896fdebb027159db72cb29.tar.gz
rust-5761c36c0a4b29c9b7896fdebb027159db72cb29.zip
Rollup merge of #120009 - Nadrieril:never_patterns_tyck, r=compiler-errors
never_patterns: typecheck never patterns

This checks that a `!` pattern is only used on an uninhabited type (modulo match ergonomics, i.e. `!` is allowed on `&Void`).

r? `@compiler-errors`
-rw-r--r--compiler/rustc_hir_typeck/src/pat.rs3
-rw-r--r--compiler/rustc_mir_build/messages.ftl5
-rw-r--r--compiler/rustc_mir_build/src/errors.rs10
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/check_match.rs16
-rw-r--r--tests/ui/pattern/never_patterns.rs73
-rw-r--r--tests/ui/pattern/never_patterns.stderr17
-rw-r--r--tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr66
-rw-r--r--tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs125
8 files changed, 223 insertions, 92 deletions
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 1b7be98600f..e8ba83a5527 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)))
         }
     }
@@ -812,6 +815,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/pattern/never_patterns.rs b/tests/ui/pattern/never_patterns.rs
deleted file mode 100644
index 8f44f8a6559..00000000000
--- a/tests/ui/pattern/never_patterns.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-#![feature(never_patterns)]
-#![allow(incomplete_features)]
-
-enum Void {}
-
-fn main() {}
-
-// The classic use for empty types.
-fn safe_unwrap_result<T>(res: Result<T, Void>) {
-    let Ok(_x) = res; //~ ERROR refutable pattern in local binding
-    let (Ok(_x) | Err(!)) = &res;
-    let (Ok(_x) | Err(&!)) = res.as_ref();
-}
-
-// Check we only accept `!` where we want to.
-fn never_pattern_location(void: Void) {
-    // FIXME(never_patterns): Don't accept on a non-empty type.
-    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::<Void> {
-        None => {}
-        !,
-    }
-    // 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::<Void> {
-        None => {}
-        Some(!)
-    }
-    match None::<&Void> {
-        None => {}
-        Some(!),
-    }
-    // Accept on a composite empty type.
-    match None::<&(u32, Void)> {
-        None => {}
-        Some(&!),
-    }
-    // Accept on an simple empty type.
-    match None::<Void> {
-        None => {}
-        Some(!),
-    }
-    match None::<&Void> {
-        None => {}
-        Some(&!),
-    }
-    match None::<&(u32, Void)> {
-        None => {}
-        Some(&(_, !)),
-    }
-}
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<T, Void>`
-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/rfcs/rfc-0000-never_patterns/typeck.fail.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr
new file mode 100644
index 00000000000..013a8b53a55
--- /dev/null
+++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr
@@ -0,0 +1,66 @@
+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 `()`
+
+error: mismatched types
+  --> $DIR/typeck.rs:29: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:33: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:38: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:45: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:52:9
+   |
+LL |         !,
+   |         ^ a never pattern must be used on an uninhabited type
+   |
+   = note: the matched value is of type `Option<Void>`
+
+error: mismatched types
+  --> $DIR/typeck.rs:57: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:63: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
+
diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs
new file mode 100644
index 00000000000..31a23fa002c
--- /dev/null
+++ b/tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs
@@ -0,0 +1,125 @@
+// revisions: pass fail
+//[pass] check-pass
+//[fail] check-fail
+#![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<T: Copy>(res: Result<T, Void>) {
+    let Ok(_x) = res;
+    let (Ok(_x) | Err(!)) = &res;
+    let (Ok(_x) | Err(!)) = res.as_ref();
+}
+
+// Check we only accept `!` where we want to.
+#[cfg(fail)]
+fn never_pattern_typeck_fail(void: Void) {
+    // Don't accept on a non-empty type.
+    match () {
+        !,
+        //[fail]~^ ERROR: mismatched types
+    }
+    match (0, false) {
+        !,
+        //[fail]~^ ERROR: mismatched types
+    }
+    match (0, false) {
+        (_, !),
+        //[fail]~^ ERROR: mismatched types
+    }
+    match Some(0) {
+        None => {}
+        Some(!),
+        //[fail]~^ ERROR: mismatched types
+    }
+
+    // Don't accept on an arbitrary type, even if there are no more branches.
+    match () {
+        () => {}
+        !,
+        //[fail]~^ ERROR: mismatched types
+    }
+
+    // Don't accept even on an empty branch.
+    match None::<Void> {
+        None => {}
+        !,
+        //[fail]~^ ERROR: mismatched types
+    }
+    match (&[] as &[Void]) {
+        [] => {}
+        !,
+        //[fail]~^ ERROR: mismatched types
+    }
+    // Let alone if the emptiness is behind a reference.
+    match None::<&Void> {
+        None => {}
+        !,
+        //[fail]~^ ERROR: mismatched types
+    }
+}
+
+#[cfg(pass)]
+fn never_pattern_typeck_pass(void: Void) {
+    // Participate in match ergonomics.
+    match &void {
+        !,
+    }
+    match &&void {
+        !,
+    }
+    match &&void {
+        &!,
+    }
+    match &None::<Void> {
+        None => {}
+        Some(!),
+    }
+    match None::<&Void> {
+        None => {}
+        Some(!),
+    }
+
+    // Accept on a directly empty type.
+    match void {
+        !,
+    }
+    match &void {
+        &!,
+    }
+    match None::<Void> {
+        None => {}
+        Some(!),
+    }
+    match None::<&Void> {
+        None => {}
+        Some(&!),
+    }
+    match None::<&(u32, 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<Void, Void>> {
+        None => {}
+        Some(!),
+    }
+}