| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; |
| use clippy_utils::source::{SpanRangeExt as _, snippet_with_applicability}; |
| use clippy_utils::{SpanlessEq, get_parent_expr, higher, is_integer_const, sym}; |
| use rustc_errors::Applicability; |
| use rustc_hir::{Expr, ExprKind, Node, Pat, PatKind, QPath}; |
| use rustc_lint::LateContext; |
| |
| use super::RANGE_ZIP_WITH_LEN; |
| |
| pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, zip_arg: &'tcx Expr<'_>) { |
| if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) |
| // range expression in `.zip()` call: `0..x.len()` |
| && let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(cx, zip_arg) |
| && is_integer_const(cx, start, 0) |
| // `.len()` call |
| && let ExprKind::MethodCall(len_path, len_recv, [], _) = end.kind |
| && len_path.ident.name == sym::len |
| // `.iter()` and `.len()` called on same `Path` |
| && let ExprKind::Path(QPath::Resolved(_, iter_path)) = recv.kind |
| && let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind |
| && SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments) |
| { |
| span_lint_and_then( |
| cx, |
| RANGE_ZIP_WITH_LEN, |
| expr.span, |
| "using `.zip()` with a range and `.len()`", |
| |diag| { |
| // If the iterator content is consumed by a pattern with exactly two elements, swap |
| // the order of those elements. Otherwise, the suggestion will be marked as |
| // `Applicability::MaybeIncorrect` (because it will be), and a note will be added |
| // to the diagnostic to underline the swapping of the index and the content. |
| let pat = methods_pattern(cx, expr).or_else(|| for_loop_pattern(cx, expr)); |
| let invert_bindings = if let Some(pat) = pat |
| && pat.span.eq_ctxt(expr.span) |
| && let PatKind::Tuple([first, second], _) = pat.kind |
| { |
| Some((first.span, second.span)) |
| } else { |
| None |
| }; |
| let mut app = Applicability::MachineApplicable; |
| let mut suggestions = vec![( |
| expr.span, |
| format!( |
| "{}.iter().enumerate()", |
| snippet_with_applicability(cx, recv.span, "_", &mut app) |
| ), |
| )]; |
| if let Some((left, right)) = invert_bindings |
| && let Some(snip_left) = left.get_source_text(cx) |
| && let Some(snip_right) = right.get_source_text(cx) |
| { |
| suggestions.extend([(left, snip_right.to_string()), (right, snip_left.to_string())]); |
| } else { |
| app = Applicability::MaybeIncorrect; |
| } |
| diag.multipart_suggestion("use", suggestions, app); |
| if app != Applicability::MachineApplicable { |
| diag.note("the order of the element and the index will be swapped"); |
| } |
| }, |
| ); |
| } |
| } |
| |
| /// If `expr` is the argument of a `for` loop, return the loop pattern. |
| fn for_loop_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> { |
| cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| { |
| if let Node::Expr(ancestor_expr) = node |
| && let Some(for_loop) = higher::ForLoop::hir(ancestor_expr) |
| && for_loop.arg.hir_id == expr.hir_id |
| { |
| Some(for_loop.pat) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| /// If `expr` is the receiver of an `Iterator` method which consumes the iterator elements and feed |
| /// them to a closure, return the pattern of the closure. |
| fn methods_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> { |
| if let Some(parent_expr) = get_parent_expr(cx, expr) |
| && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) |
| && let ExprKind::MethodCall(method, recv, [arg], _) = parent_expr.kind |
| && recv.hir_id == expr.hir_id |
| && matches!( |
| method.ident.name, |
| sym::all |
| | sym::any |
| | sym::filter_map |
| | sym::find_map |
| | sym::flat_map |
| | sym::for_each |
| | sym::is_partitioned |
| | sym::is_sorted_by_key |
| | sym::map |
| | sym::map_while |
| | sym::position |
| | sym::rposition |
| | sym::try_for_each |
| ) |
| && let ExprKind::Closure(closure) = arg.kind |
| && let body = cx.tcx.hir_body(closure.body) |
| && let [param] = body.params |
| { |
| Some(param.pat) |
| } else { |
| None |
| } |
| } |