Skip to content

Commit c41d895

Browse files
committed
Add lint to tell about let else pattern
1 parent 50f192f commit c41d895

File tree

11 files changed

+464
-1
lines changed

11 files changed

+464
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3996,6 +3996,7 @@ Released 2018-09-13
39963996
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
39973997
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
39983998
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
3999+
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
39994000
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
40004001
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
40014002
[`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
@@ -251,6 +251,7 @@ store.register_lints(&[
251251
manual_bits::MANUAL_BITS,
252252
manual_clamp::MANUAL_CLAMP,
253253
manual_instant_elapsed::MANUAL_INSTANT_ELAPSED,
254+
manual_let_else::MANUAL_LET_ELSE,
254255
manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
255256
manual_rem_euclid::MANUAL_REM_EUCLID,
256257
manual_retain::MANUAL_RETAIN,

clippy_lints/src/lib.register_pedantic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
4747
LintId::of(macro_use::MACRO_USE_IMPORTS),
4848
LintId::of(manual_assert::MANUAL_ASSERT),
4949
LintId::of(manual_instant_elapsed::MANUAL_INSTANT_ELAPSED),
50+
LintId::of(manual_let_else::MANUAL_LET_ELSE),
5051
LintId::of(manual_string_new::MANUAL_STRING_NEW),
5152
LintId::of(matches::MATCH_BOOL),
5253
LintId::of(matches::MATCH_ON_VEC_ITEMS),

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ mod manual_async_fn;
270270
mod manual_bits;
271271
mod manual_clamp;
272272
mod manual_instant_elapsed;
273+
mod manual_let_else;
273274
mod manual_non_exhaustive;
274275
mod manual_rem_euclid;
275276
mod manual_retain;
@@ -605,6 +606,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
605606
))
606607
});
607608
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv)));
609+
store.register_late_pass(move |_| Box::new(manual_let_else::ManualLetElse::new(msrv)));
608610
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv)));
609611
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv)));
610612
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::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::{for_each_expr, Descend};
3+
use clippy_utils::{higher, meets_msrv, msrvs, peel_blocks};
4+
use if_chain::if_chain;
5+
use rustc_hir::{Expr, ExprKind, Pat, QPath, Stmt, StmtKind};
6+
use rustc_lint::{LateContext, LateLintPass, LintContext};
7+
use rustc_middle::lint::in_external_macro;
8+
use rustc_semver::RustcVersion;
9+
use rustc_session::{declare_tool_lint, impl_lint_pass};
10+
use std::ops::ControlFlow;
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+
pedantic,
41+
"manual implementation of a let...else statement"
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, 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+
for_each_expr(expr, |ex| {
97+
match ex.kind {
98+
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => {
99+
does_diverge = true;
100+
ControlFlow::Break(())
101+
},
102+
ExprKind::Call(call, _) => {
103+
if is_never(cx, ex) || is_never(cx, call) {
104+
does_diverge = true;
105+
return ControlFlow::Break(());
106+
}
107+
ControlFlow::Continue(Descend::Yes)
108+
},
109+
ExprKind::MethodCall(..) => {
110+
if is_never(cx, ex) {
111+
does_diverge = true;
112+
return ControlFlow::Break(());
113+
}
114+
ControlFlow::Continue(Descend::Yes)
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+
return ControlFlow::Break(());
122+
}
123+
ControlFlow::Continue(Descend::No)
124+
},
125+
ExprKind::Match(match_expr, match_arms, _) => {
126+
let diverges =
127+
expr_diverges(cx, match_expr) || match_arms.iter().all(|arm| expr_diverges(cx, arm.body));
128+
if diverges {
129+
does_diverge = true;
130+
return ControlFlow::Break(());
131+
}
132+
ControlFlow::Continue(Descend::No)
133+
},
134+
135+
// Don't continue into loops, as they are breakable,
136+
// and we'd have to start checking labels.
137+
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Break(()),
138+
139+
// Default: descend
140+
// TODO: support breakable blocks. For this we would need label support.
141+
_ => ControlFlow::Continue(Descend::Yes),
142+
}
143+
});
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, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP.
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, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, 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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ 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,58,0 { FORMAT_ARGS_CAPTURE }
1718
1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }

src/docs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ docs! {
266266
"manual_find_map",
267267
"manual_flatten",
268268
"manual_instant_elapsed",
269+
"manual_let_else",
269270
"manual_map",
270271
"manual_memcpy",
271272
"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)