Skip to content

Commit 4f8153e

Browse files
committed
Auto merge of rust-lang#13167 - iDawer:exhaustive_patterns, r=Veykril
feat: Implement `feature(exhaustive_patterns)` from unstable Rust Closes rust-lang#12753 Recognize Rust's unstable `#![feature(exhaustive_patterns)]` (RFC 1872). Allow omitting visibly uninhabited variants from `match` expressions when the feature is on. This adjusts match checking to the current implementation of the postponed RFC 1872 in rustc.
2 parents 412d614 + ffd79c2 commit 4f8153e

File tree

9 files changed

+272
-21
lines changed

9 files changed

+272
-21
lines changed

crates/hir-def/src/nameres.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId};
6464
use itertools::Itertools;
6565
use la_arena::Arena;
6666
use profile::Count;
67-
use rustc_hash::FxHashMap;
67+
use rustc_hash::{FxHashMap, FxHashSet};
6868
use stdx::format_to;
6969
use syntax::{ast, SmolStr};
7070

@@ -114,6 +114,8 @@ pub struct DefMap {
114114
registered_attrs: Vec<SmolStr>,
115115
/// Custom tool modules registered with `#![register_tool]`.
116116
registered_tools: Vec<SmolStr>,
117+
/// Unstable features of Rust enabled with `#![feature(A, B)]`.
118+
unstable_features: FxHashSet<SmolStr>,
117119

118120
edition: Edition,
119121
recursion_limit: Option<u32>,
@@ -284,6 +286,7 @@ impl DefMap {
284286
modules,
285287
registered_attrs: Vec::new(),
286288
registered_tools: Vec::new(),
289+
unstable_features: FxHashSet::default(),
287290
diagnostics: Vec::new(),
288291
}
289292
}
@@ -314,6 +317,10 @@ impl DefMap {
314317
&self.registered_attrs
315318
}
316319

320+
pub fn is_unstable_feature_enabled(&self, feature: &str) -> bool {
321+
self.unstable_features.contains(feature)
322+
}
323+
317324
pub fn root(&self) -> LocalModuleId {
318325
self.root
319326
}
@@ -483,6 +490,7 @@ impl DefMap {
483490
registered_tools,
484491
fn_proc_macro_mapping,
485492
derive_helpers_in_scope,
493+
unstable_features,
486494
proc_macro_loading_error: _,
487495
block: _,
488496
edition: _,
@@ -500,6 +508,7 @@ impl DefMap {
500508
registered_tools.shrink_to_fit();
501509
fn_proc_macro_mapping.shrink_to_fit();
502510
derive_helpers_in_scope.shrink_to_fit();
511+
unstable_features.shrink_to_fit();
503512
for (_, module) in modules.iter_mut() {
504513
module.children.shrink_to_fit();
505514
module.scope.shrink_to_fit();

crates/hir-def/src/nameres/collector.rs

+11
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,17 @@ impl DefCollector<'_> {
294294
continue;
295295
}
296296

297+
if *attr_name == hir_expand::name![feature] {
298+
let features =
299+
attr.parse_path_comma_token_tree().into_iter().flatten().filter_map(
300+
|feat| match feat.segments() {
301+
[name] => Some(name.to_smol_str()),
302+
_ => None,
303+
},
304+
);
305+
self.def_map.unstable_features.extend(features);
306+
}
307+
297308
let attr_is_register_like = *attr_name == hir_expand::name![register_attr]
298309
|| *attr_name == hir_expand::name![register_tool];
299310
if !attr_is_register_like {

crates/hir-expand/src/name.rs

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ pub mod known {
336336
test,
337337
test_case,
338338
recursion_limit,
339+
feature,
339340
// Safe intrinsics
340341
abort,
341342
add_with_overflow,

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

+1-6
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

+10-6
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

+21-8
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ use hir_def::{AdtId, DefWithBodyId, HasModule, ModuleId};
277277
use smallvec::{smallvec, SmallVec};
278278
use typed_arena::Arena;
279279

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

282282
use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard};
283283

@@ -289,13 +289,27 @@ pub(crate) struct MatchCheckCtx<'a, 'p> {
289289
pub(crate) db: &'a dyn HirDatabase,
290290
/// Lowered patterns from arms plus generated by the check.
291291
pub(crate) pattern_arena: &'p Arena<DeconstructedPat<'p>>,
292+
exhaustive_patterns: bool,
292293
}
293294

294295
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
296+
pub(crate) fn new(
297+
module: ModuleId,
298+
body: DefWithBodyId,
299+
db: &'a dyn HirDatabase,
300+
pattern_arena: &'p Arena<DeconstructedPat<'p>>,
301+
) -> Self {
302+
let def_map = db.crate_def_map(module.krate());
303+
let exhaustive_patterns = def_map.is_unstable_feature_enabled("exhaustive_patterns");
304+
Self { module, body, db, pattern_arena, exhaustive_patterns }
305+
}
306+
307+
pub(super) fn is_uninhabited(&self, ty: &Ty) -> bool {
308+
if self.feature_exhaustive_patterns() {
309+
is_ty_uninhabited_from(ty, self.module, self.db)
310+
} else {
311+
false
312+
}
299313
}
300314

301315
/// Returns whether the given type is an enum from another crate declared `#[non_exhaustive]`.
@@ -311,10 +325,9 @@ impl<'a, 'p> MatchCheckCtx<'a, 'p> {
311325
}
312326
}
313327

314-
// Rust feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
328+
// Rust's unstable feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
315329
pub(super) fn feature_exhaustive_patterns(&self) -> bool {
316-
// FIXME see MatchCheckCtx::is_uninhabited
317-
false
330+
self.exhaustive_patterns
318331
}
319332
}
320333

crates/hir-ty/src/inhabitedness.rs

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

crates/hir-ty/src/lib.rs

+1
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;

0 commit comments

Comments
 (0)