Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

never_patterns: typecheck never patterns #120009

Merged
merged 3 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I believe this is the right way to do it, since inhabitedness can cause cycles.

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),
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_mir_build/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
}
Expand Down Expand Up @@ -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,
Expand Down
73 changes: 0 additions & 73 deletions tests/ui/pattern/never_patterns.rs

This file was deleted.

17 changes: 0 additions & 17 deletions tests/ui/pattern/never_patterns.stderr

This file was deleted.

66 changes: 66 additions & 0 deletions tests/ui/rfcs/rfc-0000-never_patterns/typeck.fail.stderr
Original file line number Diff line number Diff line change
@@ -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

125 changes: 125 additions & 0 deletions tests/ui/rfcs/rfc-0000-never_patterns/typeck.rs
Original file line number Diff line number Diff line change
@@ -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(!),
}
}
Loading