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`
This commit is contained in:
Matthias Krüger 2024-01-19 19:27:02 +01:00 committed by GitHub
commit 5761c36c0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 223 additions and 92 deletions

View File

@ -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),

View File

@ -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

View File

@ -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)]

View File

@ -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,

View File

@ -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(&(_, !)),
}
}

View File

@ -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`.

View File

@ -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

View File

@ -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(!),
}
}