@@ -21,11 +21,11 @@ use syntax::symbol::LocalInternedString;
2121use crate :: utils:: sugg;
2222use crate :: utils:: usage:: mutated_variables;
2323use 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} ;
3030use 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+
267293declare_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`
20762195fn 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
0 commit comments