Skip to content

Commit 8a30f2f

Browse files
committed
Auto merge of #10814 - y21:issue10743, r=llogiq
new lint: `explicit_into_iter_fn_arg` Closes #10743. This adds a lint that looks for `.into_iter()` calls in a call expression to a function that already expects an `IntoIterator`. In those cases, explicitly calling `.into_iter()` is unnecessary. There were a few instances of this in clippy itself so I fixed those as well in this PR. changelog: new lint [`explicit_into_iter_fn_arg`]
2 parents 2490de4 + d816eba commit 8a30f2f

File tree

7 files changed

+286
-71
lines changed

7 files changed

+286
-71
lines changed

clippy_lints/src/booleans.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
440440
diag.span_suggestions(
441441
e.span,
442442
"try",
443-
suggestions.into_iter(),
443+
suggestions,
444444
// nonminimal_bool can produce minimal but
445445
// not human readable expressions (#3141)
446446
Applicability::Unspecified,

clippy_lints/src/loops/manual_memcpy.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub(super) fn check<'tcx>(
5151
iter_b = Some(get_assignment(body));
5252
}
5353

54-
let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter());
54+
let assignments = iter_a.into_iter().flatten().chain(iter_b);
5555

5656
let big_sugg = assignments
5757
// The only statements in the for loops can be indexed assignments from
@@ -402,7 +402,7 @@ fn get_assignments<'a, 'tcx>(
402402
StmtKind::Local(..) | StmtKind::Item(..) => None,
403403
StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e),
404404
})
405-
.chain((*expr).into_iter())
405+
.chain(*expr)
406406
.filter(move |e| {
407407
if let ExprKind::AssignOp(_, place, _) = e.kind {
408408
path_to_local(place).map_or(false, |id| {

clippy_lints/src/useless_conversion.rs

+115-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
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};
22
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};
44
use clippy_utils::sugg::Sugg;
55
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
66
use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, path_to_local, paths};
77
use if_chain::if_chain;
88
use rustc_errors::Applicability;
9+
use rustc_hir::def_id::DefId;
910
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
1011
use rustc_lint::{LateContext, LateLintPass};
1112
use rustc_middle::ty;
1213
use rustc_session::{declare_tool_lint, impl_lint_pass};
13-
use rustc_span::sym;
14+
use rustc_span::{sym, Span};
1415

1516
declare_clippy_lint! {
1617
/// ### What it does
@@ -43,6 +44,65 @@ pub struct UselessConversion {
4344

4445
impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]);
4546

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+
46106
#[expect(clippy::too_many_lines)]
47107
impl<'tcx> LateLintPass<'tcx> for UselessConversion {
48108
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
@@ -82,9 +142,58 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
82142
);
83143
}
84144
}
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) &&
88197
let Node::Pat(pat) = cx.tcx.hir().get(id) &&
89198
let PatKind::Binding(ann, ..) = pat.kind &&
90199
ann != BindingAnnotation::MUT

tests/ui/auxiliary/proc_macro_derive.rs

