Skip to content

Commit c2070f0

Browse files
committed
Auto merge of #11301 - y21:issue11300, r=dswij
[`useless_conversion`]: don't lint if type parameter has unsatisfiable bounds for `.into_iter()` receiver Fixes #11300. Before this PR, clippy assumed that if it sees a `f(x.into_iter())` call and the type at that argument position is generic over any `IntoIterator`, then the `.into_iter()` call must be useless because `x` already implements `IntoIterator`, *however* this assumption is not right if the generic parameter has more than just the `IntoIterator` bound (because other traits can be implemented for the IntoIterator target type but not the IntoIterator implementor, as can be seen in the linked issue: `<[i32; 3] as IntoIterator>::IntoIter` satisfies `ExactSizeIterator`, but `[i32; 3]` does not). So, this PR makes it check that the type parameter only has a single `IntoIterator` bound. It *might* be possible to check if the type of `x` in `f(x.into_iter())` satisfies all the bounds on the generic type parameter as defined on the function (which would allow removing the `.into_iter()` call even with multiple bounds), however I'm not sure how to do that, and the current fix should always work. **Edit:** This PR has been changed to check if any of the bounds don't hold for the type of the `.into_iter()` receiver, so we can still lint in some cases. changelog: [`useless_conversion`]: don't lint `.into_iter()` if type parameter has multiple bounds
2 parents bcf856b + cf10690 commit c2070f0

File tree

4 files changed

+216
-30
lines changed

4 files changed

+216
-30
lines changed

clippy_lints/src/useless_conversion.rs

+81-19
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ use rustc_errors::Applicability;
88
use rustc_hir::def::DefKind;
99
use rustc_hir::def_id::DefId;
1010
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
11+
use rustc_infer::infer::TyCtxtInferExt;
12+
use rustc_infer::traits::Obligation;
1113
use rustc_lint::{LateContext, LateLintPass};
12-
use rustc_middle::ty;
14+
use rustc_middle::traits::ObligationCause;
15+
use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty};
1316
use rustc_session::{declare_tool_lint, impl_lint_pass};
1417
use rustc_span::{sym, Span};
18+
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
1519

1620
declare_clippy_lint! {
1721
/// ### What it does
@@ -61,22 +65,61 @@ impl MethodOrFunction {
6165
}
6266
}
6367

64-
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`
65-
fn into_iter_bound(cx: &LateContext<'_>, fn_did: DefId, into_iter_did: DefId, param_index: u32) -> Option<Span> {
66-
cx.tcx
67-
.predicates_of(fn_did)
68-
.predicates
69-
.iter()
70-
.find_map(|&(ref pred, span)| {
71-
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder()
72-
&& tr.def_id() == into_iter_did
73-
&& tr.self_ty().is_param(param_index)
74-
{
75-
Some(span)
68+
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`,
69+
/// iff all of the bounds also hold for the type of the `.into_iter()` receiver.
70+
/// ```ignore
71+
/// pub fn foo<I>(i: I)
72+
/// where I: IntoIterator<Item=i32> + ExactSizeIterator
73+
/// ^^^^^^^^^^^^^^^^^ this extra bound stops us from suggesting to remove `.into_iter()` ...
74+
/// {
75+
/// assert_eq!(i.len(), 3);
76+
/// }
77+
///
78+
/// pub fn bar() {
79+
/// foo([1, 2, 3].into_iter());
80+
/// ^^^^^^^^^^^^ ... here, because `[i32; 3]` is not `ExactSizeIterator`
81+
/// }
82+
/// ```
83+
fn into_iter_bound<'tcx>(
84+
cx: &LateContext<'tcx>,
85+
fn_did: DefId,
86+
into_iter_did: DefId,
87+
into_iter_receiver: Ty<'tcx>,
88+
param_index: u32,
89+
node_args: GenericArgsRef<'tcx>,
90+
) -> Option<Span> {
91+
let param_env = cx.tcx.param_env(fn_did);
92+
let mut into_iter_span = None;
93+
94+
for (pred, span) in cx.tcx.explicit_predicates_of(fn_did).predicates {
95+
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() {
96+
if tr.def_id() == into_iter_did && tr.self_ty().is_param(param_index) {
97+
into_iter_span = Some(*span);
7698
} else {
77-
None
99+
// Substitute generics in the predicate and replace the IntoIterator type parameter with the
100+
// `.into_iter()` receiver to see if the bound also holds for that type.
101+
let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| {
102+
if i == param_index as usize {
103+
GenericArg::from(into_iter_receiver)
104+
} else {
105+
arg
106+
}
107+
}));
108+
let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args);
109+
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), param_env, predicate);
110+
if !cx
111+
.tcx
112+
.infer_ctxt()
113+
.build()
114+
.predicate_must_hold_modulo_regions(&obligation)
115+
{
116+
return None;
117+
}
78118
}
79-
})
119+
}
120+
}
121+
122+
into_iter_span
80123
}
81124

