Skip to content

Commit 322f121

Browse files
committed
check that the receiver type satisfies bounds
1 parent c9bfd20 commit 322f121

File tree

4 files changed

+100
-63
lines changed

4 files changed

+100
-63
lines changed

clippy_lints/src/useless_conversion.rs

Lines changed: 61 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
use std::ops::ControlFlow;
2-
31
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
42
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
53
use clippy_utils::sugg::Sugg;
64
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
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-
};
5+
use clippy_utils::{get_parent_expr, is_trait_method, is_ty_alias, match_def_path, path_to_local, paths};
106
use if_chain::if_chain;
117
use rustc_errors::Applicability;
128
use rustc_hir::def_id::DefId;
139
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
10+
use rustc_infer::infer::TyCtxtInferExt;
11+
use rustc_infer::traits::Obligation;
1412
use rustc_lint::{LateContext, LateLintPass};
15-
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
13+
use rustc_middle::traits::ObligationCause;
14+
use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty};
1615
use rustc_session::{declare_tool_lint, impl_lint_pass};
1716
use rustc_span::{sym, Span};
17+
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
1818

1919
declare_clippy_lint! {
2020
/// ### What it does
@@ -64,10 +64,7 @@ impl MethodOrFunction {
6464
}
6565

6666
/// 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:
67+
/// iff all of the bounds also hold for the type of the `.into_iter()` receiver.
7168
/// ```ignore
7269
/// pub fn foo<I>(i: I)
7370
/// where I: IntoIterator<Item=i32> + ExactSizeIterator
@@ -81,61 +78,42 @@ impl MethodOrFunction {
8178
/// ^^^^^^^^^^^^ ... here, because `[i32; 3]` is not `ExactSizeIterator`
8279
/// }
8380
/// ```
84-
fn exclusive_into_iter_bound(
85-
cx: &LateContext<'_>,
81+
fn into_iter_bound<'tcx>(
82+
cx: &LateContext<'tcx>,
8683
fn_did: DefId,
8784
into_iter_did: DefId,
85+
into_iter_receiver: Ty<'tcx>,
8886
param_index: u32,
87+
node_args: GenericArgsRef<'tcx>,
8988
) -> 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()
103-
{
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(())
121-
} else {
122-
ControlFlow::Continue(())
123-
}
124-
}
125-
}
126-
89+
let param_env = cx.tcx.param_env(fn_did);
12790
let mut into_iter_span = None;
12891

12992
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;
93+
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() {
94+
if tr.def_id() == into_iter_did && tr.self_ty().is_param(param_index) {
95+
into_iter_span = Some(*span);
96+
} else {
97+
// Substitute generics in the predicate and replace the IntoIterator type parameter with the
98+
// `.into_iter()` receiver to see if the bound also holds for that type.
99+
let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| {
100+
if i == param_index as usize {
101+
GenericArg::from(into_iter_receiver)
102+
} else {
103+
arg
104+
}
105+
}));
106+
let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args);
107+
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), param_env, predicate);
108+
if !cx
109+
.tcx
110+
.infer_ctxt()
111+
.build()
112+
.predicate_must_hold_modulo_regions(&obligation)
113+
{
114+
return None;
115+
}
116+
}
139117
}
140118
}
141119

@@ -217,22 +195,43 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
217195
let parent_fn = match parent.kind {
218196
ExprKind::Call(recv, args) if let ExprKind::Path(ref qpath) = recv.kind => {
219197
cx.qpath_res(qpath, recv.hir_id).opt_def_id()
220-
.map(|did| (did, args, MethodOrFunction::Function))
198+
.map(|did| {
199+
return (
200+
did,
201+
args,
202+
cx.typeck_results().node_args(recv.hir_id),
203+
MethodOrFunction::Function
204+
);
205+
})
221206
}
222207
ExprKind::MethodCall(.., args, _) => {
223208
cx.typeck_results().type_dependent_def_id(parent.hir_id)
224-
.map(|did| (did, args, MethodOrFunction::Method))
209+
.map(|did| {
210+
return (
211+
did,
212+
args,
213+
cx.typeck_results().node_args(recv.hir_id),
214+
MethodOrFunction::Method
215+
);
216+
})
225217
}
226218
_ => None,
227219
};
228220

229-
if let Some((parent_fn_did, args, kind)) = parent_fn
221+
if let Some((parent_fn_did, args, node_args, kind)) = parent_fn
230222
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
231223
&& let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder()
232224
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id)
233225
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
234226
&& let ty::Param(param) = into_iter_param.kind()
235-
&& let Some(span) = exclusive_into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
227+
&& let Some(span) = into_iter_bound(
228+
cx,
229+
parent_fn_did,
230+
into_iter_did,
231+
cx.typeck_results().expr_ty(into_iter_recv),
232+
param.index,
233+
node_args
234+
)
236235
{
237236
// Get the "innermost" `.into_iter()` call, e.g. given this expression:
238237
// `foo.into_iter().into_iter()`

tests/ui/useless_conversion.fixed

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,24 @@ mod issue11300 {
194194
assert_eq!(i.len(), 3);
195195
}
196196

197+
trait Helper {}
198+
impl Helper for [i32; 3] {}
199+
impl Helper for std::array::IntoIter<i32, 3> {}
200+
201+
fn foo2<I>(_: I)
202+
where
203+
I: IntoIterator<Item = i32> + Helper,
204+
{
205+
}
206+
197207
pub fn bar() {
198208
// This should not trigger the lint:
199209
// `[i32, 3]` does not satisfy the `ExactSizeIterator` bound, so the into_iter call cannot be
200210
// removed and is not useless.
201211
foo([1, 2, 3].into_iter());
212+
213+
// This should trigger the lint, receiver type [i32; 3] also implements `Helper`
214+
foo2([1, 2, 3]);
202215
}
203216
}
204217

tests/ui/useless_conversion.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,24 @@ mod issue11300 {
194194
assert_eq!(i.len(), 3);
195195
}
196196

197+
trait Helper {}
198+
impl Helper for [i32; 3] {}
199+
impl Helper for std::array::IntoIter<i32, 3> {}
200+
201+
fn foo2<I>(_: I)
202+
where
203+
I: IntoIterator<Item = i32> + Helper,
204+
{
205+
}
206+
197207
pub fn bar() {
198208
// This should not trigger the lint:
199209
// `[i32, 3]` does not satisfy the `ExactSizeIterator` bound, so the into_iter call cannot be
200210
// removed and is not useless.
201211
foo([1, 2, 3].into_iter());
212+
213+
// This should trigger the lint, receiver type [i32; 3] also implements `Helper`
214+
foo2([1, 2, 3].into_iter());
202215
}
203216
}
204217

tests/ui/useless_conversion.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,5 +178,17 @@ note: this parameter accepts any `IntoIterator`, so you don't need to call `.int
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:214:14
183+
|
184+
LL | foo2([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:203:12
189+
|
190+
LL | I: IntoIterator<Item = i32> + Helper,
191+
| ^^^^^^^^^^^^^^^^^^^^^^^^
192+
193+
error: aborting due to 25 previous errors
182194

0 commit comments

Comments
 (0)