Skip to content

Commit a01fd4c

Browse files
committed
Add option_and_then_some lint
1 parent 70f12dc commit a01fd4c

File tree

8 files changed

+154
-2
lines changed

8 files changed

+154
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,7 @@ Released 2018-09-13
10881088
[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
10891089
[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
10901090
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
1091+
[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
10911092
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
10921093
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
10931094
[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 309 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 310 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
794794
methods::ITER_SKIP_NEXT,
795795
methods::NEW_RET_NO_SELF,
796796
methods::OK_EXPECT,
797+
methods::OPTION_AND_THEN_SOME,
797798
methods::OPTION_MAP_OR_NONE,
798799
methods::OR_FUN_CALL,
799800
methods::SEARCH_IS_SOME,
@@ -1032,6 +1033,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
10321033
methods::CLONE_ON_COPY,
10331034
methods::FILTER_NEXT,
10341035
methods::FLAT_MAP_IDENTITY,
1036+
methods::OPTION_AND_THEN_SOME,
10351037
methods::SEARCH_IS_SOME,
10361038
methods::UNNECESSARY_FILTER_MAP,
10371039
methods::USELESS_ASREF,

clippy_lints/src/methods/mod.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,32 @@ declare_clippy_lint! {
264264
"using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
265265
}
266266

267+
declare_clippy_lint! {
268+
/// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`.
269+
///
270+
/// **Why is this bad?** Readability, this can be written more concisely as
271+
/// `_.map(|x| y)`.
272+
///
273+
/// **Known problems:** None
274+
///
275+
/// **Example:**
276+
///
277+
/// ```rust
278+
/// let x = Some("foo");
279+
/// let _ = x.and_then(|s| Some(s.len()));
280+
/// ```
281+
///
282+
/// The correct use would be:
283+
///
284+
/// ```rust
285+
/// let x = Some("foo");
286+
/// let _ = x.map(|s| s.len());
287+
/// ```
288+
pub OPTION_AND_THEN_SOME,
289+
complexity,
290+
"using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
291+
}
292+
267293
declare_clippy_lint! {
268294
/// **What it does:** Checks for usage of `_.filter(_).next()`.
269295
///
@@ -900,6 +926,7 @@ declare_lint_pass!(Methods => [
900926
OPTION_MAP_UNWRAP_OR_ELSE,
901927
RESULT_MAP_UNWRAP_OR_ELSE,
902928
OPTION_MAP_OR_NONE,
929+
OPTION_AND_THEN_SOME,
903930
OR_FUN_CALL,
904931
EXPECT_FUN_CALL,
905932
CHARS_NEXT_CMP,
@@ -948,6 +975,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
948975
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0]),
949976
["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
950977
["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
978+
["and_then", ..] => lint_option_and_then_some(cx, expr, arg_lists[0]),
951979
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
952980
["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
953981
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
@@ -2052,6 +2080,68 @@ fn lint_map_or_none<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr,
20522080
}
20532081
}
20542082

2083+
/// Lint use of `_.and_then(|x| Some(y))` for `Option`s
2084+
fn lint_option_and_then_some(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[hir::Expr]) {
2085+
let ty = cx.tables.expr_ty(&args[0]);
2086+
if !match_type(cx, ty, &paths::OPTION) {
2087+
return;
2088+
}
2089+
2090+
const LINT_MSG: &str = "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`";
2091+
const NO_OP_MSG: &str = "using `Option.and_then(Some)`, which is no-op";
2092+
2093+
match args[1].node {
2094+
hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
2095+
let closure_body = cx.tcx.hir().body(body_id);
2096+
let closure_expr = remove_blocks(&closure_body.value);
2097+
2098+
// let note = format!("{:?}", args[1]);
2099+
// span_note_and_lint(cx, OPTION_AND_THEN_SOME, closure_args_span, "Outer debugging
2100+
// information", closure_args_span, &note);
2101+
2102+
if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.node {
2103+
if let hir::ExprKind::Path(ref qpath) = some_expr.node {
2104+
if match_qpath(qpath, &paths::OPTION_SOME) {
2105+
if some_args.len() == 1 {
2106+
let inner_expr = &some_args[0];
2107+
let some_inner_snip = snippet(cx, inner_expr.span, "..");
2108+
let closure_args_snip = snippet(cx, closure_args_span, "..");
2109+
let option_snip = snippet(cx, args[0].span, "..");
2110+
let note = format!("{}.map({} {})", option_snip, closure_args_snip, some_inner_snip);
2111+
span_lint_and_sugg(
2112+
cx,
2113+
OPTION_AND_THEN_SOME,
2114+
expr.span,
2115+
LINT_MSG,
2116+
"try this",
2117+
note,
2118+
Applicability::MachineApplicable,
2119+
);
2120+
}
2121+
}
2122+
}
2123+
}
2124+
},
2125+
// `_.and_then(Some)` case, which is no-op.
2126+
hir::ExprKind::Path(ref qpath) => {
2127+
if match_qpath(qpath, &paths::OPTION_SOME) {
2128+
let option_snip = snippet(cx, args[0].span, "..");
2129+
let note = format!("{}", option_snip);
2130+
span_lint_and_sugg(
2131+
cx,
2132+
OPTION_AND_THEN_SOME,
2133+
expr.span,
2134+
NO_OP_MSG,
2135+
"use the expression directly",
2136+
note,
2137+
Applicability::MachineApplicable,
2138+
);
2139+
}
2140+
},
2141+
_ => {},
2142+
}
2143+
}
2144+
20552145
/// lint use of `filter().next()` for `Iterators`
20562146
fn lint_filter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, filter_args: &'tcx [hir::Expr]) {
20572147
// lint if caller of `.filter().next()` is an Iterator

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 309] = [
9+
pub const ALL_LINTS: [Lint; 310] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1316,6 +1316,13 @@ pub const ALL_LINTS: [Lint; 309] = [
13161316
deprecation: None,
13171317
module: "eq_op",
13181318
},
1319+
Lint {
1320+
name: "option_and_then_some",
1321+
group: "complexity",
1322+
desc: "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`",
1323+
deprecation: None,
1324+
module: "methods",
1325+
},
13191326
Lint {
13201327
name: "option_map_or_none",
13211328
group: "style",

tests/ui/option_and_then_some.fixed

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![deny(clippy::option_and_then_some)]
3+
4+
// need a main anyway, use it get rid of unused warnings too
5+
pub fn main() {
6+
let x = Some(5);
7+
// the easiest cases
8+
let _ = x;
9+
let _ = x.map(|o| o + 1);
10+
// and an easy counter-example
11+
let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
12+
13+
// Different type
14+
let x: Result<u32, &str> = Ok(1);
15+
let _ = x.and_then(Ok);
16+
}

tests/ui/option_and_then_some.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![deny(clippy::option_and_then_some)]
3+
4+
// need a main anyway, use it get rid of unused warnings too
5+
pub fn main() {
6+
let x = Some(5);
7+
// the easiest cases
8+
let _ = x.and_then(Some);
9+
let _ = x.and_then(|o| Some(o + 1));
10+
// and an easy counter-example
11+
let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
12+
13+
// Different type
14+
let x: Result<u32, &str> = Ok(1);
15+
let _ = x.and_then(Ok);
16+
}

tests/ui/option_and_then_some.stderr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: using `Option.and_then(Some)`, which is no-op
2+
--> $DIR/option_and_then_some.rs:8:13
3+
|
4+
LL | let _ = x.and_then(Some);
5+
| ^^^^^^^^^^^^^^^^ help: use the expression directly: `x`
6+
|
7+
note: lint level defined here
8+
--> $DIR/option_and_then_some.rs:2:9
9+
|
10+
LL | #![deny(clippy::option_and_then_some)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`
14+
--> $DIR/option_and_then_some.rs:9:13
15+
|
16+
LL | let _ = x.and_then(|o| Some(o + 1));
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)`
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)