Skip to content

Commit f0b58fc

Browse files
committed
Auto merge of #64271 - Centril:non-exhaustive-peel-refs, r=estebank
check_match: refactor + improve non-exhaustive diagnostics for default binding modes Refactor `check_match` a bit with more code-reuse and improve the diagnostics for a non-exhaustive pattern match by peeling off any references from the scrutinee type so that the "defined here" label is added in more cases. For example: ```rust error[E0004]: non-exhaustive patterns: `&mut &B` not covered --> foo.rs:4:11 | 1 | enum E { A, B } | --------------- | | | | | not covered | `E` defined here ... 4 | match x { | ^ pattern `&mut &B` not covered | = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms ``` Moreover, wrt. "defined here", we give irrefutable pattern matching (i.e. in `let`, `for`, and `fn` parameters) a more consistent treatment in line with `match`. r? @estebank
2 parents fe6d05a + 20a2605 commit f0b58fc

22 files changed

+429
-159
lines changed

src/librustc/ty/util.rs

+18
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,24 @@ impl<'tcx> ty::TyS<'tcx> {
996996
debug!("is_type_representable: {:?} is {:?}", self, r);
997997
r
998998
}
999+
1000+
/// Peel off all reference types in this type until there are none left.
1001+
///
1002+
/// This method is idempotent, i.e. `ty.peel_refs().peel_refs() == ty.peel_refs()`.
1003+
///
1004+
/// # Examples
1005+
///
1006+
/// - `u8` -> `u8`
1007+
/// - `&'a mut u8` -> `u8`
1008+
/// - `&'a &'b u8` -> `u8`
1009+
/// - `&'a *const &'b u8 -> *const &'b u8`
1010+
pub fn peel_refs(&'tcx self) -> Ty<'tcx> {
1011+
let mut ty = self;
1012+
while let Ref(_, inner_ty, _) = ty.sty {
1013+
ty = inner_ty;
1014+
}
1015+
ty
1016+
}
9991017
}
10001018

10011019
fn is_copy_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {

src/librustc_mir/hair/pattern/_match.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,9 @@ struct PatternContext<'tcx> {
517517
pub struct Witness<'tcx>(Vec<Pattern<'tcx>>);
518518

519519
impl<'tcx> Witness<'tcx> {
520-
pub fn single_pattern(&self) -> &Pattern<'tcx> {
520+
pub fn single_pattern(self) -> Pattern<'tcx> {
521521
assert_eq!(self.0.len(), 1);
522-
&self.0[0]
522+
self.0.into_iter().next().unwrap()
523523
}
524524

525525
fn push_wild_constructor<'a>(

src/librustc_mir/hair/pattern/check_match.rs

+115-107
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct MatchVisitor<'a, 'tcx> {
5252
signalled_error: SignalledError,
5353
}
5454

55-
impl<'a, 'tcx> Visitor<'tcx> for MatchVisitor<'a, 'tcx> {
55+
impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> {
5656
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
5757
NestedVisitorMap::None
5858
}
@@ -89,8 +89,7 @@ impl<'a, 'tcx> Visitor<'tcx> for MatchVisitor<'a, 'tcx> {
8989
}
9090
}
9191

