Skip to content

Commit 0081ef2

Browse files
committed
Point at enum definition when match patterns are not exhaustive
``` error[E0004]: non-exhaustive patterns: type `X` is non-empty --> file.rs:9:11 | 1 | / enum X { 2 | | A, | | - variant not covered 3 | | B, | | - variant not covered 4 | | C, | | - variant not covered 5 | | } | |_- `X` defined here ... 9 | match x { | ^ | = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms error[E0004]: non-exhaustive patterns: `B` and `C` not covered --> file.rs:11:11 | 1 | / enum X { 2 | | A, 3 | | B, 4 | | C, | | - not covered 5 | | } | |_- `X` defined here ... 11 | match x { | ^ patterns `C` not covered ``` When a match expression doesn't have patterns covering every variant, point at the enum's definition span. On a best effort basis, point at the variant(s) that are missing. This does not handle the case when the missing pattern is due to a field's enum variants: ``` enum E1 { A, B, C, } enum E2 { A(E1), B, } fn foo() { match E2::A(E1::A) { E2::A(E1::B) => {} E2::B => {} } //~^ ERROR `E2::A(E1::A)` and `E2::A(E1::C)` not handled } ``` Unify look between match with no arms and match with some missing patterns. Fix #37518.
1 parent c1d2d83 commit 0081ef2

36 files changed

+390
-95
lines changed

src/librustc_mir/hair/pattern/_match.rs

-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,6 @@ impl<'tcx> Witness<'tcx> {
539539
self.apply_constructor(cx, ctor, ty)
540540
}
541541

542-
543542
/// Constructs a partial witness for a pattern given a list of
544543
/// patterns expanded by the specialization step.
545544
///

src/librustc_mir/hair/pattern/check_match.rs

+106-28
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc::middle::expr_use_visitor as euv;
1010
use rustc::middle::mem_categorization::cmt_;
1111
use rustc::middle::region;
1212
use rustc::session::Session;
13-
use rustc::ty::{self, Ty, TyCtxt};
13+
use rustc::ty::{self, Ty, TyCtxt, TyKind};
1414
use rustc::ty::subst::{InternalSubsts, SubstsRef};
1515
use rustc::lint;
1616
use rustc_errors::{Applicability, DiagnosticBuilder};
@@ -204,25 +204,42 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
204204
// is uninhabited.
205205
let pat_ty = self.tables.node_type(scrut.hir_id);
206206
let module = self.tcx.hir().get_module_parent_by_hir_id(scrut.hir_id);
207+
let mut def_span = None;
208+
let mut missing_variants = vec![];
207209
if inlined_arms.is_empty() {
208210
let scrutinee_is_uninhabited = if self.tcx.features().exhaustive_patterns {
209211
self.tcx.is_ty_uninhabited_from(module, pat_ty)
210212
} else {
211213
match pat_ty.sty {
212214
ty::Never => true,
213-
ty::Adt(def, _) => def.variants.is_empty(),
215+
ty::Adt(def, _) => {
216+
def_span = self.tcx.hir().span_if_local(def.did);
217+
if def.variants.len() < 4 && !def.variants.is_empty() {
218+
// keep around to point at the definition of non-covered variants
219+
missing_variants = def.variants.iter()
220+
.map(|variant| variant.ident.span)
221+
.collect();
222+
}
223+
def.variants.is_empty()
224+
},
214225
_ => false
215226
}
216227
};
217228
if !scrutinee_is_uninhabited {
218229
// We know the type is inhabited, so this must be wrong
219-
let mut err = create_e0004(self.tcx.sess, scrut.span,
220-
format!("non-exhaustive patterns: type `{}` \
221-
is non-empty",
222-
pat_ty));
223-
span_help!(&mut err, scrut.span,
224-
"ensure that all possible cases are being handled, \
225-
possibly by adding wildcards or more match arms");
230+
let mut err = create_e0004(self.tcx.sess, scrut.span, format!(
231+
"non-exhaustive patterns: type `{}` is non-empty",
232+
pat_ty,
233+
));
234+
err.help("ensure that all possible cases are being handled, \
235+
possibly by adding wildcards or more match arms");
236+
if let Some(sp) = def_span {
237+
err.span_label(sp, format!("`{}` defined here", pat_ty));
238+
}
239+
// point at the definition of non-covered enum variants
240+
for variant in &missing_variants {
241+
err.span_label(*variant, "variant not covered");
242+
}
226243
err.emit();
227244
}
228245
// If the type *is* uninhabited, it's vacuously exhaustive
@@ -264,7 +281,7 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
264281
};
265282

266283
let pattern_string = witness[0].single_pattern().to_string();
267-
let mut diag = struct_span_err!(
284+
let mut err = struct_span_err!(
268285
self.tcx.sess, pat.span, E0005,
269286
"refutable pattern in {}: `{}` not covered",
270287
origin, pattern_string
@@ -277,8 +294,13 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
277294
}
278295
_ => format!("pattern `{}` not covered", pattern_string),
279296
};
280-
diag.span_label(pat.span, label_msg);
281-
diag.emit();
297+
err.span_label(pat.span, label_msg);
298+
if let ty::Adt(def, _) = pattern_ty.sty {
299+
if let Some(sp) = self.tcx.hir().span_if_local(def.did){
300+
err.span_label(sp, format!("`{}` defined here", pattern_ty));
301+
}
302+
}
303+
err.emit();
282304
});
283305
}
284306
}
@@ -332,10 +354,11 @@ fn pat_is_catchall(pat: &Pat) -> bool {
332354
}
333355

