From 770c303c005261aa997e6a3a1b762c2831768365 Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Sun, 16 Apr 2023 22:22:42 +0000
Subject: [PATCH 1/3] Report reason why index impl is not satisfied deeply

---
 compiler/rustc_hir_typeck/src/expr.rs         | 97 +++++++++++++++++++
 tests/ui/typeck/bad-index-due-to-nested.rs    | 15 +++
 .../ui/typeck/bad-index-due-to-nested.stderr  | 29 ++++++
 3 files changed, 141 insertions(+)
 create mode 100644 tests/ui/typeck/bad-index-due-to-nested.rs
 create mode 100644 tests/ui/typeck/bad-index-due-to-nested.stderr

diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index ffc73d64fc04b..5c45e8f9d9410 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -38,6 +38,7 @@ use rustc_infer::infer;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::infer::DefineOpaqueTypes;
 use rustc_infer::infer::InferOk;
+use rustc_infer::traits::query::NoSolution;
 use rustc_infer::traits::ObligationCause;
 use rustc_middle::middle::stability;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
@@ -53,6 +54,8 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_target::abi::FieldIdx;
 use rustc_target::spec::abi::Abi::RustIntrinsic;
 use rustc_trait_selection::infer::InferCtxtExt;
+use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
+use rustc_trait_selection::traits::ObligationCtxt;
 use rustc_trait_selection::traits::{self, ObligationCauseCode};
 
 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -2800,6 +2803,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     element_ty
                 }
                 None => {
+                    // Attempt to *shallowly* search for an impl which matches,
+                    // but has nested obligations which are unsatisfied.
+                    for (base_t, _) in self.autoderef(base.span, base_t).silence_errors() {
+                        if let Some((_, index_ty, element_ty)) =
+                            self.find_and_report_unsatisfied_index_impl(expr.hir_id, base, base_t)
+                        {
+                            self.demand_coerce(idx, idx_t, index_ty, None, AllowTwoPhase::No);
+                            return element_ty;
+                        }
+                    }
+
                     let mut err = type_error_struct!(
                         self.tcx.sess,
                         expr.span,
@@ -2843,6 +2857,89 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
+    /// Try to match an implementation of `Index` against a self type, and report
+    /// the unsatisfied predicates that result from confirming this impl.
+    ///
+    /// Given an index expression, sometimes the `Self` type shallowly but does not
+    /// deeply satisfy an impl predicate. Instead of simply saying that the type
+    /// does not support being indexed, we want to point out exactly what nested
+    /// predicates cause this to be, so that the user can add them to fix their code.
+    fn find_and_report_unsatisfied_index_impl(
+        &self,
+        index_expr_hir_id: HirId,
+        base_expr: &hir::Expr<'_>,
+        base_ty: Ty<'tcx>,
+    ) -> Option<(ErrorGuaranteed, Ty<'tcx>, Ty<'tcx>)> {
+        let index_trait_def_id = self.tcx.lang_items().index_trait()?;
+
+        let mut relevant_impls = vec![];
+        self.tcx.for_each_relevant_impl(index_trait_def_id, base_ty, |impl_def_id| {
+            relevant_impls.push(impl_def_id);
+        });
+        let [impl_def_id] = relevant_impls[..] else {
+            // Only report unsatisfied impl predicates if there's one impl
+            return None;
+        };
+
+        self.commit_if_ok(|_| {
+            let ocx = ObligationCtxt::new_in_snapshot(self);
+            let impl_substs = self.fresh_substs_for_item(base_expr.span, impl_def_id);
+            let impl_trait_ref =
+                self.tcx.impl_trait_ref(impl_def_id).unwrap().subst(self.tcx, impl_substs);
+            let cause = self.misc(base_expr.span);
+
+            // Match the impl self type against the base ty. If this fails,
+            // we just skip this impl, since it's not particularly useful.
+            let impl_trait_ref = ocx.normalize(&cause, self.param_env, impl_trait_ref);
+            ocx.eq(&cause, self.param_env, impl_trait_ref.self_ty(), base_ty)?;
+
+            // Register the impl's predicates. One of these predicates
+            // must be unsatisfied, or else we wouldn't have gotten here
+            // in the first place.
+            ocx.register_obligations(traits::predicates_for_generics(
+                |idx, span| {
+                    traits::ObligationCause::new(
+                        base_expr.span,
+                        self.body_id,
+                        if span.is_dummy() {
+                            traits::ExprItemObligation(impl_def_id, index_expr_hir_id, idx)
+                        } else {
+                            traits::ExprBindingObligation(impl_def_id, span, index_expr_hir_id, idx)
+                        },
+                    )
+                },
+                self.param_env,
+                self.tcx.predicates_of(impl_def_id).instantiate(self.tcx, impl_substs),
+            ));
+
+            // Normalize the output type, which we can use later on as the
+            // return type of the index expression...
+            let element_ty = ocx.normalize(
+                &cause,
+                self.param_env,
+                self.tcx.mk_projection(
+                    self.tcx
+                        .associated_items(index_trait_def_id)
+                        .filter_by_name_unhygienic(sym::Output)
+                        .next()
+                        .unwrap()
+                        .def_id,
+                    impl_trait_ref.substs,
+                ),
+            );
+
+            let errors = ocx.select_where_possible();
+            // There should be at least one error reported. If not, we
+            // will still delay a span bug in `report_fulfillment_errors`.
+            Ok::<_, NoSolution>((
+                self.err_ctxt().report_fulfillment_errors(&errors),
+                impl_trait_ref.substs.type_at(1),
+                element_ty,
+            ))
+        })
+        .ok()
+    }
+
     fn point_at_index_if_possible(
         &self,
         errors: &mut Vec<traits::FulfillmentError<'tcx>>,
diff --git a/tests/ui/typeck/bad-index-due-to-nested.rs b/tests/ui/typeck/bad-index-due-to-nested.rs
new file mode 100644
index 0000000000000..8f3b97dc74147
--- /dev/null
+++ b/tests/ui/typeck/bad-index-due-to-nested.rs
@@ -0,0 +1,15 @@
+use std::collections::HashMap;
+
+pub struct Graph<V> {
+    node_index_map: HashMap<V, usize>,
+}
+
+impl<V> Graph<V> {
+    pub fn node_index(&self, node: V) -> usize {
+        self.node_index_map[&node]
+        //~^ ERROR the trait bound `V: Eq` is not satisfied
+        //~| ERROR the trait bound `V: Hash` is not satisfied
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/typeck/bad-index-due-to-nested.stderr b/tests/ui/typeck/bad-index-due-to-nested.stderr
new file mode 100644
index 0000000000000..fdacba793961b
--- /dev/null
+++ b/tests/ui/typeck/bad-index-due-to-nested.stderr
@@ -0,0 +1,29 @@
+error[E0277]: the trait bound `V: Eq` is not satisfied
+  --> $DIR/bad-index-due-to-nested.rs:9:9
+   |
+LL |         self.node_index_map[&node]
+   |         ^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `V`
+   |
+note: required by a bound in `<HashMap<K, V, S> as Index<&Q>>`
+  --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL
+help: consider restricting type parameter `V`
+   |
+LL | impl<V: std::cmp::Eq> Graph<V> {
+   |       ++++++++++++++
+
+error[E0277]: the trait bound `V: Hash` is not satisfied
+  --> $DIR/bad-index-due-to-nested.rs:9:9
+   |
+LL |         self.node_index_map[&node]
+   |         ^^^^^^^^^^^^^^^^^^^ the trait `Hash` is not implemented for `V`
+   |
+note: required by a bound in `<HashMap<K, V, S> as Index<&Q>>`
+  --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL
+help: consider restricting type parameter `V`
+   |
+LL | impl<V: std::hash::Hash> Graph<V> {
+   |       +++++++++++++++++
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0277`.

From d84b5f9b3d18cd6f6c273c4fe6ab09dba1edd6fb Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Tue, 18 Apr 2023 18:55:17 +0000
Subject: [PATCH 2/3] Use a diagnostic item instead of filtering for
 Index::Output

---
 compiler/rustc_hir_typeck/src/expr.rs | 11 ++---------
 compiler/rustc_span/src/symbol.rs     |  1 +
 library/core/src/ops/index.rs         |  1 +
 3 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 5c45e8f9d9410..0c0a7515d9c3b 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -2871,6 +2871,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         base_ty: Ty<'tcx>,
     ) -> Option<(ErrorGuaranteed, Ty<'tcx>, Ty<'tcx>)> {
         let index_trait_def_id = self.tcx.lang_items().index_trait()?;
+        let index_trait_output_def_id = self.tcx.get_diagnostic_item(sym::IndexOutput)?;
 
         let mut relevant_impls = vec![];
         self.tcx.for_each_relevant_impl(index_trait_def_id, base_ty, |impl_def_id| {
@@ -2917,15 +2918,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             let element_ty = ocx.normalize(
                 &cause,
                 self.param_env,
-                self.tcx.mk_projection(
-                    self.tcx
-                        .associated_items(index_trait_def_id)
-                        .filter_by_name_unhygienic(sym::Output)
-                        .next()
-                        .unwrap()
-                        .def_id,
-                    impl_trait_ref.substs,
-                ),
+                self.tcx.mk_projection(index_trait_output_def_id, impl_trait_ref.substs),
             );
 
             let errors = ocx.select_where_possible();
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 6ce0b66ef6a45..d6ee7ac34aaca 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -204,6 +204,7 @@ symbols! {
         HashSet,
         Hasher,
         Implied,
+        IndexOutput,
         Input,
         Into,
         IntoDiagnostic,
diff --git a/library/core/src/ops/index.rs b/library/core/src/ops/index.rs
index 228efb0bc0a5c..f3754484f6a06 100644
--- a/library/core/src/ops/index.rs
+++ b/library/core/src/ops/index.rs
@@ -59,6 +59,7 @@
 pub trait Index<Idx: ?Sized> {
     /// The returned type after indexing.
     #[stable(feature = "rust1", since = "1.0.0")]
+    #[rustc_diagnostic_item = "IndexOutput"]
     type Output: ?Sized;
 
     /// Performs the indexing (`container[index]`) operation.

From 8d75a8f699c921724b1e01ce932de77d08533957 Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Tue, 18 Apr 2023 19:25:57 +0000
Subject: [PATCH 3/3] Test downstream errors from bad index expr

---
 tests/ui/typeck/bad-index-due-to-nested.rs    | 30 +++++---
 .../ui/typeck/bad-index-due-to-nested.stderr  | 73 ++++++++++++++-----
 2 files changed, 75 insertions(+), 28 deletions(-)

diff --git a/tests/ui/typeck/bad-index-due-to-nested.rs b/tests/ui/typeck/bad-index-due-to-nested.rs
index 8f3b97dc74147..2564b530004e5 100644
--- a/tests/ui/typeck/bad-index-due-to-nested.rs
+++ b/tests/ui/typeck/bad-index-due-to-nested.rs
@@ -1,15 +1,27 @@
-use std::collections::HashMap;
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::ops::Index;
 
-pub struct Graph<V> {
-    node_index_map: HashMap<V, usize>,
-}
+struct HashMap<K, V>(PhantomData<(K, V)>);
+
+impl<K, V> Index<&K> for HashMap<K, V>
+where
+    K: Hash,
+    V: Copy,
+{
+    type Output = V;
 
-impl<V> Graph<V> {
-    pub fn node_index(&self, node: V) -> usize {
-        self.node_index_map[&node]
-        //~^ ERROR the trait bound `V: Eq` is not satisfied
-        //~| ERROR the trait bound `V: Hash` is not satisfied
+    fn index(&self, k: &K) -> &V {
+        todo!()
     }
 }
 
+fn index<'a, K, V>(map: &'a HashMap<K, V>, k: K) -> &'a V {
+    map[k]
+    //~^ ERROR the trait bound `K: Hash` is not satisfied
+    //~| ERROR the trait bound `V: Copy` is not satisfied
+    //~| ERROR mismatched types
+    //~| ERROR mismatched types
+}
+
 fn main() {}
diff --git a/tests/ui/typeck/bad-index-due-to-nested.stderr b/tests/ui/typeck/bad-index-due-to-nested.stderr
index fdacba793961b..e03b06b336eef 100644
--- a/tests/ui/typeck/bad-index-due-to-nested.stderr
+++ b/tests/ui/typeck/bad-index-due-to-nested.stderr
@@ -1,29 +1,64 @@
-error[E0277]: the trait bound `V: Eq` is not satisfied
-  --> $DIR/bad-index-due-to-nested.rs:9:9
+error[E0277]: the trait bound `K: Hash` is not satisfied
+  --> $DIR/bad-index-due-to-nested.rs:20:5
    |
-LL |         self.node_index_map[&node]
-   |         ^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `V`
+LL |     map[k]
+   |     ^^^ the trait `Hash` is not implemented for `K`
    |
-note: required by a bound in `<HashMap<K, V, S> as Index<&Q>>`
-  --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL
-help: consider restricting type parameter `V`
+note: required by a bound in `<HashMap<K, V> as Index<&K>>`
+  --> $DIR/bad-index-due-to-nested.rs:9:8
+   |
+LL |     K: Hash,
+   |        ^^^^ required by this bound in `<HashMap<K, V> as Index<&K>>`
+help: consider restricting type parameter `K`
    |
-LL | impl<V: std::cmp::Eq> Graph<V> {
-   |       ++++++++++++++
+LL | fn index<'a, K: std::hash::Hash, V>(map: &'a HashMap<K, V>, k: K) -> &'a V {
+   |               +++++++++++++++++
 
-error[E0277]: the trait bound `V: Hash` is not satisfied
-  --> $DIR/bad-index-due-to-nested.rs:9:9
+error[E0277]: the trait bound `V: Copy` is not satisfied
+  --> $DIR/bad-index-due-to-nested.rs:20:5
+   |
+LL |     map[k]
+   |     ^^^ the trait `Copy` is not implemented for `V`
    |
-LL |         self.node_index_map[&node]
-   |         ^^^^^^^^^^^^^^^^^^^ the trait `Hash` is not implemented for `V`
+note: required by a bound in `<HashMap<K, V> as Index<&K>>`
+  --> $DIR/bad-index-due-to-nested.rs:10:8
    |
-note: required by a bound in `<HashMap<K, V, S> as Index<&Q>>`
-  --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL
+LL |     V: Copy,
+   |        ^^^^ required by this bound in `<HashMap<K, V> as Index<&K>>`
 help: consider restricting type parameter `V`
    |
-LL | impl<V: std::hash::Hash> Graph<V> {
-   |       +++++++++++++++++
+LL | fn index<'a, K, V: std::marker::Copy>(map: &'a HashMap<K, V>, k: K) -> &'a V {
+   |                  +++++++++++++++++++
+
+error[E0308]: mismatched types
+  --> $DIR/bad-index-due-to-nested.rs:20:9
+   |
+LL | fn index<'a, K, V>(map: &'a HashMap<K, V>, k: K) -> &'a V {
+   |              - this type parameter
+LL |     map[k]
+   |         ^
+   |         |
+   |         expected `&K`, found type parameter `K`
+   |         help: consider borrowing here: `&k`
+   |
+   = note:   expected reference `&K`
+           found type parameter `K`
+
+error[E0308]: mismatched types
+  --> $DIR/bad-index-due-to-nested.rs:20:5
+   |
+LL | fn index<'a, K, V>(map: &'a HashMap<K, V>, k: K) -> &'a V {
+   |                 - this type parameter               ----- expected `&'a V` because of return type
+LL |     map[k]
+   |     ^^^^^^
+   |     |
+   |     expected `&V`, found type parameter `V`
+   |     help: consider borrowing here: `&map[k]`
+   |
+   = note:   expected reference `&'a V`
+           found type parameter `V`
 
-error: aborting due to 2 previous errors
+error: aborting due to 4 previous errors
 
-For more information about this error, try `rustc --explain E0277`.
+Some errors have detailed explanations: E0277, E0308.
+For more information about an error, try `rustc --explain E0277`.