Skip to content

Commit d1f1844

Browse files
committed
Auto merge of #4386 - lzutao:lint-option-and_then-some, r=flip1995
Add option_and_then_some lint changelog: Add complexity lint to warn about `option.and_then(|o| Some(x))` and suggest replacing with `option.map(|o| x)`. Closes #4299
2 parents 2ed80d4 + 41eba2f commit d1f1844

File tree

12 files changed

+215
-12
lines changed

12 files changed

+215
-12
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 310 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 311 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_dev/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,13 @@ pub fn gen_deprecated(lints: &[Lint]) -> Vec<String> {
123123
lints
124124
.iter()
125125
.filter_map(|l| {
126-
l.clone().deprecation.and_then(|depr_text| {
127-
Some(vec![
126+
l.clone().deprecation.map(|depr_text| {
127+
vec![
128128
" store.register_removed(".to_string(),
129129
format!(" \"clippy::{}\",", l.name),
130130
format!(" \"{}\",", depr_text),
131131
" );".to_string(),
132-
])
132+
]
133133
})
134134
})
135135
.flatten()

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
795795
methods::ITER_SKIP_NEXT,
796796
methods::NEW_RET_NO_SELF,
797797
methods::OK_EXPECT,
798+
methods::OPTION_AND_THEN_SOME,
798799
methods::OPTION_MAP_OR_NONE,
799800
methods::OR_FUN_CALL,
800801
methods::SEARCH_IS_SOME,
@@ -1033,6 +1034,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
10331034
methods::CLONE_ON_COPY,
10341035
methods::FILTER_NEXT,
10351036
methods::FLAT_MAP_IDENTITY,
1037+
methods::OPTION_AND_THEN_SOME,
10361038
methods::SEARCH_IS_SOME,
10371039
methods::SUSPICIOUS_MAP,
10381040
methods::UNNECESSARY_FILTER_MAP,

clippy_lints/src/methods/mod.rs

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ use syntax::symbol::LocalInternedString;
2121
use crate::utils::sugg;
2222
use crate::utils::usage::mutated_variables;
2323
use crate::utils::{
24-
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy,
25-
is_ctor_function, is_expn_of, iter_input_pats, last_path_segment, match_def_path, match_qpath, match_trait_method,
26-
match_type, match_var, method_calls, method_chain_args, remove_blocks, return_ty, same_tys, single_segment_path,
27-
snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_sugg,
28-
span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
24+
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, in_macro_or_desugar,
25+
is_copy, is_ctor_function, is_expn_of, iter_input_pats, last_path_segment, match_def_path, match_qpath,
26+
match_trait_method, match_type, match_var, method_calls, method_chain_args, remove_blocks, return_ty, same_tys,
27+
single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
28+
span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
2929
};
3030
use crate::utils::{paths, span_help_and_lint};
3131

@@ -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
///
@@ -918,6 +944,7 @@ declare_lint_pass!(Methods => [
918944
OPTION_MAP_UNWRAP_OR_ELSE,
919945
RESULT_MAP_UNWRAP_OR_ELSE,
920946
OPTION_MAP_OR_NONE,
947+
OPTION_AND_THEN_SOME,
921948
OR_FUN_CALL,
922949
EXPECT_FUN_CALL,
923950
CHARS_NEXT_CMP,
@@ -967,6 +994,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
967994
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0]),
968995
["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
969996
["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
997+
["and_then", ..] => lint_option_and_then_some(cx, expr, arg_lists[0]),
970998
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
971999
["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
9721000
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
@@ -2072,6 +2100,97 @@ fn lint_map_or_none<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr,
20722100
}
20732101
}
20742102

2103+
/// Lint use of `_.and_then(|x| Some(y))` for `Option`s
2104+
fn lint_option_and_then_some(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[hir::Expr]) {
2105+
const LINT_MSG: &str = "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`";
2106+
const NO_OP_MSG: &str = "using `Option.and_then(Some)`, which is a no-op";
2107+
2108+
// Searches an return expressions in `y` in `_.and_then(|x| Some(y))`, which we don't lint
2109+
struct RetCallFinder {
2110+
found: bool,
2111+
}
2112+
2113+
impl<'tcx> intravisit::Visitor<'tcx> for RetCallFinder {
2114+
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
2115+
if self.found {
2116+
return;
2117+
}
2118+
if let hir::ExprKind::Ret(..) = &expr.node {
2119+
self.found = true;
2120+
} else {
2121+
intravisit::walk_expr(self, expr);
2122+
}
2123+
}
2124+
2125+
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
2126+
intravisit::NestedVisitorMap::None
2127+
}
2128+
}
2129+
2130+
let ty = cx.tables.expr_ty(&args[0]);
2131+
if !match_type(cx, ty, &paths::OPTION) {
2132+
return;
2133+
}
2134+
2135+
match args[1].node {
2136+
hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
2137+
let closure_body = cx.tcx.hir().body(body_id);
2138+
let closure_expr = remove_blocks(&closure_body.value);
2139+
if_chain! {
2140+
if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.node;
2141+
if let hir::ExprKind::Path(ref qpath) = some_expr.node;
2142+
if match_qpath(qpath, &paths::OPTION_SOME);
2143+
if some_args.len() == 1;
2144+
then {
2145+
let inner_expr = &some_args[0];
2146+
2147+
let mut finder = RetCallFinder { found: false };
2148+
finder.visit_expr(inner_expr);
2149+
if finder.found {
2150+
return;
2151+
}
2152+
2153+
let some_inner_snip = if in_macro_or_desugar(inner_expr.span) {
2154+
snippet_with_macro_callsite(cx, inner_expr.span, "_")
2155+
} else {
2156+
snippet(cx, inner_expr.span, "_")
2157+
};
2158+
2159+
let closure_args_snip = snippet(cx, closure_args_span, "..");
2160+
let option_snip = snippet(cx, args[0].span, "..");
2161+
let note = format!("{}.map({} {})", option_snip, closure_args_snip, some_inner_snip);
2162+
span_lint_and_sugg(
2163+
cx,
2164+
OPTION_AND_THEN_SOME,
2165+
expr.span,
2166+
LINT_MSG,
2167+
"try this",
2168+
note,
2169+
Applicability::MachineApplicable,
2170+
);
2171+
}
2172+
}
2173+
},
2174+
// `_.and_then(Some)` case, which is no-op.
2175+
hir::ExprKind::Path(ref qpath) => {
2176+
if match_qpath(qpath, &paths::OPTION_SOME) {
2177+
let option_snip = snippet(cx, args[0].span, "..");
2178+
let note = format!("{}", option_snip);
2179+
span_lint_and_sugg(
2180+
cx,
2181+
OPTION_AND_THEN_SOME,
2182+
expr.span,
2183+
NO_OP_MSG,
2184+
"use the expression directly",
2185+
note,
2186+
Applicability::MachineApplicable,
2187+
);
2188+
}
2189+
},
2190+
_ => {},
2191+
}
2192+
}
2193+
20752194
/// lint use of `filter().next()` for `Iterators`
20762195
fn lint_filter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, filter_args: &'tcx [hir::Expr]) {
20772196
// 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; 310] = [
9+
pub const ALL_LINTS: [Lint; 311] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1316,6 +1316,13 @@ pub const ALL_LINTS: [Lint; 310] = [
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
}
17+
18+
pub fn foo() -> Option<String> {
19+
let x = Some(String::from("hello"));
20+
Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?)))
21+
}
22+
23+
pub fn example2(x: bool) -> Option<&'static str> {
24+
Some("a").and_then(|s| Some(if x { s } else { return None }))
25+
}

