Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #12634 - iDawer:match-check.witnesses, r=flodiebold
feat: Show witnesses of non-exhaustiveness in `missing-match-arm` diagnostic Shamelessly copied from rustc. Thus reporting format is same. This extends public api `hir::diagnostics::MissingMatchArms` with `uncovered_patterns: String` field. It does not expose data for implementing a quick fix yet. ----- Worth to note: current implementation does not give a comprehensive list of missing patterns. Also mentioned in [paper](http://moscova.inria.fr/~maranget/papers/warn/warn.pdf): > One may think that algorithm I should make an additional effort to provide more > non-matching values, by systematically computing recursive calls on specialized > matrices when possible, and by returning a list of all pattern vectors returned by > recursive calls. We can first observe that it is not possible in general to supply the > users with all non-matching values, since the signature of integers is (potentially) > infinite.
bors 2022-06-30
parent 7f9c054 · parent e417992 · commit 6420840
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs54
-rw-r--r--crates/hir-ty/src/diagnostics/match_check.rs140
-rw-r--r--crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs127
-rw-r--r--crates/hir/src/diagnostics.rs1
-rw-r--r--crates/hir/src/lib.rs3
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_match_arms.rs98
6 files changed, 330 insertions, 93 deletions
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 335d95c0cf..8cca522aef 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -2,11 +2,13 @@
//! through the body using inference results: mismatched arg counts, missing
//! fields, etc.
+use std::fmt;
use std::sync::Arc;
-use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule};
+use hir_def::{path::path, resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
use hir_expand::name;
use itertools::Either;
+use itertools::Itertools;
use rustc_hash::FxHashSet;
use typed_arena::Arena;
@@ -17,7 +19,8 @@ use crate::{
deconstruct_pat::DeconstructedPat,
usefulness::{compute_match_usefulness, MatchCheckCtx},
},
- InferenceResult, TyExt,
+ display::HirDisplay,
+ InferenceResult, Ty, TyExt,
};
pub(crate) use hir_def::{
@@ -37,6 +40,7 @@ pub enum BodyValidationDiagnostic {
},
MissingMatchArms {
match_expr: ExprId,
+ uncovered_patterns: String,
},
}
@@ -211,10 +215,11 @@ impl ExprValidator {
// https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
let witnesses = report.non_exhaustiveness_witnesses;
- // FIXME Report witnesses
- // eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses);
if !witnesses.is_empty() {
- self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr: id });
+ self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
+ match_expr: id,
+ uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
+ });
}
}
@@ -367,3 +372,42 @@ fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResul
walk(pat, body, infer, &mut has_type_mismatches);
!has_type_mismatches
}
+
+fn missing_match_arms<'p>(
+ cx: &MatchCheckCtx<'_, 'p>,
+ scrut_ty: &Ty,
+ witnesses: Vec<DeconstructedPat<'p>>,
+ arms: &[MatchArm],
+) -> String {
+ struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
+ impl<'a, 'p> fmt::Display for DisplayWitness<'a, 'p> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let DisplayWitness(witness, cx) = *self;
+ let pat = witness.to_pat(cx);
+ write!(f, "{}", pat.display(cx.db))
+ }
+ }
+
+ let non_empty_enum = match scrut_ty.as_adt() {
+ Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
+ _ => false,
+ };
+ if arms.is_empty() && !non_empty_enum {
+ format!("type `{}` is non-empty", scrut_ty.display(cx.db))
+ } else {
+ let pat_display = |witness| DisplayWitness(witness, cx);
+ const LIMIT: usize = 3;
+ match &*witnesses {
+ [witness] => format!("`{}` not covered", pat_display(witness)),
+ [head @ .., tail] if head.len() < LIMIT => {
+ let head = head.iter().map(pat_display);
+ format!("`{}` and `{}` not covered", head.format("`, `"), pat_display(tail))
+ }
+ _ => {
+ let (head, tail) = witnesses.split_at(LIMIT);
+ let head = head.iter().map(pat_display);
+ format!("`{}` and {} more not covered", head.format("`, `"), tail.len())
+ }
+ }
+ }
+}
diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs
index e00dfdf2fa..61cab7be03 100644
--- a/crates/hir-ty/src/diagnostics/match_check.rs
+++ b/crates/hir-ty/src/diagnostics/match_check.rs
@@ -10,11 +10,19 @@ mod pat_util;
pub(crate) mod deconstruct_pat;
pub(crate) mod usefulness;
-use hir_def::{body::Body, expr::PatId, EnumVariantId, LocalFieldId, VariantId};
+use chalk_ir::Mutability;
+use hir_def::{
+ adt::VariantData, body::Body, expr::PatId, AdtId, EnumVariantId, HasModule, LocalFieldId,
+ VariantId,
+};
+use hir_expand::name::{name, Name};
use stdx::{always, never};
use crate::{
- db::HirDatabase, infer::BindingMode, InferenceResult, Interner, Substitution, Ty, TyKind,
+ db::HirDatabase,
+ display::{HirDisplay, HirDisplayError, HirFormatter},
+ infer::BindingMode,
+ InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
};
use self::pat_util::EnumerateAndAdjustIterator;
@@ -49,6 +57,7 @@ pub(crate) enum PatKind {
/// `x`, `ref x`, `x @ P`, etc.
Binding {
+ name: Name,
subpattern: Option<Pat>,
},
@@ -148,7 +157,7 @@ impl<'a> PatCtxt<'a> {
}
_ => (),
}
- PatKind::Binding { subpattern: self.lower_opt_pattern(subpat) }
+ PatKind::Binding { name: name.clone(), subpattern: self.lower_opt_pattern(subpat) }
}
hir_def::expr::Pat::TupleStruct { ref args, ellipsis, .. } if variant.is_some() => {
@@ -282,6 +291,127 @@ impl<'a> PatCtxt<'a> {
}
}
+impl HirDisplay for Pat {
+ fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
+ match &*self.kind {
+ PatKind::Wild => write!(f, "_"),
+ PatKind::Binding { name, subpattern } => {
+ write!(f, "{name}")?;
+ if let Some(subpattern) = subpattern {
+ write!(f, " @ ")?;
+ subpattern.hir_fmt(f)?;
+ }
+ Ok(())
+ }
+ PatKind::Variant { subpatterns, .. } | PatKind::Leaf { subpatterns } => {
+ let variant = match *self.kind {
+ PatKind::Variant { enum_variant, .. } => Some(VariantId::from(enum_variant)),
+ _ => self.ty.as_adt().and_then(|(adt, _)| match adt {
+ AdtId::StructId(s) => Some(s.into()),
+ AdtId::UnionId(u) => Some(u.into()),
+ AdtId::EnumId(_) => None,
+ }),
+ };
+
+ if let Some(variant) = variant {
+ match variant {
+ VariantId::EnumVariantId(v) => {
+ let data = f.db.enum_data(v.parent);
+ write!(f, "{}", data.variants[v.local_id].name)?;
+ }
+ VariantId::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?,
+ VariantId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name)?,
+ };
+
+ let variant_data = variant.variant_data(f.db.upcast());
+ if let VariantData::Record(rec_fields) = &*variant_data {
+ write!(f, " {{ ")?;
+
+ let mut printed = 0;
+ let subpats = subpatterns
+ .iter()
+ .filter(|p| !matches!(*p.pattern.kind, PatKind::Wild))
+ .map(|p| {
+ printed += 1;
+ WriteWith(move |f| {
+ write!(f, "{}: ", rec_fields[p.field].name)?;
+ p.pattern.hir_fmt(f)
+ })
+ });
+ f.write_joined(subpats, ", ")?;
+
+ if printed < rec_fields.len() {
+ write!(f, "{}..", if printed > 0 { ", " } else { "" })?;
+ }
+
+ return write!(f, " }}");
+ }
+ }
+
+ let num_fields = variant
+ .map_or(subpatterns.len(), |v| v.variant_data(f.db.upcast()).fields().len());
+ if num_fields != 0 || variant.is_none() {
+ write!(f, "(")?;
+ let subpats = (0..num_fields).map(|i| {
+ WriteWith(move |f| {
+ let fid = LocalFieldId::from_raw((i as u32).into());
+ if let Some(p) = subpatterns.get(i) {
+ if p.field == fid {
+ return p.pattern.hir_fmt(f);
+ }
+ }
+ if let Some(p) = subpatterns.iter().find(|p| p.field == fid) {
+ p.pattern.hir_fmt(f)
+ } else {
+ write!(f, "_")
+ }
+ })
+ });
+ f.write_joined(subpats, ", ")?;
+ if let (TyKind::Tuple(..), 1) = (self.ty.kind(Interner), num_fields) {
+ write!(f, ",")?;
+ }
+ write!(f, ")")?;
+ }
+
+ Ok(())
+ }
+ PatKind::Deref { subpattern } => {
+ match self.ty.kind(Interner) {
+ TyKind::Adt(adt, _) if is_box(adt.0, f.db) => write!(f, "box ")?,
+ &TyKind::Ref(mutbl, ..) => {
+ write!(f, "&{}", if mutbl == Mutability::Mut { "mut " } else { "" })?
+ }
+ _ => never!("{:?} is a bad Deref pattern type", self.ty),
+ }
+ subpattern.hir_fmt(f)
+ }
+ PatKind::LiteralBool { value } => write!(f, "{}", value),
+ PatKind::Or { pats } => f.write_joined(pats.iter(), " | "),
+ }
+ }
+}
+
+struct WriteWith<F>(F)
+where
+ F: Fn(&mut HirFormatter) -> Result<(), HirDisplayError>;
+
+impl<F> HirDisplay for WriteWith<F>
+where
+ F: Fn(&mut HirFormatter) -> Result<(), HirDisplayError>,
+{
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ (self.0)(f)
+ }
+}
+
+fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool {
+ let owned_box = name![owned_box].to_smol_str();
+ let krate = adt.module(db.upcast()).krate();
+ let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from);
+ Some(adt) == box_adt
+}
+
pub(crate) trait PatternFoldable: Sized {
fn fold_with<F: PatternFolder>(&self, folder: &mut F) -> Self {
self.super_fold_with(folder)
@@ -357,8 +487,8 @@ impl PatternFoldable for PatKind {
fn super_fold_with<F: PatternFolder>(&self, folder: &mut F) -> Self {
match self {
PatKind::Wild => PatKind::Wild,
- PatKind::Binding { subpattern } => {
- PatKind::Binding { subpattern: subpattern.fold_with(folder) }
+ PatKind::Binding { name, subpattern } => {
+ PatKind::Binding { name: name.clone(), subpattern: subpattern.fold_with(folder) }
}
PatKind::Variant { substs, enum_variant, subpatterns } => PatKind::Variant {
substs: substs.fold_with(folder),
diff --git a/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs b/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs
index 84ded517ba..f92d79949d 100644
--- a/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs
+++ b/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs
@@ -51,13 +51,13 @@ use std::{
use hir_def::{EnumVariantId, HasModule, LocalFieldId, VariantId};
use smallvec::{smallvec, SmallVec};
use stdx::never;
-use syntax::SmolStr;
use crate::{infer::normalize, AdtId, Interner, Scalar, Ty, TyExt, TyKind};
use super::{
+ is_box,
usefulness::{helper::Captures, MatchCheckCtx, PatCtxt},
- Pat, PatKind,
+ FieldPat, Pat, PatKind,
};
use self::Constructor::*;
@@ -144,6 +144,24 @@ impl IntRange {
}
}
+ fn to_pat(&self, _cx: &MatchCheckCtx, ty: Ty) -> Pat {
+ match ty.kind(Interner) {
+ TyKind::Scalar(Scalar::Bool) => {
+ let kind = match self.boundaries() {
+ (0, 0) => PatKind::LiteralBool { value: false },
+ (1, 1) => PatKind::LiteralBool { value: true },
+ (0, 1) => PatKind::Wild,
+ (lo, hi) => {
+ never!("bad range for bool pattern: {}..={}", lo, hi);
+ PatKind::Wild
+ }
+ };
+ Pat { ty, kind: kind.into() }
+ }
+ _ => unimplemented!(),
+ }
+ }
+
/// See `Constructor::is_covered_by`
fn is_covered_by(&self, other: &Self) -> bool {
if self.intersection(other).is_some() {
@@ -260,12 +278,12 @@ pub(super) struct Slice {
impl Slice {
fn arity(self) -> usize {
- unimplemented!()
+ match self._unimplemented {}
}
/// See `Constructor::is_covered_by`
fn is_covered_by(self, _other: Self) -> bool {
- unimplemented!() // never called as Slice contains Void
+ match self._unimplemented {}
}
}
@@ -363,7 +381,7 @@ impl Constructor {
TyKind::Tuple(arity, ..) => arity,
TyKind::Ref(..) => 1,
TyKind::Adt(adt, ..) => {
- if adt_is_box(adt.0, pcx.cx) {
+ if is_box(adt.0, pcx.cx.db) {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
1
@@ -424,7 +442,7 @@ impl Constructor {
split_range.split(int_ranges.cloned());
split_range.iter().map(IntRange).collect()
}
- Slice(_) => unimplemented!(),
+ Slice(slice) => match slice._unimplemented {},
// Any other constructor can be used unchanged.
_ => smallvec![self.clone()],
}
@@ -447,12 +465,8 @@ impl Constructor {
(Variant(self_id), Variant(other_id)) => self_id == other_id,
(IntRange(self_range), IntRange(other_range)) => self_range.is_covered_by(other_range),
- (FloatRange(..), FloatRange(..)) => {
- unimplemented!()
- }
- (Str(..), Str(..)) => {
- unimplemented!()
- }
+ (FloatRange(void), FloatRange(..)) => match *void {},
+ (Str(void), Str(..)) => match *void {},
(Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice),
// We are trying to inspect an opaque constant. Thus we skip the row.
@@ -782,7 +796,7 @@ impl<'p> Fields<'p> {
}
TyKind::Ref(.., rty) => Fields::wildcards_from_tys(cx, once(rty.clone())),
&TyKind::Adt(AdtId(adt), ref substs) => {
- if adt_is_box(adt, cx) {
+ if is_box(adt, cx.db) {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
let subst_ty = substs.at(Interner, 0).assert_ty_ref(Interner).clone();
@@ -799,9 +813,7 @@ impl<'p> Fields<'p> {
Fields::wildcards_from_tys(cx, once(ty.clone()))
}
},
- Slice(..) => {
- unimplemented!()
- }
+ Slice(slice) => match slice._unimplemented {},
Str(..)
| FloatRange(..)
| IntRange(..)
@@ -865,8 +877,8 @@ impl<'p> DeconstructedPat<'p> {
let ctor;
let fields;
match pat.kind.as_ref() {
- PatKind::Binding { subpattern: Some(subpat) } => return mkpat(subpat),
- PatKind::Binding { subpattern: None } | PatKind::Wild => {
+ PatKind::Binding { subpattern: Some(subpat), .. } => return mkpat(subpat),
+ PatKind::Binding { subpattern: None, .. } | PatKind::Wild => {
ctor = Wildcard;
fields = Fields::empty();
}
@@ -889,7 +901,7 @@ impl<'p> DeconstructedPat<'p> {
}
fields = Fields::from_iter(cx, wilds)
}
- TyKind::Adt(adt, substs) if adt_is_box(adt.0, cx) => {
+ TyKind::Adt(adt, substs) if is_box(adt.0, cx.db) => {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
// FIXME(Nadrieril): A `Box` can in theory be matched either with `Box(_,
@@ -963,10 +975,67 @@ impl<'p> DeconstructedPat<'p> {
DeconstructedPat::new(ctor, fields, pat.ty.clone())
}
- // // FIXME(iDawer): implement reporting of noncovered patterns
- // pub(crate) fn to_pat(&self, _cx: &MatchCheckCtx<'_, 'p>) -> Pat {
- // Pat { ty: self.ty.clone(), kind: PatKind::Wild.into() }
- // }
+ pub(crate) fn to_pat(&self, cx: &MatchCheckCtx<'_, 'p>) -> Pat {
+ let mut subpatterns = self.iter_fields().map(|p| p.to_pat(cx));
+ let pat = match &self.ctor {
+ Single | Variant(_) => match self.ty.kind(Interner) {
+ TyKind::Tuple(..) => PatKind::Leaf {
+ subpatterns: subpatterns
+ .zip(0u32..)
+ .map(|(p, i)| FieldPat {
+ field: LocalFieldId::from_raw(i.into()),
+ pattern: p,
+ })
+ .collect(),
+ },
+ TyKind::Adt(adt, _) if is_box(adt.0, cx.db) => {
+ // Without `box_patterns`, the only legal pattern of type `Box` is `_` (outside
+ // of `std`). So this branch is only reachable when the feature is enabled and
+ // the pattern is a box pattern.
+ PatKind::Deref { subpattern: subpatterns.next().unwrap() }
+ }
+ TyKind::Adt(adt, substs) => {
+ let variant = self.ctor.variant_id_for_adt(adt.0);
+ let subpatterns = Fields::list_variant_nonhidden_fields(cx, self.ty(), variant)
+ .zip(subpatterns)
+ .map(|((field, _ty), pattern)| FieldPat { field, pattern })
+ .collect();
+
+ if let VariantId::EnumVariantId(enum_variant) = variant {
+ PatKind::Variant { substs: substs.clone(), enum_variant, subpatterns }
+ } else {
+ PatKind::Leaf { subpatterns }
+ }
+ }
+ // Note: given the expansion of `&str` patterns done in `expand_pattern`, we should
+ // be careful to reconstruct the correct constant pattern here. However a string
+ // literal pattern will never be reported as a non-exhaustiveness witness, so we
+ // ignore this issue.
+ TyKind::Ref(..) => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
+ _ => {
+ never!("unexpected ctor for type {:?} {:?}", self.ctor, self.ty);
+ PatKind::Wild
+ }
+ },
+ &Slice(slice) => match slice._unimplemented {},
+ &Str(void) => match void {},
+ &FloatRange(void) => match void {},
+ IntRange(range) => return range.to_pat(cx, self.ty.clone()),
+ Wildcard | NonExhaustive => PatKind::Wild,
+ Missing { .. } => {
+ never!(
+ "trying to convert a `Missing` constructor into a `Pat`; this is a bug, \
+ `Missing` should have been processed in `apply_constructors`"
+ );
+ PatKind::Wild
+ }
+ Opaque | Or => {
+ never!("can't convert to pattern: {:?}", self.ctor);
+ PatKind::Wild
+ }
+ };
+ Pat { ty: self.ty.clone(), kind: Box::new(pat) }
+ }
pub(super) fn is_or_pat(&self) -> bool {
matches!(self.ctor, Or)
@@ -980,7 +1049,7 @@ impl<'p> DeconstructedPat<'p> {
&self.ty
}
- pub(super) fn iter_fields<'a>(&'a self) -> impl Iterator<Item = &'a DeconstructedPat<'a>> + 'a {
+ pub(super) fn iter_fields<'a>(&'a self) -> impl Iterator<Item = &'p DeconstructedPat<'p>> + 'a {
self.fields.iter_patterns()
}
@@ -999,7 +1068,7 @@ impl<'p> DeconstructedPat<'p> {
(Slice(self_slice), Slice(other_slice))
if self_slice.arity() != other_slice.arity() =>
{
- unimplemented!()
+ match self_slice._unimplemented {}
}
_ => self.fields.iter_patterns().collect(),
}
@@ -1023,11 +1092,3 @@ fn is_field_list_non_exhaustive(variant_id: VariantId, cx: &MatchCheckCtx<'_, '_
};
cx.db.attrs(attr_def_id).by_key("non_exhaustive").exists()
}
-
-fn adt_is_box(adt: hir_def::AdtId, cx: &MatchCheckCtx<'_, '_>) -> bool {
- use hir_def::lang_item::LangItemTarget;
- match cx.db.lang_item(cx.module.krate(), SmolStr::new_inline("owned_box")) {
- Some(LangItemTarget::StructId(box_id)) => adt == box_id.into(),
- _ => false,
- }
-}
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 7ecfb0cd6f..6c6c11ea4e 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -156,6 +156,7 @@ pub struct MismatchedArgCount {
pub struct MissingMatchArms {
pub file: HirFileId,
pub match_expr: AstPtr<ast::Expr>,
+ pub uncovered_patterns: String,
}
#[derive(Debug)]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index f24edebfd0..2264bbbfd7 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1312,7 +1312,7 @@ impl DefWithBody {
);
}
}
- BodyValidationDiagnostic::MissingMatchArms { match_expr } => {
+ BodyValidationDiagnostic::MissingMatchArms { match_expr, uncovered_patterns } => {
match source_map.expr_syntax(match_expr) {
Ok(source_ptr) => {
let root = source_ptr.file_syntax(db.upcast());
@@ -1324,6 +1324,7 @@ impl DefWithBody {
MissingMatchArms {
file: source_ptr.file_id,
match_expr: AstPtr::new(&match_expr),
+ uncovered_patterns,
}
.into(),
);
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 3eb4cf60a9..9e66fbfb75 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -11,7 +11,7 @@ pub(crate) fn missing_match_arms(
) -> Diagnostic {
Diagnostic::new(
"missing-match-arm",
- "missing match arm",
+ format!("missing match arm: {}", d.uncovered_patterns),
ctx.sema.diagnostics_display_range(InFile::new(d.file, d.match_expr.clone().into())).range,
)
}
@@ -31,9 +31,9 @@ mod tests {
r#"
fn main() {
match () { }
- //^^ error: missing match arm
+ //^^ error: missing match arm: type `()` is non-empty
match (()) { }
- //^^^^ error: missing match arm
+ //^^^^ error: missing match arm: type `()` is non-empty
match () { _ => (), }
match () { () => (), }
@@ -49,7 +49,7 @@ fn main() {
r#"
fn main() {
match ((), ()) { }
- //^^^^^^^^ error: missing match arm
+ //^^^^^^^^ error: missing match arm: type `((), ())` is non-empty
match ((), ()) { ((), ()) => (), }
}
@@ -63,21 +63,21 @@ fn main() {
r#"
fn test_main() {
match false { }
- //^^^^^ error: missing match arm
+ //^^^^^ error: missing match arm: type `bool` is non-empty
match false { true => (), }
- //^^^^^ error: missing match arm
+ //^^^^^ error: missing match arm: `false` not covered
match (false, true) {}
- //^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^ error: missing match arm: type `(bool, bool)` is non-empty
match (false, true) { (true, true) => (), }
- //^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
match (false, true) {
- //^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^ error: missing match arm: `(true, true)` not covered
(false, true) => (),
(false, false) => (),
(true, false) => (),
}
match (false, true) { (true, _x) => (), }
- //^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
match false { true => (), false => (), }
match (false, true) {
@@ -116,11 +116,11 @@ fn test_main() {
r#"
fn main() {
match (false, ((), false)) {}
- //^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: type `(bool, ((), bool))` is non-empty
match (false, ((), false)) { (true, ((), true)) => (), }
- //^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
match (false, ((), false)) { (true, _) => (), }
- //^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
match (false, ((), false)) {
(true, ((), true)) => (),
@@ -146,12 +146,12 @@ enum Either { A, B, }
fn main() {
match Either::A { }
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `A` and `B` not covered
match Either::B { Either::A => (), }
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `B` not covered
match &Either::B {
- //^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^ error: missing match arm: `&B` not covered
Either::A => (),
}
@@ -174,9 +174,9 @@ enum Either { A(bool), B }
fn main() {
match Either::B { }
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `A(_)` and `B` not covered
match Either::B {
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `A(false)` not covered
Either::A(true) => (), Either::B => ()
}
@@ -207,7 +207,7 @@ enum Either { A(bool), B(bool, bool) }
fn main() {
match Either::A(false) {
- //^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^ error: missing match arm: `B(true, _)` not covered
Either::A(_) => (),
Either::B(false, _) => (),
}
@@ -353,7 +353,7 @@ fn main() {
Either::A => (),
}
match loop { break Foo::A } {
- //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `B` not covered
Either::A => (),
}
match loop { break Foo::A } {
@@ -391,9 +391,9 @@ enum Either { A { foo: bool }, B }
fn main() {
let a = Either::A { foo: true };
match a { }
- //^ error: missing match arm
+ //^ error: missing match arm: `A { .. }` and `B` not covered
match a { Either::A { foo: true } => () }
- //^ error: missing match arm
+ //^ error: missing match arm: `B` not covered
match a {
Either::A { } => (),
//^^^^^^^^^ 💡 error: missing structure fields:
@@ -401,7 +401,7 @@ fn main() {
Either::B => (),
}
match a {
- //^ error: missing match arm
+ //^ error: missing match arm: `B` not covered
Either::A { } => (),
} //^^^^^^^^^ 💡 error: missing structure fields:
// | - foo
@@ -432,7 +432,7 @@ enum Either {
fn main() {
let a = Either::A { foo: true, bar: () };
match a {
- //^ error: missing match arm
+ //^ error: missing match arm: `B` not covered
Either::A { bar: (), foo: false } => (),
Either::A { foo: true, bar: () } => (),
}
@@ -459,12 +459,12 @@ enum Either {
fn main() {
let a = Either::B;
match a {
- //^ error: missing match arm
+ //^ error: missing match arm: `A { foo: false, .. }` not covered
Either::A { foo: true, .. } => (),
Either::B => (),
}
match a {
- //^ error: missing match arm
+ //^ error: missing match arm: `B` not covered
Either::A { .. } => (),
}
@@ -494,14 +494,14 @@ enum Either {
fn main() {
match Either::B {
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `A(false, _, _, true)` not covered
Either::A(true, .., true) => (),
Either::A(true, .., false) => (),
Either::A(false, .., false) => (),
Either::B => (),
}
match Either::B {
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `A(false, _, _, false)` not covered
Either::A(true, .., true) => (),
Either::A(true, .., false) => (),
Either::A(.., true) => (),
@@ -538,7 +538,7 @@ fn enum_(never: Never) {
}
fn enum_ref(never: &Never) {
match never {}
- //^^^^^ error: missing match arm
+ //^^^^^ error: missing match arm: type `&Never` is non-empty
}
fn bang(never: !) {
match never {}
@@ -562,7 +562,7 @@ fn main() {
Some(never) => match never {},
}
match Option::<Never>::None {
- //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `None` not covered
Option::Some(_never) => {},
}
}
@@ -576,7 +576,7 @@ fn main() {
r#"
fn main() {
match (false, true, false) {
- //^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(true, _, _)` not covered
(false, ..) => (),
}
}"#,
@@ -589,7 +589,7 @@ fn main() {
r#"
fn main() {
match (false, true, false) {
- //^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(_, _, true)` not covered
(.., false) => (),
}
}"#,
@@ -602,7 +602,7 @@ fn main() {
r#"
fn main() {
match (false, true, false) {
- //^^^^^^^^^^^^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(false, _, _)` not covered
(true, .., false) => (),
}
}"#,
@@ -615,11 +615,11 @@ fn main() {
r#"struct Foo { a: bool }
fn main(f: Foo) {
match f {}
- //^ error: missing match arm
+ //^ error: missing match arm: type `Foo` is non-empty
match f { Foo { a: true } => () }
- //^ error: missing match arm
+ //^ error: missing match arm: `Foo { a: false }` not covered
match &f { Foo { a: true } => () }
- //^^ error: missing match arm
+ //^^ error: missing match arm: `&Foo { a: false }` not covered
match f { Foo { a: _ } => () }
match f {
Foo { a: true } => (),
@@ -640,9 +640,9 @@ fn main(f: Foo) {
r#"struct Foo(bool);
fn main(f: Foo) {
match f {}
- //^ error: missing match arm
+ //^ error: missing match arm: type `Foo` is non-empty
match f { Foo(true) => () }
- //^ error: missing match arm
+ //^ error: missing match arm: `Foo(false)` not covered
match f {
Foo(true) => (),
Foo(false) => (),
@@ -658,7 +658,7 @@ fn main(f: Foo) {
r#"struct Foo;
fn main(f: Foo) {
match f {}
- //^ error: missing match arm
+ //^ error: missing match arm: type `Foo` is non-empty
match f { Foo => () }
}
"#,
@@ -671,9 +671,9 @@ fn main(f: Foo) {
r#"struct Foo { foo: bool, bar: bool }
fn main(f: Foo) {
match f { Foo { foo: true, .. } => () }
- //^ error: missing match arm
+ //^ error: missing match arm: `Foo { foo: false, .. }` not covered
match f {
- //^ error: missing match arm
+ //^ error: missing match arm: `Foo { foo: false, bar: true }` not covered
Foo { foo: true, .. } => (),
Foo { bar: false, .. } => ()
}
@@ -694,7 +694,7 @@ fn main(f: Foo) {
fn main() {
enum Either { A(bool), B }
match Either::B {
- //^^^^^^^^^ error: missing match arm
+ //^^^^^^^^^ error: missing match arm: `B` not covered
Either::A(true | false) => (),
}
}
@@ -716,7 +716,7 @@ fn main(v: S) {
match v { S{..} => {} }
match v { _ => {} }
match v { }
- //^ error: missing match arm
+ //^ error: missing match arm: type `S` is non-empty
}
"#,
);
@@ -732,7 +732,7 @@ fn main() {
false => {}
}
match true { _x @ true => {} }
- //^^^^ error: missing match arm
+ //^^^^ error: missing match arm: `false` not covered
}
"#,
);
@@ -787,12 +787,12 @@ use lib::E;
fn main() {
match E::A { _ => {} }
match E::A {
- //^^^^ error: missing match arm
+ //^^^^ error: missing match arm: `_` not covered
E::A => {}
E::B => {}
}
match E::A {
- //^^^^ error: missing match arm
+ //^^^^ error: missing match arm: `_` not covered
E::A | E::B => {}
}
}
@@ -811,7 +811,7 @@ fn main() {
false => {}
}
match true {
- //^^^^ error: missing match arm
+ //^^^^ error: missing match arm: `true` not covered
true if false => {}
false => {}
}
@@ -876,7 +876,7 @@ struct Next<T: Trait>(T::Projection);
static __: () = {
let n: Next<A> = Next(E::Foo);
match n { Next(E::Foo) => {} }
- // ^ error: missing match arm
+ // ^ error: missing match arm: `Next(Bar)` not covered
match n { Next(E::Foo | E::Bar) => {} }
match n { Next(E::Foo | _ ) => {} }
match n { Next(_ | E::Bar) => {} }
@@ -919,7 +919,7 @@ enum Enum {
fn f(ty: Enum) {
match ty {
- //^^ error: missing match arm
+ //^^ error: missing match arm: `Type3` not covered
m!() => (),
}