334356
// Check for unreachable patterns
335-
fn check_arms<'a, 'tcx>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
336-
arms: &[(Vec<(&'a Pattern<'tcx>, &hir::Pat)>, Option<&hir::Expr>)],
337-
source: hir::MatchSource)
338-
{
357+
fn check_arms<'a, 'tcx>(
358+
cx: &mut MatchCheckCtxt<'a, 'tcx>,
359+
arms: &[(Vec<(&'a Pattern<'tcx>, &hir::Pat)>, Option<&hir::Expr>)],
360+
source: hir::MatchSource,
361+
) {
339362
let mut seen = Matrix::empty();
340363
let mut catchall = None;
341364
for (arm_index, &(ref pats, guard)) in arms.iter().enumerate() {
@@ -411,10 +434,12 @@ fn check_arms<'a, 'tcx>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
411434
}
412435
}
413436

414-
fn check_exhaustive<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
415-
scrut_ty: Ty<'tcx>,
416-
sp: Span,
417-
matrix: &Matrix<'p, 'tcx>) {
437+
fn check_exhaustive<'p, 'a: 'p, 'tcx: 'a>(
438+
cx: &mut MatchCheckCtxt<'a, 'tcx>,
439+
scrut_ty: Ty<'tcx>,
440+
sp: Span,
441+
matrix: &Matrix<'p, 'tcx>,
442+
) {
418443
let wild_pattern = Pattern {
419444
ty: scrut_ty,
420445
span: DUMMY_SP,
@@ -448,11 +473,26 @@ fn check_exhaustive<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
448473
1 => format!("pattern {} not covered", joined_patterns),
449474
_ => format!("patterns {} not covered", joined_patterns),
450475
};
451-
create_e0004(cx.tcx.sess, sp,
452-
format!("non-exhaustive patterns: {} not covered",
453-
joined_patterns))
454-
.span_label(sp, label_text)
455-
.emit();
476+
let mut err = create_e0004(cx.tcx.sess, sp, format!(
477+
"non-exhaustive patterns: {} not covered",
478+
joined_patterns,
479+
));
480+
err.span_label(sp, label_text);
481+
// point at the definition of non-covered enum variants
482+
if let ty::Adt(def, _) = scrut_ty.sty {
483+
if let Some(sp) = cx.tcx.hir().span_if_local(def.did){
484+
err.span_label(sp, format!("`{}` defined here", scrut_ty));
485+
}
486+
}
487+
let patterns = witnesses.iter().map(|p| (**p).clone()).collect::<Vec<Pattern>>();
488+
if patterns.len() < 4 {
489+
for sp in maybe_point_at_variant(cx, &scrut_ty.sty, patterns.as_slice()) {
490+
err.span_label(sp, "not covered");
491+
}
492+
}
493+
err.help("ensure that all possible cases are being handled, \
494+
possibly by adding wildcards or more match arms");
495+
err.emit();
456496
}
457497
NotUseful => {
458498
// This is good, wildcard pattern isn't reachable
@@ -461,10 +501,48 @@ fn check_exhaustive<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
461501
}
462502
}
463503

