Skip to content

Commit f81fd90

Browse files
Rollup merge of #119928 - d-sonuga:into-iter-sugg, r=compiler-errors
suggest `into_iter()` when `Iterator` method called on `impl IntoIterator` Fix for issue #117711.
2 parents c9a7db6 + e99766d commit f81fd90

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

+96
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,93 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
109109
self.autoderef(span, ty).any(|(ty, _)| matches!(ty.kind(), ty::Slice(..) | ty::Array(..)))
110110
}
111111

112+
fn impl_into_iterator_should_be_iterator(
113+
&self,
114+
ty: Ty<'tcx>,
115+
span: Span,
116+
unsatisfied_predicates: &Vec<(
117+
ty::Predicate<'_>,
118+
Option<ty::Predicate<'_>>,
119+
Option<ObligationCause<'_>>,
120+
)>,
121+
) -> bool {
122+
fn predicate_bounds_generic_param<'tcx>(
123+
predicate: ty::Predicate<'_>,
124+
generics: &'tcx ty::Generics,
125+
generic_param: &ty::GenericParamDef,
126+
tcx: TyCtxt<'tcx>,
127+
) -> bool {
128+
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) =
129+
predicate.kind().as_ref().skip_binder()
130+
{
131+
let ty::TraitPredicate { trait_ref: ty::TraitRef { args, .. }, .. } = trait_pred;
132+
if args.is_empty() {
133+
return false;
134+
}
135+
let Some(arg_ty) = args[0].as_type() else {
136+
return false;
137+
};
138+
let ty::Param(param) = arg_ty.kind() else {
139+
return false;
140+
};
141+
// Is `generic_param` the same as the arg for this trait predicate?
142+
generic_param.index == generics.type_param(&param, tcx).index
143+
} else {
144+
false
145+
}
146+
}
147+
148+
fn is_iterator_predicate(predicate: ty::Predicate<'_>, tcx: TyCtxt<'_>) -> bool {
149+
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) =
150+
predicate.kind().as_ref().skip_binder()
151+
{
152+
tcx.is_diagnostic_item(sym::Iterator, trait_pred.trait_ref.def_id)
153+
} else {
154+
false
155+
}
156+
}
157+
158+
// Does the `ty` implement `IntoIterator`?
159+
let Some(into_iterator_trait) = self.tcx.get_diagnostic_item(sym::IntoIterator) else {
160+
return false;
161+
};
162+
let trait_ref = ty::TraitRef::new(self.tcx, into_iterator_trait, [ty]);
163+
let cause = ObligationCause::new(span, self.body_id, ObligationCauseCode::MiscObligation);
164+
let obligation = Obligation::new(self.tcx, cause, self.param_env, trait_ref);
165+
if !self.predicate_must_hold_modulo_regions(&obligation) {
166+
return false;
167+
}
168+
169+
match ty.kind() {
170+
ty::Param(param) => {
171+
let generics = self.tcx.generics_of(self.body_id);
172+
let generic_param = generics.type_param(&param, self.tcx);
173+
for unsatisfied in unsatisfied_predicates.iter() {
174+
// The parameter implements `IntoIterator`
175+
// but it has called a method that requires it to implement `Iterator`
176+
if predicate_bounds_generic_param(
177+
unsatisfied.0,
178+
generics,
179+
generic_param,
180+
self.tcx,
181+
) && is_iterator_predicate(unsatisfied.0, self.tcx)
182+
{
183+
return true;
184+
}
185+
}
186+
}
187+
ty::Alias(ty::AliasKind::Opaque, _) => {
188+
for unsatisfied in unsatisfied_predicates.iter() {
189+
if is_iterator_predicate(unsatisfied.0, self.tcx) {
190+
return true;
191+
}
192+
}
193+
}
194+
_ => return false,
195+
}
196+
false
197+
}
198+
112199
#[instrument(level = "debug", skip(self))]
113200
pub fn report_method_error(
114201
&self,
@@ -555,6 +642,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
555642
"`count` is defined on `{iterator_trait}`, which `{rcvr_ty}` does not implement"
556643
));
557644
}
645+
} else if self.impl_into_iterator_should_be_iterator(rcvr_ty, span, unsatisfied_predicates)
646+
{
647+
err.span_label(span, format!("`{rcvr_ty}` is not an iterator"));
648+
err.multipart_suggestion_verbose(
649+
"call `.into_iter()` first",
650+
vec![(span.shrink_to_lo(), format!("into_iter()."))],
651+
Applicability::MaybeIncorrect,
652+
);
653+
return Some(err);
558654
} else if !unsatisfied_predicates.is_empty() && matches!(rcvr_ty.kind(), ty::Param(_)) {
559655
// We special case the situation where we are looking for `_` in
560656
// `<TypeParam as _>::method` because otherwise the machinery will look for blanket
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Tests that the compiler suggests an `into_iter` call when an `Iterator` method
2+
// is called on something that implements `IntoIterator`
3+
4+
fn main() {
5+
let items = items();
6+
let other_items = items.map(|i| i + 1);
7+
//~^ ERROR no method named `map` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
8+
let vec: Vec<i32> = items.collect();
9+
//~^ ERROR no method named `collect` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
10+
}
11+
12+
fn items() -> impl IntoIterator<Item = i32> {
13+
vec![1, 2, 3]
14+
}
15+
16+
fn process(items: impl IntoIterator<Item = String>) -> Vec<String> {
17+
items.collect()
18+
//~^ ERROR no method named `collect` found for type parameter `impl IntoIterator<Item = String>` in the current scope
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error[E0599]: no method named `map` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
2+
--> $DIR/collect-without-into-iter-call.rs:6:29
3+
|
4+
LL | let other_items = items.map(|i| i + 1);
5+
| ^^^ `impl IntoIterator<Item = i32>` is not an iterator
6+
|
7+
help: call `.into_iter()` first
8+
|
9+
LL | let other_items = items.into_iter().map(|i| i + 1);
10+
| ++++++++++++
11+
12+
error[E0599]: no method named `collect` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
13+
--> $DIR/collect-without-into-iter-call.rs:8:31
14+
|
15+
LL | let vec: Vec<i32> = items.collect();
16+
| ^^^^^^^ `impl IntoIterator<Item = i32>` is not an iterator
17+
|
18+
help: call `.into_iter()` first
19+
|
20+
LL | let vec: Vec<i32> = items.into_iter().collect();
21+
| ++++++++++++
22+
23+
error[E0599]: no method named `collect` found for type parameter `impl IntoIterator<Item = String>` in the current scope
24+
--> $DIR/collect-without-into-iter-call.rs:17:11
25+
|
26+
LL | items.collect()
27+
| ^^^^^^^ `impl IntoIterator<Item = String>` is not an iterator
28+
|
29+
help: call `.into_iter()` first
30+
|
31+
LL | items.into_iter().collect()
32+
| ++++++++++++
33+
34+
error: aborting due to 3 previous errors
35+
36+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)