|
1 |
| -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; |
| 1 | +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; |
2 | 2 | use clippy_utils::is_ty_alias;
|
3 |
| -use clippy_utils::source::{snippet, snippet_with_context}; |
| 3 | +use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context}; |
4 | 4 | use clippy_utils::sugg::Sugg;
|
5 | 5 | use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
|
6 | 6 | use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, path_to_local, paths};
|
7 | 7 | use if_chain::if_chain;
|
8 | 8 | use rustc_errors::Applicability;
|
| 9 | +use rustc_hir::def_id::DefId; |
9 | 10 | use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
|
10 | 11 | use rustc_lint::{LateContext, LateLintPass};
|
11 | 12 | use rustc_middle::ty;
|
12 | 13 | use rustc_session::{declare_tool_lint, impl_lint_pass};
|
13 |
| -use rustc_span::sym; |
| 14 | +use rustc_span::{sym, Span}; |
14 | 15 |
|
15 | 16 | declare_clippy_lint! {
|
16 | 17 | /// ### What it does
|
@@ -43,6 +44,65 @@ pub struct UselessConversion {
|
43 | 44 |
|
44 | 45 | impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]);
|
45 | 46 |
|
| 47 | +enum MethodOrFunction { |
| 48 | + Method, |
| 49 | + Function, |
| 50 | +} |
| 51 | + |
| 52 | +impl MethodOrFunction { |
| 53 | + /// Maps the argument position in `pos` to the parameter position. |
| 54 | + /// For methods, `self` is skipped. |
| 55 | + fn param_pos(self, pos: usize) -> usize { |
| 56 | + match self { |
| 57 | + MethodOrFunction::Method => pos + 1, |
| 58 | + MethodOrFunction::Function => pos, |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did` |
| 64 | +fn into_iter_bound(cx: &LateContext<'_>, fn_did: DefId, into_iter_did: DefId, param_index: u32) -> Option<Span> { |
| 65 | + cx.tcx |
| 66 | + .predicates_of(fn_did) |
| 67 | + .predicates |
| 68 | + .iter() |
| 69 | + .find_map(|&(ref pred, span)| { |
| 70 | + if let ty::PredicateKind::Clause(ty::Clause::Trait(tr)) = pred.kind().skip_binder() |
| 71 | + && tr.def_id() == into_iter_did |
| 72 | + && tr.self_ty().is_param(param_index) |
| 73 | + { |
| 74 | + Some(span) |
| 75 | + } else { |
| 76 | + None |
| 77 | + } |
| 78 | + }) |
| 79 | +} |
| 80 | + |
| 81 | +/// Extracts the receiver of a `.into_iter()` method call. |
| 82 | +fn into_iter_call<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Option<&'hir Expr<'hir>> { |
| 83 | + if let ExprKind::MethodCall(name, recv, _, _) = expr.kind |
| 84 | + && is_trait_method(cx, expr, sym::IntoIterator) |
| 85 | + && name.ident.name == sym::into_iter |
| 86 | + { |
| 87 | + Some(recv) |
| 88 | + } else { |
| 89 | + None |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +/// Same as [`into_iter_call`], but tries to look for the innermost `.into_iter()` call, e.g.: |
| 94 | +/// `foo.into_iter().into_iter()` |
| 95 | +/// ^^^ we want this expression |
| 96 | +fn into_iter_deep_call<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> Option<&'hir Expr<'hir>> { |
| 97 | + loop { |
| 98 | + if let Some(recv) = into_iter_call(cx, expr) { |
| 99 | + expr = recv; |
| 100 | + } else { |
| 101 | + return Some(expr); |
| 102 | + } |
| 103 | + } |
| 104 | +} |
| 105 | + |
46 | 106 | #[expect(clippy::too_many_lines)]
|
47 | 107 | impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
48 | 108 | fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
@@ -82,9 +142,58 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
82 | 142 | );
|
83 | 143 | }
|
84 | 144 | }
|
85 |
| - if is_trait_method(cx, e, sym::IntoIterator) && name.ident.name == sym::into_iter { |
86 |
| - if get_parent_expr(cx, e).is_some() && |
87 |
| - let Some(id) = path_to_local(recv) && |
| 145 | + if let Some(into_iter_recv) = into_iter_call(cx, e) |
| 146 | + // Make sure that there is no parent expression, or if there is, make sure it's not a `.into_iter()` call. |
| 147 | + // The reason for that is that we only want to lint once (the outermost call) |
| 148 | + // in cases like `foo.into_iter().into_iter()` |
| 149 | + && get_parent_expr(cx, e) |
| 150 | + .and_then(|parent| into_iter_call(cx, parent)) |
| 151 | + .is_none() |
| 152 | + { |
| 153 | + if let Some(parent) = get_parent_expr(cx, e) { |
| 154 | + let parent_fn = match parent.kind { |
| 155 | + ExprKind::Call(recv, args) if let ExprKind::Path(ref qpath) = recv.kind => { |
| 156 | + cx.qpath_res(qpath, recv.hir_id).opt_def_id() |
| 157 | + .map(|did| (did, args, MethodOrFunction::Function)) |
| 158 | + } |
| 159 | + ExprKind::MethodCall(.., args, _) => { |
| 160 | + cx.typeck_results().type_dependent_def_id(parent.hir_id) |
| 161 | + .map(|did| (did, args, MethodOrFunction::Method)) |
| 162 | + } |
| 163 | + _ => None, |
| 164 | + }; |
| 165 | + |
| 166 | + if let Some((parent_fn_did, args, kind)) = parent_fn |
| 167 | + && let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) |
| 168 | + && let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder() |
| 169 | + && let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id) |
| 170 | + && let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos)) |
| 171 | + && let ty::Param(param) = into_iter_param.kind() |
| 172 | + && let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index) |
| 173 | + // Get the "innermost" `.into_iter()` call, e.g. given this expression: |
| 174 | + // `foo.into_iter().into_iter()` |
| 175 | + // ^^^ |
| 176 | + // We want this span |
| 177 | + && let Some(into_iter_recv) = into_iter_deep_call(cx, into_iter_recv) |
| 178 | + { |
| 179 | + let mut applicability = Applicability::MachineApplicable; |
| 180 | + let sugg = snippet_with_applicability(cx, into_iter_recv.span.source_callsite(), "<expr>", &mut applicability).into_owned(); |
| 181 | + span_lint_and_then(cx, USELESS_CONVERSION, e.span, "explicit call to `.into_iter()` in function argument accepting `IntoIterator`", |diag| { |
| 182 | + diag.span_suggestion( |
| 183 | + e.span, |
| 184 | + "consider removing `.into_iter()`", |
| 185 | + sugg, |
| 186 | + applicability, |
| 187 | + ); |
| 188 | + diag.span_note(span, "this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`"); |
| 189 | + }); |
| 190 | + |
| 191 | + // Early return to avoid linting again with contradicting suggestions |
| 192 | + return; |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + if let Some(id) = path_to_local(recv) && |
88 | 197 | let Node::Pat(pat) = cx.tcx.hir().get(id) &&
|
89 | 198 | let PatKind::Binding(ann, ..) = pat.kind &&
|
90 | 199 | ann != BindingAnnotation::MUT
|
|
0 commit comments