504+
fn maybe_point_at_variant(
505+
cx: &mut MatchCheckCtxt<'a, 'tcx>,
506+
sty: &TyKind<'tcx>,
507+
patterns: &[Pattern],
508+
) -> Vec<Span> {
509+
let mut covered = vec![];
510+
if let ty::Adt(def, _) = sty {
511+
// Don't point at the variants if they are too many to avoid visual clutter
512+
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 {
516+
let sp = def.variants[*variant_index].ident.span;
517+
if covered.contains(&sp) {
518+
continue;
519+
}
520+
covered.push(sp);
521+
let subpatterns = subpatterns.iter()
522+
.map(|field_pattern| field_pattern.pattern.clone())
523+
.collect::<Vec<_>>();
524+
covered.extend(
525+
maybe_point_at_variant(cx, sty, subpatterns.as_slice()),
526+
);
527+
}
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, sty, subpatterns.as_slice()));
534+
}
535+
}
536+
}
537+
covered
538+
}
539+
464540
// Legality of move bindings checking
465-
fn check_legality_of_move_bindings(cx: &MatchVisitor<'_, '_>,
466-
has_guard: bool,
467-
pats: &[P<Pat>]) {
541+
fn check_legality_of_move_bindings(
542+
cx: &MatchVisitor<'_, '_>,
543+
has_guard: bool,
544+
pats: &[P<Pat>],
545+
) {
468546
let mut by_ref_span = None;
469547
for pat in pats {
470548
pat.each_binding(|_, hir_id, span, _path| {

src/test/ui/always-inhabited-union-ref.stderr

+2-10
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,15 @@ error[E0004]: non-exhaustive patterns: type `&'static !` is non-empty
44
LL | match uninhab_ref() {
55
| ^^^^^^^^^^^^^
66
|
7-
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
8-
--> $DIR/always-inhabited-union-ref.rs:23:11
9-
|
10-
LL | match uninhab_ref() {
11-
| ^^^^^^^^^^^^^
7+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
128

139
error[E0004]: non-exhaustive patterns: type `Foo` is non-empty
1410
--> $DIR/always-inhabited-union-ref.rs:27:11
1511
|
1612
LL | match uninhab_union() {
1713
| ^^^^^^^^^^^^^^^
1814
|
19-
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
20-
--> $DIR/always-inhabited-union-ref.rs:27:11
21-
|
22-
LL | match uninhab_union() {
23-
| ^^^^^^^^^^^^^^^
15+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
2416

2517
error: aborting due to 2 previous errors
2618

src/test/ui/check_match/issue-35609.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,70 @@ error[E0004]: non-exhaustive patterns: `(B, _)`, `(C, _)`, `(D, _)` and 2 more n
33
|
44
LL | match (A, ()) { //~ ERROR non-exhaustive
55
| ^^^^^^^ patterns `(B, _)`, `(C, _)`, `(D, _)` and 2 more not covered
6+
|
7+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
68

79
error[E0004]: non-exhaustive patterns: `(_, B)`, `(_, C)`, `(_, D)` and 2 more not covered
810
--> $DIR/issue-35609.rs:14:11
911
|
1012
LL | match (A, A) { //~ ERROR non-exhaustive
1113
| ^^^^^^ patterns `(_, B)`, `(_, C)`, `(_, D)` and 2 more not covered
14+
|
15+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
1216

1317
error[E0004]: non-exhaustive patterns: `((B, _), _)`, `((C, _), _)`, `((D, _), _)` and 2 more not covered
1418
--> $DIR/issue-35609.rs:18:11
1519
|
1620
LL | match ((A, ()), ()) { //~ ERROR non-exhaustive
1721
| ^^^^^^^^^^^^^ patterns `((B, _), _)`, `((C, _), _)`, `((D, _), _)` and 2 more not covered
22+
|
23+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
1824

1925
error[E0004]: non-exhaustive patterns: `((B, _), _)`, `((C, _), _)`, `((D, _), _)` and 2 more not covered
2026
--> $DIR/issue-35609.rs:22:11
2127
|
2228
LL | match ((A, ()), A) { //~ ERROR non-exhaustive
2329
| ^^^^^^^^^^^^ patterns `((B, _), _)`, `((C, _), _)`, `((D, _), _)` and 2 more not covered
30+
|
31+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
2432

2533
error[E0004]: non-exhaustive patterns: `((B, _), _)`, `((C, _), _)`, `((D, _), _)` and 2 more not covered
2634
--> $DIR/issue-35609.rs:26:11
2735
|
2836
LL | match ((A, ()), ()) { //~ ERROR non-exhaustive
2937
| ^^^^^^^^^^^^^ patterns `((B, _), _)`, `((C, _), _)`, `((D, _), _)` and 2 more not covered
38+
|
39+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
3040

3141
error[E0004]: non-exhaustive patterns: `S(B, _)`, `S(C, _)`, `S(D, _)` and 2 more not covered
3242
--> $DIR/issue-35609.rs:31:11
3343
|
44+
LL | struct S(Enum, ());
45+
| ------------------- `S` defined here
46+
...
3447
LL | match S(A, ()) { //~ ERROR non-exhaustive
3548
| ^^^^^^^^ patterns `S(B, _)`, `S(C, _)`, `S(D, _)` and 2 more not covered
49+
|
50+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
3651

3752
error[E0004]: non-exhaustive patterns: `Sd { x: B, .. }`, `Sd { x: C, .. }`, `Sd { x: D, .. }` and 2 more not covered
3853
--> $DIR/issue-35609.rs:35:11
3954
|
55+
LL | struct Sd { x: Enum, y: () }
56+
| ---------------------------- `Sd` defined here
57+
...
4058
LL | match (Sd { x: A, y: () }) { //~ ERROR non-exhaustive
4159
| ^^^^^^^^^^^^^^^^^^^^ patterns `Sd { x: B, .. }`, `Sd { x: C, .. }`, `Sd { x: D, .. }` and 2 more not covered
60+
|
61+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
4262

4363
error[E0004]: non-exhaustive patterns: `Some(B)`, `Some(C)`, `Some(D)` and 2 more not covered
4464
--> $DIR/issue-35609.rs:39:11
4565
|
4666
LL | match Some(A) { //~ ERROR non-exhaustive
4767
| ^^^^^^^ patterns `Some(B)`, `Some(C)`, `Some(D)` and 2 more not covered
68+
|
69+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
4870

4971
error: aborting due to 8 previous errors
5072

src/test/ui/consts/match_ice.stderr

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ error[E0004]: non-exhaustive patterns: `&S` not covered
33
|
44
LL | match C { //~ ERROR non-exhaustive
55
| ^ pattern `&S` not covered
6+
|
7+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
68

79
error: aborting due to previous error
810

src/test/ui/empty/empty-never-array.stderr

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
error[E0005]: refutable pattern in local binding: `T(_, _)` not covered
22
--> $DIR/empty-never-array.rs:10:9
33
|
4-
LL | let Helper::U(u) = Helper::T(t, []);
5-
| ^^^^^^^^^^^^ pattern `T(_, _)` not covered
4+
LL | / enum Helper<T, U> {
5+
LL | | T(T, [!; 0]),
6+
LL | | #[allow(dead_code)]
7+
LL | | U(U),
8+
LL | | }
9+
| |_- `Helper<T, U>` defined here
10+
...
11+
LL | let Helper::U(u) = Helper::T(t, []);
12+
| ^^^^^^^^^^^^ pattern `T(_, _)` not covered
613

714
error: aborting due to previous error
815

src/test/ui/error-codes/E0004-2.stderr

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ error[E0004]: non-exhaustive patterns: type `std::option::Option<i32>` is non-em
44
LL | match x { } //~ ERROR E0004
55
| ^
66
|
7-
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
8-
--> $DIR/E0004-2.rs:4:11
9-
|
10-
LL | match x { } //~ ERROR E0004
11-
| ^
7+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
128

139
error: aborting due to previous error
1410

src/test/ui/error-codes/E0004.stderr

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
error[E0004]: non-exhaustive patterns: `HastaLaVistaBaby` not covered
22
--> $DIR/E0004.rs:9:11
33
|
4-
LL | match x { //~ ERROR E0004
5-
| ^ pattern `HastaLaVistaBaby` not covered
4+
LL | / enum Terminator {
5+
LL | | HastaLaVistaBaby,
6+
| | ---------------- not covered
7+
LL | | TalkToMyHand,
8+
LL | | }
9+
| |_- `Terminator` defined here
10+
...
11+
LL | match x { //~ ERROR E0004
12+
| ^ pattern `HastaLaVistaBaby` not covered
13+
|
14+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
615

716
error: aborting due to previous error
817

0 commit comments

Comments
 (0)