Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 4ff9bed

Browse files
committed
Display witnesses of non-exhaustive match
Reporting format follows rustc and shows at most three witnesses.
1 parent ad6810e commit 4ff9bed

File tree

6 files changed

+320
-80
lines changed

6 files changed

+320
-80
lines changed

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

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
55
use std::sync::Arc;
66

7-
use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule};
7+
use hir_def::{path::path, resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
88
use hir_expand::name;
99
use itertools::Either;
10+
use itertools::Itertools;
1011
use rustc_hash::FxHashSet;
1112
use typed_arena::Arena;
1213

@@ -17,7 +18,8 @@ use crate::{
1718
deconstruct_pat::DeconstructedPat,
1819
usefulness::{compute_match_usefulness, MatchCheckCtx},
1920
},
20-
InferenceResult, TyExt,
21+
display::HirDisplay,
22+
InferenceResult, Ty, TyExt,
2123
};
2224

2325
pub(crate) use hir_def::{
@@ -37,6 +39,7 @@ pub enum BodyValidationDiagnostic {
3739
},
3840
MissingMatchArms {
3941
match_expr: ExprId,
42+
uncovered_patterns: String,
4043
},
4144
}
4245

@@ -211,10 +214,11 @@ impl ExprValidator {
211214
// https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
212215

213216
let witnesses = report.non_exhaustiveness_witnesses;
214-
// FIXME Report witnesses
215-
// eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses);
216217
if !witnesses.is_empty() {
217-
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr: id });
218+
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
219+
match_expr: id,
220+
uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
221+
});
218222
}
219223
}
220224

@@ -367,3 +371,40 @@ fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResul
367371
walk(pat, body, infer, &mut has_type_mismatches);
368372
!has_type_mismatches
369373
}
374+
375+
fn missing_match_arms<'p>(
376+
cx: &MatchCheckCtx<'_, 'p>,
377+
scrut_ty: &Ty,
378+
witnesses: Vec<DeconstructedPat<'p>>,
379+
arms: &[MatchArm],
380+
) -> String {
381+
let non_empty_enum = match scrut_ty.as_adt() {
382+
Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
383+
_ => false,
384+
};
385+
if arms.is_empty() && !non_empty_enum {
386+
format!("type `{}` is non-empty", scrut_ty.display(cx.db))
387+
} else {
388+
const LIMIT: usize = 3;
389+
match &*witnesses {
390+
[witness] => format!("`{}` not covered", witness.to_pat(&cx).display(cx.db)),
391+
[head @ .., tail] if head.len() < LIMIT => {
392+
let head: Vec<_> = head.iter().map(|w| w.to_pat(cx)).collect();
393+
format!(
394+
"`{}` and `{}` not covered",
395+
head.iter().map(|p| p.display(cx.db)).join("`, `"),
396+
tail.to_pat(&cx).display(cx.db)
397+
)
398+
}
399+
_ => {
400+
let (head, tail) = witnesses.split_at(LIMIT);
401+
let head: Vec<_> = head.iter().map(|w| w.to_pat(cx)).collect();
402+
format!(
403+
"`{}` and {} more not covered",
404+
head.iter().map(|p| p.display(cx.db)).join("`, `"),
405+
tail.len()
406+
)
407+
}
408+
}
409+
}
410+
}

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

Lines changed: 135 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,19 @@ mod pat_util;
1010
pub(crate) mod deconstruct_pat;
1111
pub(crate) mod usefulness;
1212

13-
use hir_def::{body::Body, expr::PatId, EnumVariantId, LocalFieldId, VariantId};
13+
use chalk_ir::Mutability;
14+
use hir_def::{
15+
adt::VariantData, body::Body, expr::PatId, AdtId, EnumVariantId, HasModule, LocalFieldId,
16+
VariantId,
17+
};
18+
use hir_expand::name::{name, Name};
1419
use stdx::{always, never};
1520

1621
use crate::{
17-
db::HirDatabase, infer::BindingMode, InferenceResult, Interner, Substitution, Ty, TyKind,
22+
db::HirDatabase,
23+
display::{HirDisplay, HirDisplayError, HirFormatter},
24+
infer::BindingMode,
25+
InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
1826
};
1927

2028
use self::pat_util::EnumerateAndAdjustIterator;
@@ -49,6 +57,7 @@ pub(crate) enum PatKind {
4957

5058
/// `x`, `ref x`, `x @ P`, etc.
5159
Binding {
60+
name: Name,
5261
subpattern: Option<Pat>,
5362
},
5463

@@ -148,7 +157,7 @@ impl<'a> PatCtxt<'a> {
148157
}
149158
_ => (),
150159
}
151-
PatKind::Binding { subpattern: self.lower_opt_pattern(subpat) }
160+
PatKind::Binding { name: name.clone(), subpattern: self.lower_opt_pattern(subpat) }
152161
}
153162

154163
hir_def::expr::Pat::TupleStruct { ref args, ellipsis, .. } if variant.is_some() => {
@@ -282,6 +291,127 @@ impl<'a> PatCtxt<'a> {
282291
}
283292
}
284293

