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

Exhaustiveness: reveal opaque types properly #116821

Merged
merged 2 commits into from
Dec 22, 2023
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
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,13 +28,15 @@ 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 dropless_arena = DroplessArena::default();
let mut visitor = MatchVisitor {
tcx,
thir: &*thir,
typeck_results,
param_env: tcx.param_env(def_id),
lint_level: tcx.local_def_id_to_hir_id(def_id),
let_source: LetSource::None,
Expand Down Expand Up @@ -80,6 +82,7 @@ enum LetSource {
struct MatchVisitor<'thir, 'p, 'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
thir: &'thir Thir<'tcx>,
lint_level: HirId,
let_source: LetSource,
Expand Down Expand Up @@ -382,6 +385,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true);
MatchCheckCtxt {
tcx: self.tcx,
typeck_results: self.typeck_results,
param_env: self.param_env,
module: self.tcx.parent_module(self.lint_level).to_def_id(),
pattern_arena: self.pattern_arena,
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_pattern_analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ pub trait TypeCx: Sized + Clone + fmt::Debug {
/// patterns during analysis.
type PatData: Clone + Default;

fn is_opaque_ty(ty: Self::Ty) -> bool;
/// FIXME(Nadrieril): `Cx` should only give us revealed types.
fn reveal_opaque_ty(&self, ty: Self::Ty) -> Self::Ty;
fn is_exhaustive_patterns_feature_on(&self) -> bool;

/// The number of fields for this constructor.
Expand Down
22 changes: 7 additions & 15 deletions compiler/rustc_pattern_analysis/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,14 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> {
fn is_empty(&self) -> bool {
self.patterns.is_empty()
}
fn head_ty(&self) -> Option<Ty<'tcx>> {
fn head_ty(&self, cx: MatchCtxt<'a, '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 first_ty = self.patterns[0].ty();
if RustcMatchCheckCtxt::is_opaque_ty(first_ty) {
for pat in &self.patterns {
let ty = pat.ty();
if !RustcMatchCheckCtxt::is_opaque_ty(ty) {
return Some(ty);
}
}
}
Some(first_ty)

let ty = self.patterns[0].ty();
// FIXME(Nadrieril): `Cx` should only give us revealed types.
Some(cx.tycx.reveal_opaque_ty(ty))
}

/// Do constructor splitting on the constructors of the column.
Expand Down Expand Up @@ -125,7 +117,7 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'a, 'p, 'tcx>,
) -> Vec<WitnessPat<'p, 'tcx>> {
let Some(ty) = column.head_ty() else {
let Some(ty) = column.head_ty(cx) else {
return Vec::new();
};
let pcx = &PlaceCtxt::new_dummy(cx, ty);
Expand Down Expand Up @@ -226,7 +218,7 @@ pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'a, 'p, 'tcx>,
) {
let Some(ty) = column.head_ty() else {
let Some(ty) = column.head_ty(cx) else {
return;
};
let pcx = &PlaceCtxt::new_dummy(cx, ty);
Expand Down
21 changes: 19 additions & 2 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub type WitnessPat<'p, 'tcx> = crate::pat::WitnessPat<RustcMatchCheckCtxt<'p, '
#[derive(Clone)]
pub struct RustcMatchCheckCtxt<'p, 'tcx> {
pub tcx: TyCtxt<'tcx>,
pub 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 @@ -101,6 +102,21 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {
}
}

/// 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
}

// In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide
// uninhabited fields in order not to reveal the uninhabitedness of the whole variant.
// This lists the fields we keep along with their types.
Expand Down Expand Up @@ -873,8 +889,9 @@ impl<'p, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
fn is_exhaustive_patterns_feature_on(&self) -> bool {
self.tcx.features().exhaustive_patterns
}
fn is_opaque_ty(ty: Self::Ty) -> bool {
matches!(ty.kind(), ty::Alias(ty::Opaque, ..))

fn reveal_opaque_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
self.reveal_opaque_ty(ty)
}

fn ctor_arity(&self, ctor: &crate::constructor::Constructor<Self>, ty: Self::Ty) -> usize {
Expand Down
20 changes: 5 additions & 15 deletions compiler/rustc_pattern_analysis/src/usefulness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -865,24 +865,14 @@ impl<'a, 'p, Cx: TypeCx> Matrix<'a, 'p, Cx> {
matrix
}

fn head_ty(&self) -> Option<Cx::Ty> {
fn head_ty(&self, mcx: MatchCtxt<'a, 'p, Cx>) -> Option<Cx::Ty> {
if self.column_count() == 0 {
return None;
}

let mut ty = self.wildcard_row.head().ty();
// 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.
if Cx::is_opaque_ty(ty) {
for pat in self.heads() {
let pat_ty = pat.ty();
if !Cx::is_opaque_ty(pat_ty) {
ty = pat_ty;
break;
}
}
}
Some(ty)
let ty = self.wildcard_row.head().ty();
// FIXME(Nadrieril): `Cx` should only give us revealed types.
Some(mcx.tycx.reveal_opaque_ty(ty))
}
fn column_count(&self) -> usize {
self.wildcard_row.len()
Expand Down Expand Up @@ -1181,7 +1171,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
) -> WitnessMatrix<Cx> {
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));

let Some(ty) = matrix.head_ty() else {
let Some(ty) = matrix.head_ty(mcx) else {
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
// A row is useful iff it has no (unguarded) rows above it.
for row in matrix.rows_mut() {
Expand Down
146 changes: 146 additions & 0 deletions tests/ui/pattern/usefulness/impl-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#![feature(never_type)]
#![feature(exhaustive_patterns)]
#![feature(type_alias_impl_trait)]
#![feature(non_exhaustive_omitted_patterns_lint)]
#![deny(unreachable_patterns)]
// Test that the lint traversal handles opaques correctly
#![deny(non_exhaustive_omitted_patterns)]

fn main() {}

#[derive(Copy, Clone)]
enum Void {}

fn return_never_rpit(x: Void) -> impl Copy {
if false {
match return_never_rpit(x) {
_ => {} //~ ERROR unreachable
}
}
x
}
fn friend_of_return_never_rpit(x: Void) {
match return_never_rpit(x) {}
//~^ ERROR non-empty
}

type T = impl Copy;
fn return_never_tait(x: Void) -> T {
if false {
match return_never_tait(x) {
_ => {} //~ ERROR unreachable
}
}
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 false {
match option_never(x) {
None => {}
Some(_) => {} //~ ERROR unreachable
}
match option_never(x) {
None => {}
// FIXME: Unreachable not detected because `is_uninhabited` does not look into opaque
// types.
_ => {}
}
}
Some(x)
}

fn option_never2(x: Void) -> impl Copy {
if false {
match option_never2(x) {
None => {}
Some(_) => {} //~ ERROR unreachable
}
match option_never2(x) {
None => {}
_ => {} //~ ERROR unreachable
}
match option_never2(x) {
None => {}
}
}
Some(x)
}

fn inner_never(x: Void) {
type T = impl Copy;
let y: T = x;
match y {
_ => {} //~ ERROR unreachable
}
}

// This one caused ICE https://github.com/rust-lang/rust/issues/117100.
fn inner_tuple() {
type T = impl Copy;
let foo: T = Some((1u32, 2u32));
match foo {
_ => {}
Some((a, b)) => {} //~ ERROR unreachable
}
}

type U = impl Copy;
fn unify_never(x: Void, u: U) -> U {
if false {
match u {
_ => {} //~ ERROR unreachable
}
}
x
}

type V = impl Copy;
fn infer_in_match(x: Option<V>) {
match x {
None => {}
Some((a, b)) => {}
Some((mut x, mut y)) => {
//~^ ERROR unreachable
x = 42;
y = "foo";
}
}
}

type W = impl Copy;
#[derive(Copy, Clone)]
struct Rec<'a> {
n: u32,
w: Option<&'a W>,
}
Nadrieril marked this conversation as resolved.
Show resolved Hide resolved
fn recursive_opaque() -> W {
if false {
match recursive_opaque() {
// Check for the ol' ICE when the type is recursively opaque.
_ => {}
Rec { n: 0, w: Some(Rec { n: 0, w: _ }) } => {} //~ ERROR unreachable
}
}
let w: Option<&'static W> = None;
Rec { n: 0, w }
}

type X = impl Copy;
struct SecretelyVoid(X);
fn nested_empty_opaque(x: Void) -> X {
if false {
let opaque_void = nested_empty_opaque(x);
let secretely_void = SecretelyVoid(opaque_void);
match secretely_void {
// FIXME: Unreachable not detected because `is_uninhabited` does not look into opaque
// types.
_ => {}
}
}
x
}
Loading
Loading