diff --git a/clippy_lints/src/map_identity.rs b/clippy_lints/src/map_identity.rs index 41cda23510e..9bcb010ff6d 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 769836aaf18..a765abe6b76 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();