mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 13:51:31 +00:00
Display witnesses of non-exhaustive match
Reporting format follows rustc and shows at most three witnesses.
This commit is contained in:
parent
ad6810e90b
commit
4ff9bedbed
6 changed files with 320 additions and 80 deletions
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue