Skip to content

Commit d02a7a3

Browse files
committed
Add lint to tell about let else pattern
1 parent e120fb1 commit d02a7a3

File tree

11 files changed

+457
-2
lines changed

11 files changed

+457
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3839,6 +3839,7 @@ Released 2018-09-13
38393839
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
38403840
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
38413841
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
3842+
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
38423843
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
38433844
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
38443845
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ store.register_lints(&[
244244
manual_async_fn::MANUAL_ASYNC_FN,
245245
manual_bits::MANUAL_BITS,
246246
manual_instant_elapsed::MANUAL_INSTANT_ELAPSED,
247+
manual_let_else::MANUAL_LET_ELSE,
247248
manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
248249
manual_rem_euclid::MANUAL_REM_EUCLID,
249250
manual_retain::MANUAL_RETAIN,

clippy_lints/src/lib.register_restriction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
3030
LintId::of(large_include_file::LARGE_INCLUDE_FILE),
3131
LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
3232
LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
33+
LintId::of(manual_let_else::MANUAL_LET_ELSE),
3334
LintId::of(matches::REST_PAT_IN_FULLY_BOUND_STRUCTS),
3435
LintId::of(matches::TRY_ERR),
3536
LintId::of(matches::WILDCARD_ENUM_MATCH_ARM),

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ mod manual_assert;
269269
mod manual_async_fn;
270270
mod manual_bits;
271271
mod manual_instant_elapsed;
272+
mod manual_let_else;
272273
mod manual_non_exhaustive;
273274
mod manual_rem_euclid;
274275
mod manual_retain;
@@ -610,6 +611,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
610611
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv)));
611612
store.register_late_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv)));
612613
store.register_late_pass(move || Box::new(manual_strip::ManualStrip::new(msrv)));
614+
store.register_late_pass(move || Box::new(manual_let_else::ManualLetElse::new(msrv)));
613615
store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)));
614616
store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv)));
615617
store.register_late_pass(move || Box::new(checked_conversions::CheckedConversions::new(msrv)));

