|
| 1 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 2 | +use clippy_utils::is_path_mutable; |
| 3 | +use clippy_utils::source::snippet_with_applicability; |
| 4 | +use clippy_utils::ty::{deref_chain, get_inherent_method, implements_trait, make_normalized_projection}; |
| 5 | +use rustc_errors::Applicability; |
| 6 | +use rustc_hir::Expr; |
| 7 | +use rustc_lint::LateContext; |
| 8 | +use rustc_middle::ty::{self, Ty}; |
| 9 | +use rustc_span::sym; |
| 10 | + |
| 11 | +use super::{UNNECESSARY_COLLECTION_CLONE, method_call}; |
| 12 | + |
| 13 | +// FIXME: This does not check if the iter method is actually compatible with the replacement, but |
| 14 | +// you have to be actively evil to have an `IntoIterator` impl that returns one type and an `iter` |
| 15 | +// method that returns something other than references of that type.... and it is a massive |
| 16 | +// complicated hassle to check this |
| 17 | +fn has_iter_method<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { |
| 18 | + deref_chain(cx, ty).any(|ty| match ty.kind() { |
| 19 | + ty::Adt(adt_def, _) => get_inherent_method(cx, adt_def.did(), sym::iter).is_some(), |
| 20 | + ty::Slice(_) => true, |
| 21 | + _ => false, |
| 22 | + }) |
| 23 | +} |
| 24 | + |
| 25 | +/// Check for `x.clone().into_iter()` to suggest `x.iter().cloned()`. |
| 26 | +// ^^^^^^^^^ is recv |
| 27 | +// ^^^^^^^^^^^^^^^^^^^^^ is expr |
| 28 | +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) { |
| 29 | + let typeck_results = cx.typeck_results(); |
| 30 | + let diagnostic_items = cx.tcx.all_diagnostic_items(()); |
| 31 | + |
| 32 | + // If the call before `into_iter` is `.clone()` |
| 33 | + if let Some(("clone", collection_expr, [], _, _)) = method_call(recv) |
| 34 | + // and the binding being cloned is not mutable |
| 35 | + && let Some(false) = is_path_mutable(cx, collection_expr) |
| 36 | + // and the result of `into_iter` is an Iterator |
| 37 | + && let Some(&iterator_def_id) = diagnostic_items.name_to_id.get(&sym::Iterator) |
| 38 | + && let expr_ty = typeck_results.expr_ty(expr) |
| 39 | + && implements_trait(cx, expr_ty, iterator_def_id, &[]) |
| 40 | + // with an item that implements clone |
| 41 | + && let Some(&clone_def_id) = diagnostic_items.name_to_id.get(&sym::Clone) |
| 42 | + && let Some(item_ty) = make_normalized_projection(cx.tcx, cx.param_env, iterator_def_id, sym::Item, [expr_ty]) |
| 43 | + && implements_trait(cx, item_ty, clone_def_id, &[]) |
| 44 | + // and the type has an `iter` method |
| 45 | + && has_iter_method(cx, typeck_results.expr_ty(collection_expr)) |
| 46 | + { |
| 47 | + let mut applicability = Applicability::MachineApplicable; |
| 48 | + let collection_expr_snippet = snippet_with_applicability(cx, collection_expr.span, "...", &mut applicability); |
| 49 | + span_lint_and_sugg( |
| 50 | + cx, |
| 51 | + UNNECESSARY_COLLECTION_CLONE, |
| 52 | + expr.span, |
| 53 | + "using clone on collection to own iterated items", |
| 54 | + "replace with", |
| 55 | + format!("{collection_expr_snippet}.iter().cloned()"), |
| 56 | + applicability, |
| 57 | + ); |
| 58 | + } |
| 59 | +} |
0 commit comments