Skip to content

Commit

Permalink
Reveal opaque types in exhaustiveness checking
Browse files Browse the repository at this point in the history
  • Loading branch information
Nadrieril committed Oct 29, 2023
1 parent 364bbff commit 39105be
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 112 deletions.
4 changes: 4 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 @@ -28,12 28,14 @@ use rustc_span::hygiene::DesugaringKind;
use rustc_span::Span;

pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
let typeck_results = tcx.typeck(def_id);
let (thir, expr) = tcx.thir_body(def_id)?;
let thir = thir.borrow();
let pattern_arena = TypedArena::default();
let mut visitor = MatchVisitor {
tcx,
thir: &*thir,
typeck_results,
param_env: tcx.param_env(def_id),
lint_level: tcx.hir().local_def_id_to_hir_id(def_id),
let_source: LetSource::None,
Expand Down Expand Up @@ -77,6 79,7 @@ enum LetSource {
struct MatchVisitor<'a, 'p, 'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
thir: &'a Thir<'tcx>,
lint_level: HirId,
let_source: LetSource,
Expand Down Expand Up @@ -221,6 224,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
fn new_cx(&self, hir_id: HirId, refutable: bool) -> MatchCheckCtxt<'p, 'tcx> {
MatchCheckCtxt {
tcx: self.tcx,
typeck_results: self.typeck_results,
param_env: self.param_env,
module: self.tcx.parent_module(hir_id).to_def_id(),
pattern_arena: &self.pattern_arena,
Expand Down
47 changes: 22 additions & 25 deletions compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 328,7 @@ use std::fmt;

pub(crate) struct MatchCheckCtxt<'p, 'tcx> {
pub(crate) tcx: TyCtxt<'tcx>,
pub(crate) typeck_results: &'tcx ty::TypeckResults<'tcx>,
/// The module in which the match occurs. This is necessary for
/// checking inhabited-ness of types because whether a type is (visibly)
/// inhabited can depend on whether it was defined in the current module or
Expand Down Expand Up @@ -358,6 359,21 @@ impl<'a, 'tcx> MatchCheckCtxt<'a, 'tcx> {
_ => false,
}
}

/// Type inference occasionally gives us opaque types in places where corresponding patterns
/// have more specific types. To avoid inconsistencies as well as detect opaque uninhabited
/// types, we use the corresponding concrete type if possible.
fn reveal_opaque_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() {
if let Some(local_def_id) = alias_ty.def_id.as_local() {
let key = ty::OpaqueTypeKey { def_id: local_def_id, args: alias_ty.args };
if let Some(real_ty) = self.typeck_results.concrete_opaque_types.get(&key) {
return real_ty.ty;
}
}
}
ty
}
}

#[derive(Copy, Clone)]
Expand Down Expand Up @@ -817,15 833,7 @@ fn is_useful<'p, 'tcx>(
}
}
} else {
let mut ty = v.head().ty();

// Opaque types can't get destructured/split, but the patterns can
// actually hint at hidden types, so we use the patterns' types instead.
if let ty::Alias(ty::Opaque, ..) = ty.kind() {
if let Some(row) = rows.first() {
ty = row.head().ty();
}
}
let ty = cx.reveal_opaque_ty(v.head().ty());
debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span());
let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level };

Expand Down Expand Up @@ -882,23 890,12 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
fn is_empty(&self) -> bool {
self.patterns.is_empty()
}
fn head_ty(&self) -> Option<Ty<'tcx>> {
fn head_ty(&self, cx: &MatchCheckCtxt<'p, 'tcx>) -> Option<Ty<'tcx>> {
if self.patterns.len() == 0 {
return None;
}
// If the type is opaque and it is revealed anywhere in the column, we take the revealed
// version. Otherwise we could encounter constructors for the revealed type and crash.
let is_opaque = |ty: Ty<'tcx>| matches!(ty.kind(), ty::Alias(ty::Opaque, ..));
let first_ty = self.patterns[0].ty();
if is_opaque(first_ty) {
for pat in &self.patterns {
let ty = pat.ty();
if !is_opaque(ty) {
return Some(ty);
}
}
}
Some(first_ty)
// Important: we reaveal the opaque type if necessary.
Some(cx.reveal_opaque_ty(self.patterns[0].ty()))
}

