From 967d815a426dd16354dd42c96ad4b3f2eb036f09 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 7 Jun 2021 22:21:56 +0200 Subject: [PATCH 1/4] Extracting `is_expr_identity_function` into `clippy_utils` for reusability --- clippy_lints/src/map_identity.rs | 54 ++------------------------------ clippy_utils/src/lib.rs | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/clippy_lints/src/map_identity.rs b/clippy_lints/src/map_identity.rs index 41cda2351..9bcb010ff 100644 --- a/clippy_lints/src/map_identity.rs +++ b/clippy_lints/src/map_identity.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_adjusted, is_qpath_def_path, is_trait_method, match_var, paths, remove_blocks}; +use clippy_utils::{is_expr_identity_function, is_trait_method}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Body, Expr, ExprKind, Pat, PatKind, QPath, StmtKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -74,53 +74,3 @@ fn get_map_argument<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a } } } - -/// Checks if an expression represents the identity function -/// Only examines closures and `std::convert::identity` -fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)), - ExprKind::Path(ref path) => is_qpath_def_path(cx, path, expr.hir_id, &paths::CONVERT_IDENTITY), - _ => false, - } -} - -/// Checks if a function's body represents the identity function -/// Looks for bodies of the form `|x| x`, `|x| return x`, `|x| { return x }` or `|x| { -/// return x; }` -fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { - let params = func.params; - let body = remove_blocks(&func.value); - - // if there's less/more than one parameter, then it is not the identity function - if params.len() != 1 { - return false; - } - - match body.kind { - ExprKind::Path(QPath::Resolved(None, _)) => match_expr_param(cx, body, params[0].pat), - ExprKind::Ret(Some(ret_val)) => match_expr_param(cx, ret_val, params[0].pat), - ExprKind::Block(block, _) => { - if_chain! { - if block.stmts.len() == 1; - if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = block.stmts[0].kind; - if let ExprKind::Ret(Some(ret_val)) = expr.kind; - then { - match_expr_param(cx, ret_val, params[0].pat) - } else { - false - } - } - }, - _ => false, - } -} - -/// Returns true iff an expression returns the same thing as a parameter's pattern -fn match_expr_param(cx: &LateContext<'_>, expr: &Expr<'_>, pat: &Pat<'_>) -> bool { - if let PatKind::Binding(_, _, ident, _) = pat.kind { - match_var(expr, ident.name) && !(cx.typeck_results().hir_owner == expr.hir_id.owner && is_adjusted(cx, expr)) - } else { - false - } -} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 769836aaf..a765abe6b 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1399,6 +1399,60 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { did.map_or(false, |did| must_use_attr(cx.tcx.get_attrs(did)).is_some()) } +/// Checks if an expression represents the identity function +/// Only examines closures and `std::convert::identity` +pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + /// Returns true if the expression is a binding to the given pattern + fn is_expr_pat_binding(cx: &LateContext<'_>, expr: &Expr<'_>, pat: &Pat<'_>) -> bool { + if let PatKind::Binding(_, _, ident, _) = pat.kind { + if match_var(expr, ident.name) { + return !(cx.typeck_results().hir_owner == expr.hir_id.owner && is_adjusted(cx, expr)); + } + } + + false + } + + /// Checks if a function's body represents the identity function. Looks for bodies of the form: + /// * `|x| x` + /// * `|x| return x` + /// * `|x| { return x }` + /// * `|x| { return x; }` + fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { + let body = remove_blocks(&func.value); + + let value_pat = if let [value_param] = func.params { + value_param.pat + } else { + return false; + }; + + match body.kind { + ExprKind::Path(QPath::Resolved(None, _)) => is_expr_pat_binding(cx, body, value_pat), + ExprKind::Ret(Some(ret_val)) => is_expr_pat_binding(cx, ret_val, value_pat), + ExprKind::Block(block, _) => { + if_chain! { + if let &[block_stmt] = &block.stmts; + if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = block_stmt.kind; + if let ExprKind::Ret(Some(ret_val)) = expr.kind; + then { + is_expr_pat_binding(cx, ret_val, value_pat) + } else { + false + } + } + }, + _ => false, + } + } + + match expr.kind { + ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)), + ExprKind::Path(ref path) => is_qpath_def_path(cx, path, expr.hir_id, &paths::CONVERT_IDENTITY), + _ => false, + } +} + /// Gets the node where an expression is either used, or it's type is unified with another branch. pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option> { let map = tcx.hir(); From bb3b58cfccdfbc4f95a2f03dc800aa87fd3fdd2c Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 7 Jun 2021 23:10:42 +0200 Subject: [PATCH 2/4] Reuse `is_expr_identity_function` for `flat_map_identity` --- clippy_lints/src/methods/flat_map_identity.rs | 44 +++++-------------- tests/ui/flat_map_identity.fixed | 5 ++- tests/ui/flat_map_identity.rs | 5 ++- tests/ui/flat_map_identity.stderr | 12 +++-- 4 files changed, 28 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/methods/flat_map_identity.rs b/clippy_lints/src/methods/flat_map_identity.rs index 25f8434cb..6f911d79d 100644 --- a/clippy_lints/src/methods/flat_map_identity.rs +++ b/clippy_lints/src/methods/flat_map_identity.rs @@ -1,6 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_expr_path_def_path, is_trait_method, paths}; -use if_chain::if_chain; +use clippy_utils::{is_expr_identity_function, is_trait_method}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -15,36 +14,15 @@ pub(super) fn check<'tcx>( flat_map_arg: &'tcx hir::Expr<'_>, flat_map_span: Span, ) { - if is_trait_method(cx, expr, sym::Iterator) { - let apply_lint = |message: &str| { - span_lint_and_sugg( - cx, - FLAT_MAP_IDENTITY, - flat_map_span.with_hi(expr.span.hi()), - message, - "try", - "flatten()".to_string(), - Applicability::MachineApplicable, - ); - }; - - if_chain! { - if let hir::ExprKind::Closure(_, _, body_id, _, _) = flat_map_arg.kind; - let body = cx.tcx.hir().body(body_id); - - if let hir::PatKind::Binding(_, _, binding_ident, _) = body.params[0].pat.kind; - if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = body.value.kind; - - if path.segments.len() == 1; - if path.segments[0].ident.name == binding_ident.name; - - then { - apply_lint("called `flat_map(|x| x)` on an `Iterator`"); - } - } - - if is_expr_path_def_path(cx, flat_map_arg, &paths::CONVERT_IDENTITY) { - apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`"); - } + if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, flat_map_arg) { + span_lint_and_sugg( + cx, + FLAT_MAP_IDENTITY, + flat_map_span.with_hi(expr.span.hi()), + "use of `flat_map` with an identity function", + "try", + "flatten()".to_string(), + Applicability::MachineApplicable, + ); } } diff --git a/tests/ui/flat_map_identity.fixed b/tests/ui/flat_map_identity.fixed index dfe3bd47e..1f4b880ef 100644 --- a/tests/ui/flat_map_identity.fixed +++ b/tests/ui/flat_map_identity.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused_imports)] +#![allow(unused_imports, clippy::needless_return)] #![warn(clippy::flat_map_identity)] use std::convert; @@ -11,4 +11,7 @@ fn main() { let iterator = [[0, 1], [2, 3], [4, 5]].iter(); let _ = iterator.flatten(); + + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flatten(); } diff --git a/tests/ui/flat_map_identity.rs b/tests/ui/flat_map_identity.rs index 393b95692..de14a06d4 100644 --- a/tests/ui/flat_map_identity.rs +++ b/tests/ui/flat_map_identity.rs @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused_imports)] +#![allow(unused_imports, clippy::needless_return)] #![warn(clippy::flat_map_identity)] use std::convert; @@ -11,4 +11,7 @@ fn main() { let iterator = [[0, 1], [2, 3], [4, 5]].iter(); let _ = iterator.flat_map(convert::identity); + + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flat_map(|x| return x); } diff --git a/tests/ui/flat_map_identity.stderr b/tests/ui/flat_map_identity.stderr index e4686ae5a..e776c9fdf 100644 --- a/tests/ui/flat_map_identity.stderr +++ b/tests/ui/flat_map_identity.stderr @@ -1,4 +1,4 @@ -error: called `flat_map(|x| x)` on an `Iterator` +error: use of `flat_map` with an identity function --> $DIR/flat_map_identity.rs:10:22 | LL | let _ = iterator.flat_map(|x| x); @@ -6,11 +6,17 @@ LL | let _ = iterator.flat_map(|x| x); | = note: `-D clippy::flat-map-identity` implied by `-D warnings` -error: called `flat_map(std::convert::identity)` on an `Iterator` +error: use of `flat_map` with an identity function --> $DIR/flat_map_identity.rs:13:22 | LL | let _ = iterator.flat_map(convert::identity); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` -error: aborting due to 2 previous errors +error: use of `flat_map` with an identity function + --> $DIR/flat_map_identity.rs:16:22 + | +LL | let _ = iterator.flat_map(|x| return x); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 3 previous errors From 9e54ce865c67af65370e1ec3822742a22fd20dfe Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 7 Jun 2021 23:31:17 +0200 Subject: [PATCH 3/4] Reuse `is_expr_identity_function` for `filter_map_identity` --- .../src/methods/filter_map_identity.rs | 40 +++++-------------- tests/ui/filter_map_identity.fixed | 5 ++- tests/ui/filter_map_identity.rs | 5 ++- tests/ui/filter_map_identity.stderr | 14 +++++-- 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/clippy_lints/src/methods/filter_map_identity.rs b/clippy_lints/src/methods/filter_map_identity.rs index 403fe8d35..d1b5e945d 100644 --- a/clippy_lints/src/methods/filter_map_identity.rs +++ b/clippy_lints/src/methods/filter_map_identity.rs @@ -1,6 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_expr_path_def_path, is_trait_method, path_to_local_id, paths}; -use if_chain::if_chain; +use clippy_utils::{is_expr_identity_function, is_trait_method}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -9,32 +8,15 @@ use rustc_span::{source_map::Span, sym}; use super::FILTER_MAP_IDENTITY; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) { - if is_trait_method(cx, expr, sym::Iterator) { - let apply_lint = |message: &str| { - span_lint_and_sugg( - cx, - FILTER_MAP_IDENTITY, - filter_map_span.with_hi(expr.span.hi()), - message, - "try", - "flatten()".to_string(), - Applicability::MachineApplicable, - ); - }; - - if_chain! { - if let hir::ExprKind::Closure(_, _, body_id, _, _) = filter_map_arg.kind; - let body = cx.tcx.hir().body(body_id); - - if let hir::PatKind::Binding(_, binding_id, ..) = body.params[0].pat.kind; - if path_to_local_id(&body.value, binding_id); - then { - apply_lint("called `filter_map(|x| x)` on an `Iterator`"); - } - } - - if is_expr_path_def_path(cx, filter_map_arg, &paths::CONVERT_IDENTITY) { - apply_lint("called `filter_map(std::convert::identity)` on an `Iterator`"); - } + if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, filter_map_arg) { + span_lint_and_sugg( + cx, + FILTER_MAP_IDENTITY, + filter_map_span.with_hi(expr.span.hi()), + "use of `filter_map` with an identity function", + "try", + "flatten()".to_string(), + Applicability::MachineApplicable, + ); } } diff --git a/tests/ui/filter_map_identity.fixed b/tests/ui/filter_map_identity.fixed index 23ce28d8e..a5860aa49 100644 --- a/tests/ui/filter_map_identity.fixed +++ b/tests/ui/filter_map_identity.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused_imports)] +#![allow(unused_imports, clippy::needless_return)] #![warn(clippy::filter_map_identity)] fn main() { @@ -13,4 +13,7 @@ fn main() { use std::convert::identity; let iterator = vec![Some(1), None, Some(2)].into_iter(); let _ = iterator.flatten(); + + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.flatten(); } diff --git a/tests/ui/filter_map_identity.rs b/tests/ui/filter_map_identity.rs index e698df13e..7e998b9cd 100644 --- a/tests/ui/filter_map_identity.rs +++ b/tests/ui/filter_map_identity.rs @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused_imports)] +#![allow(unused_imports, clippy::needless_return)] #![warn(clippy::filter_map_identity)] fn main() { @@ -13,4 +13,7 @@ fn main() { use std::convert::identity; let iterator = vec![Some(1), None, Some(2)].into_iter(); let _ = iterator.filter_map(identity); + + let iterator = vec![Some(1), None, Some(2)].into_iter(); + let _ = iterator.filter_map(|x| return x); } diff --git a/tests/ui/filter_map_identity.stderr b/tests/ui/filter_map_identity.stderr index 596a63206..43c9fdca4 100644 --- a/tests/ui/filter_map_identity.stderr +++ b/tests/ui/filter_map_identity.stderr @@ -1,4 +1,4 @@ -error: called `filter_map(|x| x)` on an `Iterator` +error: use of `filter_map` with an identity function --> $DIR/filter_map_identity.rs:8:22 | LL | let _ = iterator.filter_map(|x| x); @@ -6,17 +6,23 @@ LL | let _ = iterator.filter_map(|x| x); | = note: `-D clippy::filter-map-identity` implied by `-D warnings` -error: called `filter_map(std::convert::identity)` on an `Iterator` +error: use of `filter_map` with an identity function --> $DIR/filter_map_identity.rs:11:22 | LL | let _ = iterator.filter_map(std::convert::identity); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` -error: called `filter_map(std::convert::identity)` on an `Iterator` +error: use of `filter_map` with an identity function --> $DIR/filter_map_identity.rs:15:22 | LL | let _ = iterator.filter_map(identity); | ^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` -error: aborting due to 3 previous errors +error: use of `filter_map` with an identity function + --> $DIR/filter_map_identity.rs:18:22 + | +LL | let _ = iterator.filter_map(|x| return x); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 4 previous errors From 5336f88403aae270e1e8bbf0aee31707311c590f Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 8 Jun 2021 00:14:43 +0200 Subject: [PATCH 4/4] Move `map_identity` into the `methods` module --- clippy_lints/src/lib.rs | 8 +-- clippy_lints/src/map_identity.rs | 76 ------------------------ clippy_lints/src/methods/map_identity.rs | 38 ++++++++++++ clippy_lints/src/methods/mod.rs | 26 ++++++++ 4 files changed, 67 insertions(+), 81 deletions(-) delete mode 100644 clippy_lints/src/map_identity.rs create mode 100644 clippy_lints/src/methods/map_identity.rs diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e7dd3952b..c6d9b3786 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -254,7 +254,6 @@ mod manual_strip; mod manual_unwrap_or; mod map_clone; mod map_err_ignore; -mod map_identity; mod map_unit_fn; mod match_on_vec_items; mod matches; @@ -705,7 +704,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: manual_unwrap_or::MANUAL_UNWRAP_OR, map_clone::MAP_CLONE, map_err_ignore::MAP_ERR_IGNORE, - map_identity::MAP_IDENTITY, map_unit_fn::OPTION_MAP_UNIT_FN, map_unit_fn::RESULT_MAP_UNIT_FN, match_on_vec_items::MATCH_ON_VEC_ITEMS, @@ -765,6 +763,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: methods::MANUAL_STR_REPEAT, methods::MAP_COLLECT_RESULT_UNIT, methods::MAP_FLATTEN, + methods::MAP_IDENTITY, methods::MAP_UNWRAP_OR, methods::NEW_RET_NO_SELF, methods::OK_EXPECT, @@ -1260,7 +1259,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(manual_strip::MANUAL_STRIP), LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR), LintId::of(map_clone::MAP_CLONE), - LintId::of(map_identity::MAP_IDENTITY), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH), @@ -1301,6 +1299,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), + LintId::of(methods::MAP_IDENTITY), LintId::of(methods::NEW_RET_NO_SELF), LintId::of(methods::OK_EXPECT), LintId::of(methods::OPTION_AS_REF_DEREF), @@ -1586,7 +1585,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(loops::WHILE_LET_LOOP), LintId::of(manual_strip::MANUAL_STRIP), LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR), - LintId::of(map_identity::MAP_IDENTITY), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), LintId::of(matches::MATCH_AS_REF), @@ -1601,6 +1599,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(methods::ITER_COUNT), LintId::of(methods::MANUAL_FILTER_MAP), LintId::of(methods::MANUAL_FIND_MAP), + LintId::of(methods::MAP_IDENTITY), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::SEARCH_IS_SOME), @@ -2039,7 +2038,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: single_char_binding_names_threshold, }); store.register_late_pass(|| box macro_use::MacroUseImports::default()); - store.register_late_pass(|| box map_identity::MapIdentity); store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch); store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive); store.register_late_pass(|| box repeat_once::RepeatOnce); diff --git a/clippy_lints/src/map_identity.rs b/clippy_lints/src/map_identity.rs deleted file mode 100644 index 9bcb010ff..000000000 --- a/clippy_lints/src/map_identity.rs +++ /dev/null @@ -1,76 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_expr_identity_function, is_trait_method}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// **What it does:** Checks for instances of `map(f)` where `f` is the identity function. - /// - /// **Why is this bad?** It can be written more concisely without the call to `map`. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// ```rust - /// let x = [1, 2, 3]; - /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); - /// ``` - /// Use instead: - /// ```rust - /// let x = [1, 2, 3]; - /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); - /// ``` - pub MAP_IDENTITY, - complexity, - "using iterator.map(|x| x)" -} - -declare_lint_pass!(MapIdentity => [MAP_IDENTITY]); - -impl<'tcx> LateLintPass<'tcx> for MapIdentity { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if expr.span.from_expansion() { - return; - } - - if_chain! { - if let Some([caller, func]) = get_map_argument(cx, expr); - if is_expr_identity_function(cx, func); - then { - span_lint_and_sugg( - cx, - MAP_IDENTITY, - expr.span.trim_start(caller.span).unwrap(), - "unnecessary map of the identity function", - "remove the call to `map`", - String::new(), - Applicability::MachineApplicable - ) - } - } - } -} - -/// Returns the arguments passed into map() if the expression is a method call to -/// map(). Otherwise, returns None. -fn get_map_argument<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a [Expr<'a>]> { - if_chain! { - if let ExprKind::MethodCall(method, _, args, _) = expr.kind; - if args.len() == 2 && method.ident.name == sym::map; - let caller_ty = cx.typeck_results().expr_ty(&args[0]); - if is_trait_method(cx, expr, sym::Iterator) - || is_type_diagnostic_item(cx, caller_ty, sym::result_type) - || is_type_diagnostic_item(cx, caller_ty, sym::option_type); - then { - Some(args) - } else { - None - } - } -} diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints/src/methods/map_identity.rs new file mode 100644 index 000000000..538a12566 --- /dev/null +++ b/clippy_lints/src/methods/map_identity.rs @@ -0,0 +1,38 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_expr_identity_function, is_trait_method}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::{source_map::Span, sym}; + +use super::MAP_IDENTITY; + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + caller: &hir::Expr<'_>, + map_arg: &hir::Expr<'_>, + _map_span: Span, +) { + let caller_ty = cx.typeck_results().expr_ty(caller); + + if_chain! { + if is_trait_method(cx, expr, sym::Iterator) + || is_type_diagnostic_item(cx, caller_ty, sym::result_type) + || is_type_diagnostic_item(cx, caller_ty, sym::option_type); + if is_expr_identity_function(cx, map_arg); + if let Some(sugg_span) = expr.span.trim_start(caller.span); + then { + span_lint_and_sugg( + cx, + MAP_IDENTITY, + sugg_span, + "unnecessary map of the identity function", + "remove the call to `map`", + String::new(), + Applicability::MachineApplicable, + ) + } + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c8ae972f1..d4f8cef4f 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -35,6 +35,7 @@ mod manual_saturating_arithmetic; mod manual_str_repeat; mod map_collect_result_unit; mod map_flatten; +mod map_identity; mod map_unwrap_or; mod ok_expect; mod option_as_ref_deref; @@ -1561,6 +1562,29 @@ declare_clippy_lint! { "call to `filter_map` where `flatten` is sufficient" } +declare_clippy_lint! { + /// **What it does:** Checks for instances of `map(f)` where `f` is the identity function. + /// + /// **Why is this bad?** It can be written more concisely without the call to `map`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); + /// ``` + /// Use instead: + /// ```rust + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); + /// ``` + pub MAP_IDENTITY, + complexity, + "using iterator.map(|x| x)" +} + declare_clippy_lint! { /// **What it does:** Checks for the use of `.bytes().nth()`. /// @@ -1728,6 +1752,7 @@ impl_lint_pass!(Methods => [ FILTER_NEXT, SKIP_WHILE_NEXT, FILTER_MAP_IDENTITY, + MAP_IDENTITY, MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, @@ -2058,6 +2083,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio _ => {}, } } + map_identity::check(cx, expr, recv, m_arg, span); }, ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), ("next", []) => {