Skip to content

Commit 59636a2

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 daadab5 + 18f3689 commit 59636a2

File tree

4 files changed

+331
-31
lines changed

4 files changed

+331
-31
lines changed

clippy_lints/src/useless_conversion.rs

+90-20
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, TypeVisitableExt};
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,69 @@ 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)
76-
} else {
77-
None
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.self_ty().is_param(param_index) {
97+
if tr.def_id() == into_iter_did {
98+
into_iter_span = Some(*span);
99+
} else {
100+
let tr = cx.tcx.erase_regions(tr);
101+
if tr.has_escaping_bound_vars() {
102+
return None;
103+
}
104+
105+
// Substitute generics in the predicate and replace the IntoIterator type parameter with the
106+
// `.into_iter()` receiver to see if the bound also holds for that type.
107+
let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| {
108+
if i == param_index as usize {
109+
GenericArg::from(into_iter_receiver)
110+
} else {
111+
arg
112+
}
113+
}));
114+
115+
let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args);
116+
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), param_env, predicate);
117+
if !cx
118+
.tcx
119+
.infer_ctxt()
120+
.build()
121+
.predicate_must_hold_modulo_regions(&obligation)
122+
{
123+
return None;
124+
}
125+
}
78126
}
79-
})
127+
}
128+
}
129+
130+
into_iter_span
80131
}
81132

82133
/// Extracts the receiver of a `.into_iter()` method call.
@@ -160,22 +211,41 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
160211
// `fn_sig` does not ICE. (see #11065)
161212
&& cx.tcx.opt_def_kind(did).is_some_and(DefKind::is_fn_like) =>
162213
{
163-
Some((did, args, MethodOrFunction::Function))
214+
Some((
215+
did,
216+
args,
217+
cx.typeck_results().node_args(recv.hir_id),
218+
MethodOrFunction::Function
219+
))
164220
}
165221
ExprKind::MethodCall(.., args, _) => {
166222
cx.typeck_results().type_dependent_def_id(parent.hir_id)
167-
.map(|did| (did, args, MethodOrFunction::Method))
223+
.map(|did| {
224+
return (
225+
did,
226+
args,
227+
cx.typeck_results().node_args(parent.hir_id),
228+
MethodOrFunction::Method
229+
);
230+
})
168231
}
169232
_ => None,
170233
};
171234

172-
if let Some((parent_fn_did, args, kind)) = parent_fn
235+
if let Some((parent_fn_did, args, node_args, kind)) = parent_fn
173236
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
174237
&& let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder()
175238
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id)
176239
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
177240
&& let ty::Param(param) = into_iter_param.kind()
178-
&& let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
241+
&& let Some(span) = into_iter_bound(
242+
cx,
243+
parent_fn_did,
244+
into_iter_did,
245+
cx.typeck_results().expr_ty(into_iter_recv),
246+
param.index,
247+
node_args
248+
)
179249
&& self.expn_depth == 0
180250
{
181251
// Get the "innermost" `.into_iter()` call, e.g. given this expression:

tests/ui/useless_conversion.fixed

+91
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,95 @@ 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+
fn ice() {
249+
struct S1;
250+
impl S1 {
251+
pub fn foo<I: IntoIterator>(&self, _: I) {}
252+
}
253+
254+
S1.foo([1, 2]);
255+
256+
// ICE that occured in itertools
257+
trait Itertools {
258+
fn interleave_shortest<J>(self, other: J)
259+
where
260+
J: IntoIterator,
261+
Self: Sized;
262+
}
263+
impl<I: Iterator> Itertools for I {
264+
fn interleave_shortest<J>(self, other: J)
265+
where
266+
J: IntoIterator,
267+
Self: Sized,
268+
{
269+
}
270+
}
271+
let v0: Vec<i32> = vec![0, 2, 4];
272+
let v1: Vec<i32> = vec![1, 3, 5, 7];
273+
v0.into_iter().interleave_shortest(v1);
274+
275+
trait TraitWithLifetime<'a> {}
276+
impl<'a> TraitWithLifetime<'a> for std::array::IntoIter<&'a i32, 2> {}
277+
278+
struct Helper;
279+
impl<'a> Helper {
280+
fn with_lt<I>(&self, _: I)
281+
where
282+
I: IntoIterator + TraitWithLifetime<'a>,
283+
{
284+
}
285+
}
286+
Helper.with_lt([&1, &2].into_iter());
287+
}
288+
}
289+
199290
#[derive(Copy, Clone)]
200291
struct Foo<const C: char>;
201292

tests/ui/useless_conversion.rs

+91
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,95 @@ 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+
fn ice() {
249+
struct S1;
250+
impl S1 {
251+
pub fn foo<I: IntoIterator>(&self, _: I) {}
252+
}
253+
254+
S1.foo([1, 2].into_iter());
255+
256+
// ICE that occured in itertools
257+
trait Itertools {
258+
fn interleave_shortest<J>(self, other: J)
259+
where
260+
J: IntoIterator,
261+
Self: Sized;
262+
}
263+
impl<I: Iterator> Itertools for I {
264+
fn interleave_shortest<J>(self, other: J)
265+
where
266+
J: IntoIterator,
267+
Self: Sized,
268+
{
269+
}
270+
}
271+
let v0: Vec<i32> = vec![0, 2, 4];
272+
let v1: Vec<i32> = vec![1, 3, 5, 7];
273+
v0.into_iter().interleave_shortest(v1.into_iter());
274+
275+
trait TraitWithLifetime<'a> {}
276+
impl<'a> TraitWithLifetime<'a> for std::array::IntoIter<&'a i32, 2> {}
277+
278+
struct Helper;
279+
impl<'a> Helper {
280+
fn with_lt<I>(&self, _: I)
281+
where
282+
I: IntoIterator + TraitWithLifetime<'a>,
283+
{
284+
}
285+
}
286+
Helper.with_lt([&1, &2].into_iter());
287+
}
288+
}
289+
199290
#[derive(Copy, Clone)]
200291
struct Foo<const C: char>;
201292

0 commit comments

Comments
 (0)