diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 7fc51e36a2b64..1912e7398b69d 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -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>, + Option>, + )>, + ) -> 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(¶m, 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(¶m, 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, @@ -566,6 +672,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // `TypeParam: Ord` or `TypeParam: Iterator`"". That is done further down when calling // `self.suggest_traits_to_import`, so we ignore the `unsatisfied_predicates` // suggestions. + } 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() { let mut type_params = FxIndexMap::default(); diff --git a/tests/ui/did_you_mean/collect-without-into-iter-call.rs b/tests/ui/did_you_mean/collect-without-into-iter-call.rs new file mode 100644 index 0000000000000..ee4d75615bd01 --- /dev/null +++ b/tests/ui/did_you_mean/collect-without-into-iter-call.rs @@ -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` in the current scope + let vec: Vec = items.collect(); + //~^ ERROR no method named `collect` found for opaque type `impl IntoIterator` in the current scope +} + +fn items() -> impl IntoIterator { + vec![1, 2, 3] +} + +fn process(items: impl IntoIterator) -> Vec { + items.collect() + //~^ ERROR no method named `collect` found for type parameter `impl IntoIterator` in the current scope +} diff --git a/tests/ui/did_you_mean/collect-without-into-iter-call.stderr b/tests/ui/did_you_mean/collect-without-into-iter-call.stderr new file mode 100644 index 0000000000000..30241e00eb3dd --- /dev/null +++ b/tests/ui/did_you_mean/collect-without-into-iter-call.stderr @@ -0,0 +1,38 @@ +error[E0599]: no method named `map` found for opaque type `impl IntoIterator` in the current scope + --> $DIR/collect-without-into-iter-call.rs:6:29 + | +LL | let other_items = items.map(|i| i + 1); + | ^^^ `impl IntoIterator` 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` in the current scope + --> $DIR/collect-without-into-iter-call.rs:8:31 + | +LL | let vec: Vec = items.collect(); + | ^^^^^^^ `impl IntoIterator` is not an iterator + | +help: call `.into_iter()` first + | +LL | let vec: Vec = items.into_iter().collect(); + | ++++++++++++ + +error[E0599]: no method named `collect` found for type parameter `impl IntoIterator` in the current scope + --> $DIR/collect-without-into-iter-call.rs:17:11 + | +LL | fn process(items: impl IntoIterator) -> Vec { + | -------------------------------- method `collect` not found for this type parameter +LL | items.collect() + | ^^^^^^^ `impl IntoIterator` 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`.