Skip to content

Commit 0d92752

Browse files
committed
Suggest if let/let_else for refutable pat in let
1 parent b97dc20 commit 0d92752

15 files changed

+204
-56
lines changed

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

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use super::{PatCtxt, PatternError};
77
use rustc_arena::TypedArena;
88
use rustc_ast::Mutability;
99
use rustc_errors::{
10-
error_code, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
10+
error_code, pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder,
11+
ErrorGuaranteed,
1112
};
1213
use rustc_hir as hir;
1314
use rustc_hir::def::*;
@@ -20,7 +21,7 @@ use rustc_session::lint::builtin::{
2021
};
2122
use rustc_session::Session;
2223
use rustc_span::source_map::Spanned;
23-
use rustc_span::{DesugaringKind, ExpnKind, MultiSpan, Span};
24+
use rustc_span::{BytePos, DesugaringKind, ExpnKind, MultiSpan, Span};
2425

2526
crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) {
2627
let body_id = match def_id.as_local() {
@@ -241,6 +242,9 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
241242
}
242243

243244
let joined_patterns = joined_uncovered_patterns(&cx, &witnesses);
245+
246+
let mut bindings = vec![];
247+
244248
let mut err = struct_span_err!(
245249
self.tcx.sess,
246250
pat.span,
@@ -257,6 +261,16 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
257261
false
258262
}
259263
_ => {
264+
pat.walk(&mut |pat: &hir::Pat<'_>| {
265+
match pat.kind {
266+
hir::PatKind::Binding(_, _, ident, _) => {
267+
bindings.push(ident);
268+
}
269+
_ => {}
270+
}
271+
true
272+
});
273+
260274
err.span_label(pat.span, pattern_not_covered_label(&witnesses, &joined_patterns));
261275
true
262276
}
@@ -267,13 +281,71 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
267281
"`let` bindings require an \"irrefutable pattern\", like a `struct` or \
268282
an `enum` with only one variant",
269283
);
270-
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
271-
err.span_suggestion(
272-
span,
273-
"you might want to use `if let` to ignore the variant that isn't matched",
274-
format!("if {} {{ /* */ }}", &snippet[..snippet.len() - 1]),
284+
if self.tcx.sess.source_map().span_to_snippet(span).is_ok() {
285+
let semi_span = span.shrink_to_hi().with_lo(span.hi() - BytePos(1));
286+
let start_span = span.shrink_to_lo();
287+
let end_span = semi_span.shrink_to_lo();
288+
err.multipart_suggestion(
289+
&format!(
290+
"you might want to use `if let` to ignore the variant{} that {} matched",
291+
pluralize!(witnesses.len()),
292+
match witnesses.len() {
293+
1 => "isn't",
294+
_ => "aren't",
295+
},
296+
),
297+
vec![
298+
match &bindings[..] {
299+
[] => (start_span, "if ".to_string()),
300+
[binding] => (start_span, format!("let {} = if ", binding)),
301+
bindings => (
302+
start_span,
303+
format!(
304+
"let ({}) = if ",
305+
bindings
306+
.iter()
307+
.map(|ident| ident.to_string())
308+
.collect::<Vec<_>>()
309+
.join(", ")
310+
),
311+
),
312+
},
313+
match &bindings[..] {
314+
[] => (semi_span, " { todo!() }".to_string()),
315+
[binding] => {
316+
(end_span, format!(" {{ {} }} else {{ todo!() }}", binding))
317+
}
318+
bindings => (
319+
end_span,
320+
format!(
321+
" {{ ({}) }} else {{ todo!() }}",
322+
bindings
323+
.iter()
324+
.map(|ident| ident.to_string())
325+
.collect::<Vec<_>>()
326+
.join(", ")
327+
),
328+
),
329+
},
330+
],
275331
Applicability::HasPlaceholders,
276332
);
333+
if cx.tcx.sess.is_nightly_build() {
334+
err.span_suggestion_verbose(
335+
semi_span.shrink_to_lo(),
336+
&format!(
337+
"alternatively, on nightly, you might want to use \
338+
`#![feature(let_else)]` to handle the variant{} that {} matched",
339+
pluralize!(witnesses.len()),
340+
match witnesses.len() {
341+
1 => "isn't",
342+
_ => "aren't",
343+
},
344+
),
345+
" else { todo!() }".to_string(),
346+
Applicability::HasPlaceholders,
347+
);
348+
}
277349
}
278350
err.note(
279351
"for more information, visit \

src/test/ui/consts/const-match-check.eval1.stderr

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ LL | A = { let 0 = 0; 0 },
77
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
88
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
99
= note: the matched value is of type `i32`
10-
help: you might want to use `if let` to ignore the variant that isn't matched
10+
help: you might want to use `if let` to ignore the variants that aren't matched
1111
|
12-
LL | A = { if let 0 = 0 { /* */ } 0 },
13-
| ~~~~~~~~~~~~~~~~~~~~~~
12+
LL | A = { if let 0 = 0 { todo!() } 0 },
13+
| ++ ~~~~~~~~~~~
14+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
15+
|
16+
LL | A = { let 0 = 0 else { todo!() }; 0 },
17+
| ++++++++++++++++
1418

1519
error: aborting due to previous error
1620

src/test/ui/consts/const-match-check.eval2.stderr

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ LL | let x: [i32; { let 0 = 0; 0 }] = [];
77
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
88
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
99
= note: the matched value is of type `i32`
10-
help: you might want to use `if let` to ignore the variant that isn't matched
10+
help: you might want to use `if let` to ignore the variants that aren't matched
1111
|
12-
LL | let x: [i32; { if let 0 = 0 { /* */ } 0 }] = [];
13-
| ~~~~~~~~~~~~~~~~~~~~~~
12+
LL | let x: [i32; { if let 0 = 0 { todo!() } 0 }] = [];
13+
| ++ ~~~~~~~~~~~
14+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
15+
|
16+
LL | let x: [i32; { let 0 = 0 else { todo!() }; 0 }] = [];
17+
| ++++++++++++++++
1418

1519
error: aborting due to previous error
1620

src/test/ui/consts/const-match-check.matchck.stderr

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ LL | const X: i32 = { let 0 = 0; 0 };
77
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
88
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
99
= note: the matched value is of type `i32`
10-
help: you might want to use `if let` to ignore the variant that isn't matched
10+
help: you might want to use `if let` to ignore the variants that aren't matched
1111
|
12-
LL | const X: i32 = { if let 0 = 0 { /* */ } 0 };
13-
| ~~~~~~~~~~~~~~~~~~~~~~
12+
LL | const X: i32 = { if let 0 = 0 { todo!() } 0 };
13+
| ++ ~~~~~~~~~~~
14+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
15+
|
16+
LL | const X: i32 = { let 0 = 0 else { todo!() }; 0 };
17+
| ++++++++++++++++
1418

1519
error[E0005]: refutable pattern in local binding: `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered
1620
--> $DIR/const-match-check.rs:8:23
@@ -21,10 +25,14 @@ LL | static Y: i32 = { let 0 = 0; 0 };
2125
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
2226
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
2327
= note: the matched value is of type `i32`
24-
help: you might want to use `if let` to ignore the variant that isn't matched
28+
help: you might want to use `if let` to ignore the variants that aren't matched
29+
|
30+
LL | static Y: i32 = { if let 0 = 0 { todo!() } 0 };
31+
| ++ ~~~~~~~~~~~
32+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
2533
|
26-
LL | static Y: i32 = { if let 0 = 0 { /* */ } 0 };
27-
| ~~~~~~~~~~~~~~~~~~~~~~
34+
LL | static Y: i32 = { let 0 = 0 else { todo!() }; 0 };
35+
| ++++++++++++++++
2836

2937
error[E0005]: refutable pattern in local binding: `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered
3038
--> $DIR/const-match-check.rs:13:26
@@ -35,10 +43,14 @@ LL | const X: i32 = { let 0 = 0; 0 };
3543
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
3644
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
3745
= note: the matched value is of type `i32`
38-
help: you might want to use `if let` to ignore the variant that isn't matched
46+
help: you might want to use `if let` to ignore the variants that aren't matched
3947
|
40-
LL | const X: i32 = { if let 0 = 0 { /* */ } 0 };
41-
| ~~~~~~~~~~~~~~~~~~~~~~
48+
LL | const X: i32 = { if let 0 = 0 { todo!() } 0 };
49+
| ++ ~~~~~~~~~~~
50+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
51+
|
52+
LL | const X: i32 = { let 0 = 0 else { todo!() }; 0 };
53+
| ++++++++++++++++
4254

4355
error[E0005]: refutable pattern in local binding: `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered
4456
--> $DIR/const-match-check.rs:19:26
@@ -49,10 +61,14 @@ LL | const X: i32 = { let 0 = 0; 0 };
4961
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
5062
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
5163
= note: the matched value is of type `i32`
52-
help: you might want to use `if let` to ignore the variant that isn't matched
64+
help: you might want to use `if let` to ignore the variants that aren't matched
65+
|
66+
LL | const X: i32 = { if let 0 = 0 { todo!() } 0 };
67+
| ++ ~~~~~~~~~~~
68+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
5369
|
54-
LL | const X: i32 = { if let 0 = 0 { /* */ } 0 };
55-
| ~~~~~~~~~~~~~~~~~~~~~~
70+
LL | const X: i32 = { let 0 = 0 else { todo!() }; 0 };
71+
| ++++++++++++++++
5672

5773
error: aborting due to 4 previous errors
5874

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ LL | T(T, [!; 0]),
1616
= note: the matched value is of type `Helper<T, U>`
1717
help: you might want to use `if let` to ignore the variant that isn't matched
1818
|
19-
LL | if let Helper::U(u) = Helper::T(t, []) { /* */ }
19+
LL | let u = if let Helper::U(u) = Helper::T(t, []) { u } else { todo!() };
20+
| ++++++++++ ++++++++++++++++++++++
21+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variant that isn't matched
2022
|
23+
LL | let Helper::U(u) = Helper::T(t, []) else { todo!() };
24+
| ++++++++++++++++
2125

2226
error: aborting due to previous error
2327

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ LL | | }
2222
= note: the matched value is of type `Option<i32>`
2323
help: you might want to use `if let` to ignore the variant that isn't matched
2424
|
25-
LL | if let Some(y) = x { /* */ }
26-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
LL | let y = if let Some(y) = x { y } else { todo!() };
26+
| ++++++++++ ++++++++++++++++++++++
27+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variant that isn't matched
28+
|
29+
LL | let Some(y) = x else { todo!() };
30+
| ++++++++++++++++
2731

2832
error: aborting due to previous error
2933

src/test/ui/feature-gates/feature-gate-exhaustive-patterns.stderr

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ LL | | }
2121
= note: the matched value is of type `Result<u32, !>`
2222
help: you might want to use `if let` to ignore the variant that isn't matched
2323
|
24-
LL | if let Ok(_x) = foo() { /* */ }
25-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24+
LL | let _x = if let Ok(_x) = foo() { _x } else { todo!() };
25+
| +++++++++++ +++++++++++++++++++++++
26+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variant that isn't matched
27+
|
28+
LL | let Ok(_x) = foo() else { todo!() };
29+
| ++++++++++++++++
2630

2731
error: aborting due to previous error
2832

src/test/ui/or-patterns/issue-69875-should-have-been-expanded-earlier-non-exhaustive.stderr

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ LL | let (0 | (1 | 2)) = 0;
77
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
88
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
99
= note: the matched value is of type `i32`
10-
help: you might want to use `if let` to ignore the variant that isn't matched
10+
help: you might want to use `if let` to ignore the variants that aren't matched
1111
|
12-
LL | if let (0 | (1 | 2)) = 0 { /* */ }
13-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12+
LL | if let (0 | (1 | 2)) = 0 { todo!() }
13+
| ++ ~~~~~~~~~~~
14+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
15+
|
16+
LL | let (0 | (1 | 2)) = 0 else { todo!() };
17+
| ++++++++++++++++
1418

1519
error[E0004]: non-exhaustive patterns: `i32::MIN..=-1_i32` and `3_i32..=i32::MAX` not covered
1620
--> $DIR/issue-69875-should-have-been-expanded-earlier-non-exhaustive.rs:3:11

src/test/ui/pattern/usefulness/issue-31561.stderr

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ LL | Bar,
1717
LL | Baz
1818
| ^^^ not covered
1919
= note: the matched value is of type `Thing`
20-
help: you might want to use `if let` to ignore the variant that isn't matched
20+
help: you might want to use `if let` to ignore the variants that aren't matched
2121
|
22-
LL | if let Thing::Foo(y) = Thing::Foo(1) { /* */ }
22+
LL | let y = if let Thing::Foo(y) = Thing::Foo(1) { y } else { todo!() };
23+
| ++++++++++ ++++++++++++++++++++++
24+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
2325
|
26+
LL | let Thing::Foo(y) = Thing::Foo(1) else { todo!() };
27+
| ++++++++++++++++
2428

2529
error: aborting due to previous error
2630

src/test/ui/pattern/usefulness/non-exhaustive-defined-here.stderr

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ LL | B,
4242
LL | C
4343
| ^ not covered
4444
= note: the matched value is of type `E`
45-
help: you might want to use `if let` to ignore the variant that isn't matched
45+
help: you might want to use `if let` to ignore the variants that aren't matched
46+
|
47+
LL | if let E::A = e { todo!() }
48+
| ++ ~~~~~~~~~~~
49+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
4650
|
47-
LL | if let E::A = e { /* */ }
48-
| ~~~~~~~~~~~~~~~~~~~~~~~~~
51+
LL | let E::A = e else { todo!() };
52+
| ++++++++++++++++
4953

5054
error[E0004]: non-exhaustive patterns: `&B` and `&C` not covered
5155
--> $DIR/non-exhaustive-defined-here.rs:52:11
@@ -91,10 +95,14 @@ LL | B,
9195
LL | C
9296
| ^ not covered
9397
= note: the matched value is of type `&E`
94-
help: you might want to use `if let` to ignore the variant that isn't matched
98+
help: you might want to use `if let` to ignore the variants that aren't matched
9599
|
96-
LL | if let E::A = e { /* */ }
97-
| ~~~~~~~~~~~~~~~~~~~~~~~~~
100+
LL | if let E::A = e { todo!() }
101+
| ++ ~~~~~~~~~~~
102+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
103+
|
104+
LL | let E::A = e else { todo!() };
105+
| ++++++++++++++++
98106

99107
error[E0004]: non-exhaustive patterns: `&&mut &B` and `&&mut &C` not covered
100108
--> $DIR/non-exhaustive-defined-here.rs:66:11
@@ -140,10 +148,14 @@ LL | B,
140148
LL | C
141149
| ^ not covered
142150
= note: the matched value is of type `&&mut &E`
143-
help: you might want to use `if let` to ignore the variant that isn't matched
151+
help: you might want to use `if let` to ignore the variants that aren't matched
144152
|
145-
LL | if let E::A = e { /* */ }
153+
LL | if let E::A = e { todo!() }
154+
| ++ ~~~~~~~~~~~
155+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
146156
|
157+
LL | let E::A = e else { todo!() };
158+
| ++++++++++++++++
147159

148160
error[E0004]: non-exhaustive patterns: `None` not covered
149161
--> $DIR/non-exhaustive-defined-here.rs:92:11
@@ -185,8 +197,12 @@ LL | None,
185197
= note: the matched value is of type `Opt`
186198
help: you might want to use `if let` to ignore the variant that isn't matched
187199
|
188-
LL | if let Opt::Some(ref _x) = e { /* */ }
189-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
200+
LL | let _x = if let Opt::Some(ref _x) = e { _x } else { todo!() };
201+
| +++++++++++ +++++++++++++++++++++++
202+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variant that isn't matched
203+
|
204+
LL | let Opt::Some(ref _x) = e else { todo!() };
205+
| ++++++++++++++++
190206

191207
error: aborting due to 8 previous errors
192208

src/test/ui/pattern/usefulness/refutable-pattern-errors.stderr

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ LL | let (1, (Some(1), 2..=3)) = (1, (None, 2));
1515
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
1616
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
1717
= note: the matched value is of type `(i32, (Option<i32>, i32))`
18-
help: you might want to use `if let` to ignore the variant that isn't matched
18+
help: you might want to use `if let` to ignore the variants that aren't matched
1919
|
20-
LL | if let (1, (Some(1), 2..=3)) = (1, (None, 2)) { /* */ }
20+
LL | if let (1, (Some(1), 2..=3)) = (1, (None, 2)) { todo!() }
21+
| ++ ~~~~~~~~~~~
22+
help: alternatively, on nightly, you might want to use `#![feature(let_else)]` to handle the variants that aren't matched
2123
|
24+
LL | let (1, (Some(1), 2..=3)) = (1, (None, 2)) else { todo!() };
25+
| ++++++++++++++++
2226

2327
error: aborting due to 2 previous errors
2428

0 commit comments

Comments
 (0)