Skip to content

Commit 23bb228

Browse files
authored
Rollup merge of rust-lang#91993 - estebank:match-span-suggestion, r=oli-obk
Tweak output for non-exhaustive `match` expression * Provide structured suggestion when missing `match` arms * Move pointing at the missing variants *after* the main error <img width="1164" alt="" src="https://user-images.githubusercontent.com/1606434/146312085-b57ef4a3-6e96-4f32-aa2a-803637d9eeba.png">
2 parents 86fe070 + 9dd390f commit 23bb228

File tree

69 files changed

+2413
-970
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2413
-970
lines changed

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+103-19
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_session::lint::builtin::{
1717
BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS,
1818
};
1919
use rustc_session::Session;
20-
use rustc_span::{DesugaringKind, ExpnKind, Span};
20+
use rustc_span::{DesugaringKind, ExpnKind, MultiSpan, Span};
2121

2222
crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) {
2323
let body_id = match def_id.as_local() {
@@ -63,7 +63,9 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {
6363
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
6464
intravisit::walk_expr(self, ex);
6565
match &ex.kind {
66-
hir::ExprKind::Match(scrut, arms, source) => self.check_match(scrut, arms, *source),
66+
hir::ExprKind::Match(scrut, arms, source) => {
67+
self.check_match(scrut, arms, *source, ex.span)
68+
}
6769
hir::ExprKind::Let(pat, scrut, span) => self.check_let(pat, scrut, *span),
6870
_ => {}
6971
}
@@ -160,6 +162,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
160162
scrut: &hir::Expr<'_>,
161163
hir_arms: &'tcx [hir::Arm<'tcx>],
162164
source: hir::MatchSource,
165+
expr_span: Span,
163166
) {
164167
let mut cx = self.new_cx(scrut.hir_id);
165168

@@ -205,15 +208,14 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
205208
}
206209

207210
// Check if the match is exhaustive.
208-
let is_empty_match = arms.is_empty();
209211
let witnesses = report.non_exhaustiveness_witnesses;
210212
if !witnesses.is_empty() {
211213
if source == hir::MatchSource::ForLoopDesugar && hir_arms.len() == 2 {
212214
// the for loop pattern is not irrefutable
213215
let pat = hir_arms[1].pat.for_loop_some().unwrap();
214216
self.check_irrefutable(pat, "`for` loop binding", None);
215217
} else {
216-
non_exhaustive_match(&cx, scrut_ty, scrut.span, witnesses, is_empty_match);
218+
non_exhaustive_match(&cx, scrut_ty, scrut.span, witnesses, hir_arms, expr_span);
217219
}
218220
}
219221
}
@@ -496,21 +498,25 @@ fn non_exhaustive_match<'p, 'tcx>(
496498
scrut_ty: Ty<'tcx>,
497499
sp: Span,
498500
witnesses: Vec<DeconstructedPat<'p, 'tcx>>,
499-
is_empty_match: bool,
501+
arms: &[hir::Arm<'tcx>],
502+
expr_span: Span,
500503
) {
504+
let is_empty_match = arms.is_empty();
501505
let non_empty_enum = match scrut_ty.kind() {
502506
ty::Adt(def, _) => def.is_enum() && !def.variants.is_empty(),
503507
_ => false,
504508
};
505509
// In the case of an empty match, replace the '`_` not covered' diagnostic with something more
506510
// informative.
507511
let mut err;
512+
let pattern;
508513
if is_empty_match && !non_empty_enum {
509514
err = create_e0004(
510515
cx.tcx.sess,
511516
sp,
512517
format!("non-exhaustive patterns: type `{}` is non-empty", scrut_ty),
513518
);
519+
pattern = "_".to_string();
514520
} else {
515521
let joined_patterns = joined_uncovered_patterns(cx, &witnesses);
516522
err = create_e0004(
@@ -519,6 +525,15 @@ fn non_exhaustive_match<'p, 'tcx>(
519525
format!("non-exhaustive patterns: {} not covered", joined_patterns),
520526
);
521527
err.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns));
528+
pattern = if witnesses.len() < 4 {
529+
witnesses
530+
.iter()
531+
.map(|witness| witness.to_pat(cx).to_string())
532+
.collect::<Vec<String>>()
533+
.join(" | ")
534+
} else {
535+
"_".to_string()
536+
};
522537
};
523538

524539
let is_variant_list_non_exhaustive = match scrut_ty.kind() {
@@ -527,10 +542,6 @@ fn non_exhaustive_match<'p, 'tcx>(
527542
};
528543

529544
adt_defined_here(cx, &mut err, scrut_ty, &witnesses);
530-
err.help(
531-
"ensure that all possible cases are being handled, \
532-
possibly by adding wildcards or more match arms",
533-
);
534545
err.note(&format!(
535546
"the matched value is of type `{}`{}",
536547
scrut_ty,
@@ -542,14 +553,14 @@ fn non_exhaustive_match<'p, 'tcx>(
542553
&& matches!(witnesses[0].ctor(), Constructor::NonExhaustive)
543554
{
544555
err.note(&format!(
545-
"`{}` does not have a fixed maximum value, \
546-
so a wildcard `_` is necessary to match exhaustively",
556+
"`{}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \
557+
exhaustively",
547558
scrut_ty,
548559
));
549560
if cx.tcx.sess.is_nightly_build() {
550561
err.help(&format!(
551-
"add `#![feature(precise_pointer_size_matching)]` \
552-
to the crate attributes to enable precise `{}` matching",
562+
"add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \
563+
enable precise `{}` matching",
553564
scrut_ty,
554565
));
555566
}
@@ -559,6 +570,67 @@ fn non_exhaustive_match<'p, 'tcx>(
559570
err.note("references are always considered inhabited");
560571
}
561572
}
573+
574+
let mut suggestion = None;
575+
let sm = cx.tcx.sess.source_map();
576+
match arms {
577+
[] if sp.ctxt() == expr_span.ctxt() => {
578+
// Get the span for the empty match body `{}`.
579+
let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) {
580+
(format!("\n{}", snippet), " ")
581+
} else {
582+
(" ".to_string(), "")
583+
};
584+
suggestion = Some((
585+
sp.shrink_to_hi().with_hi(expr_span.hi()),
586+
format!(
587+
" {{{indentation}{more}{pattern} => todo!(),{indentation}}}",
588+
indentation = indentation,
589+
more = more,
590+
pattern = pattern,
591+
),
592+
));
593+
}
594+
[only] => {
595+
let pre_indentation = if let (Some(snippet), true) = (
596+
sm.indentation_before(only.span),
597+
sm.is_multiline(sp.shrink_to_hi().with_hi(only.span.lo())),
598+
) {
599+
format!("\n{}", snippet)
600+
} else {
601+
" ".to_string()
602+
};
603+
let comma = if matches!(only.body.kind, hir::ExprKind::Block(..)) { "" } else { "," };
604+
suggestion = Some((
605+
only.span.shrink_to_hi(),
606+
format!("{}{}{} => todo!()", comma, pre_indentation, pattern),
607+
));
608+
}
609+
[.., prev, last] if prev.span.ctxt() == last.span.ctxt() => {
610+
if let Ok(snippet) = sm.span_to_snippet(prev.span.between(last.span)) {
611+
let comma =
612+
if matches!(last.body.kind, hir::ExprKind::Block(..)) { "" } else { "," };
613+
suggestion = Some((
614+
last.span.shrink_to_hi(),
615+
format!(
616+
"{}{}{} => todo!()",
617+
comma,
618+
snippet.strip_prefix(",").unwrap_or(&snippet),
619+
pattern
620+
),
621+
));
622+
}
623+
}
624+
_ => {}
625+
}
626+
627+
let msg = "ensure that all possible cases are being handled, possibly by adding wildcards \
628+
or more match arms";
629+
if let Some((span, sugg)) = suggestion {
630+
err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders);
631+
} else {
632+
err.help(msg);
633+
}
562634
err.emit();
563635
}
564636

@@ -599,15 +671,27 @@ fn adt_defined_here<'p, 'tcx>(
599671
) {
600672
let ty = ty.peel_refs();
601673
if let ty::Adt(def, _) = ty.kind() {
602-
if let Some(sp) = cx.tcx.hir().span_if_local(def.did) {
603-
err.span_label(sp, format!("`{}` defined here", ty));
604-
}
605-
606-
if witnesses.len() < 4 {
674+
let mut spans = vec![];
675+
if witnesses.len() < 5 {
607676
for sp in maybe_point_at_variant(cx, def, witnesses.iter()) {
608-
err.span_label(sp, "not covered");
677+
spans.push(sp);
609678
}
610679
}
680+
let def_span = cx
681+
.tcx
682+
.hir()
683+
.get_if_local(def.did)
684+
.and_then(|node| node.ident())
685+
.map(|ident| ident.span)
686+
.unwrap_or_else(|| cx.tcx.def_span(def.did));
687+
let mut span: MultiSpan =
688+
if spans.is_empty() { def_span.into() } else { spans.clone().into() };
689+
690+
span.push_span_label(def_span, String::new());
691+
for pat in spans {
692+
span.push_span_label(pat, "not covered".to_string());
693+
}
694+
err.span_note(span, &format!("`{}` defined here", ty));
611695
}
612696
}
613697

src/test/ui/closures/2229_closure_analysis/match/issue-88331.stderr

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
error[E0004]: non-exhaustive patterns: `Opcode(0_u8)` and `Opcode(2_u8..=u8::MAX)` not covered
22
--> $DIR/issue-88331.rs:11:20
33
|
4-
LL | pub struct Opcode(pub u8);
5-
| -------------------------- `Opcode` defined here
6-
...
74
LL | move |i| match msg_type {
85
| ^^^^^^^^ patterns `Opcode(0_u8)` and `Opcode(2_u8..=u8::MAX)` not covered
96
|
10-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
7+
note: `Opcode` defined here
8+
--> $DIR/issue-88331.rs:4:12
9+
|
10+
LL | pub struct Opcode(pub u8);
11+
| ^^^^^^
1112
= note: the matched value is of type `Opcode`
13+
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
14+
|
15+
LL ~ Opcode::OP1 => unimplemented!(),
16+
LL ~ Opcode(0_u8) | Opcode(2_u8..=u8::MAX) => todo!(),
17+
|
1218

1319
error[E0004]: non-exhaustive patterns: `Opcode2(Opcode(0_u8))` and `Opcode2(Opcode(2_u8..=u8::MAX))` not covered
1420
--> $DIR/issue-88331.rs:27:20
1521
|
16-
LL | pub struct Opcode2(Opcode);
17-
| --------------------------- `Opcode2` defined here
18-
...
1922
LL | move |i| match msg_type {
2023
| ^^^^^^^^ patterns `Opcode2(Opcode(0_u8))` and `Opcode2(Opcode(2_u8..=u8::MAX))` not covered
2124
|
22-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
25+
note: `Opcode2` defined here
26+
--> $DIR/issue-88331.rs:18:12
27+
|
28+
LL | pub struct Opcode2(Opcode);
29+
| ^^^^^^^
2330
= note: the matched value is of type `Opcode2`
31+
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
32+
|
33+
LL ~ Opcode2::OP2=> unimplemented!(),
34+
LL ~ Opcode2(Opcode(0_u8)) | Opcode2(Opcode(2_u8..=u8::MAX)) => todo!(),
35+
|
2436

2537
error: aborting due to 2 previous errors
2638

src/test/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr

+29-9
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,55 @@
11
error[E0004]: non-exhaustive patterns: `B` not covered
22
--> $DIR/non-exhaustive-match.rs:26:25
33
|
4-
LL | enum L1 { A, B }
5-
| ----------------
6-
| | |
7-
| | not covered
8-
| `L1` defined here
9-
...
104
LL | let _b = || { match l1 { L1::A => () } };
115
| ^^ pattern `B` not covered
126
|
13-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
7+
note: `L1` defined here
8+
--> $DIR/non-exhaustive-match.rs:12:14
9+
|
10+
LL | enum L1 { A, B }
11+
| -- ^ not covered
1412
= note: the matched value is of type `L1`
13+
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
14+
|
15+
LL | let _b = || { match l1 { L1::A => (), B => todo!() } };
16+
| ++++++++++++++
1517

1618
error[E0004]: non-exhaustive patterns: type `E1` is non-empty
1719
--> $DIR/non-exhaustive-match.rs:37:25
1820
|
1921
LL | let _d = || { match e1 {} };
2022
| ^^
2123
|
22-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
24+
note: `E1` defined here
25+
--> $DIR/auxiliary/match_non_exhaustive_lib.rs:2:1
26+
|
27+
LL | pub enum E1 {}
28+
| ^^^^^^^^^^^^^^
2329
= note: the matched value is of type `E1`, which is marked as non-exhaustive
30+
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
31+
|
32+
LL ~ let _d = || { match e1 {
33+
LL + _ => todo!(),
34+
LL ~ } };
35+
|
2436

2537
error[E0004]: non-exhaustive patterns: `_` not covered
2638
--> $DIR/non-exhaustive-match.rs:39:25
2739
|
2840
LL | let _e = || { match e2 { E2::A => (), E2::B => () } };
2941
| ^^ pattern `_` not covered
3042
|
31-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
43+
note: `E2` defined here
44+
--> $DIR/auxiliary/match_non_exhaustive_lib.rs:5:1
45+
|
46+
LL | pub enum E2 { A, B }
47+
| ^^^^^^^^^^^^^^^^^^^^
3248
= note: the matched value is of type `E2`, which is marked as non-exhaustive
49+
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
50+
|
51+
LL | let _e = || { match e2 { E2::A => (), E2::B => (), _ => todo!() } };
52+
| ++++++++++++++
3353

3454
error[E0505]: cannot move out of `e3` because it is borrowed
3555
--> $DIR/non-exhaustive-match.rs:46:22

src/test/ui/closures/2229_closure_analysis/match/pattern-matching-should-fail.stderr

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ error[E0004]: non-exhaustive patterns: type `u8` is non-empty
44
LL | let c1 = || match x { };
55
| ^
66
|
7-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
87
= note: the matched value is of type `u8`
8+
help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
9+
|
10+
LL ~ let c1 = || match x {
11+
LL + _ => todo!(),
12+
LL ~ };
13+
|
914

1015
error[E0381]: use of possibly-uninitialized variable: `x`
1116
--> $DIR/pattern-matching-should-fail.rs:8:23

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

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
error[E0005]: refutable pattern in local binding: `T(_, _)` not covered
22
--> $DIR/empty-never-array.rs:10:9
33
|
4-
LL | / enum Helper<T, U> {
5-
LL | | T(T, [!; 0]),
6-
| | - not covered
7-
LL | | #[allow(dead_code)]
8-
LL | | U(U),
9-
LL | | }
10-
| |_- `Helper<T, U>` defined here
11-
...
12-
LL | let Helper::U(u) = Helper::T(t, []);
13-
| ^^^^^^^^^^^^ pattern `T(_, _)` not covered
4+
LL | let Helper::U(u) = Helper::T(t, []);
5+
| ^^^^^^^^^^^^ pattern `T(_, _)` not covered
146
|
157
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
168
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
9+
note: `Helper<T, U>` defined here
10+
--> $DIR/empty-never-array.rs:4:5
11+
|
12+
LL | enum Helper<T, U> {
13+
| ------
14+
LL | T(T, [!; 0]),
15+
| ^ not covered
1716
= note: the matched value is of type `Helper<T, U>`
1817
help: you might want to use `if let` to ignore the variant that isn't matched
1918
|

0 commit comments

Comments
 (0)