Skip to content

Commit 6f7ad66

Browse files
committed
Add option_and_then_some lint
1 parent e92c489 commit 6f7ad66

File tree

10 files changed

+202
-10
lines changed

10 files changed

+202
-10
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_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/booleans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
189189
{
190190
return None;
191191
}
192+
192193
METHODS_WITH_NEGATION
193194
.iter()
194195
.cloned()

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: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ use crate::utils::paths;
2222
use crate::utils::sugg;
2323
use crate::utils::usage::mutated_variables;
2424
use crate::utils::{
25-
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy,
26-
is_ctor_function, is_expn_of, iter_input_pats, last_path_segment, match_def_path, match_qpath, match_trait_method,
27-
match_type, match_var, method_calls, method_chain_args, remove_blocks, return_ty, same_tys, single_segment_path,
28-
snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_sugg,
29-
span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
25+
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, in_macro_or_desugar,
26+
is_copy, is_ctor_function, is_expn_of, iter_input_pats, last_path_segment, match_def_path, match_qpath,
27+
match_trait_method, match_type, match_var, method_calls, method_chain_args, remove_blocks, return_ty, same_tys,
28+
single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
29+
span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
3030
};
3131

3232
declare_clippy_lint! {
@@ -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,97 @@ 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+
const LINT_MSG: &str = "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`";
2086+
const NO_OP_MSG: &str = "using `Option.and_then(Some)`, which is a no-op";
2087+
2088+
// Searches an return expressions in `y` in `_.and_then(|x| Some(y))`, which we don't lint
2089+
struct RetCallFinder {
2090+
found: bool,
2091+
}
2092+
2093+
impl<'tcx> intravisit::Visitor<'tcx> for RetCallFinder {
2094+
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
2095+
if self.found {
2096+
return;
2097+
}
2098+
if let hir::ExprKind::Ret(..) = &expr.node {
2099+
self.found = true;
2100+
} else {
2101+
intravisit::walk_expr(self, expr);
2102+
}
2103+
}
2104+
2105+
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
2106+
intravisit::NestedVisitorMap::None
2107+
}
2108+
}
2109+
2110+
let ty = cx.tables.expr_ty(&args[0]);
2111+
if !match_type(cx, ty, &paths::OPTION) {
2112+
return;
2113+
}
2114+
2115+
match args[1].node {
2116+
hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
2117+
let closure_body = cx.tcx.hir().body(body_id);
2118+
let closure_expr = remove_blocks(&closure_body.value);
2119+
if_chain! {
2120+
if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.node;
2121+
if let hir::ExprKind::Path(ref qpath) = some_expr.node;
2122+
if match_qpath(qpath, &paths::OPTION_SOME);
2123+
if some_args.len() == 1;
2124+
then {
2125+
let inner_expr = &some_args[0];
2126+
2127+
let mut finder = RetCallFinder { found: false };
2128+
finder.visit_expr(inner_expr);
2129+
if finder.found {
2130+
return;
2131+
}
2132+
2133+
let some_inner_snip = if in_macro_or_desugar(inner_expr.span) {
2134+
snippet_with_macro_callsite(cx, inner_expr.span, "_")
2135+
} else {
2136+
snippet(cx, inner_expr.span, "_")
2137+
};
2138+
2139+
let closure_args_snip = snippet(cx, closure_args_span, "..");
2140+
let option_snip = snippet(cx, args[0].span, "..");
2141+
let note = format!("{}.map({} {})", option_snip, closure_args_snip, some_inner_snip);
2142+
span_lint_and_sugg(
2143+
cx,
2144+
OPTION_AND_THEN_SOME,
2145+
expr.span,
2146+
LINT_MSG,
2147+
"try this",
2148+
note,
2149+
Applicability::MachineApplicable,
2150+
);
2151+
}
2152+
}
2153+
},
2154+
// `_.and_then(Some)` case, which is no-op.
2155+
hir::ExprKind::Path(ref qpath) => {
2156+
if match_qpath(qpath, &paths::OPTION_SOME) {
2157+
let option_snip = snippet(cx, args[0].span, "..");
2158+
let note = format!("{}", option_snip);
2159+
span_lint_and_sugg(
2160+
cx,
2161+
OPTION_AND_THEN_SOME,
2162+
expr.span,
2163+
NO_OP_MSG,
2164+
"use the expression directly",
2165+
note,
2166+
Applicability::MachineApplicable,
2167+
);
2168+
}
2169+
},
2170+
_ => {},
2171+
}
2172+
}
2173+
20552174
/// lint use of `filter().next()` for `Iterators`
20562175
fn lint_filter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, filter_args: &'tcx [hir::Expr]) {
20572176
// 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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
}

tests/ui/option_and_then_some.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
}

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+

0 commit comments

Comments
 (0)