From 39105be4dbbe2686ccb723f82ac0f63b69e3230f Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 29 Oct 2023 21:40:53 +0100 Subject: [PATCH] Reveal opaque types in exhaustiveness checking --- .../src/thir/pattern/check_match.rs | 4 + .../src/thir/pattern/usefulness.rs | 47 ++++---- tests/ui/pattern/usefulness/impl-trait.rs | 13 +- tests/ui/pattern/usefulness/impl-trait.stderr | 112 ++++++------------ 4 files changed, 64 insertions(+), 112 deletions(-) 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 9156af3425aea..40683d390202f 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -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, @@ -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, @@ -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, diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 25e0f3ceaa473..a75f17766a80d 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -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 @@ -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)] @@ -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 }; @@ -882,23 +890,12 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> { fn is_empty(&self) -> bool { self.patterns.is_empty() } - fn head_ty(&self) -> Option> { + fn head_ty(&self, cx: &MatchCheckCtxt<'p, 'tcx>) -> Option> { 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> { @@ -954,7 +951,7 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, column: &PatternColumn<'p, 'tcx>, ) -> Vec> { - 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 }; @@ -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 }; diff --git a/tests/ui/pattern/usefulness/impl-trait.rs b/tests/ui/pattern/usefulness/impl-trait.rs index 6d89363f4afd0..d971e3655bc2e 100644 --- a/tests/ui/pattern/usefulness/impl-trait.rs +++ b/tests/ui/pattern/usefulness/impl-trait.rs @@ -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 { 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. _ => {} } } @@ -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. @@ -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; diff --git a/tests/ui/pattern/usefulness/impl-trait.stderr b/tests/ui/pattern/usefulness/impl-trait.stderr index a6f6cfbe627fc..c29ceb48b5afa 100644 --- a/tests/ui/pattern/usefulness/impl-trait.stderr +++ b/tests/ui/pattern/usefulness/impl-trait.stderr @@ -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(_) => {} | ^^^^^^^ @@ -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`.