Skip to content

Commit 6a76101

Browse files
committed
update docs, simplify arg->param map, dont lint on chain
1 parent 0181d77 commit 6a76101

File tree

4 files changed

+90
-74
lines changed

4 files changed

+90
-74
lines changed

clippy_lints/src/explicit_into_iter_fn_arg.rs

+61-63
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::source::snippet;
2+
use clippy_utils::source::snippet_with_applicability;
33
use clippy_utils::{get_parent_expr, is_trait_method};
44
use rustc_errors::Applicability;
55
use rustc_hir::def_id::DefId;
@@ -16,50 +16,26 @@ declare_clippy_lint! {
1616
/// in a call argument that accepts `IntoIterator`.
1717
///
1818
/// ### Why is this bad?
19-
/// If a function has a generic parameter with an `IntoIterator` trait bound, it means that the function
20-
/// will *have* to call `.into_iter()` to get an iterator out of it, thereby making the call to `.into_iter()`
21-
/// at call site redundant.
22-
///
23-
/// Consider this example:
24-
/// ```rs,ignore
25-
/// fn foo<T: IntoIterator<Item = u32>>(iter: T) {
26-
/// let it = iter.into_iter();
27-
/// ^^^^^^^^^^^^ the function has to call `.into_iter()` to get the iterator
28-
/// }
29-
///
30-
/// foo(vec![1, 2, 3].into_iter());
31-
/// ^^^^^^^^^^^^ ... making this `.into_iter()` call redundant.
32-
/// ```
33-
///
34-
/// The reason for why calling `.into_iter()` twice (once at call site and again inside of the function) works in the first place
35-
/// is because there is a blanket implementation of `IntoIterator` for all types that implement `Iterator` in the standard library,
36-
/// in which it simply returns itself, effectively making the second call to `.into_iter()` a "no-op":
37-
/// ```rust,ignore
38-
/// impl<I: Iterator> IntoIterator for I {
39-
/// type Item = I::Item;
40-
/// type IntoIter = I;
41-
///
42-
/// fn into_iter(self) -> I {
43-
/// self
44-
/// }
45-
/// }
46-
/// ```
19+
/// If a generic parameter has an `IntoIterator` bound, there is no need to call `.into_iter()` at call site.
20+
/// Calling `IntoIterator::into_iter()` on a value implies that its type already implements `IntoIterator`,
21+
/// so you can just pass the value as is.
4722
///
4823
/// ### Example
4924
/// ```rust
50-
/// fn even_sum<I: IntoIterator<Item = u32>>(iter: I) -> u32 {
25+
/// fn even_sum<I: IntoIterator<Item = i32>>(iter: I) -> i32 {
5126
/// iter.into_iter().filter(|&x| x % 2 == 0).sum()
5227
/// }
5328
///
54-
/// let _ = even_sum(vec![1, 2, 3].into_iter());
29+
/// let _ = even_sum([1, 2, 3].into_iter());
30+
/// // ^^^^^^^^^^^^ redundant. `[i32; 3]` implements `IntoIterator`
5531
/// ```
5632
/// Use instead:
5733
/// ```rust
58-
/// fn even_sum<I: IntoIterator<Item = u32>>(iter: I) -> u32 {
34+
/// fn even_sum<I: IntoIterator<Item = i32>>(iter: I) -> i32 {
5935
/// iter.into_iter().filter(|&x| x % 2 == 0).sum()
6036
/// }
6137
///
62-
/// let _ = even_sum(vec![1, 2, 3]);
38+
/// let _ = even_sum([1, 2, 3]);
6339
/// ```
6440
#[clippy::version = "1.71.0"]
6541
pub EXPLICIT_INTO_ITER_FN_ARG,
@@ -73,23 +49,41 @@ enum MethodOrFunction {
7349
Function,
7450
}
7551

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+
7663
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`
77-
fn into_iter_bound(cx: &LateContext<'_>, fn_did: DefId, param_index: u32) -> Option<Span> {
78-
if let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) {
79-
cx.tcx
80-
.predicates_of(fn_did)
81-
.predicates
82-
.iter()
83-
.find_map(|&(ref pred, span)| {
84-
if let ty::PredicateKind::Clause(ty::Clause::Trait(tr)) = pred.kind().skip_binder()
85-
&& tr.def_id() == into_iter_did
86-
&& tr.self_ty().is_param(param_index)
87-
{
88-
Some(span)
89-
} else {
90-
None
91-
}
92-
})
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+
fn into_iter_call<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Option<&'hir Expr<'hir>> {
82+
if let ExprKind::MethodCall(name, recv, _, _) = expr.kind
83+
&& is_trait_method(cx, expr, sym::IntoIterator)
84+
&& name.ident.name == sym::into_iter
85+
{
86+
Some(recv)
9387
} else {
9488
None
9589
}
@@ -101,40 +95,44 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitIntoIterFnArg {
10195
return;
10296
}
10397

104-
if let ExprKind::MethodCall(name, recv, ..) = expr.kind
105-
&& is_trait_method(cx, expr, sym::IntoIterator)
106-
&& name.ident.name == sym::into_iter
107-
&& let Some(parent_expr) = get_parent_expr(cx, expr)
98+
if let Some(recv) = into_iter_call(cx, expr)
99+
&& let Some(parent) = get_parent_expr(cx, expr)
100+
// Make sure that this is not a chained into_iter call (i.e. `x.into_iter().into_iter()`)
101+
// That case is already covered by `useless_conversion` and we don't want to lint twice
102+
// with two contradicting suggestions.
103+
&& into_iter_call(cx, parent).is_none()
104+
&& into_iter_call(cx, recv).is_none()
105+
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
108106
{
109-
let parent_expr = match parent_expr.kind {
107+
108+
let parent = match parent.kind {
110109
ExprKind::Call(recv, args) if let ExprKind::Path(ref qpath) = recv.kind => {
111110
cx.qpath_res(qpath, recv.hir_id).opt_def_id()
112111
.map(|did| (did, args, MethodOrFunction::Function))
113112
}
114113
ExprKind::MethodCall(.., args, _) => {
115-
cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
114+
cx.typeck_results().type_dependent_def_id(parent.hir_id)
116115
.map(|did| (did, args, MethodOrFunction::Method))
117116
}
118117
_ => None,
119118
};
120119

121-
if let Some((parent_fn_did, args, kind)) = parent_expr
120+
if let Some((parent_fn_did, args, kind)) = parent
122121
&& let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder()
123122
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == expr.hir_id)
124-
&& let Some(&into_iter_param) = sig.inputs().get(match kind {
125-
MethodOrFunction::Function => arg_pos,
126-
MethodOrFunction::Method => arg_pos + 1, // skip self arg
127-
})
123+
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
128124
&& let ty::Param(param) = into_iter_param.kind()
129-
&& let Some(span) = into_iter_bound(cx, parent_fn_did, param.index)
125+
&& let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
130126
{
131-
let sugg = snippet(cx, recv.span.source_callsite(), "<expr>").into_owned();
127+
let mut applicability = Applicability::MachineApplicable;
128+
let sugg = snippet_with_applicability(cx, recv.span.source_callsite(), "<expr>", &mut applicability).into_owned();
129+
132130
span_lint_and_then(cx, EXPLICIT_INTO_ITER_FN_ARG, expr.span, "explicit call to `.into_iter()` in function argument accepting `IntoIterator`", |diag| {
133131
diag.span_suggestion(
134132
expr.span,
135133
"consider removing `.into_iter()`",
136134
sugg,
137-
Applicability::MachineApplicable,
135+
applicability,
138136
);
139137
diag.span_note(span, "this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`");
140138
});
+13-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//@run-rustfix
22

