|
1 | 1 | use super::UNUSED_ENUMERATE_INDEX; |
2 | | -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; |
| 2 | +use clippy_utils::diagnostics::span_lint_hir_and_then; |
3 | 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}; |
| 4 | +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; |
| 5 | +use clippy_utils::{expr_or_init, pat_is_wild}; |
6 | 6 | use rustc_errors::Applicability; |
7 | | -use rustc_hir::def::DefKind; |
8 | | -use rustc_hir::{Expr, ExprKind, FnDecl, Pat, PatKind, TyKind}; |
| 7 | +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; |
9 | 8 | use rustc_lint::LateContext; |
10 | | -use rustc_span::{Span, sym}; |
| 9 | +use rustc_span::{Span, SyntaxContext, sym}; |
11 | 10 |
|
12 | | -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. |
13 | | -/// |
14 | | -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. |
15 | | -pub(super) fn check_loop<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { |
16 | | - if let PatKind::Tuple([index, elem], _) = pat.kind |
17 | | - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind |
18 | | - && let ty = cx.typeck_results().expr_ty(arg) |
19 | | - && pat_is_wild(cx, &index.kind, body) |
20 | | - && ty.is_diag_item(cx, sym::Enumerate) |
21 | | - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) |
22 | | - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) |
| 11 | +pub(super) fn check<'tcx>( |
| 12 | + cx: &LateContext<'tcx>, |
| 13 | + iter_expr: &'tcx Expr<'tcx>, |
| 14 | + pat: &Pat<'tcx>, |
| 15 | + ty_spans: Option<(Span, Span)>, |
| 16 | + body: &'tcx Expr<'tcx>, |
| 17 | +) { |
| 18 | + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind |
| 19 | + && cx.typeck_results().expr_ty(iter_expr).is_diag_item(cx, sym::Enumerate) |
| 20 | + && pat_is_wild(cx, &idx_pat.kind, body) |
| 21 | + && let enumerate_call = expr_or_init(cx, iter_expr) |
| 22 | + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind |
| 23 | + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) |
| 24 | + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) |
| 25 | + && !enumerate_call.span.from_expansion() |
| 26 | + && !pat.span.from_expansion() |
| 27 | + && !idx_pat.span.from_expansion() |
| 28 | + && !inner_pat.span.from_expansion() |
| 29 | + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { |
| 30 | + text.get(..range.start)? |
| 31 | + .ends_with('.') |
| 32 | + .then_some(range.start - 1..range.end) |
| 33 | + }) |
23 | 34 | { |
24 | | - span_lint_and_then( |
25 | | - cx, |
26 | | - UNUSED_ENUMERATE_INDEX, |
27 | | - arg.span, |
28 | | - "you seem to use `.enumerate()` and immediately discard the index", |
29 | | - |diag| { |
30 | | - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); |
31 | | - diag.multipart_suggestion( |
32 | | - "remove the `.enumerate()` call", |
33 | | - vec![ |
34 | | - (pat.span, snippet(cx, elem.span, "..").into_owned()), |
35 | | - (arg.span, base_iter.to_string()), |
36 | | - ], |
37 | | - Applicability::MachineApplicable, |
38 | | - ); |
39 | | - }, |
40 | | - ); |
41 | | - } |
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`. |
| 35 | + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); |
130 | 36 | span_lint_hir_and_then( |
131 | 37 | cx, |
132 | 38 | UNUSED_ENUMERATE_INDEX, |
133 | | - recv_init_expr.hir_id, |
| 39 | + enumerate_call.hir_id, |
134 | 40 | enumerate_span, |
135 | 41 | "you seem to use `.enumerate()` and immediately discard the index", |
136 | 42 | |diag| { |
| 43 | + let mut spans = Vec::with_capacity(5); |
| 44 | + spans.push((enumerate_span, String::new())); |
| 45 | + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); |
| 46 | + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); |
| 47 | + if let Some((outer, inner)) = ty_spans { |
| 48 | + spans.push((outer.with_hi(inner.lo()), String::new())); |
| 49 | + spans.push((outer.with_lo(inner.hi()), String::new())); |
| 50 | + } |
137 | 51 | diag.multipart_suggestion( |
138 | 52 | "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 | | - ], |
| 53 | + spans, |
146 | 54 | Applicability::MachineApplicable, |
147 | 55 | ); |
148 | 56 | }, |
149 | 57 | ); |
150 | 58 | } |
151 | 59 | } |
152 | 60 |
|
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(())) |
| 61 | +pub(super) fn check_method<'tcx>( |
| 62 | + cx: &LateContext<'tcx>, |
| 63 | + e: &'tcx Expr<'tcx>, |
| 64 | + recv: &'tcx Expr<'tcx>, |
| 65 | + arg: &'tcx Expr<'tcx>, |
| 66 | +) { |
| 67 | + if let ExprKind::Closure(closure) = arg.kind |
| 68 | + && let body = cx.tcx.hir_body(closure.body) |
| 69 | + && let [param] = body.params |
| 70 | + && cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator) |
| 71 | + && let [input] = closure.fn_decl.inputs |
| 72 | + && !arg.span.from_expansion() |
| 73 | + && !input.span.from_expansion() |
| 74 | + && !recv.span.from_expansion() |
| 75 | + && !param.span.from_expansion() |
165 | 76 | { |
166 | | - Some(elem_ty.span) |
167 | | - } else { |
168 | | - None |
| 77 | + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { |
| 78 | + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { |
| 79 | + return; |
| 80 | + }; |
| 81 | + Some((input.span, inner)) |
| 82 | + } else { |
| 83 | + None |
| 84 | + }; |
| 85 | + check(cx, recv, param.pat, ty_spans, body.value); |
169 | 86 | } |
170 | 87 | } |
0 commit comments