@@ -21,11 +21,11 @@ use syntax::symbol::LocalInternedString;
21
21
use crate :: utils:: sugg;
22
22
use crate :: utils:: usage:: mutated_variables;
23
23
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 ,
29
29
} ;
30
30
use crate :: utils:: { paths, span_help_and_lint} ;
31
31
@@ -264,6 +264,32 @@ declare_clippy_lint! {
264
264
"using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
265
265
}
266
266
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
+
267
293
declare_clippy_lint ! {
268
294
/// **What it does:** Checks for usage of `_.filter(_).next()`.
269
295
///
@@ -918,6 +944,7 @@ declare_lint_pass!(Methods => [
918
944
OPTION_MAP_UNWRAP_OR_ELSE ,
919
945
RESULT_MAP_UNWRAP_OR_ELSE ,
920
946
OPTION_MAP_OR_NONE ,
947
+ OPTION_AND_THEN_SOME ,
921
948
OR_FUN_CALL ,
922
949
EXPECT_FUN_CALL ,
923
950
CHARS_NEXT_CMP ,
@@ -967,6 +994,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
967
994
[ "unwrap_or" , "map" ] => option_map_unwrap_or:: lint ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ,
968
995
[ "unwrap_or_else" , "map" ] => lint_map_unwrap_or_else ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ,
969
996
[ "map_or" , ..] => lint_map_or_none ( cx, expr, arg_lists[ 0 ] ) ,
997
+ [ "and_then" , ..] => lint_option_and_then_some ( cx, expr, arg_lists[ 0 ] ) ,
970
998
[ "next" , "filter" ] => lint_filter_next ( cx, expr, arg_lists[ 1 ] ) ,
971
999
[ "map" , "filter" ] => lint_filter_map ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ,
972
1000
[ "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,
2072
2100
}
2073
2101
}
2074
2102
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
+
2075
2194
/// lint use of `filter().next()` for `Iterators`
2076
2195
fn lint_filter_next < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & ' tcx hir:: Expr , filter_args : & ' tcx [ hir:: Expr ] ) {
2077
2196
// lint if caller of `.filter().next()` is an Iterator
0 commit comments