3-
#![allow(unused)]
3+
#![allow(unused, clippy::useless_conversion)]
44
#![warn(clippy::explicit_into_iter_fn_arg)]
55

66
fn a<T>(_: T) {}
@@ -11,14 +11,23 @@ where
1111
T: IntoIterator<Item = i32>,
1212
{
1313
}
14-
fn e<T: IntoIterator<Item = i32>>(_: T) {}
1514
fn f(_: std::vec::IntoIter<i32>) {}
1615

1716
fn main() {
1817
a(vec![1, 2].into_iter());
1918
b(vec![1, 2]);
2019
c(vec![1, 2]);
2120
d(vec![1, 2]);
22-
e([&1, &2, &3].into_iter().cloned());
23-
f(vec![1, 2].into_iter());
21+
b([&1, &2, &3].into_iter().cloned());
22+
23+
// Don't lint chained `.into_iter().into_iter()` calls. Covered by useless_conversion.
24+
b(vec![1, 2].into_iter().into_iter());
25+
b(vec![1, 2].into_iter().into_iter().into_iter());
26+
27+
macro_rules! macro_generated {
28+
() => {
29+
vec![1, 2].into_iter()
30+
};
31+
}
32+
b(macro_generated!());
2433
}

tests/ui/explicit_into_iter_fn_arg.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//@run-rustfix
22

