From 5a8a0b62697c01ef881e7e2a0387e3649cab2034 Mon Sep 17 00:00:00 2001 From: Dawer <7803845+iDawer@users.noreply.github.com> Date: Fri, 30 Apr 2021 19:12:04 +0500 Subject: Check enum patterns --- crates/hir_ty/src/diagnostics/pattern.rs | 45 +++++++++++++++ .../src/diagnostics/pattern/deconstruct_pat.rs | 64 ++++++++++++++++++++-- .../hir_ty/src/diagnostics/pattern/usefulness.rs | 21 ++++++- 3 files changed, 124 insertions(+), 6 deletions(-) (limited to 'crates/hir_ty') diff --git a/crates/hir_ty/src/diagnostics/pattern.rs b/crates/hir_ty/src/diagnostics/pattern.rs index 4dcbd7f9f..3e90461cc 100644 --- a/crates/hir_ty/src/diagnostics/pattern.rs +++ b/crates/hir_ty/src/diagnostics/pattern.rs @@ -73,6 +73,51 @@ fn main(v: S) { match v { } //^ Missing match arm } +"#, + ); + } + + #[test] + fn c_enum() { + check_diagnostics( + r#" +enum E { A, B } +fn main(v: E) { + match v { E::A | E::B => {} } + match v { _ => {} } + match v { E::A => {} } + //^ Missing match arm + match v { } + //^ Missing match arm +} +"#, + ); + } + + #[test] + fn enum_() { + check_diagnostics( + r#" +struct A; struct B; +enum E { Tuple(A, B), Struct{ a: A, b: B } } +fn main(v: E) { + match v { + E::Tuple(a, b) => {} + E::Struct{ a, b } => {} + } + match v { + E::Tuple(_, _) => {} + E::Struct{..} => {} + } + match v { + E::Tuple(..) => {} + _ => {} + } + match v { E::Tuple(..) => {} } + //^ Missing match arm + match v { } + //^ Missing match arm +} "#, ); } diff --git a/crates/hir_ty/src/diagnostics/pattern/deconstruct_pat.rs b/crates/hir_ty/src/diagnostics/pattern/deconstruct_pat.rs index 60323aea3..3c1811f95 100644 --- a/crates/hir_ty/src/diagnostics/pattern/deconstruct_pat.rs +++ b/crates/hir_ty/src/diagnostics/pattern/deconstruct_pat.rs @@ -135,6 +135,7 @@ impl Constructor { Pat::Bind { .. } | Pat::Wild => Wildcard, Pat::Tuple { .. } | Pat::Ref { .. } | Pat::Box { .. } => Single, Pat::Record { .. } | Pat::Path(_) | Pat::TupleStruct { .. } => { + // TODO: path to const let variant_id = cx.infer.variant_resolution_for_pat(pat).unwrap_or_else(|| todo!()); match variant_id { @@ -144,8 +145,8 @@ impl Constructor { } Pat::Or(..) => panic!("bug: Or-pattern should have been expanded earlier on."), + Pat::Missing => todo!("Fail gracefully when there is an error in a pattern"), pat => todo!("Constructor::from_pat {:?}", pat), - // Pat::Missing => {} // Pat::Range { start, end } => {} // Pat::Slice { prefix, slice, suffix } => {} // Pat::Lit(_) => {} @@ -280,7 +281,7 @@ pub(super) struct SplitWildcard { impl SplitWildcard { pub(super) fn new(pcx: PatCtxt<'_>) -> Self { - // let cx = pcx.cx; + let cx = pcx.cx; // let make_range = |start, end| IntRange(todo!()); // This determines the set of all possible constructors for the type `pcx.ty`. For numbers, @@ -292,9 +293,62 @@ impl SplitWildcard { // Invariant: this is empty if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). let all_ctors = match pcx.ty.kind(&Interner) { - TyKind::Adt(AdtId(hir_def::AdtId::EnumId(_)), _) => todo!(), + TyKind::Scalar(Scalar::Bool) => todo!(), + // TyKind::Array(..) if ... => todo!(), + TyKind::Array(..) | TyKind::Slice(..) => todo!(), + &TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), ref _substs) => { + 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. + // There is no point in enumerating all possible variants, because the user can't + // actually match against them all themselves. So we always return only the fictitious + // constructor. + // E.g., in an example like: + // + // ``` + // let err: io::ErrorKind = ...; + // match err { + // io::ErrorKind::NotFound => {}, + // } + // ``` + // + // we don't want to show every possible IO error, but instead have only `_` as the + // witness. + let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(enum_id); + + // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it + // as though it had an "unknown" constructor to avoid exposing its emptiness. The + // exception is if the pattern is at the top level, because we want empty matches to be + // considered exhaustive. + let is_secretly_empty = enum_data.variants.is_empty() + && !cx.feature_exhaustive_patterns() + && !pcx.is_top_level; + + if is_secretly_empty || is_declared_nonexhaustive { + smallvec![NonExhaustive] + } else if cx.feature_exhaustive_patterns() { + // If `exhaustive_patterns` is enabled, we exclude variants known to be + // uninhabited. + todo!() + } else { + enum_data + .variants + .iter() + .map(|(local_id, ..)| Variant(EnumVariantId { parent: enum_id, local_id })) + .collect() + } + } + TyKind::Scalar(Scalar::Char) => todo!(), + TyKind::Scalar(Scalar::Int(..)) | TyKind::Scalar(Scalar::Uint(..)) => todo!(), + TyKind::Never if !cx.feature_exhaustive_patterns() && !pcx.is_top_level => { + smallvec![NonExhaustive] + } + TyKind::Never => SmallVec::new(), + _ if cx.is_uninhabited(&pcx.ty) => SmallVec::new(), TyKind::Adt(..) | TyKind::Tuple(..) | TyKind::Ref(..) => smallvec![Single], - _ => todo!(), + // This type is one for which we cannot list constructors, like `str` or `f64`. + _ => smallvec![NonExhaustive], }; SplitWildcard { matrix_ctors: Vec::new(), all_ctors } } @@ -496,7 +550,7 @@ impl Fields { pub(super) fn apply(self, pcx: PatCtxt<'_>, ctor: &Constructor) -> Pat { let subpatterns_and_indices = self.patterns_and_indices(); let mut subpatterns = subpatterns_and_indices.iter().map(|&(_, p)| p); - // TODO witnesses are not yet used + // TODO witnesses are not yet used const TODO: Pat = Pat::Wild; match ctor { diff --git a/crates/hir_ty/src/diagnostics/pattern/usefulness.rs b/crates/hir_ty/src/diagnostics/pattern/usefulness.rs index 4b55aee97..2df87ccea 100644 --- a/crates/hir_ty/src/diagnostics/pattern/usefulness.rs +++ b/crates/hir_ty/src/diagnostics/pattern/usefulness.rs @@ -3,7 +3,11 @@ use std::{cell::RefCell, iter::FromIterator, ops::Index, sync::Arc}; -use hir_def::{ModuleId, body::Body, expr::{ExprId, Pat, PatId}}; +use hir_def::{ + body::Body, + expr::{ExprId, Pat, PatId}, + HasModule, ModuleId, +}; use la_arena::Arena; use once_cell::unsync::OnceCell; use rustc_hash::FxHashMap; @@ -36,6 +40,21 @@ impl<'a> MatchCheckCtx<'a> { false } + /// Returns whether the given type is an enum from another crate declared `#[non_exhaustive]`. + pub(super) fn is_foreign_non_exhaustive_enum(&self, enum_id: hir_def::EnumId) -> bool { + let has_non_exhaustive_attr = + self.db.attrs(enum_id.into()).by_key("non_exhaustive").exists(); + let is_local = + hir_def::AdtId::from(enum_id).module(self.db.upcast()).krate() == self.module.krate(); + has_non_exhaustive_attr && !is_local + } + + // Rust feature described as "Allows exhaustive pattern matching on types that contain uninhabited types." + pub(super) fn feature_exhaustive_patterns(&self) -> bool { + // TODO + false + } + pub(super) fn alloc_pat(&self, pat: Pat, ty: &Ty) -> PatId { self.pattern_arena.borrow_mut().alloc(pat, ty) } -- cgit v1.2.3