@@ -22,11 +22,11 @@ use crate::utils::paths;
2222use crate :: utils:: sugg;
2323use crate :: utils:: usage:: mutated_variables;
2424use 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
3232declare_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+
267293declare_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`
20562175fn 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
0 commit comments