Skip to content

Commit c9bfd20

Browse files
committed
useless_conversion: don't lint if ty param has multiple bounds
1 parent 5818225 commit c9bfd20

File tree

4 files changed

+127
-26
lines changed

4 files changed

+127
-26
lines changed

clippy_lints/src/useless_conversion.rs

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
24
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
35
use clippy_utils::sugg::Sugg;
46
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
5-
use clippy_utils::{get_parent_expr, is_trait_method, is_ty_alias, match_def_path, path_to_local, paths};
7+
use clippy_utils::{
8+
get_parent_expr, is_diag_trait_item, is_trait_method, is_ty_alias, match_def_path, path_to_local, paths,
9+
};
610
use if_chain::if_chain;
711
use rustc_errors::Applicability;
812
use rustc_hir::def_id::DefId;
913
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
1014
use rustc_lint::{LateContext, LateLintPass};
11-
use rustc_middle::ty;
15+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
1216
use rustc_session::{declare_tool_lint, impl_lint_pass};
1317
use rustc_span::{sym, Span};
1418

@@ -59,22 +63,83 @@ impl MethodOrFunction {
5963
}
6064
}
6165

62-
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`
63-
fn into_iter_bound(cx: &LateContext<'_>, fn_did: DefId, into_iter_did: DefId, param_index: u32) -> Option<Span> {
64-
cx.tcx
65-
.predicates_of(fn_did)
66-
.predicates
67-
.iter()
68-
.find_map(|&(ref pred, span)| {
69-
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder()
70-
&& tr.def_id() == into_iter_did
71-
&& tr.self_ty().is_param(param_index)
66+
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`,
67+
/// iff the `IntoIterator` bound is the only bound on the type parameter.
68+
///
69+
/// This last part is important because it might be that the type the user is calling `.into_iter()`
70+
/// on might not satisfy those other bounds and would result in compile errors:
71+
/// ```ignore
72+
/// pub fn foo<I>(i: I)
73+
/// where I: IntoIterator<Item=i32> + ExactSizeIterator
74+
/// ^^^^^^^^^^^^^^^^^ this extra bound stops us from suggesting to remove `.into_iter()` ...
75+
/// {
76+
/// assert_eq!(i.len(), 3);
77+
/// }
78+
///
79+
/// pub fn bar() {
80+
/// foo([1, 2, 3].into_iter());
81+
/// ^^^^^^^^^^^^ ... here, because `[i32; 3]` is not `ExactSizeIterator`
82+
/// }
83+
/// ```
84+
fn exclusive_into_iter_bound(
85+
cx: &LateContext<'_>,
86+
fn_did: DefId,
87+
into_iter_did: DefId,
88+
param_index: u32,
89+
) -> Option<Span> {
90+
#[derive(Clone)]
91+
struct ExplicitlyUsedTyParam<'a, 'tcx> {
92+
cx: &'a LateContext<'tcx>,
93+
param_index: u32,
94+
}
95+
96+
impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for ExplicitlyUsedTyParam<'a, 'tcx> {
97+
type BreakTy = ();
98+
fn visit_predicate(&mut self, p: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
99+
// Ignore implicit `T: Sized` bound
100+
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(tr)) = p.kind().skip_binder()
101+
&& let Some(sized_trait_did) = self.cx.tcx.lang_items().sized_trait()
102+
&& sized_trait_did == tr.def_id()
72103
{
73-
Some(span)
104+
return ControlFlow::Continue(());
105+
}
106+
107+
// Ignore `<T as IntoIterator>::Item` projection, this use of the ty param specifically is fine
108+
// because it's what we're already looking for
109+
if let ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj)) = p.kind().skip_binder()
110+
&& is_diag_trait_item(self.cx,proj.projection_ty.def_id, sym::IntoIterator)
111+
{
112+
return ControlFlow::Continue(());
113+
}
114+
115+
p.super_visit_with(self)
116+
}
117+
118+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
119+
if t.is_param(self.param_index) {
120+
ControlFlow::Break(())
74121
} else {
75-
None
122+
ControlFlow::Continue(())
76123
}
77-
})
124+
}
125+
}
126+
127+
let mut into_iter_span = None;
128+
129+
for (pred, span) in cx.tcx.explicit_predicates_of(fn_did).predicates {
130+
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder()
131+
&& tr.def_id() == into_iter_did
132+
&& tr.self_ty().is_param(param_index)
133+
{
134+
into_iter_span = Some(*span);
135+
} else if pred.visit_with(&mut ExplicitlyUsedTyParam { cx, param_index }).is_break() {
136+
// Found another reference of the type parameter; conservatively assume
137+
// that we can't remove the bound.
138+
return None;
139+
}
140+
}
141+
142+
into_iter_span
78143
}
79144

