summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs')
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs272
1 files changed, 272 insertions, 0 deletions
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
new file mode 100644
index 000000000..e6907637c
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
@@ -0,0 +1,272 @@
+use super::{
+ ObligationCauseCode, OnUnimplementedDirective, OnUnimplementedNote, PredicateObligation,
+};
+use crate::infer::InferCtxt;
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_middle::ty::subst::{Subst, SubstsRef};
+use rustc_middle::ty::{self, GenericParamDefKind};
+use rustc_span::symbol::sym;
+use std::iter;
+
+use super::InferCtxtPrivExt;
+
+pub trait InferCtxtExt<'tcx> {
+ /*private*/
+ fn impl_similar_to(
+ &self,
+ trait_ref: ty::PolyTraitRef<'tcx>,
+ obligation: &PredicateObligation<'tcx>,
+ ) -> Option<(DefId, SubstsRef<'tcx>)>;
+
+ /*private*/
+ fn describe_enclosure(&self, hir_id: hir::HirId) -> Option<&'static str>;
+
+ fn on_unimplemented_note(
+ &self,
+ trait_ref: ty::PolyTraitRef<'tcx>,
+ obligation: &PredicateObligation<'tcx>,
+ ) -> OnUnimplementedNote;
+}
+
+impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
+ fn impl_similar_to(
+ &self,
+ trait_ref: ty::PolyTraitRef<'tcx>,
+ obligation: &PredicateObligation<'tcx>,
+ ) -> Option<(DefId, SubstsRef<'tcx>)> {
+ let tcx = self.tcx;
+ let param_env = obligation.param_env;
+ let trait_ref = tcx.erase_late_bound_regions(trait_ref);
+ let trait_self_ty = trait_ref.self_ty();
+
+ let mut self_match_impls = vec![];
+ let mut fuzzy_match_impls = vec![];
+
+ self.tcx.for_each_relevant_impl(trait_ref.def_id, trait_self_ty, |def_id| {
+ let impl_substs = self.fresh_substs_for_item(obligation.cause.span, def_id);
+ let impl_trait_ref = tcx.bound_impl_trait_ref(def_id).unwrap().subst(tcx, impl_substs);
+
+ let impl_self_ty = impl_trait_ref.self_ty();
+
+ if let Ok(..) = self.can_eq(param_env, trait_self_ty, impl_self_ty) {
+ self_match_impls.push((def_id, impl_substs));
+
+ if iter::zip(
+ trait_ref.substs.types().skip(1),
+ impl_trait_ref.substs.types().skip(1),
+ )
+ .all(|(u, v)| self.fuzzy_match_tys(u, v, false).is_some())
+ {
+ fuzzy_match_impls.push((def_id, impl_substs));
+ }
+ }
+ });
+
+ let impl_def_id_and_substs = if self_match_impls.len() == 1 {
+ self_match_impls[0]
+ } else if fuzzy_match_impls.len() == 1 {
+ fuzzy_match_impls[0]
+ } else {
+ return None;
+ };
+
+ tcx.has_attr(impl_def_id_and_substs.0, sym::rustc_on_unimplemented)
+ .then_some(impl_def_id_and_substs)
+ }
+
+ /// Used to set on_unimplemented's `ItemContext`
+ /// to be the enclosing (async) block/function/closure
+ fn describe_enclosure(&self, hir_id: hir::HirId) -> Option<&'static str> {
+ let hir = self.tcx.hir();
+ let node = hir.find(hir_id)?;
+ match &node {
+ hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, _, body_id), .. }) => {
+ self.describe_generator(*body_id).or_else(|| {
+ Some(match sig.header {
+ hir::FnHeader { asyncness: hir::IsAsync::Async, .. } => "an async function",
+ _ => "a function",
+ })
+ })
+ }
+ hir::Node::TraitItem(hir::TraitItem {
+ kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body_id)),
+ ..
+ }) => self.describe_generator(*body_id).or_else(|| Some("a trait method")),
+ hir::Node::ImplItem(hir::ImplItem {
+ kind: hir::ImplItemKind::Fn(sig, body_id),
+ ..
+ }) => self.describe_generator(*body_id).or_else(|| {
+ Some(match sig.header {
+ hir::FnHeader { asyncness: hir::IsAsync::Async, .. } => "an async method",
+ _ => "a method",
+ })
+ }),
+ hir::Node::Expr(hir::Expr {
+ kind: hir::ExprKind::Closure(hir::Closure { body, movability, .. }),
+ ..
+ }) => self.describe_generator(*body).or_else(|| {
+ Some(if movability.is_some() { "an async closure" } else { "a closure" })
+ }),
+ hir::Node::Expr(hir::Expr { .. }) => {
+ let parent_hid = hir.get_parent_node(hir_id);
+ if parent_hid != hir_id { self.describe_enclosure(parent_hid) } else { None }
+ }
+ _ => None,
+ }
+ }
+
+ fn on_unimplemented_note(
+ &self,
+ trait_ref: ty::PolyTraitRef<'tcx>,
+ obligation: &PredicateObligation<'tcx>,
+ ) -> OnUnimplementedNote {
+ let (def_id, substs) = self
+ .impl_similar_to(trait_ref, obligation)
+ .unwrap_or_else(|| (trait_ref.def_id(), trait_ref.skip_binder().substs));
+ let trait_ref = trait_ref.skip_binder();
+
+ let mut flags = vec![(
+ sym::ItemContext,
+ self.describe_enclosure(obligation.cause.body_id).map(|s| s.to_owned()),
+ )];
+
+ match obligation.cause.code() {
+ ObligationCauseCode::BuiltinDerivedObligation(..)
+ | ObligationCauseCode::ImplDerivedObligation(..)
+ | ObligationCauseCode::DerivedObligation(..) => {}
+ _ => {
+ // this is a "direct", user-specified, rather than derived,
+ // obligation.
+ flags.push((sym::direct, None));
+ }
+ }
+
+ if let ObligationCauseCode::ItemObligation(item)
+ | ObligationCauseCode::BindingObligation(item, _) = *obligation.cause.code()
+ {
+ // FIXME: maybe also have some way of handling methods
+ // from other traits? That would require name resolution,
+ // which we might want to be some sort of hygienic.
+ //
+ // Currently I'm leaving it for what I need for `try`.
+ if self.tcx.trait_of_item(item) == Some(trait_ref.def_id) {
+ let method = self.tcx.item_name(item);
+ flags.push((sym::from_method, None));
+ flags.push((sym::from_method, Some(method.to_string())));
+ }
+ }
+
+ if let Some(k) = obligation.cause.span.desugaring_kind() {
+ flags.push((sym::from_desugaring, None));
+ flags.push((sym::from_desugaring, Some(format!("{:?}", k))));
+ }
+
+ // Add all types without trimmed paths.
+ ty::print::with_no_trimmed_paths!({
+ let generics = self.tcx.generics_of(def_id);
+ let self_ty = trait_ref.self_ty();
+ // This is also included through the generics list as `Self`,
+ // but the parser won't allow you to use it
+ flags.push((sym::_Self, Some(self_ty.to_string())));
+ if let Some(def) = self_ty.ty_adt_def() {
+ // We also want to be able to select self's original
+ // signature with no type arguments resolved
+ flags.push((sym::_Self, Some(self.tcx.type_of(def.did()).to_string())));
+ }
+
+ for param in generics.params.iter() {
+ let value = match param.kind {
+ GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
+ substs[param.index as usize].to_string()
+ }
+ GenericParamDefKind::Lifetime => continue,
+ };
+ let name = param.name;
+ flags.push((name, Some(value)));
+
+ if let GenericParamDefKind::Type { .. } = param.kind {
+ let param_ty = substs[param.index as usize].expect_ty();
+ if let Some(def) = param_ty.ty_adt_def() {
+ // We also want to be able to select the parameter's
+ // original signature with no type arguments resolved
+ flags.push((name, Some(self.tcx.type_of(def.did()).to_string())));
+ }
+ }
+ }
+
+ if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) {
+ flags.push((sym::crate_local, None));
+ }
+
+ // Allow targeting all integers using `{integral}`, even if the exact type was resolved
+ if self_ty.is_integral() {
+ flags.push((sym::_Self, Some("{integral}".to_owned())));
+ }
+
+ if self_ty.is_array_slice() {
+ flags.push((sym::_Self, Some("&[]".to_owned())));
+ }
+
+ if self_ty.is_fn() {
+ let fn_sig = self_ty.fn_sig(self.tcx);
+ let shortname = match fn_sig.unsafety() {
+ hir::Unsafety::Normal => "fn",
+ hir::Unsafety::Unsafe => "unsafe fn",
+ };
+ flags.push((sym::_Self, Some(shortname.to_owned())));
+ }
+
+ // Slices give us `[]`, `[{ty}]`
+ if let ty::Slice(aty) = self_ty.kind() {
+ flags.push((sym::_Self, Some("[]".to_string())));
+ if let Some(def) = aty.ty_adt_def() {
+ // We also want to be able to select the slice's type's original
+ // signature with no type arguments resolved
+ flags.push((sym::_Self, Some(format!("[{}]", self.tcx.type_of(def.did())))));
+ }
+ if aty.is_integral() {
+ flags.push((sym::_Self, Some("[{integral}]".to_string())));
+ }
+ }
+
+ // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]`
+ if let ty::Array(aty, len) = self_ty.kind() {
+ flags.push((sym::_Self, Some("[]".to_string())));
+ let len = len.kind().try_to_value().and_then(|v| v.try_to_machine_usize(self.tcx));
+ flags.push((sym::_Self, Some(format!("[{}; _]", aty))));
+ if let Some(n) = len {
+ flags.push((sym::_Self, Some(format!("[{}; {}]", aty, n))));
+ }
+ if let Some(def) = aty.ty_adt_def() {
+ // We also want to be able to select the array's type's original
+ // signature with no type arguments resolved
+ let def_ty = self.tcx.type_of(def.did());
+ flags.push((sym::_Self, Some(format!("[{def_ty}; _]"))));
+ if let Some(n) = len {
+ flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]"))));
+ }
+ }
+ if aty.is_integral() {
+ flags.push((sym::_Self, Some("[{integral}; _]".to_string())));
+ if let Some(n) = len {
+ flags.push((sym::_Self, Some(format!("[{{integral}}; {n}]"))));
+ }
+ }
+ }
+ if let ty::Dynamic(traits, _) = self_ty.kind() {
+ for t in traits.iter() {
+ if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() {
+ flags.push((sym::_Self, Some(self.tcx.def_path_str(trait_ref.def_id))))
+ }
+ }
+ }
+ });
+
+ if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
+ command.evaluate(self.tcx, trait_ref, &flags)
+ } else {
+ OnUnimplementedNote::default()
+ }
+ }
+}