Skip to content

Commit 44771f5

Browse files
committed
Don't suggest let else in match if the else arm explicitly mentions non obvious paths
1 parent d4a6ac0 commit 44771f5

File tree

3 files changed

+107
-30
lines changed

3 files changed

+107
-30
lines changed

clippy_lints/src/manual_let_else.rs

+38-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use clippy_utils::diagnostics::span_lint;
22
use clippy_utils::higher::IfLetOrMatch;
3+
use clippy_utils::ty::is_type_diagnostic_item;
34
use clippy_utils::visitors::expr_visitor_no_bodies;
45
use clippy_utils::{meets_msrv, msrvs, peel_blocks};
56
use if_chain::if_chain;
7+
use rustc_data_structures::stable_set::FxHashSet;
68
use rustc_hir::intravisit::Visitor;
7-
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, QPath, Stmt, StmtKind};
9+
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
810
use rustc_lint::{LateContext, LateLintPass, LintContext};
911
use rustc_middle::lint::in_external_macro;
1012
use rustc_semver::RustcVersion;
11-
use rustc_data_structures::stable_set::FxHashSet;
1213
use rustc_session::{declare_tool_lint, impl_lint_pass};
14+
use rustc_span::symbol::sym;
1315
use rustc_span::Span;
1416

1517
declare_clippy_lint! {
@@ -108,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
108110
}
109111
if expr_is_simple_identity(arm.pat, arm.body) {
110112
found_identity_arm = true;
111-
} else if expr_diverges(cx, arm.body) && pat_has_no_bindings(arm.pat) {
113+
} else if expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat) {
112114
found_diverging_arm = true;
113115
}
114116
}
@@ -199,10 +201,39 @@ fn from_different_macros(span_a: Span, span_b: Span) -> bool {
199201
data_for_comparison(span_a) != data_for_comparison(span_b)
200202
}
201203

202-
fn pat_has_no_bindings(pat: &'_ Pat<'_>) -> bool {
203-
let mut has_no_bindings = true;
204-
pat.each_binding_or_first(&mut |_, _, _, _| has_no_bindings = false);
205-
has_no_bindings
204+
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>) -> bool {
205+
// Check whether the pattern contains any bindings, as the
206+
// binding might potentially be used in the body.
207+
// TODO: only look for *used* bindings.
208+
let mut has_bindings = false;
209+
pat.each_binding_or_first(&mut |_, _, _, _| has_bindings = true);
210+
if has_bindings {
211+
return false;
212+
}
213+
214+
// Check whether any possibly "unknown" patterns are included,
215+
// because users might not which values some enum has.
216+
// Well-known enums are excepted, as we assume people know them.
217+
// We do a deep check, to be able to disallow Err(En::Foo(_))
218+
// for usage of the En::Foo variant, as we disallow En::Foo(_),
219+
// but we allow Err(_).
220+
let typeck_results = cx.typeck_results();
221+
let mut has_disallowed = false;
222+
pat.walk_always(|pat| {
223+
// Only do the check if the type is "spelled out" in the pattern
224+
if !matches!(
225+
pat.kind,
226+
PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..)
227+
) {
228+
return;
229+
};
230+
let ty = typeck_results.pat_ty(pat);
231+
// Option and Result are allowed, everything else isn't.
232+
if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) {
233+
has_disallowed = true;
234+
}
235+
});
236+
!has_disallowed
206237
}
207238

208239
/// Checks if the passed block is a simple identity referring to bindings created by the pattern

tests/ui/manual_let_else_match.rs

+45-17
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44
// Ensure that we don't conflict with match -> if let lints
55
#![warn(clippy::single_match_else, clippy::single_match)]
66

7-
enum Variant {
8-
Foo,
9-
Bar(u32),
10-
Baz(u32),
11-
}
12-
137
fn f() -> Result<u32, u32> {
148
Ok(0)
159
}
@@ -18,7 +12,17 @@ fn g() -> Option<()> {
1812
None
1913
}
2014

21-
fn h() -> Variant {
15+
fn h() -> (Option<()>, Option<()>) {
16+
(None, None)
17+
}
18+
19+
enum Variant {
20+
Foo,
21+
Bar(u32),
22+
Baz(u32),
23+
}
24+
25+
fn build_enum() -> Variant {
2226
Variant::Foo
2327
}
2428

@@ -36,9 +40,14 @@ fn fire() {
3640
};
3741

3842
loop {
39-
// More complex pattern for the identity arm
43+
// More complex pattern for the identity arm and diverging arm
4044
let v = match h() {
41-
Variant::Foo => continue,
45+
(Some(_), Some(_)) | (None, None) => continue,
46+
(Some(v), None) | (None, Some(v)) => v,
47+
};
48+
// Custom enums are supported as long as the "else" arm is a simple _
49+
let v = match build_enum() {
50+
_ => continue,
4251
Variant::Bar(v) | Variant::Baz(v) => v,
4352
};
4453
}
@@ -49,21 +58,27 @@ fn fire() {
4958
Ok(v) => v,
5059
Err(_) => return,
5160
};
61+
62+
// Err(()) is an allowed pattern
63+
let v = match f().map_err(|_| ()) {
64+
Ok(v) => v,
65+
Err(()) => return,
66+
};
5267
}
5368

