use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, strip_pat_refs}; use rustc_ast::ast; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::PatKind; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{sym, Span}; use super::UNNECESSARY_FOLD; /// Do we need to suggest turbofish when suggesting a replacement method? /// Changing `fold` to `sum` needs it sometimes when the return type can't be /// inferred. This checks for some common cases where it can be safely omitted fn needs_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { let parent = cx.tcx.hir().get_parent(expr.hir_id); // some common cases where turbofish isn't needed: // - assigned to a local variable with a type annotation if let hir::Node::Local(local) = parent && local.ty.is_some() { return false; } // - part of a function call argument, can be inferred from the function signature (provided that // the parameter is not a generic type parameter) if let hir::Node::Expr(parent_expr) = parent && let hir::ExprKind::Call(recv, args) = parent_expr.kind && let hir::ExprKind::Path(ref qpath) = recv.kind && let Some(fn_def_id) = cx.qpath_res(qpath, recv.hir_id).opt_def_id() && let fn_sig = cx.tcx.fn_sig(fn_def_id).skip_binder().skip_binder() && let Some(arg_pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id) && let Some(ty) = fn_sig.inputs().get(arg_pos) && !matches!(ty.kind(), ty::Param(_)) { return false; } // if it's neither of those, stay on the safe side and suggest turbofish, // even if it could work! true } #[derive(Copy, Clone)] struct Replacement { method_name: &'static str, has_args: bool, has_generic_return: bool, } fn check_fold_with_op( cx: &LateContext<'_>, expr: &hir::Expr<'_>, acc: &hir::Expr<'_>, fold_span: Span, op: hir::BinOpKind, replacement: Replacement, ) { if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind // Extract the body of the closure passed to fold && let closure_body = cx.tcx.hir().body(body) && let closure_expr = peel_blocks(closure_body.value) // Check if the closure body is of the form `acc some_expr(x)` && let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind && bin_op.node == op // Extract the names of the two arguments to the closure && let [param_a, param_b] = closure_body.params && let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind && let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind && path_to_local_id(left_expr, first_arg_id) && (replacement.has_args || path_to_local_id(right_expr, second_arg_id)) { let mut applicability = Applicability::MachineApplicable; let turbofish = if replacement.has_generic_return { format!("::<{}>", cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs()) } else { String::new() }; let sugg = if replacement.has_args { format!( "{method}{turbofish}(|{second_arg_ident}| {r})", method = replacement.method_name, r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), ) } else { format!("{method}{turbofish}()", method = replacement.method_name,) }; span_lint_and_sugg( cx, UNNECESSARY_FOLD, fold_span.with_hi(expr.span.hi()), "this `.fold` can be written more succinctly using another method", "try", sugg, applicability, ); } } pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, init: &hir::Expr<'_>, acc: &hir::Expr<'_>, fold_span: Span, ) { // Check that this is a call to Iterator::fold rather than just some function called fold if !is_trait_method(cx, expr, sym::Iterator) { return; } // Check if the first argument to .fold is a suitable literal if let hir::ExprKind::Lit(lit) = init.kind { match lit.node { ast::LitKind::Bool(false) => { check_fold_with_op( cx, expr, acc, fold_span, hir::BinOpKind::Or, Replacement { has_args: true, has_generic_return: false, method_name: "any", }, ); }, ast::LitKind::Bool(true) => { check_fold_with_op( cx, expr, acc, fold_span, hir::BinOpKind::And, Replacement { has_args: true, has_generic_return: false, method_name: "all", }, ); }, ast::LitKind::Int(Pu128(0), _) => check_fold_with_op( cx, expr, acc, fold_span, hir::BinOpKind::Add, Replacement { has_args: false, has_generic_return: needs_turbofish(cx, expr), method_name: "sum", }, ), ast::LitKind::Int(Pu128(1), _) => { check_fold_with_op( cx, expr, acc, fold_span, hir::BinOpKind::Mul, Replacement { has_args: false, has_generic_return: needs_turbofish(cx, expr), method_name: "product", }, ); }, _ => (), } } }