From a006a82724d1efec712f0f0d337aeeaf3f14258c Mon Sep 17 00:00:00 2001
From: Gulshan Singh <gsingh2011@gmail.com>
Date: Thu, 18 Jun 2015 22:20:45 -0400
Subject: [PATCH] Suggest missing trait bounds when a method exists but the
 bounds aren't satisfied

---
 src/librustc_typeck/check/method/mod.rs       | 39 ++++++++---
 src/librustc_typeck/check/method/probe.rs     | 69 ++++++++++++++-----
 src/librustc_typeck/check/method/suggest.rs   | 26 +++++--
 .../method-help-unsatisfied-bound.rs          | 18 +++++
 4 files changed, 120 insertions(+), 32 deletions(-)
 create mode 100644 src/test/compile-fail/method-help-unsatisfied-bound.rs

diff --git a/src/librustc_typeck/check/method/mod.rs b/src/librustc_typeck/check/method/mod.rs
index 85a4b02cf804e..e5a57fa9c138e 100644
--- a/src/librustc_typeck/check/method/mod.rs
+++ b/src/librustc_typeck/check/method/mod.rs
@@ -16,7 +16,7 @@ use middle::def;
 use middle::privacy::{AllPublic, DependsOn, LastPrivate, LastMod};
 use middle::subst;
 use middle::traits;
-use middle::ty::{self, AsPredicate, ToPolyTraitRef};
+use middle::ty::{self, AsPredicate, ToPolyTraitRef, TraitRef};
 use middle::infer;
 
 use syntax::ast::DefId;
@@ -32,11 +32,9 @@ mod confirm;
 mod probe;
 mod suggest;
 
