Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 1a580a3

Browse files
committed
Implement unstable RFC 1872 exhaustive_patterns
1 parent e3dc5a5 commit 1a580a3

File tree

6 files changed

+265
-20
lines changed

6 files changed

+265
-20
lines changed

crates/hir-ty/src/diagnostics/expr.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,7 @@ impl ExprValidator {
159159
}
160160

161161
let pattern_arena = Arena::new();
162-
let cx = MatchCheckCtx {
163-
module: self.owner.module(db.upcast()),
164-
body: self.owner,
165-
db,
166-
pattern_arena: &pattern_arena,
167-
};
162+
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
168163

169164
let mut m_arms = Vec::with_capacity(arms.len());
170165
let mut has_lowering_errors = false;

crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ use hir_def::{EnumVariantId, HasModule, LocalFieldId, VariantId};
5252
use smallvec::{smallvec, SmallVec};
5353
use stdx::never;
5454

55-
use crate::{infer::normalize, AdtId, Interner, Scalar, Ty, TyExt, TyKind};
55+
use crate::{
56+
infer::normalize, inhabitedness::is_enum_variant_uninhabited_from, AdtId, Interner, Scalar, Ty,
57+
TyExt, TyKind,
58+
};
5659

5760
use super::{
5861
is_box,
@@ -557,8 +560,8 @@ impl SplitWildcard {
557560
TyKind::Scalar(Scalar::Bool) => smallvec![make_range(0, 1, Scalar::Bool)],
558561
// TyKind::Array(..) if ... => unhandled(),
559562
TyKind::Array(..) | TyKind::Slice(..) => unhandled(),
560-
&TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), ..) => {
561-
let enum_data = cx.db.enum_data(enum_id);
563+
TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), subst) => {
564+
let enum_data = cx.db.enum_data(*enum_id);
562565

563566
// If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an
564567
// additional "unknown" constructor.
@@ -591,14 +594,15 @@ impl SplitWildcard {
591594
let mut ctors: SmallVec<[_; 1]> = enum_data
592595
.variants
593596
.iter()
594-
.filter(|&(_, _v)| {
597+
.map(|(local_id, _)| EnumVariantId { parent: *enum_id, local_id })
598+
.filter(|&variant| {
595599
// If `exhaustive_patterns` is enabled, we exclude variants known to be
596600
// uninhabited.
597601
let is_uninhabited = is_exhaustive_pat_feature
598-
&& unimplemented!("after MatchCheckCtx.feature_exhaustive_patterns()");
602+
&& is_enum_variant_uninhabited_from(variant, subst, cx.module, cx.db);
599603
!is_uninhabited
600604
})
601-
.map(|(local_id, _)| Variant(EnumVariantId { parent: enum_id, local_id }))
605+
.map(Variant)
602606
.collect();
603607

604608
if is_secretly_empty || is_declared_nonexhaustive {

crates/hir-ty/src/diagnostics/match_check/usefulness.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,11 @@
274274
use std::iter::once;
275275

276276
use hir_def::{AdtId, DefWithBodyId, HasModule, ModuleId};
277+
use once_cell::unsync::OnceCell;
277278
use smallvec::{smallvec, SmallVec};
278279
use typed_arena::Arena;
279280

280-
use crate::{db::HirDatabase, Ty, TyExt};
281+
use crate::{db::HirDatabase, inhabitedness::is_ty_uninhabited_from, Ty, TyExt};
281282

282283
use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard};
283284

@@ -289,13 +290,25 @@ pub(crate) struct MatchCheckCtx<'a, 'p> {
289290
pub(crate) db: &'a dyn HirDatabase,
290291
/// Lowered patterns from arms plus generated by the check.
291292
pub(crate) pattern_arena: &'p Arena<DeconstructedPat<'p>>,
293+
feature_exhaustive_patterns: OnceCell<bool>,
292294
}
293295

294296
impl<'a, 'p> MatchCheckCtx<'a, 'p> {
295-
pub(super) fn is_uninhabited(&self, _ty: &Ty) -> bool {
296-
// FIXME(iDawer) implement exhaustive_patterns feature. More info in:
297-
// Tracking issue for RFC 1872: exhaustive_patterns feature https://github.com/rust-lang/rust/issues/51085
298-
false
297+
pub(crate) fn new(
298+
module: ModuleId,
299+
body: DefWithBodyId,
300+
db: &'a dyn HirDatabase,
301+
pattern_arena: &'p Arena<DeconstructedPat<'p>>,
302+
) -> Self {
303+
Self { module, body, db, pattern_arena, feature_exhaustive_patterns: Default::default() }
304+
}
305+
306+
pub(super) fn is_uninhabited(&self, ty: &Ty) -> bool {
307+
if self.feature_exhaustive_patterns() {
308+
is_ty_uninhabited_from(ty, self.module, self.db)
309+
} else {
310+
false
311+
}
299312
}
300313

301314
/// Returns whether the given type is an enum from another crate declared `#[non_exhaustive]`.
@@ -311,10 +324,22 @@ impl<'a, 'p> MatchCheckCtx<'a, 'p> {
311324
}
312325
}
313326

314-
// Rust feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
327+
// Rust's unstable feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
315328
pub(super) fn feature_exhaustive_patterns(&self) -> bool {
316-
// FIXME see MatchCheckCtx::is_uninhabited
317-
false
329+
*self.feature_exhaustive_patterns.get_or_init(|| {
330+
let def_map = self.db.crate_def_map(self.module.krate());
331+
let root_mod = def_map.module_id(def_map.root());
332+
let rood_attrs = self.db.attrs(root_mod.into());
333+
let mut nightly_features = rood_attrs
334+
.by_key("feature")
335+
.attrs()
336+
.map(|attr| attr.parse_path_comma_token_tree())
337+
.flatten()
338+
.flatten();
339+
nightly_features.any(
340+
|feat| matches!(feat.segments(), [name] if name.to_smol_str() == "exhaustive_patterns"),
341+
)
342+
})
318343
}
319344
}
320345

crates/hir-ty/src/inhabitedness.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use std::ops::ControlFlow::{self, Break, Continue};
2+
3+
use chalk_ir::{
4+
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
5+
DebruijnIndex,
6+
};
7+
use hir_def::{
8+
adt::VariantData, attr::Attrs, type_ref::ConstScalar, visibility::Visibility, AdtId,
9+
EnumVariantId, HasModule, Lookup, ModuleId, VariantId,
10+
};
11+
12+
use crate::{
13+
db::HirDatabase, Binders, ConcreteConst, Const, ConstValue, Interner, Substitution, Ty, TyKind,
14+
};
15+
16+
pub(crate) fn is_ty_uninhabited_from(ty: &Ty, target_mod: ModuleId, db: &dyn HirDatabase) -> bool {
17+
let mut uninhabited_from = UninhabitedFrom { target_mod, db };
18+
let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST);
19+
inhabitedness == BREAK_VISIBLY_UNINHABITED
20+
}
21+
22+
pub(crate) fn is_enum_variant_uninhabited_from(
23+
variant: EnumVariantId,
24+
subst: &Substitution,
25+
target_mod: ModuleId,
26+
db: &dyn HirDatabase,
27+
) -> bool {
28+
let enum_data = db.enum_data(variant.parent);
29+
let vars_attrs = db.variants_attrs(variant.parent);
30+
let is_local = variant.parent.lookup(db.upcast()).container.krate() == target_mod.krate();
31+
32+
let mut uninhabited_from = UninhabitedFrom { target_mod, db };
33+
let inhabitedness = uninhabited_from.visit_variant(
34+
variant.into(),
35+
&enum_data.variants[variant.local_id].variant_data,
36+
subst,
37+
&vars_attrs[variant.local_id],
38+
is_local,
39+
);
40+
inhabitedness == BREAK_VISIBLY_UNINHABITED
41+
}
42+
43+
struct UninhabitedFrom<'a> {
44+
target_mod: ModuleId,
45+
db: &'a dyn HirDatabase,
46+
}
47+
48+
const CONTINUE_OPAQUELY_INHABITED: ControlFlow<VisiblyUninhabited> = Continue(());
49+
const BREAK_VISIBLY_UNINHABITED: ControlFlow<VisiblyUninhabited> = Break(VisiblyUninhabited);
50+
#[derive(PartialEq, Eq)]
51+
struct VisiblyUninhabited;
52+
53+
impl TypeVisitor<Interner> for UninhabitedFrom<'_> {
54+
type BreakTy = VisiblyUninhabited;
55+
56+
fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = VisiblyUninhabited> {
57+
self
58+
}
59+
60+
fn visit_ty(
61+
&mut self,
62+
ty: &Ty,
63+
outer_binder: DebruijnIndex,
64+
) -> ControlFlow<VisiblyUninhabited> {
65+
match ty.kind(Interner) {
66+
TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst),
67+
TyKind::Never => BREAK_VISIBLY_UNINHABITED,
68+
TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder),
69+
TyKind::Array(item_ty, len) => match try_usize_const(len) {
70+
Some(0) | None => CONTINUE_OPAQUELY_INHABITED,
71+
Some(1..) => item_ty.super_visit_with(self, outer_binder),
72+
},
73+
74+
TyKind::Ref(..) | _ => CONTINUE_OPAQUELY_INHABITED,
75+
}
76+
}
77+
78+
fn interner(&self) -> Interner {
79+
Interner
80+
}
81+
}
82+
83+
impl UninhabitedFrom<'_> {
84+
fn visit_adt(&mut self, adt: AdtId, subst: &Substitution) -> ControlFlow<VisiblyUninhabited> {
85+
let attrs = self.db.attrs(adt.into());
86+
let adt_non_exhaustive = attrs.by_key("non_exhaustive").exists();
87+
let is_local = adt.module(self.db.upcast()).krate() == self.target_mod.krate();
88+
if adt_non_exhaustive && !is_local {
89+
return CONTINUE_OPAQUELY_INHABITED;
90+
}
91+
92+
// An ADT is uninhabited iff all its variants uninhabited.
93+
match adt {
94+
// rustc: For now, `union`s are never considered uninhabited.
95+
AdtId::UnionId(_) => CONTINUE_OPAQUELY_INHABITED,
96+
AdtId::StructId(s) => {
97+
let struct_data = self.db.struct_data(s);
98+
self.visit_variant(s.into(), &struct_data.variant_data, subst, &attrs, is_local)
99+
}
100+
AdtId::EnumId(e) => {
101+
let vars_attrs = self.db.variants_attrs(e);
102+
let enum_data = self.db.enum_data(e);
103+
104+
for (local_id, enum_var) in enum_data.variants.iter() {
105+
let variant_inhabitedness = self.visit_variant(
106+
EnumVariantId { parent: e, local_id }.into(),
107+
&enum_var.variant_data,
108+
subst,
109+
&vars_attrs[local_id],
110+
is_local,
111+
);
112+
match variant_inhabitedness {
113+
Break(VisiblyUninhabited) => continue,
114+
Continue(()) => return CONTINUE_OPAQUELY_INHABITED,
115+
}
116+
}
117+
BREAK_VISIBLY_UNINHABITED
118+
}
119+
}
120+
}
121+
122+
fn visit_variant(
123+
&mut self,
124+
variant: VariantId,
125+
variant_data: &VariantData,
126+
subst: &Substitution,
127+
attrs: &Attrs,
128+
is_local: bool,
129+
) -> ControlFlow<VisiblyUninhabited> {
130+
let non_exhaustive_field_list = attrs.by_key("non_exhaustive").exists();
131+
if non_exhaustive_field_list && !is_local {
132+
return CONTINUE_OPAQUELY_INHABITED;
133+
}
134+
135+
let is_enum = matches!(variant, VariantId::EnumVariantId(..));
136+
let field_tys = self.db.field_types(variant);
137+
let field_vis = self.db.field_visibilities(variant);
138+
139+
for (fid, _) in variant_data.fields().iter() {
140+
self.visit_field(field_vis[fid], &field_tys[fid], subst, is_enum)?;
141+
}
142+
CONTINUE_OPAQUELY_INHABITED
143+
}
144+
145+
fn visit_field(
146+
&mut self,
147+
vis: Visibility,
148+
ty: &Binders<Ty>,
149+
subst: &Substitution,
150+
is_enum: bool,
151+
) -> ControlFlow<VisiblyUninhabited> {
152+
let target_mod = self.target_mod;
153+
let mut data_uninhabitedness =
154+
|| ty.clone().substitute(Interner, subst).visit_with(self, DebruijnIndex::INNERMOST);
155+
if is_enum {
156+
data_uninhabitedness()
157+
} else {
158+
match vis {
159+
Visibility::Module(mod_id) if mod_id == target_mod => data_uninhabitedness(),
160+
Visibility::Module(_) => CONTINUE_OPAQUELY_INHABITED,
161+
Visibility::Public => data_uninhabitedness(),
162+
}
163+
}
164+
}
165+
}
166+
167+
fn try_usize_const(c: &Const) -> Option<u128> {
168+
let data = &c.data(Interner);
169+
if data.ty.kind(Interner) != &TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)) {
170+
return None;
171+
}
172+
match data.value {
173+
ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(value) }) => Some(value),
174+
_ => None,
175+
}
176+
}

