about summary refs log tree commit diff
diff options
context:
space:
mode:
authoriDawer <ilnur.iskhakov.oss@outlook.com>2022-06-20 15:48:09 +0500
committeriDawer <ilnur.iskhakov.oss@outlook.com>2022-06-20 15:48:09 +0500
commit4ff9bedbedb6dedf46e03d7ce2648e24514f9ba8 (patch)
treec31e9b7948f15e39967277fa77cc2a4bf5dbd146
parentad6810e90bf89a4ef0ae21349d077050bc2a4fa2 (diff)
Display witnesses of non-exhaustive match
Reporting format follows rustc and shows at most three witnesses.
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs51
-rw-r--r--crates/hir-ty/src/diagnostics/match_check.rs140
-rw-r--r--crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs107
-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, 320 insertions, 80 deletions
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 335d95c0cf2..e9743b4dfe7 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -4,9 +4,10 @@
 
 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 +18,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 +39,7 @@ pub enum BodyValidationDiagnostic {
     },
     MissingMatchArms {
         match_expr: ExprId,
+        uncovered_patterns: String,
     },
 }
 
@@ -211,10 +214,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 +371,40 @@ 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 {
+    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 {
+        const LIMIT: usize = 3;
+        match &*witnesses {
+            [witness] => format!("`{}` not covered", witness.to_pat(&cx).display(cx.db)),
+            [head @ .., tail] if head.len() < LIMIT => {
+                let head: Vec<_> = head.iter().map(|w| w.to_pat(cx)).collect();
+                format!(
+                    "`{}` and `{}` not covered",
+                    head.iter().map(|p| p.display(cx.db)).join("`, `"),
+                    tail.to_pat(&cx).display(cx.db)
+                )
+            }
+            _ => {
+                let (head, tail) = witnesses.split_at(LIMIT);
+                let head: Vec<_> = head.iter().map(|w| w.to_pat(cx)).collect();
+                format!(
+                    "`{}` and {} more not covered",
+                    head.iter().map(|p| p.display(cx.db)).join("`, `"),
+                    tail.len()
+                )
+            }
+        }
+    }
+}
diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs
index e00dfdf2fa7..276246f266c 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 84ded517ba7..7011ed9f106 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() {
@@ -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
@@ -782,7 +800,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();
@@ -865,8 +883,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 +907,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 +981,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 { _unimplemented: _void }) => unimplemented!(),
+            &Str(_void) => unimplemented!(),
+            &FloatRange(_void) => unimplemented!(),
+            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 probably 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 +1055,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()
     }
 
@@ -1023,11 +1098,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 08e239804c0..00a6d4584f9 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -150,6 +150,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 3f62a2cd334..1d2912b9ddf 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1295,7 +1295,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());
@@ -1307,6 +1307,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 3eb4cf60a96..9e66fbfb752 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!() => (),
     }