tests/ui/option_and_then_some.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
}
17+
18+
pub fn foo() -> Option<String> {
19+
let x = Some(String::from("hello"));
20+
Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?)))
21+
}
22+
23+
pub fn example2(x: bool) -> Option<&'static str> {
24+
Some("a").and_then(|s| Some(if x { s } else { return None }))
25+
}

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 a 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+

tests/ui/option_map_or_none.fixed

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// run-rustfix
22

3+
#![allow(clippy::option_and_then_some)]
4+
35
fn main() {
46
let opt = Some(1);
57

tests/ui/option_map_or_none.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// run-rustfix
22

3+
#![allow(clippy::option_and_then_some)]
4+
35
fn main() {
46
let opt = Some(1);
57

tests/ui/option_map_or_none.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
error: called `map_or(None, f)` on an Option value. This can be done more directly by calling `and_then(f)` instead
2-
--> $DIR/option_map_or_none.rs:8:13
2+
--> $DIR/option_map_or_none.rs:10:13
33
|
44
LL | let _ = opt.map_or(None, |x| Some(x + 1));
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using and_then instead: `opt.and_then(|x| Some(x + 1))`
66
|
77
= note: `-D clippy::option-map-or-none` implied by `-D warnings`
88

99
error: called `map_or(None, f)` on an Option value. This can be done more directly by calling `and_then(f)` instead
10-
--> $DIR/option_map_or_none.rs:11:13
10+
--> $DIR/option_map_or_none.rs:13:13
1111
|
1212
LL | let _ = opt.map_or(None, |x| {
1313
| _____________^

0 commit comments

Comments
 (0)