+49-61
Original file line numberDiff line numberDiff line change
@@ -90,70 +90,58 @@ pub fn extra_lifetime(_input: TokenStream) -> TokenStream {
9090
#[allow(unused)]
9191
#[proc_macro_derive(ArithmeticDerive)]
9292
pub fn arithmetic_derive(_: TokenStream) -> TokenStream {
93-
<TokenStream as FromIterator<TokenTree>>::from_iter(
94-
[
95-
Ident::new("fn", Span::call_site()).into(),
96-
Ident::new("_foo", Span::call_site()).into(),
97-
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
98-
Group::new(
99-
Delimiter::Brace,
100-
<TokenStream as FromIterator<TokenTree>>::from_iter(
101-
[
102-
Ident::new("let", Span::call_site()).into(),
103-
Ident::new("mut", Span::call_site()).into(),
104-
Ident::new("_n", Span::call_site()).into(),
105-
Punct::new('=', Spacing::Alone).into(),
106-
Literal::i32_unsuffixed(9).into(),
107-
Punct::new(';', Spacing::Alone).into(),
108-
Ident::new("_n", Span::call_site()).into(),
109-
Punct::new('=', Spacing::Alone).into(),
110-
Literal::i32_unsuffixed(9).into(),
111-
Punct::new('/', Spacing::Alone).into(),
112-
Literal::i32_unsuffixed(2).into(),
113-
Punct::new(';', Spacing::Alone).into(),
114-
Ident::new("_n", Span::call_site()).into(),
115-
Punct::new('=', Spacing::Alone).into(),
116-
Punct::new('-', Spacing::Alone).into(),
117-
Ident::new("_n", Span::call_site()).into(),
118-
Punct::new(';', Spacing::Alone).into(),
119-
]
120-
.into_iter(),
121-
),
122-
)
123-
.into(),
124-
]
125-
.into_iter(),
126-
)
93+
<TokenStream as FromIterator<TokenTree>>::from_iter([
94+
Ident::new("fn", Span::call_site()).into(),
95+
Ident::new("_foo", Span::call_site()).into(),
96+
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
97+
Group::new(
98+
Delimiter::Brace,
99+
<TokenStream as FromIterator<TokenTree>>::from_iter([
100+
Ident::new("let", Span::call_site()).into(),
101+
Ident::new("mut", Span::call_site()).into(),
102+
Ident::new("_n", Span::call_site()).into(),
103+
Punct::new('=', Spacing::Alone).into(),
104+
Literal::i32_unsuffixed(9).into(),
105+
Punct::new(';', Spacing::Alone).into(),
106+
Ident::new("_n", Span::call_site()).into(),
107+
Punct::new('=', Spacing::Alone).into(),
108+
Literal::i32_unsuffixed(9).into(),
109+
Punct::new('/', Spacing::Alone).into(),
110+
Literal::i32_unsuffixed(2).into(),
111+
Punct::new(';', Spacing::Alone).into(),
112+
Ident::new("_n", Span::call_site()).into(),
113+
Punct::new('=', Spacing::Alone).into(),
114+
Punct::new('-', Spacing::Alone).into(),
115+
Ident::new("_n", Span::call_site()).into(),
116+
Punct::new(';', Spacing::Alone).into(),
117+
]),
118+
)
119+
.into(),
120+
])
127121
}
128122

129123
#[allow(unused)]
130124
#[proc_macro_derive(ShadowDerive)]
131125
pub fn shadow_derive(_: TokenStream) -> TokenStream {
132-
<TokenStream as FromIterator<TokenTree>>::from_iter(
133-
[
134-
Ident::new("fn", Span::call_site()).into(),
135-
Ident::new("_foo", Span::call_site()).into(),
136-
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
137-
Group::new(
138-
Delimiter::Brace,
139-
<TokenStream as FromIterator<TokenTree>>::from_iter(
140-
[
141-
Ident::new("let", Span::call_site()).into(),
142-
Ident::new("_x", Span::call_site()).into(),
143-
Punct::new('=', Spacing::Alone).into(),
144-
Literal::i32_unsuffixed(2).into(),
145-
Punct::new(';', Spacing::Alone).into(),
146-
Ident::new("let", Span::call_site()).into(),
147-
Ident::new("_x", Span::call_site()).into(),
148-
Punct::new('=', Spacing::Alone).into(),
149-
Ident::new("_x", Span::call_site()).into(),
150-
Punct::new(';', Spacing::Alone).into(),
151-
]
152-
.into_iter(),
153-
),
154-
)
155-
.into(),
156-
]
157-
.into_iter(),
158-
)
126+
<TokenStream as FromIterator<TokenTree>>::from_iter([
127+
Ident::new("fn", Span::call_site()).into(),
128+
Ident::new("_foo", Span::call_site()).into(),
129+
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
130+
Group::new(
131+
Delimiter::Brace,
132+
<TokenStream as FromIterator<TokenTree>>::from_iter([
133+
Ident::new("let", Span::call_site()).into(),
134+
Ident::new("_x", Span::call_site()).into(),
135+
Punct::new('=', Spacing::Alone).into(),
136+
Literal::i32_unsuffixed(2).into(),
137+
Punct::new(';', Spacing::Alone).into(),
138+
Ident::new("let", Span::call_site()).into(),
139+
Ident::new("_x", Span::call_site()).into(),
140+
Punct::new('=', Spacing::Alone).into(),
141+
Ident::new("_x", Span::call_site()).into(),
142+
Punct::new(';', Spacing::Alone).into(),
143+
]),
144+
)
145+
.into(),
146+
])
159147
}

tests/ui/useless_conversion.fixed

+29
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,35 @@ fn main() {
155155
let _ = vec![s4, s4, s4].into_iter();
156156
}
157157

158+
#[allow(dead_code)]
159+
fn explicit_into_iter_fn_arg() {
160+
fn a<T>(_: T) {}
161+
fn b<T: IntoIterator<Item = i32>>(_: T) {}
162+
fn c(_: impl IntoIterator<Item = i32>) {}
163+
fn d<T>(_: T)
164+
where
165+
T: IntoIterator<Item = i32>,
166+
{
167+
}
168+
fn f(_: std::vec::IntoIter<i32>) {}
169+
170+
a(vec![1, 2].into_iter());
171+
b(vec![1, 2]);
172+
c(vec![1, 2]);
173+
d(vec![1, 2]);
174+
b([&1, &2, &3].into_iter().cloned());
175+
176+
b(vec![1, 2]);
177+
b(vec![1, 2]);
178+
179+
macro_rules! macro_generated {
180+
() => {
181+
vec![1, 2].into_iter()
182+
};
183+
}
184+
b(macro_generated!());
185+
}
186+
158187
#[derive(Copy, Clone)]
159188
struct Foo<const C: char>;
160189

tests/ui/useless_conversion.rs

+29
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,35 @@ fn main() {
155155
let _ = vec![s4, s4, s4].into_iter().into_iter();
156156
}
157157

158+
#[allow(dead_code)]
159+
fn explicit_into_iter_fn_arg() {
160+
fn a<T>(_: T) {}
161+
fn b<T: IntoIterator<Item = i32>>(_: T) {}
162+
fn c(_: impl IntoIterator<Item = i32>) {}
163+
fn d<T>(_: T)
164+
where
165+
T: IntoIterator<Item = i32>,
166+
{
167+
}
168+
fn f(_: std::vec::IntoIter<i32>) {}
169+
170+
a(vec![1, 2].into_iter());
171+
b(vec![1, 2].into_iter());
172+
c(vec![1, 2].into_iter());
173+
d(vec![1, 2].into_iter());
174+
b([&1, &2, &3].into_iter().cloned());
175+
176+
b(vec![1, 2].into_iter().into_iter());
177+
b(vec![1, 2].into_iter().into_iter().into_iter());
178+
179+
macro_rules! macro_generated {
180+
() => {
181+
vec![1, 2].into_iter()
182+
};
183+
}
184+
b(macro_generated!());
185+
}
186+
158187
#[derive(Copy, Clone)]
159188
struct Foo<const C: char>;
160189

0 commit comments

Comments
 (0)