|
| 1 | +use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_hir_and_then}; |
| 2 | +use clippy_utils::paths::{CORE_ITER_ENUMERATE_METHOD, CORE_ITER_ENUMERATE_STRUCT}; |
| 3 | +use clippy_utils::source::{snippet, snippet_opt}; |
| 4 | +use clippy_utils::{expr_or_init, is_trait_method, match_def_path, pat_is_wild}; |
| 5 | +use rustc_errors::Applicability; |
| 6 | +use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; |
| 7 | +use rustc_lint::LateContext; |
| 8 | +use rustc_middle::ty::AdtDef; |
| 9 | +use rustc_span::{sym, Span}; |
| 10 | + |
| 11 | +use crate::loops::UNUSED_ENUMERATE_INDEX; |
| 12 | + |
| 13 | +/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. |
| 14 | +/// |
| 15 | +/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is |
| 16 | +/// checked: |
| 17 | +/// ```ignore |
| 18 | +/// for (_, x) in some_iter.enumerate() { |
| 19 | +/// // Index is ignored |
| 20 | +/// } |
| 21 | +/// ``` |
| 22 | +/// |
| 23 | +/// This `check` function checks for chained method calls constructs where we can detect that the |
| 24 | +/// index is unused. Currently, this checks only for the following patterns: |
| 25 | +/// ```ignore |
| 26 | +/// some_iter.enumerate().map_function(|(_, x)| ..) |
| 27 | +/// let x = some_iter.enumerate(); |
| 28 | +/// x.map_function(|(_, x)| ..) |
| 29 | +/// ``` |
| 30 | +/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or |
| 31 | +/// `map`. |
| 32 | +/// |
| 33 | +/// # Preconditions |
| 34 | +/// This function must be called not on the `enumerate` call expression itself, but on any of the |
| 35 | +/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and |
| 36 | +/// that the method call is one of the `std::iter::Iterator` trait. |
| 37 | +/// |
| 38 | +/// * `call_expr`: The map function call expression |
| 39 | +/// * `recv`: The receiver of the call |
| 40 | +/// * `closure_arg`: The argument to the map function call containing the closure/function to apply |
| 41 | +pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { |
| 42 | + let recv_ty = cx.typeck_results().expr_ty(recv); |
| 43 | + if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) |
| 44 | + // If we call a method on a `std::iter::Enumerate` instance |
| 45 | + && match_def_path(cx, recv_ty_defid, &CORE_ITER_ENUMERATE_STRUCT) |
| 46 | + // If we are calling a method of the `Iterator` trait |
| 47 | + && is_trait_method(cx, call_expr, sym::Iterator) |
| 48 | + // And the map argument is a closure |
| 49 | + && let ExprKind::Closure(closure) = closure_arg.kind |
| 50 | + && let closure_body = cx.tcx.hir().body(closure.body) |
| 51 | + // And that closure has one argument ... |
| 52 | + && let [closure_param] = closure_body.params |
| 53 | + // .. which is a tuple of 2 elements |
| 54 | + && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind |
| 55 | + // And that the first element (the index) is either `_` or unused in the body |
| 56 | + && pat_is_wild(cx, &index.kind, closure_body) |
| 57 | + // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the |
| 58 | + // first example below, `expr_or_init` would return `recv`. |
| 59 | + // ``` |
| 60 | + // iter.enumerate().map(|(_, x)| x) |
| 61 | + // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` |
| 62 | + // |
| 63 | + // let binding = iter.enumerate(); |
| 64 | + // ^^^^^^^^^^^^^^^^ `recv_init_expr` |
| 65 | + // binding.map(|(_, x)| x) |
| 66 | + // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` |
| 67 | + // ``` |
| 68 | + && let recv_init_expr = expr_or_init(cx, recv) |
| 69 | + // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something |
| 70 | + // that we cannot control. |
| 71 | + // This would for instance happen with: |
| 72 | + // ``` |
| 73 | + // external_lib::some_function_returning_enumerate().map(|(_, x)| x) |
| 74 | + // ``` |
| 75 | + && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind |
| 76 | + && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) |
| 77 | + // Make sure the method call is `std::iter::Iterator::enumerate`. |
| 78 | + && match_def_path(cx, enumerate_defid, &CORE_ITER_ENUMERATE_METHOD) |
| 79 | + { |
| 80 | + // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element |
| 81 | + // that would be explicited in the closure. |
| 82 | + let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { |
| 83 | + // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. |
| 84 | + // Fallback to `..` if we fail getting either snippet. |
| 85 | + Some(ty_span) => snippet_opt(cx, elem.span) |
| 86 | + .and_then(|binding_name| snippet_opt(cx, ty_span).map(|ty_name| format!("{binding_name}: {ty_name}"))) |
| 87 | + .unwrap_or_else(|| "..".to_string()), |
| 88 | + // Otherwise, we have no explicit type. We can replace with the binding name of the element. |
| 89 | + None => snippet(cx, elem.span, "..").into_owned(), |
| 90 | + }; |
| 91 | + |
| 92 | + // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we |
| 93 | + // can get from the `MethodCall`. |
| 94 | + span_lint_hir_and_then( |
| 95 | + cx, |
| 96 | + UNUSED_ENUMERATE_INDEX, |
| 97 | + recv_init_expr.hir_id, |
| 98 | + enumerate_span, |
| 99 | + "you seem to use `.enumerate()` and immediately discard the index", |
| 100 | + |diag| { |
| 101 | + multispan_sugg_with_applicability( |
| 102 | + diag, |
| 103 | + "remove the `.enumerate()` call", |
| 104 | + Applicability::MachineApplicable, |
| 105 | + vec![ |
| 106 | + (closure_param.span, new_closure_param), |
| 107 | + ( |
| 108 | + enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), |
| 109 | + String::new(), |
| 110 | + ), |
| 111 | + ], |
| 112 | + ); |
| 113 | + }, |
| 114 | + ); |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +/// Find the span of the explicit type of the element. |
| 119 | +/// |
| 120 | +/// # Returns |
| 121 | +/// If the tuple argument: |
| 122 | +/// * Has no explicit type, returns `None` |
| 123 | +/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` |
| 124 | +/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for |
| 125 | +/// the element type. |
| 126 | +fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option<Span> { |
| 127 | + if let [tuple_ty] = fn_decl.inputs |
| 128 | + && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind |
| 129 | + && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer) |
| 130 | + { |
| 131 | + Some(elem_ty.span) |
| 132 | + } else { |
| 133 | + None |
| 134 | + } |
| 135 | +} |
0 commit comments