about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir_typeck/src/method/suggest.rs96
-rw-r--r--tests/ui/did_you_mean/collect-without-into-iter-call.rs19
-rw-r--r--tests/ui/did_you_mean/collect-without-into-iter-call.stderr36
3 files changed, 151 insertions, 0 deletions
diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index 729ce1f00cd..f39b496154b 100644
--- a/compiler/rustc_hir_typeck/src/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -109,6 +109,93 @@ 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>,
+        span: Span,
+        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_iterator_predicate(predicate: ty::Predicate<'_>, tcx: TyCtxt<'_>) -> bool {
+            if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) =
+                predicate.kind().as_ref().skip_binder()
+            {
+                tcx.is_diagnostic_item(sym::Iterator, trait_pred.trait_ref.def_id)
+            } else {
+                false
+            }
+        }
+
+        // Does the `ty` implement `IntoIterator`?
+        let Some(into_iterator_trait) = self.tcx.get_diagnostic_item(sym::IntoIterator) else {
+            return false;
+        };
+        let trait_ref = ty::TraitRef::new(self.tcx, into_iterator_trait, [ty]);
+        let cause = ObligationCause::new(span, self.body_id, ObligationCauseCode::MiscObligation);
+        let obligation = Obligation::new(self.tcx, cause, self.param_env, trait_ref);
+        if !self.predicate_must_hold_modulo_regions(&obligation) {
+            return false;
+        }
+
+        match ty.kind() {
+            ty::Param(param) => {
+                let generics = self.tcx.generics_of(self.body_id);
+                let generic_param = generics.type_param(&param, self.tcx);
+                for unsatisfied in unsatisfied_predicates.iter() {
+                    // 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, _) => {
+                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,
@@ -555,6 +642,15 @@ 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, span, 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
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 00000000000..ee4d75615bd
--- /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<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
+}
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 00000000000..797bd1e9e6f
--- /dev/null
+++ b/tests/ui/did_you_mean/collect-without-into-iter-call.stderr
@@ -0,0 +1,36 @@
+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 |     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`.