-pub enum MethodError {
-    // Did not find an applicable method, but we did find various
-    // static methods that may apply, as well as a list of
-    // not-in-scope traits which may work.
-    NoMatch(Vec<CandidateSource>, Vec<ast::DefId>, probe::Mode),
+pub enum MethodError<'tcx> {
+    // Did not find an applicable method, but we did find various near-misses that may work.
+    NoMatch(NoMatchData<'tcx>),
 
     // Multiple methods might apply.
     Ambiguity(Vec<CandidateSource>),
@@ -45,9 +43,32 @@ pub enum MethodError {
     ClosureAmbiguity(/* DefId of fn trait */ ast::DefId),
 }
 
+// Contains a list of static methods that may apply, a list of unsatisfied trait predicates which
+// could lead to matches if satisfied, and a list of not-in-scope traits which may work.
+pub struct NoMatchData<'tcx> {
+    pub static_candidates: Vec<CandidateSource>,
+    pub unsatisfied_predicates: Vec<TraitRef<'tcx>>,
+    pub out_of_scope_traits: Vec<ast::DefId>,
+    pub mode: probe::Mode
+}
+
+impl<'tcx> NoMatchData<'tcx> {
+    pub fn new(static_candidates: Vec<CandidateSource>,
+               unsatisfied_predicates: Vec<TraitRef<'tcx>>,
+               out_of_scope_traits: Vec<ast::DefId>,
+               mode: probe::Mode) -> Self {
+        NoMatchData {
+            static_candidates: static_candidates,
+            unsatisfied_predicates: unsatisfied_predicates,
+            out_of_scope_traits: out_of_scope_traits,
+            mode: mode
+        }
+    }
+}
+
 // A pared down enum describing just the places from which a method
 // candidate can arise. Used for error reporting only.
-#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
 pub enum CandidateSource {
     ImplSource(ast::DefId),
     TraitSource(/* trait id */ ast::DefId),
@@ -93,7 +114,7 @@ pub fn lookup<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
                         supplied_method_types: Vec<ty::Ty<'tcx>>,
                         call_expr: &'tcx ast::Expr,
                         self_expr: &'tcx ast::Expr)
-                        -> Result<ty::MethodCallee<'tcx>, MethodError>
+                        -> Result<ty::MethodCallee<'tcx>, MethodError<'tcx>>
 {
     debug!("lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})",
            method_name,
@@ -305,7 +326,7 @@ pub fn resolve_ufcs<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
                               method_name: ast::Name,
                               self_ty: ty::Ty<'tcx>,
                               expr_id: ast::NodeId)
-                              -> Result<(def::Def, LastPrivate), MethodError>
+                              -> Result<(def::Def, LastPrivate), MethodError<'tcx>>
 {
     let mode = probe::Mode::Path;
     let pick = try!(probe::probe(fcx, span, mode, method_name, self_ty, expr_id));
diff --git a/src/librustc_typeck/check/method/probe.rs b/src/librustc_typeck/check/method/probe.rs
index ecf6cad32be76..46bffa8ccabda 100644
--- a/src/librustc_typeck/check/method/probe.rs
+++ b/src/librustc_typeck/check/method/probe.rs
@@ -9,8 +9,9 @@
 // except according to those terms.
 
 use super::MethodError;
+use super::NoMatchData;
 use super::ItemIndex;
-use super::{CandidateSource,ImplSource,TraitSource};
+use super::{CandidateSource, ImplSource, TraitSource};
 use super::suggest;
 
 use check;
@@ -19,7 +20,7 @@ use middle::fast_reject;
 use middle::subst;
 use middle::subst::Subst;
 use middle::traits;
-use middle::ty::{self, RegionEscape, Ty, ToPolyTraitRef};
+use middle::ty::{self, RegionEscape, Ty, ToPolyTraitRef, TraitRef};
 use middle::ty_fold::TypeFoldable;
 use middle::infer;
 use middle::infer::InferCtxt;
@@ -42,7 +43,14 @@ struct ProbeContext<'a, 'tcx:'a> {
     inherent_candidates: Vec<Candidate<'tcx>>,
     extension_candidates: Vec<Candidate<'tcx>>,
     impl_dups: HashSet<ast::DefId>,
+
+    /// Collects near misses when the candidate functions are missing a `self` keyword and is only
+    /// used for error reporting
     static_candidates: Vec<CandidateSource>,
+
+    /// Collects near misses when trait bounds for type parameters are unsatisfied and is only used
+    /// for error reporting
+    unsatisfied_predicates: Vec<TraitRef<'tcx>>
 }
 
 #[derive(Debug)]
@@ -104,7 +112,7 @@ pub enum PickKind<'tcx> {
     WhereClausePick(/* Trait */ ty::PolyTraitRef<'tcx>, ItemIndex),
 }
 
-pub type PickResult<'tcx> = Result<Pick<'tcx>, MethodError>;
+pub type PickResult<'tcx> = Result<Pick<'tcx>, MethodError<'tcx>>;
 
 #[derive(PartialEq, Eq, Copy, Clone, Debug)]
 pub enum Mode {
@@ -141,7 +149,8 @@ pub fn probe<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
     let steps = if mode == Mode::MethodCall {
         match create_steps(fcx, span, self_ty) {
             Some(steps) => steps,
-            None => return Err(MethodError::NoMatch(Vec::new(), Vec::new(), mode)),
+            None =>return Err(MethodError::NoMatch(NoMatchData::new(Vec::new(), Vec::new(),
+                                                                    Vec::new(), mode))),
         }
     } else {
         vec![CandidateStep {
@@ -242,6 +251,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
             steps: Rc::new(steps),
             opt_simplified_steps: opt_simplified_steps,
             static_candidates: Vec::new(),
+            unsatisfied_predicates: Vec::new(),
         }
     }
 
@@ -563,7 +573,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
 
     fn assemble_extension_candidates_for_traits_in_scope(&mut self,
                                                          expr_id: ast::NodeId)
-                                                         -> Result<(),MethodError>
+                                                         -> Result<(), MethodError<'tcx>>
     {
         let mut duplicates = HashSet::new();
         let opt_applicable_traits = self.fcx.ccx.trait_map.get(&expr_id);
@@ -577,7 +587,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
         Ok(())
     }
 
-    fn assemble_extension_candidates_for_all_traits(&mut self) -> Result<(),MethodError> {
+    fn assemble_extension_candidates_for_all_traits(&mut self) -> Result<(), MethodError<'tcx>> {
         let mut duplicates = HashSet::new();
         for trait_info in suggest::all_traits(self.fcx.ccx) {
             if duplicates.insert(trait_info.def_id) {
@@ -589,7 +599,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
 
     fn assemble_extension_candidates_for_trait(&mut self,
                                                trait_def_id: ast::DefId)
-                                               -> Result<(),MethodError>
+                                               -> Result<(), MethodError<'tcx>>
     {
         debug!("assemble_extension_candidates_for_trait(trait_def_id={:?})",
                trait_def_id);
@@ -709,7 +719,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
                                    trait_def_id: ast::DefId,
                                    item: ty::ImplOrTraitItem<'tcx>,
                                    item_index: usize)
-                                   -> Result<(),MethodError>
+                                   -> Result<(), MethodError<'tcx>>
     {
         // Check if this is one of the Fn,FnMut,FnOnce traits.
         let tcx = self.tcx();
@@ -868,6 +878,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
         }
 
         let static_candidates = mem::replace(&mut self.static_candidates, vec![]);
+        let unsatisfied_predicates = mem::replace(&mut self.unsatisfied_predicates, vec![]);
 
         // things failed, so lets look at all traits, for diagnostic purposes now:
         self.reset();
@@ -892,7 +903,7 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
                     }
                 }
             }).collect(),
-            Some(Err(MethodError::NoMatch(_, others, _))) => {
+            Some(Err(MethodError::NoMatch(NoMatchData { out_of_scope_traits: others, .. }))) => {
                 assert!(others.is_empty());
                 vec![]
             }
@@ -903,7 +914,8 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
             None => vec![],
         };
 
-        Err(MethodError::NoMatch(static_candidates, out_of_scope_traits, self.mode))
+        Err(MethodError::NoMatch(NoMatchData::new(static_candidates, unsatisfied_predicates,
+                                                  out_of_scope_traits, self.mode)))
     }
 
     fn pick_core(&mut self) -> Option<PickResult<'tcx>> {
@@ -991,8 +1003,11 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
     fn pick_method(&mut self, self_ty: Ty<'tcx>) -> Option<PickResult<'tcx>> {
         debug!("pick_method(self_ty={})", self.infcx().ty_to_string(self_ty));
 
+        let mut possibly_unsatisfied_predicates = Vec::new();
+
         debug!("searching inherent candidates");
-        match self.consider_candidates(self_ty, &self.inherent_candidates) {
+        match self.consider_candidates(self_ty, &self.inherent_candidates,
+                                       &mut possibly_unsatisfied_predicates) {
             None => {}
             Some(pick) => {
                 return Some(pick);
@@ -1000,16 +1015,23 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
         }
 
         debug!("searching extension candidates");
-        self.consider_candidates(self_ty, &self.extension_candidates)
+        let res = self.consider_candidates(self_ty, &self.extension_candidates,
+                                           &mut possibly_unsatisfied_predicates);
+        if let None = res {
+            self.unsatisfied_predicates.extend(possibly_unsatisfied_predicates);
+        }
+        res
     }
 
     fn consider_candidates(&self,
                            self_ty: Ty<'tcx>,
-                           probes: &[Candidate<'tcx>])
+                           probes: &[Candidate<'tcx>],
+                           possibly_unsatisfied_predicates: &mut Vec<TraitRef<'tcx>>)
                            -> Option<PickResult<'tcx>> {
         let mut applicable_candidates: Vec<_> =
             probes.iter()
-                  .filter(|&probe| self.consider_probe(self_ty, probe))
+                  .filter(|&probe| self.consider_probe(self_ty,
+                                                       probe,possibly_unsatisfied_predicates))
                   .collect();
 
         debug!("applicable_candidates: {:?}", applicable_candidates);
@@ -1032,7 +1054,8 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
         })
     }
 
-    fn consider_probe(&self, self_ty: Ty<'tcx>, probe: &Candidate<'tcx>) -> bool {
+    fn consider_probe(&self, self_ty: Ty<'tcx>, probe: &Candidate<'tcx>,
+                      possibly_unsatisfied_predicates: &mut Vec<TraitRef<'tcx>>) -> bool {
         debug!("consider_probe: self_ty={:?} probe={:?}",
                self_ty,
                probe);
@@ -1071,10 +1094,18 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
                     debug!("impl_obligations={:?}", obligations);
 
                     // Evaluate those obligations to see if they might possibly hold.
-                    obligations.iter()
-                        .chain(norm_obligations.iter()).chain(ref_obligations.iter())
-                        .all(|o| selcx.evaluate_obligation(o))
-
+                    let mut all_true = true;
+                    for o in obligations.iter()
+                        .chain(norm_obligations.iter())
+                        .chain(ref_obligations.iter()) {
+                        if !selcx.evaluate_obligation(o) {
+                            all_true = false;
+                            if let &ty::Predicate::Trait(ref pred) = &o.predicate {
+                                possibly_unsatisfied_predicates.push(pred.0.trait_ref);
+                            }
+                        }
+                    }
+                    all_true
                 }
 
                 ProjectionCandidate(..) |
diff --git a/src/librustc_typeck/check/method/suggest.rs b/src/librustc_typeck/check/method/suggest.rs
index 4fbe42455ae86..2325b9852c73d 100644
--- a/src/librustc_typeck/check/method/suggest.rs
+++ b/src/librustc_typeck/check/method/suggest.rs
@@ -29,7 +29,7 @@ use syntax::print::pprust;
 use std::cell;
 use std::cmp::Ordering;
 
-use super::{MethodError, CandidateSource, impl_item, trait_item};
+use super::{MethodError, NoMatchData, CandidateSource, impl_item, trait_item};
 use super::probe::Mode;
 
 pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
@@ -37,7 +37,7 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
                               rcvr_ty: Ty<'tcx>,
                               item_name: ast::Name,
                               rcvr_expr: Option<&ast::Expr>,
-                              error: MethodError)
+                              error: MethodError<'tcx>)
 {
     // avoid suggestions when we don't know what's going on.
     if ty::type_is_error(rcvr_ty) {
@@ -45,7 +45,10 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
     }
 
     match error {
-        MethodError::NoMatch(static_sources, out_of_scope_traits, mode) => {
+        MethodError::NoMatch(NoMatchData { static_candidates: static_sources,
+                                           unsatisfied_predicates,
+                                           out_of_scope_traits,
+                                           mode }) => {
             let cx = fcx.tcx();
 
             fcx.type_error_message(
@@ -118,13 +121,28 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
             }
 
             if !static_sources.is_empty() {
-                fcx.tcx().sess.fileline_note(
+                cx.sess.fileline_note(
                     span,
                     "found defined static methods, maybe a `self` is missing?");
 
                 report_candidates(fcx, span, item_name, static_sources);
             }
 
+            if !unsatisfied_predicates.is_empty() {
+                let bound_list = unsatisfied_predicates.iter()
+                    .map(|p| format!("`{} : {}`",
+                                     p.self_ty(),
+                                     p))
+                    .collect::<Vec<_>>()
+                    .connect(", ");
+                cx.sess.fileline_note(
+                    span,
+                    &format!("the method `{}` exists but the \
+                             following trait bounds were not satisfied: {}",
+                             item_name,
+                             bound_list));
+            }
+
             suggest_traits_to_import(fcx, span, rcvr_ty, item_name,
                                      rcvr_expr, out_of_scope_traits)
         }
diff --git a/src/test/compile-fail/method-help-unsatisfied-bound.rs b/src/test/compile-fail/method-help-unsatisfied-bound.rs
new file mode 100644
index 0000000000000..375d5076ea2f8
--- /dev/null
+++ b/src/test/compile-fail/method-help-unsatisfied-bound.rs
@@ -0,0 +1,18 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+struct Foo;
+
+fn main() {
+    let a: Result<(), Foo> = Ok(());
+    a.unwrap();
+    //~^ ERROR no method named `unwrap` found for type `core::result::Result<(), Foo>`
+    //~| NOTE the following trait bounds were not satisfied: `Foo : core::fmt::Debug`
+}