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

Commit 6420840

Browse files
committed
Auto merge of rust-lang#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.
2 parents 7f9c054 + e417992 commit 6420840

File tree

6 files changed

+330
-93
lines changed

6 files changed

+330
-93
lines changed

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

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
//! through the body using inference results: mismatched arg counts, missing
33
//! fields, etc.
44
5+
use std::fmt;
56
use std::sync::Arc;
67

7-
use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule};
8+
use hir_def::{path::path, resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
89
use hir_expand::name;
910
use itertools::Either;
11+
use itertools::Itertools;
1012
use rustc_hash::FxHashSet;
1113
use typed_arena::Arena;
1214

@@ -17,7 +19,8 @@ use crate::{
1719
deconstruct_pat::DeconstructedPat,
1820
usefulness::{compute_match_usefulness, MatchCheckCtx},
1921
},
20-
InferenceResult, TyExt,
22+
display::HirDisplay,
23+
InferenceResult, Ty, TyExt,
2124
};
2225

2326
pub(crate) use hir_def::{
@@ -37,6 +40,7 @@ pub enum BodyValidationDiagnostic {
3740
},
3841
MissingMatchArms {
3942
match_expr: ExprId,
43+
uncovered_patterns: String,
4044
},
4145
}
4246

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

213217
let witnesses = report.non_exhaustiveness_witnesses;
214-
// FIXME Report witnesses
215-
// eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses);
216218
if !witnesses.is_empty() {
217-
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr: id });
219+
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
220+
match_expr: id,
221+
uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
222+
});
218223
}
219224
}
220225

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

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)