crates/hir-ty/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod chalk_db;
1414
mod chalk_ext;
1515
pub mod consteval;
1616
mod infer;
17+
mod inhabitedness;
1718
mod interner;
1819
mod lower;
1920
mod mapping;

crates/ide-diagnostics/src/handlers/missing_match_arms.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,50 @@ fn f() {
947947
);
948948
}
949949

950+
mod rust_unstable {
951+
use super::*;
952+
953+
#[test]
954+
fn rfc_1872_exhaustive_patterns() {
955+
check_diagnostics_no_bails(
956+
r"
957+
//- minicore: option, result
958+
#![feature(exhaustive_patterns)]
959+
enum Void {}
960+
fn test() {
961+
match None::<!> { None => () }
962+
match Result::<u8, !>::Ok(2) { Ok(_) => () }
963+
match Result::<u8, Void>::Ok(2) { Ok(_) => () }
964+
match (2, loop {}) {}
965+
match Result::<!, !>::Ok(loop {}) {}
966+
match (&loop {}) {} // https://github.com/rust-lang/rust/issues/50642#issuecomment-388234919
967+
// ^^^^^^^^^^ error: missing match arm: type `&!` is non-empty
968+
}",
969+
);
970+
}
971+
972+
#[test]
973+
fn rfc_1872_private_uninhabitedness() {
974+
check_diagnostics_no_bails(
975+
r"
976+
//- minicore: option
977+
//- /lib.rs crate:lib
978+
#![feature(exhaustive_patterns)]
979+
pub struct PrivatelyUninhabited { private_field: Void }
980+
enum Void {}
981+
fn test_local(x: Option<PrivatelyUninhabited>) {
982+
match x {}
983+
} // ^ error: missing match arm: `None` not covered
984+
//- /main.rs crate:main deps:lib
985+
#![feature(exhaustive_patterns)]
986+
fn test(x: Option<lib::PrivatelyUninhabited>) {
987+
match x {}
988+
// ^ error: missing match arm: `None` and `Some(_)` not covered
989+
}",
990+
);
991+
}
992+
}
993+
950994
mod false_negatives {
951995
//! The implementation of match checking here is a work in progress. As we roll this out, we
952996
//! prefer false negatives to false positives (ideally there would be no false positives). This

0 commit comments

Comments
 (0)