fn analyze_ctors(&self, pcx: &PatCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'tcx> {
Expand Down Expand Up @@ -954,7 951,7 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
cx: &MatchCheckCtxt<'p, 'tcx>,
column: &PatternColumn<'p, 'tcx>,
) -> Vec<WitnessPat<'tcx>> {
let Some(ty) = column.head_ty() else {
let Some(ty) = column.head_ty(cx) else {
return Vec::new();
};
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
Expand Down Expand Up @@ -1004,7 1001,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>(
column: &PatternColumn<'p, 'tcx>,
lint_root: HirId,
) {
let Some(ty) = column.head_ty() else {
let Some(ty) = column.head_ty(cx) else {
return;
};
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
Expand Down
13 changes: 5 additions & 8 deletions tests/ui/pattern/usefulness/impl-trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 14,36 @@ enum Void {}
fn return_never_rpit(x: Void) -> impl Copy {
if false {
match return_never_rpit(x) {}
//~^ERROR non-empty
}
x
}
fn friend_of_return_never_rpit(x: Void) {
match return_never_rpit(x) {}
//~^ERROR non-empty
}

type T = impl Copy;
//~^ERROR unconstrained
fn return_never_tait(x: Void) -> T {
if false {
match return_never_tait(x) {}
//~^ERROR non-empty
}
x
}
fn friend_of_return_never_tait(x: Void) {
match return_never_tait(x) {}
//~^ERROR non-empty
}

fn option_never(x: Void) -> Option<impl Copy> {
if true {
match option_never(x) {
None => {}
Some(_) => {}
Some(_) => {} //~ERROR unreachable
}
match option_never(x) {
None => {}
// FIXME: Unreachable not detected because `is_uninhabited` did not look into the
// opaque type.
_ => {}
}
}
Expand All @@ -68,10 69,8 @@ fn option_never2(x: Void) -> impl Copy {

fn inner_never(x: Void) {
type T = impl Copy;
//~^ERROR unconstrained
let y: T = x;
match y {}
//~^ERROR non-empty
}

// This one caused ICE https://github.com/rust-lang/rust/issues/117100.
Expand All @@ -85,10 84,8 @@ fn inner_tuple() {
}

type U = impl Copy;
//~^ERROR unconstrained
fn unify_never(x: Void, u: U) -> U {
if true { match u {} } else { x }
//~^ERROR non-empty
}

type V = impl Copy;
Expand Down
112 changes: 33 additions & 79 deletions tests/ui/pattern/usefulness/impl-trait.stderr
Original file line number Diff line number Diff line change
@@ -1,41 1,5 @@
error[E0004]: non-exhaustive patterns: type `impl Copy` is non-empty
--> $DIR/impl-trait.rs:16:15
|
LL | match return_never_rpit(x) {}
| ^^^^^^^^^^^^^^^^^^^^
|
= note: the matched value is of type `impl Copy`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match return_never_rpit(x) {
LL _ => todo!(),
LL }
|

error[E0004]: non-exhaustive patterns: type `T` is non-empty
--> $DIR/impl-trait.rs:29:15
|
LL | match return_never_tait(x) {}
| ^^^^^^^^^^^^^^^^^^^^
|
= note: the matched value is of type `T`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match return_never_tait(x) {
LL _ => todo!(),
LL }
|

error: unconstrained opaque type
--> $DIR/impl-trait.rs:25:10
|
LL | type T = impl Copy;
| ^^^^^^^^^
|
= note: `T` must be used in combination with a concrete type within the same module

error: unreachable pattern
--> $DIR/impl-trait.rs:56:13
--> $DIR/impl-trait.rs:41:13
|
LL | Some(_) => {}
| ^^^^^^^
Expand All @@ -47,69 11,59 @@ LL | #![deny(unreachable_patterns)]
| ^^^^^^^^^^^^^^^^^^^^

error: unreachable pattern
--> $DIR/impl-trait.rs:60:13
|
LL | _ => {}
| ^

error[E0004]: non-exhaustive patterns: type `inner_never::T` is non-empty
--> $DIR/impl-trait.rs:73:11
|
LL | match y {}
| ^
|
= note: the matched value is of type `inner_never::T`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match y {
LL _ => todo!(),
LL }
--> $DIR/impl-trait.rs:57:13
|
LL | Some(_) => {}
| ^^^^^^^

error: unconstrained opaque type
--> $DIR/impl-trait.rs:70:14
|
LL | type T = impl Copy;
| ^^^^^^^^^
error: unreachable pattern
--> $DIR/impl-trait.rs:61:13
|
= note: `T` must be used in combination with a concrete type within the same item
LL | _ => {}
| ^

error: unreachable pattern
--> $DIR/impl-trait.rs:83:9
--> $DIR/impl-trait.rs:82:9
|
LL | _ => {}
| - matches any value
LL | Some((a, b)) => {}
| ^^^^^^^^^^^^ unreachable pattern

error[E0004]: non-exhaustive patterns: type `U` is non-empty
--> $DIR/impl-trait.rs:90:21
error: unreachable pattern
--> $DIR/impl-trait.rs:96:9
|
LL | Some((mut x, mut y)) => {
| ^^^^^^^^^^^^^^^^^^^^

error[E0004]: non-exhaustive patterns: type `impl Copy` is non-empty
--> $DIR/impl-trait.rs:21:11
|
LL | if true { match u {} } else { x }
| ^
LL | match return_never_rpit(x) {}
| ^^^^^^^^^^^^^^^^^^^^
|
= note: the matched value is of type `U`
= note: the matched value is of type `impl Copy`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ if true { match u {
LL ~ match return_never_rpit(x) {
LL _ => todo!(),
LL ~ } } else { x }
LL }
|

error: unconstrained opaque type
--> $DIR/impl-trait.rs:87:10
error[E0004]: non-exhaustive patterns: type `T` is non-empty
--> $DIR/impl-trait.rs:33:11
|
LL | type U = impl Copy;
| ^^^^^^^^^
LL | match return_never_tait(x) {}
| ^^^^^^^^^^^^^^^^^^^^
|
= note: `U` must be used in combination with a concrete type within the same module

error: unreachable pattern
--> $DIR/impl-trait.rs:99:9
= note: the matched value is of type `T`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match return_never_tait(x) {
LL _ => todo!(),
LL }
|
LL | Some((mut x, mut y)) => {
| ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 11 previous errors
error: aborting due to 7 previous errors

For more information about this error, try `rustc --explain E0004`.

0 comments on commit 39105be

Please sign in to comment.