|
| 1 | +use super::UNUSED_ENUMERATE_VALUE; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 3 | +use clippy_utils::source::snippet; |
| 4 | +use clippy_utils::sugg::Sugg; |
| 5 | +use clippy_utils::ty::{get_adt_inherent_method, implements_trait}; |
| 6 | +use clippy_utils::{get_trait_def_id, pat_is_wild, paths}; |
| 7 | +use rustc_errors::Applicability; |
| 8 | +use rustc_hir::def::DefKind; |
| 9 | +use rustc_hir::{Expr, ExprKind, Pat, PatKind}; |
| 10 | +use rustc_lint::LateContext; |
| 11 | +use rustc_middle::ty::{self, Ty}; |
| 12 | +use rustc_span::sym; |
| 13 | + |
| 14 | +/// Checks for the `UNUSED_ENUMERATE_VALUE` lint. |
| 15 | +/// |
| 16 | +/// TODO: Extend this lint to cover iterator chains. |
| 17 | +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'tcx>) { |
| 18 | + if let PatKind::Tuple([index, elem], _) = pat.kind |
| 19 | + && let ExprKind::MethodCall(_method, recv, [], _) = arg.kind |
| 20 | + && pat_is_wild(cx, &elem.kind, body) |
| 21 | + && let arg_ty = cx.typeck_results().expr_ty(arg) |
| 22 | + && let ty::Adt(base, _) = *arg_ty.kind() |
| 23 | + && cx.tcx.is_diagnostic_item(sym::Enumerate, base.did()) |
| 24 | + && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) |
| 25 | + && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) |
| 26 | + && let receiver_ty = cx.typeck_results().expr_ty(recv) |
| 27 | + // TODO: Replace with `sym` when it's available |
| 28 | + && let Some(exact_size_iter) = get_trait_def_id(cx.tcx, &paths::ITER_EXACT_SIZE_ITERATOR) |
| 29 | + && implements_trait(cx, receiver_ty, exact_size_iter, &[]) |
| 30 | + { |
| 31 | + let (recv, applicability) = remove_trailing_iter(cx, recv); |
| 32 | + span_lint_and_then( |
| 33 | + cx, |
| 34 | + UNUSED_ENUMERATE_VALUE, |
| 35 | + arg.span, |
| 36 | + "you seem to use `.enumerate()` and immediately discard the value", |
| 37 | + |diag| { |
| 38 | + let range_end = Sugg::hir(cx, recv, ".."); |
| 39 | + if applicability != Applicability::MachineApplicable { |
| 40 | + diag.help(format!("consider using `0..{range_end}.len()` instead")); |
| 41 | + return; |
| 42 | + } |
| 43 | + |
| 44 | + diag.multipart_suggestion( |
| 45 | + format!("replace `{}` with `0..{range_end}.len()`", snippet(cx, arg.span, "..")), |
| 46 | + vec![ |
| 47 | + (pat.span, snippet(cx, index.span, "..").into_owned()), |
| 48 | + (arg.span, format!("0..{range_end}.len()")), |
| 49 | + ], |
| 50 | + applicability, |
| 51 | + ); |
| 52 | + }, |
| 53 | + ); |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +/// Removes trailing `.iter()`, `.iter_mut()`, or `.into_iter()` calls from the given expression if |
| 58 | +/// `len` can be called directly on the receiver. Note that this may be incorrect if the receiver is |
| 59 | +/// a user-defined type whose `len` method has a different meaning than the standard library. |
| 60 | +fn remove_trailing_iter<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> (&'tcx Expr<'tcx>, Applicability) { |
| 61 | + let mut app = Applicability::MachineApplicable; |
| 62 | + if let ExprKind::MethodCall(iter_path, iter_recv, _, _) = expr.kind |
| 63 | + && matches!(iter_path.ident.name, sym::iter | sym::iter_mut | sym::into_iter) |
| 64 | + && let iter_recv_ty = cx.typeck_results().expr_ty(iter_recv).peel_refs() |
| 65 | + && let ty_is_builtin_container = is_builtin_container(cx, iter_recv_ty) |
| 66 | + && (ty_is_builtin_container || get_adt_inherent_method(cx, iter_recv_ty, sym::len).is_some()) |
| 67 | + { |
| 68 | + if !ty_is_builtin_container { |
| 69 | + app = Applicability::MaybeIncorrect; |
| 70 | + } |
| 71 | + |
| 72 | + return (iter_recv, app); |
| 73 | + } |
| 74 | + |
| 75 | + (expr, app) |
| 76 | +} |
| 77 | + |
| 78 | +/// Return `true` if the given type is a built-in container type (array, slice, or collection). |
| 79 | +fn is_builtin_container(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { |
| 80 | + ty.is_array() |
| 81 | + || ty.is_slice() |
| 82 | + || ty.is_array_slice() |
| 83 | + || (matches!(*ty.kind(), ty::Adt(iter_base, _) |
| 84 | + if [sym::Vec, sym::VecDeque, sym::LinkedList, sym::BTreeMap, sym::BTreeSet, sym::HashMap, sym::HashSet, sym::BinaryHeap] |
| 85 | + .iter() |
| 86 | + .any(|sym| cx.tcx.is_diagnostic_item(*sym, iter_base.did())))) |
| 87 | +} |
0 commit comments