294+
impl HirDisplay for Pat {
295+
fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
296+
match &*self.kind {
297+
PatKind::Wild => write!(f, "_"),
298+
PatKind::Binding { name, subpattern } => {
299+
write!(f, "{name}")?;
300+
if let Some(subpattern) = subpattern {
301+
write!(f, " @ ")?;
302+
subpattern.hir_fmt(f)?;
303+
}
304+
Ok(())
305+
}
306+
PatKind::Variant { subpatterns, .. } | PatKind::Leaf { subpatterns } => {
307+
let variant = match *self.kind {
308+
PatKind::Variant { enum_variant, .. } => Some(VariantId::from(enum_variant)),
309+
_ => self.ty.as_adt().and_then(|(adt, _)| match adt {
310+
AdtId::StructId(s) => Some(s.into()),
311+
AdtId::UnionId(u) => Some(u.into()),
312+
AdtId::EnumId(_) => None,
313+
}),
314+
};
315+
316+
if let Some(variant) = variant {
317+
match variant {
318+
VariantId::EnumVariantId(v) => {
319+
let data = f.db.enum_data(v.parent);
320+
write!(f, "{}", data.variants[v.local_id].name)?;
321+
}
322+
VariantId::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?,
323+
VariantId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name)?,
324+
};
325+
326+
let variant_data = variant.variant_data(f.db.upcast());
327+
if let VariantData::Record(rec_fields) = &*variant_data {
328+
write!(f, " {{ ")?;
329+
330+
let mut printed = 0;
331+
let subpats = subpatterns
332+
.iter()
333+
.filter(|p| !matches!(*p.pattern.kind, PatKind::Wild))
334+
.map(|p| {
335+
printed += 1;
336+
WriteWith(move |f| {
337+
write!(f, "{}: ", rec_fields[p.field].name)?;
338+
p.pattern.hir_fmt(f)
339+
})
340+
});
341+
f.write_joined(subpats, ", ")?;
342+
343+
if printed < rec_fields.len() {
344+
write!(f, "{}..", if printed > 0 { ", " } else { "" })?;
345+
}
346+
347+
return write!(f, " }}");
348+
}
349+
}
350+
351+
let num_fields = variant
352+
.map_or(subpatterns.len(), |v| v.variant_data(f.db.upcast()).fields().len());
353+
if num_fields != 0 || variant.is_none() {
354+
write!(f, "(")?;
355+
let subpats = (0..num_fields).map(|i| {
356+
WriteWith(move |f| {
357+
let fid = LocalFieldId::from_raw((i as u32).into());
358+
if let Some(p) = subpatterns.get(i) {
359+
if p.field == fid {
360+
return p.pattern.hir_fmt(f);
361+
}
362+
}
363+
if let Some(p) = subpatterns.iter().find(|p| p.field == fid) {
364+
p.pattern.hir_fmt(f)
365+
} else {
366+
write!(f, "_")
367+
}
368+
})
369+
});
370+
f.write_joined(subpats, ", ")?;
371+
if let (TyKind::Tuple(..), 1) = (self.ty.kind(Interner), num_fields) {
372+
write!(f, ",")?;
373+
}
374+
write!(f, ")")?;
375+
}
376+
377+
Ok(())
378+
}
379+
PatKind::Deref { subpattern } => {
380+
match self.ty.kind(Interner) {
381+
TyKind::Adt(adt, _) if is_box(adt.0, f.db) => write!(f, "box ")?,
382+
&TyKind::Ref(mutbl, ..) => {
383+
write!(f, "&{}", if mutbl == Mutability::Mut { "mut " } else { "" })?
384+
}
385+
_ => never!("{:?} is a bad Deref pattern type", self.ty),
386+
}
387+
subpattern.hir_fmt(f)
388+
}
389+
PatKind::LiteralBool { value } => write!(f, "{}", value),
390+
PatKind::Or { pats } => f.write_joined(pats.iter(), " | "),
391+
}
392+
}
393+
}
394+
395+
struct WriteWith<F>(F)
396+
where
397+
F: Fn(&mut HirFormatter) -> Result<(), HirDisplayError>;
398+
399+
impl<F> HirDisplay for WriteWith<F>
400+
where
401+
F: Fn(&mut HirFormatter) -> Result<(), HirDisplayError>,
402+
{
403+
fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
404+
(self.0)(f)
405+
}
406+
}
407+
408+
fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool {
409+
let owned_box = name![owned_box].to_smol_str();
410+
let krate = adt.module(db.upcast()).krate();
411+
let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from);
412+
Some(adt) == box_adt
413+
}
414+
285415
pub(crate) trait PatternFoldable: Sized {
286416
fn fold_with<F: PatternFolder>(&self, folder: &mut F) -> Self {
287417
self.super_fold_with(folder)
@@ -357,8 +487,8 @@ impl PatternFoldable for PatKind {
357487
fn super_fold_with<F: PatternFolder>(&self, folder: &mut F) -> Self {
358488
match self {
359489
PatKind::Wild => PatKind::Wild,
360-
PatKind::Binding { subpattern } => {
361-
PatKind::Binding { subpattern: subpattern.fold_with(folder) }
490+
PatKind::Binding { name, subpattern } => {
491+
PatKind::Binding { name: name.clone(), subpattern: subpattern.fold_with(folder) }
362492
}
363493
PatKind::Variant { substs, enum_variant, subpatterns } => PatKind::Variant {
364494
substs: substs.fold_with(folder),

0 commit comments

Comments
 (0)