80145
/// Extracts the receiver of a `.into_iter()` method call.
@@ -167,7 +232,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
167232
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id)
168233
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
169234
&& let ty::Param(param) = into_iter_param.kind()
170-
&& let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
235+
&& let Some(span) = exclusive_into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
171236
{
172237
// Get the "innermost" `.into_iter()` call, e.g. given this expression:
173238
// `foo.into_iter().into_iter()`

tests/ui/useless_conversion.fixed

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ fn main() {
153153
let _ = s3;
154154
let s4: Foo<'a'> = Foo;
155155
let _ = vec![s4, s4, s4].into_iter();
156+
157+
issue11300::bar();
156158
}
157159

158160
#[allow(dead_code)]
@@ -184,6 +186,22 @@ fn explicit_into_iter_fn_arg() {
184186
b(macro_generated!());
185187
}
186188

189+
mod issue11300 {
190+
pub fn foo<I>(i: I)
191+
where
192+
I: IntoIterator<Item = i32> + ExactSizeIterator,
193+
{
194+
assert_eq!(i.len(), 3);
195+
}
196+
197+
pub fn bar() {
198+
// This should not trigger the lint:
199+
// `[i32, 3]` does not satisfy the `ExactSizeIterator` bound, so the into_iter call cannot be
200+
// removed and is not useless.
201+
foo([1, 2, 3].into_iter());
202+
}
203+
}
204+
187205
#[derive(Copy, Clone)]
188206
struct Foo<const C: char>;
189207

tests/ui/useless_conversion.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ fn main() {
153153
let _ = Foo::<'a'>::from(s3);
154154
let s4: Foo<'a'> = Foo;
155155
let _ = vec![s4, s4, s4].into_iter().into_iter();
156+
157+
issue11300::bar();
156158
}
157159

158160
#[allow(dead_code)]
@@ -184,6 +186,22 @@ fn explicit_into_iter_fn_arg() {
184186
b(macro_generated!());
185187
}
186188

189+
mod issue11300 {
190+
pub fn foo<I>(i: I)
191+
where
192+
I: IntoIterator<Item = i32> + ExactSizeIterator,
193+
{
194+
assert_eq!(i.len(), 3);
195+
}
196+
197+
pub fn bar() {
198+
// This should not trigger the lint:
199+
// `[i32, 3]` does not satisfy the `ExactSizeIterator` bound, so the into_iter call cannot be
200+
// removed and is not useless.
201+
foo([1, 2, 3].into_iter());
202+
}
203+
}
204+
187205
#[derive(Copy, Clone)]
188206
struct Foo<const C: char>;
189207

tests/ui/useless_conversion.stderr

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,61 +119,61 @@ 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:171:7
122+
--> $DIR/useless_conversion.rs:173: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:161:13
128+
--> $DIR/useless_conversion.rs:163: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:172:7
134+
--> $DIR/useless_conversion.rs:174: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:162:18
140+
--> $DIR/useless_conversion.rs:164: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:173:7
146+
--> $DIR/useless_conversion.rs:175: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:165:12
152+
--> $DIR/useless_conversion.rs:167: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:176:7
158+
--> $DIR/useless_conversion.rs:178: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:161:13
164+
--> $DIR/useless_conversion.rs:163: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:177:7
170+
--> $DIR/useless_conversion.rs:179: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:161:13
176+
--> $DIR/useless_conversion.rs:163:13
177177
|
178178
LL | fn b<T: IntoIterator<Item = i32>>(_: T) {}
179179
| ^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)