Skip to content

Commit

Permalink
Unrolled build for rust-lang#120009
Browse files Browse the repository at this point in the history
Rollup merge of rust-lang#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`
  • Loading branch information
rust-timer authored Jan 20, 2024
2 parents 0547c41 + ff6fa67 commit 48e6fc7
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 92 deletions.
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.
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(!),
}
}

0 comments on commit 48e6fc7

Please sign in to comment.