82125
/// Extracts the receiver of a `.into_iter()` method call.
@@ -160,22 +203,41 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
160203
// `fn_sig` does not ICE. (see #11065)
161204
&& cx.tcx.opt_def_kind(did).is_some_and(DefKind::is_fn_like) =>
162205
{
163-
Some((did, args, MethodOrFunction::Function))
206+
Some((
207+
did,
208+
args,
209+
cx.typeck_results().node_args(recv.hir_id),
210+
MethodOrFunction::Function
211+
))
164212
}
165213
ExprKind::MethodCall(.., args, _) => {
166214
cx.typeck_results().type_dependent_def_id(parent.hir_id)
167-
.map(|did| (did, args, MethodOrFunction::Method))
215+
.map(|did| {
216+
return (
217+
did,
218+
args,
219+
cx.typeck_results().node_args(recv.hir_id),
220+
MethodOrFunction::Method
221+
);
222+
})
168223
}
169224
_ => None,
170225
};
171226

172-
if let Some((parent_fn_did, args, kind)) = parent_fn
227+
if let Some((parent_fn_did, args, node_args, kind)) = parent_fn
173228
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
174229
&& let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder()
175230
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id)
176231
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
177232
&& let ty::Param(param) = into_iter_param.kind()
178-
&& let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
233+
&& let Some(span) = into_iter_bound(
234+
cx,
235+
parent_fn_did,
236+
into_iter_did,
237+
cx.typeck_results().expr_ty(into_iter_recv),
238+
param.index,
239+
node_args
240+
)
179241
&& self.expn_depth == 0
180242
{
181243
// Get the "innermost" `.into_iter()` call, e.g. given this expression:

tests/ui/useless_conversion.fixed

+50
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ fn main() {
151151
let _ = s3;
152152
let s4: Foo<'a'> = Foo;
153153
let _ = vec![s4, s4, s4].into_iter();
154+
155+
issue11300::bar();
154156
}
155157

156158
#[allow(dead_code)]
@@ -196,6 +198,54 @@ fn explicit_into_iter_fn_arg() {
196198
b(macro_generated!());
197199
}
198200

201+
mod issue11300 {
202+
pub fn foo<I>(i: I)
203+
where
204+
I: IntoIterator<Item = i32> + ExactSizeIterator,
205+
{
206+
assert_eq!(i.len(), 3);
207+
}
208+
209+
trait Helper<T: ?Sized> {}
210+
impl Helper<i32> for [i32; 3] {}
211+
impl Helper<i32> for std::array::IntoIter<i32, 3> {}
212+
impl Helper<()> for std::array::IntoIter<i32, 3> {}
213+
214+
fn foo2<X: ?Sized, I>(_: I)
215+
where
216+
I: IntoIterator<Item = i32> + Helper<X>,
217+
{
218+
}
219+
220+
trait Helper2<T> {}
221+
impl Helper2<std::array::IntoIter<i32, 3>> for i32 {}
222+
impl Helper2<[i32; 3]> for i32 {}
223+
fn foo3<I>(_: I)
224+
where
225+
I: IntoIterator<Item = i32>,
226+
i32: Helper2<I>,
227+
{
228+
}
229+
230+
pub fn bar() {
231+
// This should not trigger the lint:
232+
// `[i32, 3]` does not satisfy the `ExactSizeIterator` bound, so the into_iter call cannot be
233+
// removed and is not useless.
234+
foo([1, 2, 3].into_iter());
235+
236+
// This should trigger the lint, receiver type [i32; 3] also implements `Helper`
237+
foo2::<i32, _>([1, 2, 3]);
238+
239+
// This again should *not* lint, since X = () and I = std::array::IntoIter<i32, 3>,
240+
// and `[i32; 3]: Helper<()>` is not true (only `std::array::IntoIter<i32, 3>: Helper<()>` is).
241+
foo2::<(), _>([1, 2, 3].into_iter());
242+
243+
// This should lint. Removing the `.into_iter()` means that `I` gets substituted with `[i32; 3]`,
244+
// and `i32: Helper2<[i32, 3]>` is true, so this call is indeed unncessary.
245+
foo3([1, 2, 3]);
246+
}
247+
}
248+
199249
#[derive(Copy, Clone)]
200250
struct Foo<const C: char>;
201251

tests/ui/useless_conversion.rs

+50
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ fn main() {
151151
let _ = Foo::<'a'>::from(s3);
152152
let s4: Foo<'a'> = Foo;
153153
let _ = vec![s4, s4, s4].into_iter().into_iter();
154+
155+
issue11300::bar();
154156
}
155157

156158
#[allow(dead_code)]
@@ -196,6 +198,54 @@ fn explicit_into_iter_fn_arg() {
196198
b(macro_generated!());
197199
}
198200

201+
mod issue11300 {
202+
pub fn foo<I>(i: I)
203+
where
204+
I: IntoIterator<Item = i32> + ExactSizeIterator,
205+
{
206+
assert_eq!(i.len(), 3);
207+
}
208+
209+
trait Helper<T: ?Sized> {}
210+
impl Helper<i32> for [i32; 3] {}
211+
impl Helper<i32> for std::array::IntoIter<i32, 3> {}
212+
impl Helper<()> for std::array::IntoIter<i32, 3> {}
213+
214+
fn foo2<X: ?Sized, I>(_: I)
215+
where
216+
I: IntoIterator<Item = i32> + Helper<X>,
217+
{
218+
}
219+
220+
trait Helper2<T> {}
221+
impl Helper2<std::array::IntoIter<i32, 3>> for i32 {}
222+
impl Helper2<[i32; 3]> for i32 {}
223+
fn foo3<I>(_: I)
224+
where
225+
I: IntoIterator<Item = i32>,
226+
i32: Helper2<I>,
227+
{
228+
}
229+
230+
pub fn bar() {
231+
// This should not trigger the lint:
232+
// `[i32, 3]` does not satisfy the `ExactSizeIterator` bound, so the into_iter call cannot be
233+
// removed and is not useless.
234+
foo([1, 2, 3].into_iter());
235+
236+
// This should trigger the lint, receiver type [i32; 3] also implements `Helper`
237+
foo2::<i32, _>([1, 2, 3].into_iter());
238+
239+
// This again should *not* lint, since X = () and I = std::array::IntoIter<i32, 3>,
240+
// and `[i32; 3]: Helper<()>` is not true (only `std::array::IntoIter<i32, 3>: Helper<()>` is).
241+
foo2::<(), _>([1, 2, 3].into_iter());
242+
243+
// This should lint. Removing the `.into_iter()` means that `I` gets substituted with `[i32; 3]`,
244+
// and `i32: Helper2<[i32, 3]>` is true, so this call is indeed unncessary.
245+
foo3([1, 2, 3].into_iter());
246+
}
247+
}
248+
199249
#[derive(Copy, Clone)]
200250
struct Foo<const C: char>;
201251

tests/ui/useless_conversion.stderr

+35-11
Original file line numberDiff line numberDiff line change
@@ -119,64 +119,88 @@ LL | let _ = vec![s4, s4, s4].into_iter().into_iter();
119119
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![s4, s4, s4].into_iter()`
120120

121121
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
122-
--> $DIR/useless_conversion.rs:183:7
122+
--> $DIR/useless_conversion.rs:185:7
123123
|
124124
LL | b(vec![1, 2].into_iter());
125125
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `vec![1, 2]`
126126
|
127127
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
128-
--> $DIR/useless_conversion.rs:173:13
128+
--> $DIR/useless_conversion.rs:175:13
129129
|
130130
LL | fn b<T: IntoIterator<Item = i32>>(_: T) {}
131131
| ^^^^^^^^^^^^^^^^^^^^^^^^
132132

133133
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
134-
--> $DIR/useless_conversion.rs:184:7
134+
--> $DIR/useless_conversion.rs:186:7
135135
|
136136
LL | c(vec![1, 2].into_iter());
137137
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `vec![1, 2]`
138138
|
139139
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
140-
--> $DIR/useless_conversion.rs:174:18
140+
--> $DIR/useless_conversion.rs:176:18
141141
|
142142
LL | fn c(_: impl IntoIterator<Item = i32>) {}
143143
| ^^^^^^^^^^^^^^^^^^^^^^^^
144144

145145
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
146-
--> $DIR/useless_conversion.rs:185:7
146+
--> $DIR/useless_conversion.rs:187:7
147147
|
148148
LL | d(vec![1, 2].into_iter());
149149
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `vec![1, 2]`
150150
|
151151
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
152-
--> $DIR/useless_conversion.rs:177:12
152+
--> $DIR/useless_conversion.rs:179:12
153153
|
154154
LL | T: IntoIterator<Item = i32>,
155155
| ^^^^^^^^^^^^^^^^^^^^^^^^
156156

157157
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
158-
--> $DIR/useless_conversion.rs:188:7
158+
--> $DIR/useless_conversion.rs:190:7
159159
|
160160
LL | b(vec![1, 2].into_iter().into_iter());
161161
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`s: `vec![1, 2]`
162162
|
163163
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
164-
--> $DIR/useless_conversion.rs:173:13
164+
--> $DIR/useless_conversion.rs:175:13
165165
|
166166
LL | fn b<T: IntoIterator<Item = i32>>(_: T) {}
167167
| ^^^^^^^^^^^^^^^^^^^^^^^^
168168

169169
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
170-
--> $DIR/useless_conversion.rs:189:7
170+
--> $DIR/useless_conversion.rs:191:7
171171
|
172172
LL | b(vec![1, 2].into_iter().into_iter().into_iter());
173173
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`s: `vec![1, 2]`
174174
|
175175
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
176-
--> $DIR/useless_conversion.rs:173:13
176+
--> $DIR/useless_conversion.rs:175:13
177177
|
178178
LL | fn b<T: IntoIterator<Item = i32>>(_: T) {}
179179
| ^^^^^^^^^^^^^^^^^^^^^^^^
180180

181-
error: aborting due to 24 previous errors
181+
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
182+
--> $DIR/useless_conversion.rs:237:24
183+
|
184+
LL | foo2::<i32, _>([1, 2, 3].into_iter());
185+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `[1, 2, 3]`
186+
|
187+
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
188+
--> $DIR/useless_conversion.rs:216:12
189+
|
190+
LL | I: IntoIterator<Item = i32> + Helper<X>,
191+
| ^^^^^^^^^^^^^^^^^^^^^^^^
192+
193+
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
194+
--> $DIR/useless_conversion.rs:245:14
195+
|
196+
LL | foo3([1, 2, 3].into_iter());
197+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `[1, 2, 3]`
198+
|
199+
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
200+
--> $DIR/useless_conversion.rs:225:12
201+
|
202+
LL | I: IntoIterator<Item = i32>,
203+
| ^^^^^^^^^^^^^^^^^^^^^^^^
204+
205+
error: aborting due to 26 previous errors
182206

0 commit comments

Comments
 (0)