clippy_lints/src/manual_let_else.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::visitors::expr_visitor_no_bodies;
3+
use clippy_utils::{higher, meets_msrv, msrvs, peel_blocks};
4+
use if_chain::if_chain;
5+
use rustc_hir::intravisit::Visitor;
6+
use rustc_hir::{Expr, ExprKind, Pat, QPath, Stmt, StmtKind};
7+
use rustc_lint::{LateContext, LateLintPass, LintContext};
8+
use rustc_middle::lint::in_external_macro;
9+
use rustc_semver::RustcVersion;
10+
use rustc_session::{declare_tool_lint, impl_lint_pass};
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
///
15+
/// Warn of cases where `let else` could be used
16+
///
17+
/// ### Why is this bad?
18+
///
19+
/// `let else` provides a standard construct for this pattern
20+
/// that people can easily recognize. It's also more compact.
21+
///
22+
/// ### Example
23+
///
24+
/// ```rust
25+
/// # let w = Some(0);
26+
/// let v = if let Some(v) = w { v } else { return };
27+
/// ```
28+
///
29+
/// Could be written:
30+
///
31+
/// ```rust
32+
/// # #![feature(let_else)]
33+
/// # fn main () {
34+
/// # let w = Some(0);
35+
/// let Some(v) = w else { return };
36+
/// # }
37+
/// ```
38+
#[clippy::version = "1.60.0"]
39+
pub MANUAL_LET_ELSE,
40+
restriction,
41+
"TODO"
42+
}
43+
44+
pub struct ManualLetElse {
45+
msrv: Option<RustcVersion>,
46+
}
47+
48+
impl ManualLetElse {
49+
#[must_use]
50+
pub fn new(msrv: Option<RustcVersion>) -> Self {
51+
Self { msrv }
52+
}
53+
}
54+
55+
impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
56+
57+
impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
58+
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
59+
if !meets_msrv(self.msrv.as_ref(), &msrvs::LET_ELSE) {
60+
return;
61+
}
62+
63+
if in_external_macro(cx.sess(), stmt.span) {
64+
return;
65+
}
66+
67+
if_chain! {
68+
if let StmtKind::Local(local) = stmt.kind;
69+
if let Some(init) = local.init;
70+
if let Some(higher::IfLet { let_pat, let_expr: _, if_then, if_else }) = higher::IfLet::hir(cx, init);
71+
if if_then_simple_identity(let_pat, if_then);
72+
if let Some(if_else) = if_else;
73+
if expr_diverges(cx, if_else);
74+
then {
75+
span_lint(
76+
cx,
77+
MANUAL_LET_ELSE,
78+
stmt.span,
79+
"this could be rewritten as `let else`",
80+
);
81+
}
82+
}
83+
}
84+
85+
extract_msrv_attr!(LateContext);
86+
}
87+
88+
fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
89+
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
90+
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
91+
return ty.is_never();
92+
}
93+
false
94+
}
95+
let mut does_diverge = false;
96+
expr_visitor_no_bodies(|ex| {
97+
match ex.kind {
98+
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => {
99+
does_diverge = true;
100+
false
101+
},
102+
ExprKind::Call(call, _) => {
103+
if is_never(cx, ex) || is_never(cx, call) {
104+
does_diverge = true;
105+
return false;
106+
}
107+
true
108+
},
109+
ExprKind::MethodCall(..) => {
110+
if is_never(cx, ex) {
111+
does_diverge = true;
112+
return false;
113+
}
114+
true
115+
},
116+
ExprKind::If(if_expr, if_then, if_else) => {
117+
let else_diverges = if_else.map_or(false, |ex| expr_diverges(cx, ex));
118+
let diverges = expr_diverges(cx, if_expr) || (else_diverges && expr_diverges(cx, if_then));
119+
if diverges {
120+
does_diverge = true;
121+
}
122+
false
123+
},
124+
ExprKind::Match(match_expr, match_arms, _) => {
125+
let diverges =
126+
expr_diverges(cx, match_expr) || match_arms.iter().all(|arm| expr_diverges(cx, arm.body));
127+
if diverges {
128+
does_diverge = true;
129+
}
130+
false
131+
},
132+
133+
// Don't continue into loops, as they are breakable,
134+
// and we'd have to start checking labels.
135+
ExprKind::Loop(..) => false,
136+
137+
// Default: descend
138+
// TODO: support breakable blocks once they are stable.
139+
// For this we'd need to add label support.
140+
_ => true,
141+
}
142+
})
143+
.visit_expr(expr);
144+
does_diverge
145+
}
146+
147+
/// Checks if the passed `if_then` is a simple identity
148+
fn if_then_simple_identity(let_pat: &'_ Pat<'_>, if_then: &'_ Expr<'_>) -> bool {
149+
// TODO support patterns with multiple bindings and tuples, like:
150+
// let (foo, bar) = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
151+
if_chain! {
152+
if let ExprKind::Path(QPath::Resolved(_ty, path)) = &peel_blocks(if_then).kind;
153+
if let [path_seg] = path.segments;
154+
then {
155+
let mut pat_bindings = Vec::new();
156+
let_pat.each_binding(|_ann, _hir_id, _sp, ident| {
157+
pat_bindings.push(ident);
158+
});
159+
if let [binding] = &pat_bindings[..] {
160+
return path_seg.ident == *binding;
161+
}
162+
}
163+
}
164+
false
165+
}

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ define_Conf! {
213213
///
214214
/// Suppress lints whenever the suggested change would cause breakage for other crates.
215215
(avoid_breaking_exported_api: bool = true),
216-
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED.
216+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, MANUAL_LET_ELSE.
217217
///
218218
/// The minimum rust version that the project supports
219219
(msrv: Option<String> = None),

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ macro_rules! msrv_aliases {
1212

1313
// names may refer to stabilized feature flags or library items
1414
msrv_aliases! {
15+
1,65,0 { LET_ELSE }
1516
1,62,0 { BOOL_THEN_SOME }
1617
1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
17-
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
1818
1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
1919
1,50,0 { BOOL_THEN }
2020
1,47,0 { TAU }

src/docs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ docs! {
259259
"manual_find_map",
260260
"manual_flatten",
261261
"manual_instant_elapsed",
262+
"manual_let_else",
262263
"manual_map",
263264
"manual_memcpy",
264265
"manual_non_exhaustive",

src/docs/manual_let_else.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
### What it does
2+
3+
Warn of cases where `let else` could be used
4+
5+
### Why is this bad?
6+
7+
`let else` provides a standard construct for this pattern
8+
that people can easily recognize. It's also more compact.
9+
10+
### Example
11+
12+
```
13+
let v = if let Some(v) = w { v } else { return };
14+
```
15+
16+
Could be written:
17+
18+
```
19+
let Some(v) = w else { return };
20+
```

0 commit comments

Comments
 (0)