5469
fn not_fire() {
5570
// Multiple diverging arms
5671
let v = match h() {
57-
Variant::Foo => panic!(),
58-
Variant::Bar(_v) => return,
59-
Variant::Baz(v) => v,
72+
_ => panic!(),
73+
(None, Some(_v)) => return,
74+
(Some(v), None) => v,
6075
};
6176

6277
// Multiple identity arms
6378
let v = match h() {
64-
Variant::Foo => panic!(),
65-
Variant::Bar(v) => v,
66-
Variant::Baz(v) => v,
79+
_ => panic!(),
80+
(None, Some(v)) => v,
81+
(Some(v), None) => v,
6782
};
6883

6984
// No diverging arm at all, only identity arms.
@@ -74,8 +89,8 @@ fn not_fire() {
7489
};
7590

7691
// The identity arm has a guard
77-
let v = match h() {
78-
Variant::Bar(v) if g().is_none() => v,
92+
let v = match g() {
93+
Some(v) if g().is_none() => v,
7994
_ => return,
8095
};
8196

@@ -90,4 +105,17 @@ fn not_fire() {
90105
Ok(v) => v,
91106
Err(e) => panic!("error: {e}"),
92107
};
108+
109+
// Custom enum where the diverging arm
110+
// explicitly mentions the variant
111+
let v = match build_enum() {
112+
Variant::Foo => return,
113+
Variant::Bar(v) | Variant::Baz(v) => v,
114+
};
115+
116+
// The custom enum is surrounded by an Err()
117+
let v = match Err(build_enum()) {
118+
Ok(v) | Err(Variant::Bar(v) | Variant::Baz(v)) => v,
119+
Err(Variant::Foo) => return,
120+
};
93121
}

tests/ui/manual_let_else_match.stderr

+24-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this could be rewritten as `let else`
2-
--> $DIR/manual_let_else_match.rs:28:5
2+
--> $DIR/manual_let_else_match.rs:32:5
33
|
44
LL | / let v = match g() {
55
LL | | Some(v_some) => v_some,
@@ -10,7 +10,7 @@ LL | | };
1010
= note: `-D clippy::manual-let-else` implied by `-D warnings`
1111

1212
error: this could be rewritten as `let else`
13-
--> $DIR/manual_let_else_match.rs:33:5
13+
--> $DIR/manual_let_else_match.rs:37:5
1414
|
1515
LL | / let v = match g() {
1616
LL | | Some(v_some) => v_some,
@@ -19,22 +19,40 @@ LL | | };
1919
| |______^
2020

2121
error: this could be rewritten as `let else`
22-
--> $DIR/manual_let_else_match.rs:40:9
22+
--> $DIR/manual_let_else_match.rs:44:9
2323
|
2424
LL | / let v = match h() {
25-
LL | | Variant::Foo => continue,
25+
LL | | (Some(_), Some(_)) | (None, None) => continue,
26+
LL | | (Some(v), None) | (None, Some(v)) => v,
27+
LL | | };
28+
| |__________^
29+
30+
error: this could be rewritten as `let else`
31+
--> $DIR/manual_let_else_match.rs:49:9
32+
|
33+
LL | / let v = match build_enum() {
34+
LL | | _ => continue,
2635
LL | | Variant::Bar(v) | Variant::Baz(v) => v,
2736
LL | | };
2837
| |__________^
2938

3039
error: this could be rewritten as `let else`
31-
--> $DIR/manual_let_else_match.rs:48:5
40+
--> $DIR/manual_let_else_match.rs:57:5
3241
|
3342
LL | / let v = match f() {
3443
LL | | Ok(v) => v,
3544
LL | | Err(_) => return,
3645
LL | | };
3746
| |______^
3847

39-
error: aborting due to 4 previous errors
48+
error: this could be rewritten as `let else`
49+
--> $DIR/manual_let_else_match.rs:63:5
50+
|
51+
LL | / let v = match f().map_err(|_| ()) {
52+
LL | | Ok(v) => v,
53+
LL | | Err(()) => return,
54+
LL | | };
55+
| |______^
56+
57+
error: aborting due to 6 previous errors
4058

0 commit comments

Comments
 (0)