From 770c303c005261aa997e6a3a1b762c2831768365 Mon Sep 17 00:00:00 2001 From: Michael Goulet 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 ffc73d64fc0..5c45e8f9d94 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>, 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 00000000000..8f3b97dc741 --- /dev/null +++ b/tests/ui/typeck/bad-index-due-to-nested.rs @@ -0,0 +1,15 @@ +use std::collections::HashMap; + +pub struct Graph { + node_index_map: HashMap, +} + +impl Graph { + 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 00000000000..fdacba79396 --- /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 ` as Index<&Q>>` + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL +help: consider restricting type parameter `V` + | +LL | impl Graph { + | ++++++++++++++ + +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 ` as Index<&Q>>` + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL +help: consider restricting type parameter `V` + | +LL | impl Graph { + | +++++++++++++++++ + +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 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 5c45e8f9d94..0c0a7515d9c 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 6ce0b66ef6a..d6ee7ac34aa 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 228efb0bc0a..f3754484f6a 100644 --- a/library/core/src/ops/index.rs +++ b/library/core/src/ops/index.rs @@ -59,6 +59,7 @@ pub trait Index { /// 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 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 | 75 ++++++++++++++----- 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/tests/ui/typeck/bad-index-due-to-nested.rs b/tests/ui/typeck/bad-index-due-to-nested.rs index 8f3b97dc741..2564b530004 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 { - node_index_map: HashMap, -} +struct HashMap(PhantomData<(K, V)>); -impl Graph { - 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 +impl Index<&K> for HashMap +where + K: Hash, + V: Copy, +{ + type Output = V; + + fn index(&self, k: &K) -> &V { + todo!() } } +fn index<'a, K, V>(map: &'a HashMap, 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 fdacba79396..e03b06b336e 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 ` as Index<&Q>>` - --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL +note: required by a bound in ` as Index<&K>>` + --> $DIR/bad-index-due-to-nested.rs:9:8 + | +LL | K: Hash, + | ^^^^ required by this bound in ` as Index<&K>>` +help: consider restricting type parameter `K` + | +LL | fn index<'a, K: std::hash::Hash, V>(map: &'a HashMap, k: K) -> &'a V { + | +++++++++++++++++ + +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` + | +note: required by a bound in ` as Index<&K>>` + --> $DIR/bad-index-due-to-nested.rs:10:8 + | +LL | V: Copy, + | ^^^^ required by this bound in ` as Index<&K>>` help: consider restricting type parameter `V` | -LL | impl Graph { - | ++++++++++++++ +LL | fn index<'a, K, V: std::marker::Copy>(map: &'a HashMap, k: K) -> &'a V { + | +++++++++++++++++++ -error[E0277]: the trait bound `V: Hash` is not satisfied - --> $DIR/bad-index-due-to-nested.rs:9:9 +error[E0308]: mismatched types + --> $DIR/bad-index-due-to-nested.rs:20:9 | -LL | self.node_index_map[&node] - | ^^^^^^^^^^^^^^^^^^^ the trait `Hash` is not implemented for `V` +LL | fn index<'a, K, V>(map: &'a HashMap, k: K) -> &'a V { + | - this type parameter +LL | map[k] + | ^ + | | + | expected `&K`, found type parameter `K` + | help: consider borrowing here: `&k` | -note: required by a bound in ` as Index<&Q>>` - --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL -help: consider restricting type parameter `V` - | -LL | impl Graph { - | +++++++++++++++++ + = note: expected reference `&K` + found type parameter `K` -error: aborting due to 2 previous errors +error[E0308]: mismatched types + --> $DIR/bad-index-due-to-nested.rs:20:5 + | +LL | fn index<'a, K, V>(map: &'a HashMap, 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` -For more information about this error, try `rustc --explain E0277`. +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0277, E0308. +For more information about an error, try `rustc --explain E0277`.