Skip to content

Commit

Permalink
suggest into_iter() when Iterator method called on `impl IntoIter…
Browse files Browse the repository at this point in the history
…ator`
  • Loading branch information
d-sonuga committed Feb 12, 2024
1 parent de4d615 commit 7b4201d
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
114 changes: 114 additions & 0 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,112 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.autoderef(span, ty).any(|(ty, _)| matches!(ty.kind(), ty::Slice(..) | ty::Array(..)))
}

fn impl_into_iterator_should_be_iterator(
&self,
ty: Ty<'tcx>,
unsatisfied_predicates: &Vec<(
ty::Predicate<'_>,
Option<ty::Predicate<'_>>,
Option<ObligationCause<'_>>,
)>,
) -> bool {
fn predicate_bounds_generic_param<'tcx>(
predicate: ty::Predicate<'_>,
generics: &'tcx ty::Generics,
generic_param: &ty::GenericParamDef,
tcx: TyCtxt<'tcx>,
) -> bool {
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) =
predicate.kind().as_ref().skip_binder()
{
let ty::TraitPredicate { trait_ref: ty::TraitRef { args, .. }, .. } = trait_pred;
if args.is_empty() {
return false;
}
let Some(arg_ty) = args[0].as_type() else {
return false;
};
let ty::Param(param) = arg_ty.kind() else {
return false;
};
// Is `generic_param` the same as the arg for this trait predicate?
generic_param.index == generics.type_param(&param, tcx).index
} else {
false
}
}

fn is_into_iterator_predicate(predicate: ty::Predicate<'_>, tcx: TyCtxt<'_>) -> bool {
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) =
predicate.kind().as_ref().skip_binder()
&& let Some(into_iterator_trait) = tcx.get_diagnostic_item(sym::IntoIterator)
{
tcx.def_path_str(trait_pred.trait_ref.def_id)
== tcx.def_path_str(into_iterator_trait)
} else {
false
}
}

fn is_iterator_predicate(predicate: ty::Predicate<'_>, tcx: TyCtxt<'_>) -> bool {
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) =
predicate.kind().as_ref().skip_binder()
&& let Some(iterator_trait) = tcx.get_diagnostic_item(sym::Iterator)
{
tcx.def_path_str(trait_pred.trait_ref.def_id) == tcx.def_path_str(iterator_trait)
} else {
false
}
}

match ty.kind() {
ty::Param(param) => {
let predicates = self.tcx.predicates_of(self.body_id);
let generics = self.tcx.generics_of(self.body_id);
let generic_param = generics.type_param(&param, self.tcx);
for clause in predicates.predicates.iter() {
// Checking to see if this predicate sets an impl `IntoIterator` bound for `ty`
if predicate_bounds_generic_param(
clause.0.as_predicate(),
generics,
generic_param,
self.tcx,
) && is_into_iterator_predicate(clause.0.as_predicate(), self.tcx)
{
for unsatisfied in unsatisfied_predicates.iter() {
// The specified bounds state that the parameter implements `IntoIterator`
// but it has called a method that requires it to implement `Iterator`
if predicate_bounds_generic_param(
unsatisfied.0,
generics,
generic_param,
self.tcx,
) && is_iterator_predicate(unsatisfied.0, self.tcx)
{
return true;
}
}
}
}
}
ty::Alias(ty::AliasKind::Opaque, alias_ty) => {
let item_bounds = self.tcx.explicit_item_bounds(alias_ty.def_id);
let item_bounds = item_bounds.as_ref().skip_binder();
for clause in item_bounds.iter() {
if is_into_iterator_predicate(clause.0.as_predicate(), self.tcx) {
for unsatisfied in unsatisfied_predicates.iter() {
if is_iterator_predicate(unsatisfied.0, self.tcx) {
return true;
}
}
}
}
}
_ => return false,
}
false
}

#[instrument(level = "debug", skip(self))]
pub fn report_method_error(
&self,
Expand Down Expand Up @@ -555,6 +661,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
"`count` is defined on `{iterator_trait}`, which `{rcvr_ty}` does not implement"
));
}
} else if self.impl_into_iterator_should_be_iterator(rcvr_ty, unsatisfied_predicates) {
err.span_label(span, format!("`{rcvr_ty}` is not an iterator"));
err.multipart_suggestion_verbose(
"call `.into_iter()` first",
vec![(span.shrink_to_lo(), format!("into_iter()."))],
Applicability::MaybeIncorrect,
);
return Some(err);
} else if !unsatisfied_predicates.is_empty() && matches!(rcvr_ty.kind(), ty::Param(_)) {
// We special case the situation where we are looking for `_` in
// `<TypeParam as _>::method` because otherwise the machinery will look for blanket
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/did_you_mean/collect-without-into-iter-call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Tests that the compiler suggests an `into_iter` call when an `Iterator` method
// is called on something that implements `IntoIterator`

fn main() {
let items = items();
let other_items = items.map(|i| i + 1);
//~^ ERROR no method named `map` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
let vec: Vec<i32> = items.collect();
//~^ ERROR no method named `collect` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
}

fn items() -> impl IntoIterator<Item = i32> {
vec![1, 2, 3]
}

fn process(items: impl IntoIterator<Item = String>) -> Vec<String> {
items.collect()
//~^ ERROR no method named `collect` found for type parameter `impl IntoIterator<Item = String>` in the current scope
}
38 changes: 38 additions & 0 deletions tests/ui/did_you_mean/collect-without-into-iter-call.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
error[E0599]: no method named `map` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
--> $DIR/collect-without-into-iter-call.rs:6:29
|
LL | let other_items = items.map(|i| i + 1);
| ^^^ `impl IntoIterator<Item = i32>` is not an iterator
|
help: call `.into_iter()` first
|
LL | let other_items = items.into_iter().map(|i| i + 1);
| ++++++++++++

error[E0599]: no method named `collect` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
--> $DIR/collect-without-into-iter-call.rs:8:31
|
LL | let vec: Vec<i32> = items.collect();
| ^^^^^^^ `impl IntoIterator<Item = i32>` is not an iterator
|
help: call `.into_iter()` first
|
LL | let vec: Vec<i32> = items.into_iter().collect();
| ++++++++++++

error[E0599]: no method named `collect` found for type parameter `impl IntoIterator<Item = String>` in the current scope
--> $DIR/collect-without-into-iter-call.rs:17:11
|
LL | fn process(items: impl IntoIterator<Item = String>) -> Vec<String> {
| -------------------------------- method `collect` not found for this type parameter
LL | items.collect()
| ^^^^^^^ `impl IntoIterator<Item = String>` is not an iterator
|
help: call `.into_iter()` first
|
LL | items.into_iter().collect()
| ++++++++++++

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0599`.

0 comments on commit 7b4201d

Please sign in to comment.