3-
#![allow(unused)]
3+
#![allow(unused, clippy::useless_conversion)]
44
#![warn(clippy::explicit_into_iter_fn_arg)]
55

66
fn a<T>(_: T) {}
@@ -11,14 +11,23 @@ where
1111
T: IntoIterator<Item = i32>,
1212
{
1313
}
14-
fn e<T: IntoIterator<Item = i32>>(_: T) {}
1514
fn f(_: std::vec::IntoIter<i32>) {}
1615

1716
fn main() {
1817
a(vec![1, 2].into_iter());
1918
b(vec![1, 2].into_iter());
2019
c(vec![1, 2].into_iter());
2120
d(vec![1, 2].into_iter());
22-
e([&1, &2, &3].into_iter().cloned());
23-
f(vec![1, 2].into_iter());
21+
b([&1, &2, &3].into_iter().cloned());
22+
23+
// Don't lint chained `.into_iter().into_iter()` calls. Covered by useless_conversion.
24+
b(vec![1, 2].into_iter().into_iter());
25+
b(vec![1, 2].into_iter().into_iter().into_iter());
26+
27+
macro_rules! macro_generated {
28+
() => {
29+
vec![1, 2].into_iter()
30+
};
31+
}
32+
b(macro_generated!());
2433
}

tests/ui/explicit_into_iter_fn_arg.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
2-
--> $DIR/explicit_into_iter_fn_arg.rs:19:7
2+
--> $DIR/explicit_into_iter_fn_arg.rs:18:7
33
|
44
LL | b(vec![1, 2].into_iter());
55
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2]`
@@ -12,7 +12,7 @@ LL | fn b<T: IntoIterator<Item = i32>>(_: T) {}
1212
= note: `-D clippy::explicit-into-iter-fn-arg` implied by `-D warnings`
1313

1414
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
15-
--> $DIR/explicit_into_iter_fn_arg.rs:20:7
15+
--> $DIR/explicit_into_iter_fn_arg.rs:19:7
1616
|
1717
LL | c(vec![1, 2].into_iter());
1818
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2]`
@@ -24,7 +24,7 @@ LL | fn c(_: impl IntoIterator<Item = i32>) {}
2424
| ^^^^^^^^^^^^^^^^^^^^^^^^
2525

2626
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
27-
--> $DIR/explicit_into_iter_fn_arg.rs:21:7
27+
--> $DIR/explicit_into_iter_fn_arg.rs:20:7
2828
|
2929
LL | d(vec![1, 2].into_iter());
3030
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2]`

0 commit comments

Comments
 (0)