92-
93-
impl<'a, 'tcx> PatternContext<'a, 'tcx> {
92+
impl PatternContext<'_, '_> {
9493
fn report_inlining_errors(&self, pat_span: Span) {
9594
for error in &self.errors {
9695
match *error {
@@ -122,7 +121,7 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
122121
}
123122
}
124123

125-
impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
124+
impl<'tcx> MatchVisitor<'_, 'tcx> {
126125
fn check_patterns(&mut self, has_guard: bool, pats: &[P<Pat>]) {
127126
check_legality_of_move_bindings(self, has_guard, pats);
128127
for pat in pats {
@@ -265,37 +264,26 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
265264
expand_pattern(cx, pattern)
266265
]].into_iter().collect();
267266

268-
let wild_pattern = Pattern {
269-
ty: pattern_ty,
270-
span: DUMMY_SP,
271-
kind: box PatternKind::Wild,
272-
};
273-
let witness = match is_useful(cx, &pats, &[&wild_pattern], ConstructWitness) {
274-
UsefulWithWitness(witness) => witness,
275-
NotUseful => return,
276-
Useful => bug!()
267+
let witnesses = match check_not_useful(cx, pattern_ty, &pats) {
268+
Ok(_) => return,
269+
Err(err) => err,
277270
};
278271

279-
let pattern_string = witness[0].single_pattern().to_string();
272+
let joined_patterns = joined_uncovered_patterns(&witnesses);
280273
let mut err = struct_span_err!(
281274
self.tcx.sess, pat.span, E0005,
282-
"refutable pattern in {}: `{}` not covered",
283-
origin, pattern_string
275+
"refutable pattern in {}: {} not covered",
276+
origin, joined_patterns
284277
);
285-
let label_msg = match pat.node {
286-
PatKind::Path(hir::QPath::Resolved(None, ref path))
287-
if path.segments.len() == 1 && path.segments[0].args.is_none() => {
278+
err.span_label(pat.span, match &pat.node {
279+
PatKind::Path(hir::QPath::Resolved(None, path))
280+
if path.segments.len() == 1 && path.segments[0].args.is_none() => {
288281
format!("interpreted as {} {} pattern, not new variable",
289282
path.res.article(), path.res.descr())
290283
}
291-
_ => format!("pattern `{}` not covered", pattern_string),
292-
};
293-
err.span_label(pat.span, label_msg);
294-
if let ty::Adt(def, _) = pattern_ty.sty {
295-
if let Some(sp) = self.tcx.hir().span_if_local(def.did){
296-
err.span_label(sp, format!("`{}` defined here", pattern_ty));
297-
}
298-
}
284+
_ => pattern_not_convered_label(&witnesses, &joined_patterns),
285+
});
286+
adt_defined_here(cx, &mut err, pattern_ty, &witnesses);
299287
err.emit();
300288
});
301289
}
@@ -350,9 +338,9 @@ fn pat_is_catchall(pat: &Pat) -> bool {
350338
}
351339

352340
// Check for unreachable patterns
353-
fn check_arms<'a, 'tcx>(
354-
cx: &mut MatchCheckCtxt<'a, 'tcx>,
355-
arms: &[(Vec<(&'a Pattern<'tcx>, &hir::Pat)>, Option<&hir::Expr>)],
341+
fn check_arms<'tcx>(
342+
cx: &mut MatchCheckCtxt<'_, 'tcx>,
343+
arms: &[(Vec<(&Pattern<'tcx>, &hir::Pat)>, Option<&hir::Expr>)],
356344
source: hir::MatchSource,
357345
) {
358346
let mut seen = Matrix::empty();
@@ -433,104 +421,124 @@ fn check_arms<'a, 'tcx>(
433421
}
434422
}
435423

436-
fn check_exhaustive<'p, 'a, 'tcx>(
437-
cx: &mut MatchCheckCtxt<'a, 'tcx>,
424+
fn check_not_useful(
425+
cx: &mut MatchCheckCtxt<'_, 'tcx>,
426+
ty: Ty<'tcx>,
427+
matrix: &Matrix<'_, 'tcx>,
428+
) -> Result<(), Vec<Pattern<'tcx>>> {
429+
let wild_pattern = Pattern { ty, span: DUMMY_SP, kind: box PatternKind::Wild };
430+
match is_useful(cx, matrix, &[&wild_pattern], ConstructWitness) {
431+
NotUseful => Ok(()), // This is good, wildcard pattern isn't reachable.
432+
UsefulWithWitness(pats) => Err(if pats.is_empty() {
433+
vec![wild_pattern]
434+
} else {
435+
pats.into_iter().map(|w| w.single_pattern()).collect()
436+
}),
437+
Useful => bug!(),
438+
}
439+
}
440+
441+
fn check_exhaustive<'tcx>(
442+
cx: &mut MatchCheckCtxt<'_, 'tcx>,
438443
scrut_ty: Ty<'tcx>,
439444
sp: Span,
440-
matrix: &Matrix<'p, 'tcx>,
445+
matrix: &Matrix<'_, 'tcx>,
441446
) {
442-
let wild_pattern = Pattern {
443-
ty: scrut_ty,
444-
span: DUMMY_SP,
445-
kind: box PatternKind::Wild,
447+
let witnesses = match check_not_useful(cx, scrut_ty, matrix) {
448+
Ok(_) => return,
449+
Err(err) => err,
446450
};
447-
match is_useful(cx, matrix, &[&wild_pattern], ConstructWitness) {
448-
UsefulWithWitness(pats) => {
449-
let witnesses = if pats.is_empty() {
450-
vec![&wild_pattern]
451-
} else {
452-
pats.iter().map(|w| w.single_pattern()).collect()
453-
};
454451

455-
const LIMIT: usize = 3;
456-
let joined_patterns = match witnesses.len() {
457-
0 => bug!(),
458-
1 => format!("`{}`", witnesses[0]),
459-
2..=LIMIT => {
460-
let (tail, head) = witnesses.split_last().unwrap();
461-
let head: Vec<_> = head.iter().map(|w| w.to_string()).collect();
462-
format!("`{}` and `{}`", head.join("`, `"), tail)
463-
}
464-
_ => {
465-
let (head, tail) = witnesses.split_at(LIMIT);
466-
let head: Vec<_> = head.iter().map(|w| w.to_string()).collect();
467-
format!("`{}` and {} more", head.join("`, `"), tail.len())
468-
}
469-
};
452+
let joined_patterns = joined_uncovered_patterns(&witnesses);
453+
let mut err = create_e0004(
454+
cx.tcx.sess, sp,
455+
format!("non-exhaustive patterns: {} not covered", joined_patterns),
456+
);
457+
err.span_label(sp, pattern_not_convered_label(&witnesses, &joined_patterns));
458+
adt_defined_here(cx, &mut err, scrut_ty, &witnesses);
459+
err.help(
460+
"ensure that all possible cases are being handled, \
461+
possibly by adding wildcards or more match arms"
462+
)
463+
.emit();
464+
}
470465

471-
let label_text = match witnesses.len() {
472-
1 => format!("pattern {} not covered", joined_patterns),
473-
_ => format!("patterns {} not covered", joined_patterns),
474-
};
475-
let mut err = create_e0004(cx.tcx.sess, sp, format!(
476-
"non-exhaustive patterns: {} not covered",
477-
joined_patterns,
478-
));
479-
err.span_label(sp, label_text);
480-
// point at the definition of non-covered enum variants
481-
if let ty::Adt(def, _) = scrut_ty.sty {
482-
if let Some(sp) = cx.tcx.hir().span_if_local(def.did){
483-
err.span_label(sp, format!("`{}` defined here", scrut_ty));
484-
}
485-
}
486-
let patterns = witnesses.iter().map(|p| (**p).clone()).collect::<Vec<Pattern<'_>>>();
487-
if patterns.len() < 4 {
488-
for sp in maybe_point_at_variant(cx, scrut_ty, patterns.as_slice()) {
489-
err.span_label(sp, "not covered");
490-
}
491-
}
492-
err.help("ensure that all possible cases are being handled, \
493-
possibly by adding wildcards or more match arms");
494-
err.emit();
466+
fn joined_uncovered_patterns(witnesses: &[Pattern<'_>]) -> String {
467+
const LIMIT: usize = 3;
468+
match witnesses {
469+
[] => bug!(),
470+
[witness] => format!("`{}`", witness),
471+
[head @ .., tail] if head.len() < LIMIT => {
472+
let head: Vec<_> = head.iter().map(<_>::to_string).collect();
473+
format!("`{}` and `{}`", head.join("`, `"), tail)
495474
}
496-
NotUseful => {
497-
// This is good, wildcard pattern isn't reachable
475+
_ => {
476+
let (head, tail) = witnesses.split_at(LIMIT);
477+
let head: Vec<_> = head.iter().map(<_>::to_string).collect();
478+
format!("`{}` and {} more", head.join("`, `"), tail.len())
498479
}
499-
_ => bug!()
500480
}
501481
}
502482

503-
fn maybe_point_at_variant(
504-
cx: &mut MatchCheckCtxt<'a, 'tcx>,
505-
ty: Ty<'tcx>,
506-
patterns: &[Pattern<'_>],
507-
) -> Vec<Span> {
483+
fn pattern_not_convered_label(witnesses: &[Pattern<'_>], joined_patterns: &str) -> String {
484+
format!("pattern{} {} not covered", rustc_errors::pluralise!(witnesses.len()), joined_patterns)
485+
}
486+
487+
/// Point at the definition of non-covered `enum` variants.
488+
fn adt_defined_here(
489+
cx: &MatchCheckCtxt<'_, '_>,
490+
err: &mut DiagnosticBuilder<'_>,
491+
ty: Ty<'_>,
492+
witnesses: &[Pattern<'_>],
493+
) {
494+
let ty = ty.peel_refs();
495+
if let ty::Adt(def, _) = ty.sty {
496+
if let Some(sp) = cx.tcx.hir().span_if_local(def.did) {
497+
err.span_label(sp, format!("`{}` defined here", ty));
498+
}
499+
500+
if witnesses.len() < 4 {
501+
for sp in maybe_point_at_variant(ty, &witnesses) {
502+
err.span_label(sp, "not covered");
503+
}
504+
}
505+
}
506+
}
507+
508+
fn maybe_point_at_variant(ty: Ty<'_>, patterns: &[Pattern<'_>]) -> Vec<Span> {
508509
let mut covered = vec![];
509510
if let ty::Adt(def, _) = ty.sty {
510511
// Don't point at variants that have already been covered due to other patterns to avoid
511-
// visual clutter
512+
// visual clutter.
512513
for pattern in patterns {
513-
let pk: &PatternKind<'_> = &pattern.kind;
514-
if let PatternKind::Variant { adt_def, variant_index, subpatterns, .. } = pk {
515-
if adt_def.did == def.did {
514+
use PatternKind::{AscribeUserType, Deref, Variant, Or, Leaf};
515+
match &*pattern.kind {
516+
AscribeUserType { subpattern, .. } | Deref { subpattern } => {
517+
covered.extend(maybe_point_at_variant(ty, slice::from_ref(&subpattern)));
518+
}
519+
Variant { adt_def, variant_index, subpatterns, .. } if adt_def.did == def.did => {
516520
let sp = def.variants[*variant_index].ident.span;
517521
if covered.contains(&sp) {
518522
continue;
519523
}
520524
covered.push(sp);
521-
let subpatterns = subpatterns.iter()
525+
526+
let pats = subpatterns.iter()
522527
.map(|field_pattern| field_pattern.pattern.clone())
523-
.collect::<Vec<_>>();
524-
covered.extend(
525-
maybe_point_at_variant(cx, ty, subpatterns.as_slice()),
526-
);
528+
.collect::<Box<[_]>>();
529+
covered.extend(maybe_point_at_variant(ty, &pats));
527530
}
528-
}
529-
if let PatternKind::Leaf { subpatterns } = pk {
530-
let subpatterns = subpatterns.iter()
531-
.map(|field_pattern| field_pattern.pattern.clone())
532-
.collect::<Vec<_>>();
533-
covered.extend(maybe_point_at_variant(cx, ty, subpatterns.as_slice()));
531+
Leaf { subpatterns } => {
532+
let pats = subpatterns.iter()
533+
.map(|field_pattern| field_pattern.pattern.clone())
534+
.collect::<Box<[_]>>();
535+
covered.extend(maybe_point_at_variant(ty, &pats));
536+
}
537+
Or { pats } => {
538+
let pats = pats.iter().cloned().collect::<Box<[_]>>();
539+
covered.extend(maybe_point_at_variant(ty, &pats));
540+
}
541+
_ => {}
534542
}
535543
}
536544
}
@@ -627,7 +635,7 @@ struct AtBindingPatternVisitor<'a, 'b, 'tcx> {
627635
bindings_allowed: bool
628636
}
629637

630-
impl<'a, 'b, 'tcx, 'v> Visitor<'v> for AtBindingPatternVisitor<'a, 'b, 'tcx> {
638+
impl<'v> Visitor<'v> for AtBindingPatternVisitor<'_, '_, '_> {
631639
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> {
632640
NestedVisitorMap::None
633641
}

src/librustc_typeck/check/op.rs

+10-17
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
268268
op.node.as_str(), lhs_ty),
269269
);
270270
let mut suggested_deref = false;
271-
if let Ref(_, mut rty, _) = lhs_ty.sty {
271+
if let Ref(_, rty, _) = lhs_ty.sty {
272272
if {
273273
self.infcx.type_is_copy_modulo_regions(self.param_env,
274274
rty,
@@ -279,13 +279,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
279279
.is_ok()
280280
} {
281281
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
282-
while let Ref(_, rty_inner, _) = rty.sty {
283-
rty = rty_inner;
284-
}
285282
let msg = &format!(
286283
"`{}=` can be used on '{}', you can dereference `{}`",
287284
op.node.as_str(),
288-
rty,
285+
rty.peel_refs(),
289286
lstring,
290287
);
291288
err.span_suggestion(
@@ -361,7 +358,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
361358
}
362359

363360
let mut suggested_deref = false;
364-
if let Ref(_, mut rty, _) = lhs_ty.sty {
361+
if let Ref(_, rty, _) = lhs_ty.sty {
365362
if {
366363
self.infcx.type_is_copy_modulo_regions(self.param_env,
367364
rty,
@@ -372,17 +369,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
372369
.is_ok()
373370
} {
374371
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
375-
while let Ref(_, rty_inner, _) = rty.sty {
376-
rty = rty_inner;
377-
}
378-
let msg = &format!(
379-
"`{}` can be used on '{}', you can \
380-
dereference `{2}`: `*{2}`",
381-
op.node.as_str(),
382-
rty,
383-
lstring
384-
);
385-
err.help(msg);
372+
err.help(&format!(
373+
"`{}` can be used on '{}', you can \
374+
dereference `{2}`: `*{2}`",
375+
op.node.as_str(),
376+
rty.peel_refs(),
377+
lstring
378+
));
386379
suggested_deref = true;
387380
}
388381
}

0 commit comments

Comments
 (0)