blob: 7ece83ba7ca39414522de0491a00dbad708a1ba3 [file] [log] [blame] [edit]
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
}
}