|
1 | 1 | use super::UNUSED_ENUMERATE_INDEX; |
2 | | -use clippy_utils::diagnostics::span_lint_and_then; |
3 | | -use clippy_utils::res::MaybeDef; |
4 | | -use clippy_utils::source::snippet; |
5 | | -use clippy_utils::{pat_is_wild, sugg}; |
| 2 | +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; |
| 3 | +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; |
| 4 | +use clippy_utils::source::{SpanRangeExt, snippet}; |
| 5 | +use clippy_utils::{expr_or_init, pat_is_wild, sugg}; |
6 | 6 | use rustc_errors::Applicability; |
7 | 7 | use rustc_hir::def::DefKind; |
8 | | -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; |
| 8 | +use rustc_hir::{Expr, ExprKind, FnDecl, Pat, PatKind, TyKind}; |
9 | 9 | use rustc_lint::LateContext; |
10 | | -use rustc_span::sym; |
| 10 | +use rustc_span::{Span, sym}; |
11 | 11 |
|
12 | 12 | /// Checks for the `UNUSED_ENUMERATE_INDEX` lint. |
13 | 13 | /// |
14 | 14 | /// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. |
15 | | -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { |
| 15 | +pub(super) fn check_loop<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { |
16 | 16 | if let PatKind::Tuple([index, elem], _) = pat.kind |
17 | 17 | && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind |
18 | 18 | && let ty = cx.typeck_results().expr_ty(arg) |
@@ -40,3 +40,131 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_ |
40 | 40 | ); |
41 | 41 | } |
42 | 42 | } |
| 43 | + |
| 44 | +/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. |
| 45 | +/// |
| 46 | +/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is |
| 47 | +/// checked: |
| 48 | +/// ```ignore |
| 49 | +/// for (_, x) in some_iter.enumerate() { |
| 50 | +/// // Index is ignored |
| 51 | +/// } |
| 52 | +/// ``` |
| 53 | +/// |
| 54 | +/// This `check` function checks for chained method calls constructs where we can detect that the |
| 55 | +/// index is unused. Currently, this checks only for the following patterns: |
| 56 | +/// ```ignore |
| 57 | +/// some_iter.enumerate().map_function(|(_, x)| ..) |
| 58 | +/// let x = some_iter.enumerate(); |
| 59 | +/// x.map_function(|(_, x)| ..) |
| 60 | +/// ``` |
| 61 | +/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or |
| 62 | +/// `map`. |
| 63 | +/// |
| 64 | +/// # Preconditions |
| 65 | +/// This function must be called not on the `enumerate` call expression itself, but on any of the |
| 66 | +/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and |
| 67 | +/// that the method call is one of the `std::iter::Iterator` trait. |
| 68 | +/// |
| 69 | +/// * `call_expr`: The map function call expression |
| 70 | +/// * `recv`: The receiver of the call |
| 71 | +/// * `closure_arg`: The argument to the map function call containing the closure/function to apply |
| 72 | +pub(super) fn check_method(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { |
| 73 | + let recv_ty = cx.typeck_results().expr_ty(recv); |
| 74 | + // If we call a method on a `std::iter::Enumerate` instance |
| 75 | + if recv_ty.is_diag_item(cx, sym::Enumerate) |
| 76 | + // If we are calling a method of the `Iterator` trait |
| 77 | + && cx.ty_based_def(call_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) |
| 78 | + // And the map argument is a closure |
| 79 | + && let ExprKind::Closure(closure) = closure_arg.kind |
| 80 | + && let closure_body = cx.tcx.hir_body(closure.body) |
| 81 | + // And that closure has one argument ... |
| 82 | + && let [closure_param] = closure_body.params |
| 83 | + // .. which is a tuple of 2 elements |
| 84 | + && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind |
| 85 | + // And that the first element (the index) is either `_` or unused in the body |
| 86 | + && pat_is_wild(cx, &index.kind, closure_body) |
| 87 | + // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the |
| 88 | + // first example below, `expr_or_init` would return `recv`. |
| 89 | + // ``` |
| 90 | + // iter.enumerate().map(|(_, x)| x) |
| 91 | + // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` |
| 92 | + // |
| 93 | + // let binding = iter.enumerate(); |
| 94 | + // ^^^^^^^^^^^^^^^^ `recv_init_expr` |
| 95 | + // binding.map(|(_, x)| x) |
| 96 | + // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` |
| 97 | + // ``` |
| 98 | + && let recv_init_expr = expr_or_init(cx, recv) |
| 99 | + // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something |
| 100 | + // that we cannot control. |
| 101 | + // This would for instance happen with: |
| 102 | + // ``` |
| 103 | + // external_lib::some_function_returning_enumerate().map(|(_, x)| x) |
| 104 | + // ``` |
| 105 | + && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind |
| 106 | + && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) |
| 107 | + // Make sure the method call is `std::iter::Iterator::enumerate`. |
| 108 | + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) |
| 109 | + { |
| 110 | + // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element |
| 111 | + // that would be explicitly in the closure. |
| 112 | + let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { |
| 113 | + // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. |
| 114 | + // Fallback to `..` if we fail getting either snippet. |
| 115 | + Some(ty_span) => elem |
| 116 | + .span |
| 117 | + .get_source_text(cx) |
| 118 | + .and_then(|binding_name| { |
| 119 | + ty_span |
| 120 | + .get_source_text(cx) |
| 121 | + .map(|ty_name| format!("{binding_name}: {ty_name}")) |
| 122 | + }) |
| 123 | + .unwrap_or_else(|| "..".to_string()), |
| 124 | + // Otherwise, we have no explicit type. We can replace with the binding name of the element. |
| 125 | + None => snippet(cx, elem.span, "..").into_owned(), |
| 126 | + }; |
| 127 | + |
| 128 | + // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we |
| 129 | + // can get from the `MethodCall`. |
| 130 | + span_lint_hir_and_then( |
| 131 | + cx, |
| 132 | + UNUSED_ENUMERATE_INDEX, |
| 133 | + recv_init_expr.hir_id, |
| 134 | + enumerate_span, |
| 135 | + "you seem to use `.enumerate()` and immediately discard the index", |
| 136 | + |diag| { |
| 137 | + diag.multipart_suggestion( |
| 138 | + "remove the `.enumerate()` call", |
| 139 | + vec![ |
| 140 | + (closure_param.span, new_closure_param), |
| 141 | + ( |
| 142 | + enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), |
| 143 | + String::new(), |
| 144 | + ), |
| 145 | + ], |
| 146 | + Applicability::MachineApplicable, |
| 147 | + ); |
| 148 | + }, |
| 149 | + ); |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +/// Find the span of the explicit type of the element. |
| 154 | +/// |
| 155 | +/// # Returns |
| 156 | +/// If the tuple argument: |
| 157 | +/// * Has no explicit type, returns `None` |
| 158 | +/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` |
| 159 | +/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for |
| 160 | +/// the element type. |
| 161 | +fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option<Span> { |
| 162 | + if let [tuple_ty] = fn_decl.inputs |
| 163 | + && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind |
| 164 | + && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) |
| 165 | + { |
| 166 | + Some(elem_ty.span) |
| 167 | + } else { |
| 168 | + None |
| 169 | + } |
| 170 | +} |
0 commit comments