Skip to content

Commit

Permalink
Auto merge of rust-lang#13167 - iDawer:exhaustive_patterns, r=Veykril
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Sep 2, 2022
2 parents 412d614 + ffd79c2 commit 4f8153e
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 21 deletions.
11 changes: 10 additions & 1 deletion crates/hir-def/src/nameres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId};
use itertools::Itertools;
use la_arena::Arena;
use profile::Count;
use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet};
use stdx::format_to;
use syntax::{ast, SmolStr};

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

edition: Edition,
recursion_limit: Option<u32>,
Expand Down Expand Up @@ -284,6 +286,7 @@ impl DefMap {
modules,
registered_attrs: Vec::new(),
registered_tools: Vec::new(),
unstable_features: FxHashSet::default(),
diagnostics: Vec::new(),
}
}
Expand Down Expand Up @@ -314,6 +317,10 @@ impl DefMap {
&self.registered_attrs
}

pub fn is_unstable_feature_enabled(&self, feature: &str) -> bool {
self.unstable_features.contains(feature)
}

pub fn root(&self) -> LocalModuleId {
self.root
}
Expand Down Expand Up @@ -483,6 +490,7 @@ impl DefMap {
registered_tools,
fn_proc_macro_mapping,
derive_helpers_in_scope,
unstable_features,
proc_macro_loading_error: _,
block: _,
edition: _,
Expand All @@ -500,6 +508,7 @@ impl DefMap {
registered_tools.shrink_to_fit();
fn_proc_macro_mapping.shrink_to_fit();
derive_helpers_in_scope.shrink_to_fit();
unstable_features.shrink_to_fit();
for (_, module) in modules.iter_mut() {
module.children.shrink_to_fit();
module.scope.shrink_to_fit();
Expand Down
11 changes: 11 additions & 0 deletions crates/hir-def/src/nameres/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,17 @@ impl DefCollector<'_> {
continue;
}

if *attr_name == hir_expand::name![feature] {
let features =
attr.parse_path_comma_token_tree().into_iter().flatten().filter_map(
|feat| match feat.segments() {
[name] => Some(name.to_smol_str()),
_ => None,
},
);
self.def_map.unstable_features.extend(features);
}

let attr_is_register_like = *attr_name == hir_expand::name![register_attr]
|| *attr_name == hir_expand::name![register_tool];
if !attr_is_register_like {
Expand Down
1 change: 1 addition & 0 deletions crates/hir-expand/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ pub mod known {
test,
test_case,
recursion_limit,
feature,
// Safe intrinsics
abort,
add_with_overflow,
Expand Down
7 changes: 1 addition & 6 deletions crates/hir-ty/src/diagnostics/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,7 @@ impl ExprValidator {
}

let pattern_arena = Arena::new();
let cx = MatchCheckCtx {
module: self.owner.module(db.upcast()),
body: self.owner,
db,
pattern_arena: &pattern_arena,
};
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);

let mut m_arms = Vec::with_capacity(arms.len());
let mut has_lowering_errors = false;
Expand Down
16 changes: 10 additions & 6 deletions crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ use hir_def::{EnumVariantId, HasModule, LocalFieldId, VariantId};
use smallvec::{smallvec, SmallVec};
use stdx::never;

use crate::{infer::normalize, AdtId, Interner, Scalar, Ty, TyExt, TyKind};
use crate::{
infer::normalize, inhabitedness::is_enum_variant_uninhabited_from, AdtId, Interner, Scalar, Ty,
TyExt, TyKind,
};

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

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

if is_secretly_empty || is_declared_nonexhaustive {
Expand Down
29 changes: 21 additions & 8 deletions crates/hir-ty/src/diagnostics/match_check/usefulness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ use hir_def::{AdtId, DefWithBodyId, HasModule, ModuleId};
use smallvec::{smallvec, SmallVec};
use typed_arena::Arena;

use crate::{db::HirDatabase, Ty, TyExt};
use crate::{db::HirDatabase, inhabitedness::is_ty_uninhabited_from, Ty, TyExt};

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

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

impl<'a, 'p> MatchCheckCtx<'a, 'p> {
pub(super) fn is_uninhabited(&self, _ty: &Ty) -> bool {
// FIXME(iDawer) implement exhaustive_patterns feature. More info in:
// Tracking issue for RFC 1872: exhaustive_patterns feature https://github.com/rust-lang/rust/issues/51085
false
pub(crate) fn new(
module: ModuleId,
body: DefWithBodyId,
db: &'a dyn HirDatabase,
pattern_arena: &'p Arena<DeconstructedPat<'p>>,
) -> Self {
let def_map = db.crate_def_map(module.krate());
let exhaustive_patterns = def_map.is_unstable_feature_enabled("exhaustive_patterns");
Self { module, body, db, pattern_arena, exhaustive_patterns }
}

pub(super) fn is_uninhabited(&self, ty: &Ty) -> bool {
if self.feature_exhaustive_patterns() {
is_ty_uninhabited_from(ty, self.module, self.db)
} else {
false
}
}

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

// Rust feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
// Rust's unstable feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
pub(super) fn feature_exhaustive_patterns(&self) -> bool {
// FIXME see MatchCheckCtx::is_uninhabited
false
self.exhaustive_patterns
}
}

Expand Down
173 changes: 173 additions & 0 deletions crates/hir-ty/src/inhabitedness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! Type inhabitedness logic.
use std::ops::ControlFlow::{self, Break, Continue};

use chalk_ir::{
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
DebruijnIndex,
};
use hir_def::{
adt::VariantData, attr::Attrs, type_ref::ConstScalar, visibility::Visibility, AdtId,
EnumVariantId, HasModule, Lookup, ModuleId, VariantId,
};

use crate::{
db::HirDatabase, Binders, ConcreteConst, Const, ConstValue, Interner, Substitution, Ty, TyKind,
};

/// Checks whether a type is visibly uninhabited from a particular module.
pub(crate) fn is_ty_uninhabited_from(ty: &Ty, target_mod: ModuleId, db: &dyn HirDatabase) -> bool {
let mut uninhabited_from = UninhabitedFrom { target_mod, db };
let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}

/// Checks whether a variant is visibly uninhabited from a particular module.
pub(crate) fn is_enum_variant_uninhabited_from(
variant: EnumVariantId,
subst: &Substitution,
target_mod: ModuleId,
db: &dyn HirDatabase,
) -> bool {
let enum_data = db.enum_data(variant.parent);
let vars_attrs = db.variants_attrs(variant.parent);
let is_local = variant.parent.lookup(db.upcast()).container.krate() == target_mod.krate();

let mut uninhabited_from = UninhabitedFrom { target_mod, db };
let inhabitedness = uninhabited_from.visit_variant(
variant.into(),
&enum_data.variants[variant.local_id].variant_data,
subst,
&vars_attrs[variant.local_id],
is_local,
);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}

struct UninhabitedFrom<'a> {
target_mod: ModuleId,
db: &'a dyn HirDatabase,
}

const CONTINUE_OPAQUELY_INHABITED: ControlFlow<VisiblyUninhabited> = Continue(());
const BREAK_VISIBLY_UNINHABITED: ControlFlow<VisiblyUninhabited> = Break(VisiblyUninhabited);
#[derive(PartialEq, Eq)]
struct VisiblyUninhabited;

impl TypeVisitor<Interner> for UninhabitedFrom<'_> {
type BreakTy = VisiblyUninhabited;

fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = VisiblyUninhabited> {
self
}

fn visit_ty(
&mut self,
ty: &Ty,
outer_binder: DebruijnIndex,
) -> ControlFlow<VisiblyUninhabited> {
match ty.kind(Interner) {
TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst),
TyKind::Never => BREAK_VISIBLY_UNINHABITED,
TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder),
TyKind::Array(item_ty, len) => match try_usize_const(len) {
Some(0) | None => CONTINUE_OPAQUELY_INHABITED,
Some(1..) => item_ty.super_visit_with(self, outer_binder),
},

TyKind::Ref(..) | _ => CONTINUE_OPAQUELY_INHABITED,
}
}

fn interner(&self) -> Interner {
Interner
}
}

impl UninhabitedFrom<'_> {
fn visit_adt(&mut self, adt: AdtId, subst: &Substitution) -> ControlFlow<VisiblyUninhabited> {
let attrs = self.db.attrs(adt.into());
let adt_non_exhaustive = attrs.by_key("non_exhaustive").exists();
let is_local = adt.module(self.db.upcast()).krate() == self.target_mod.krate();
if adt_non_exhaustive && !is_local {
return CONTINUE_OPAQUELY_INHABITED;
}

// An ADT is uninhabited iff all its variants uninhabited.
match adt {
// rustc: For now, `union`s are never considered uninhabited.
AdtId::UnionId(_) => CONTINUE_OPAQUELY_INHABITED,
AdtId::StructId(s) => {
let struct_data = self.db.struct_data(s);
self.visit_variant(s.into(), &struct_data.variant_data, subst, &attrs, is_local)
}
AdtId::EnumId(e) => {
let vars_attrs = self.db.variants_attrs(e);
let enum_data = self.db.enum_data(e);

for (local_id, enum_var) in enum_data.variants.iter() {
let variant_inhabitedness = self.visit_variant(
EnumVariantId { parent: e, local_id }.into(),
&enum_var.variant_data,
subst,
&vars_attrs[local_id],
is_local,
);
match variant_inhabitedness {
Break(VisiblyUninhabited) => continue,
Continue(()) => return CONTINUE_OPAQUELY_INHABITED,
}
}
BREAK_VISIBLY_UNINHABITED
}
}
}

fn visit_variant(
&mut self,
variant: VariantId,
variant_data: &VariantData,
subst: &Substitution,
attrs: &Attrs,
is_local: bool,
) -> ControlFlow<VisiblyUninhabited> {
let non_exhaustive_field_list = attrs.by_key("non_exhaustive").exists();
if non_exhaustive_field_list && !is_local {
return CONTINUE_OPAQUELY_INHABITED;
}

let is_enum = matches!(variant, VariantId::EnumVariantId(..));
let field_tys = self.db.field_types(variant);
let field_vis = self.db.field_visibilities(variant);

for (fid, _) in variant_data.fields().iter() {
self.visit_field(field_vis[fid], &field_tys[fid], subst, is_enum)?;
}
CONTINUE_OPAQUELY_INHABITED
}

fn visit_field(
&mut self,
vis: Visibility,
ty: &Binders<Ty>,
subst: &Substitution,
is_enum: bool,
) -> ControlFlow<VisiblyUninhabited> {
if is_enum || vis.is_visible_from(self.db.upcast(), self.target_mod) {
let ty = ty.clone().substitute(Interner, subst);
ty.visit_with(self, DebruijnIndex::INNERMOST)
} else {
CONTINUE_OPAQUELY_INHABITED
}
}
}

fn try_usize_const(c: &Const) -> Option<u128> {
let data = &c.data(Interner);
if data.ty.kind(Interner) != &TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)) {
return None;
}
match data.value {
ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(value) }) => Some(value),
_ => None,
}
}
1 change: 1 addition & 0 deletions crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod chalk_db;
mod chalk_ext;
pub mod consteval;
mod infer;
mod inhabitedness;
mod interner;
mod lower;
mod mapping;
Expand Down
Loading

0 comments on commit 4f8153e

Please sign in to comment.