From af506fa5d15b892c2fa6df442e106e1afcb7abca Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Tue, 13 Jan 2015 17:05:04 +1100 Subject: [PATCH 1/3] typeck: move method errors/suggestions to their own file. --- src/librustc_typeck/check/method/mod.rs | 105 +---------------- src/librustc_typeck/check/method/suggest.rs | 122 ++++++++++++++++++++ 2 files changed, 126 insertions(+), 101 deletions(-) create mode 100644 src/librustc_typeck/check/method/suggest.rs diff --git a/src/librustc_typeck/check/method/mod.rs b/src/librustc_typeck/check/method/mod.rs index 0c53a16a811..8637a915305 100644 --- a/src/librustc_typeck/check/method/mod.rs +++ b/src/librustc_typeck/check/method/mod.rs @@ -12,7 +12,6 @@ use astconv::AstConv; use check::{FnCtxt}; -use check::{impl_self_ty}; use check::vtable; use check::vtable::select_new_fcx_obligations; use middle::subst; @@ -20,7 +19,7 @@ use middle::traits; use middle::ty::*; use middle::ty; use middle::infer; -use util::ppaux::{Repr, UserString}; +use util::ppaux::Repr; use std::rc::Rc; use syntax::ast::{DefId}; @@ -30,9 +29,12 @@ use syntax::codemap::Span; pub use self::MethodError::*; pub use self::CandidateSource::*; +pub use self::suggest::report_error; + mod confirm; mod doc; mod probe; +mod suggest; pub enum MethodError { // Did not find an applicable method, but we did find various @@ -294,105 +296,6 @@ pub fn lookup_in_trait_adjusted<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, Some(callee) } -pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, - span: Span, - rcvr_ty: Ty<'tcx>, - method_name: ast::Name, - error: MethodError) -{ - match error { - NoMatch(static_sources) => { - let cx = fcx.tcx(); - let method_ustring = method_name.user_string(cx); - - // True if the type is a struct and contains a field with - // the same name as the not-found method - let is_field = match rcvr_ty.sty { - ty_struct(did, _) => - ty::lookup_struct_fields(cx, did) - .iter() - .any(|f| f.name.user_string(cx) == method_ustring), - _ => false - }; - - fcx.type_error_message( - span, - |actual| { - format!("type `{}` does not implement any \ - method in scope named `{}`", - actual, - method_ustring) - }, - rcvr_ty, - None); - - // If the method has the name of a field, give a help note - if is_field { - cx.sess.span_note(span, - &format!("use `(s.{0})(...)` if you meant to call the \ - function stored in the `{0}` field", method_ustring)[]); - } - - if static_sources.len() > 0 { - fcx.tcx().sess.fileline_note( - span, - "found defined static methods, maybe a `self` is missing?"); - - report_candidates(fcx, span, method_name, static_sources); - } - } - - Ambiguity(sources) => { - span_err!(fcx.sess(), span, E0034, - "multiple applicable methods in scope"); - - report_candidates(fcx, span, method_name, sources); - } - } - - fn report_candidates(fcx: &FnCtxt, - span: Span, - method_name: ast::Name, - mut sources: Vec) { - sources.sort(); - sources.dedup(); - - for (idx, source) in sources.iter().enumerate() { - match *source { - ImplSource(impl_did) => { - // Provide the best span we can. Use the method, if local to crate, else - // the impl, if local to crate (method may be defaulted), else the call site. - let method = impl_method(fcx.tcx(), impl_did, method_name).unwrap(); - let impl_span = fcx.tcx().map.def_id_span(impl_did, span); - let method_span = fcx.tcx().map.def_id_span(method.def_id, impl_span); - - let impl_ty = impl_self_ty(fcx, span, impl_did).ty; - - let insertion = match impl_trait_ref(fcx.tcx(), impl_did) { - None => format!(""), - Some(trait_ref) => format!(" of the trait `{}`", - ty::item_path_str(fcx.tcx(), - trait_ref.def_id)), - }; - - span_note!(fcx.sess(), method_span, - "candidate #{} is defined in an impl{} for the type `{}`", - idx + 1u, - insertion, - impl_ty.user_string(fcx.tcx())); - } - TraitSource(trait_did) => { - let (_, method) = trait_method(fcx.tcx(), trait_did, method_name).unwrap(); - let method_span = fcx.tcx().map.def_id_span(method.def_id, span); - span_note!(fcx.sess(), method_span, - "candidate #{} is defined in the trait `{}`", - idx + 1u, - ty::item_path_str(fcx.tcx(), trait_did)); - } - } - } - } -} /// Find method with name `method_name` defined in `trait_def_id` and return it, along with its /// index (or `None`, if no such method). diff --git a/src/librustc_typeck/check/method/suggest.rs b/src/librustc_typeck/check/method/suggest.rs new file mode 100644 index 00000000000..b6d97d2df9d --- /dev/null +++ b/src/librustc_typeck/check/method/suggest.rs @@ -0,0 +1,122 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Give useful errors and suggestions to users when a method can't be +//! found or is otherwise invalid. + +use astconv::AstConv; +use check::{self, FnCtxt}; +use middle::ty::{self, Ty}; +use util::ppaux::UserString; + +use syntax::ast; +use syntax::codemap::Span; + +use super::{MethodError, CandidateSource, impl_method, trait_method}; + +pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, + span: Span, + rcvr_ty: Ty<'tcx>, + method_name: ast::Name, + error: MethodError) +{ + match error { + MethodError::NoMatch(static_sources) => { + let cx = fcx.tcx(); + let method_ustring = method_name.user_string(cx); + + // True if the type is a struct and contains a field with + // the same name as the not-found method + let is_field = match rcvr_ty.sty { + ty::ty_struct(did, _) => + ty::lookup_struct_fields(cx, did) + .iter() + .any(|f| f.name.user_string(cx) == method_ustring), + _ => false + }; + + fcx.type_error_message( + span, + |actual| { + format!("type `{}` does not implement any \ + method in scope named `{}`", + actual, + method_ustring) + }, + rcvr_ty, + None); + + // If the method has the name of a field, give a help note + if is_field { + cx.sess.span_note(span, + &format!("use `(s.{0})(...)` if you meant to call the \ + function stored in the `{0}` field", method_ustring)[]); + } + + if static_sources.len() > 0 { + fcx.tcx().sess.fileline_note( + span, + "found defined static methods, maybe a `self` is missing?"); + + report_candidates(fcx, span, method_name, static_sources); + } + } + + MethodError::Ambiguity(sources) => { + span_err!(fcx.sess(), span, E0034, + "multiple applicable methods in scope"); + + report_candidates(fcx, span, method_name, sources); + } + } + + fn report_candidates(fcx: &FnCtxt, + span: Span, + method_name: ast::Name, + mut sources: Vec) { + sources.sort(); + sources.dedup(); + + for (idx, source) in sources.iter().enumerate() { + match *source { + CandidateSource::ImplSource(impl_did) => { + // Provide the best span we can. Use the method, if local to crate, else + // the impl, if local to crate (method may be defaulted), else the call site. + let method = impl_method(fcx.tcx(), impl_did, method_name).unwrap(); + let impl_span = fcx.tcx().map.def_id_span(impl_did, span); + let method_span = fcx.tcx().map.def_id_span(method.def_id, impl_span); + + let impl_ty = check::impl_self_ty(fcx, span, impl_did).ty; + + let insertion = match ty::impl_trait_ref(fcx.tcx(), impl_did) { + None => format!(""), + Some(trait_ref) => format!(" of the trait `{}`", + ty::item_path_str(fcx.tcx(), + trait_ref.def_id)), + }; + + span_note!(fcx.sess(), method_span, + "candidate #{} is defined in an impl{} for the type `{}`", + idx + 1u, + insertion, + impl_ty.user_string(fcx.tcx())); + } + CandidateSource::TraitSource(trait_did) => { + let (_, method) = trait_method(fcx.tcx(), trait_did, method_name).unwrap(); + let method_span = fcx.tcx().map.def_id_span(method.def_id, span); + span_note!(fcx.sess(), method_span, + "candidate #{} is defined in the trait `{}`", + idx + 1u, + ty::item_path_str(fcx.tcx(), trait_did)); + } + } + } + } +} From 06ad8bb872c93ed0aa1db00cf02b31b0f30d57b3 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Wed, 14 Jan 2015 10:49:17 +1100 Subject: [PATCH 2/3] Implement suggestions for traits to import. For a call like `foo.bar()` where the method `bar` can't be resolved, the compiler will search for traits that have methods with name `bar` to give a more informative error, providing a list of possibilities. Closes #7643. --- src/librustc/session/mod.rs | 3 + src/librustc_typeck/check/method/mod.rs | 2 +- src/librustc_typeck/check/method/suggest.rs | 167 +++++++++++++++++- src/librustc_typeck/lib.rs | 8 + src/libsyntax/diagnostic.rs | 3 + .../auxiliary/no_method_suggested_traits.rs | 38 ++++ .../no-method-suggested-traits.rs | 30 ++++ 7 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 src/test/auxiliary/no_method_suggested_traits.rs create mode 100644 src/test/compile-fail/no-method-suggested-traits.rs diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 27acc39c778..8a7c7b38287 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -174,6 +174,9 @@ impl Session { pub fn fileline_note(&self, sp: Span, msg: &str) { self.diagnostic().fileline_note(sp, msg) } + pub fn fileline_help(&self, sp: Span, msg: &str) { + self.diagnostic().fileline_help(sp, msg) + } pub fn note(&self, msg: &str) { self.diagnostic().handler().note(msg) } diff --git a/src/librustc_typeck/check/method/mod.rs b/src/librustc_typeck/check/method/mod.rs index 8637a915305..a28b4ac3475 100644 --- a/src/librustc_typeck/check/method/mod.rs +++ b/src/librustc_typeck/check/method/mod.rs @@ -29,7 +29,7 @@ use syntax::codemap::Span; pub use self::MethodError::*; pub use self::CandidateSource::*; -pub use self::suggest::report_error; +pub use self::suggest::{report_error, AllTraitsVec}; mod confirm; mod doc; diff --git a/src/librustc_typeck/check/method/suggest.rs b/src/librustc_typeck/check/method/suggest.rs index b6d97d2df9d..aab1fa2a958 100644 --- a/src/librustc_typeck/check/method/suggest.rs +++ b/src/librustc_typeck/check/method/suggest.rs @@ -11,14 +11,21 @@ //! Give useful errors and suggestions to users when a method can't be //! found or is otherwise invalid. +use CrateCtxt; + use astconv::AstConv; use check::{self, FnCtxt}; use middle::ty::{self, Ty}; +use middle::def; +use metadata::{csearch, cstore, decoder}; use util::ppaux::UserString; -use syntax::ast; +use syntax::{ast, ast_util}; use syntax::codemap::Span; +use std::cell; +use std::cmp::Ordering; + use super::{MethodError, CandidateSource, impl_method, trait_method}; pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, @@ -67,6 +74,8 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, report_candidates(fcx, span, method_name, static_sources); } + + suggest_traits_to_import(fcx, span, rcvr_ty, method_name) } MethodError::Ambiguity(sources) => { @@ -120,3 +129,159 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, } } } + + +pub type AllTraitsVec = Vec; + +fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, + span: Span, + _rcvr_ty: Ty<'tcx>, + method_name: ast::Name) +{ + let tcx = fcx.tcx(); + + let mut candidates = all_traits(fcx.ccx) + .filter(|info| trait_method(tcx, info.def_id, method_name).is_some()) + .collect::>(); + + if candidates.len() > 0 { + // sort from most relevant to least relevant + candidates.sort_by(|a, b| a.cmp(b).reverse()); + + let method_ustring = method_name.user_string(tcx); + + span_help!(fcx.sess(), span, + "methods from traits can only be called if the trait is implemented \ + and in scope; the following trait{s} define{inv_s} a method `{name}`:", + s = if candidates.len() == 1 {""} else {"s"}, + inv_s = if candidates.len() == 1 {"s"} else {""}, + name = method_ustring); + + for (i, trait_info) in candidates.iter().enumerate() { + // provide a good-as-possible span; the span of + // the trait if it is local, or the span of the + // method call itself if not + let trait_span = fcx.tcx().map.def_id_span(trait_info.def_id, span); + + fcx.sess().fileline_help(trait_span, + &*format!("candidate #{}: `{}`", + i + 1, + ty::item_path_str(fcx.tcx(), trait_info.def_id))) + } + } +} + +#[derive(Copy)] +pub struct TraitInfo { + def_id: ast::DefId, +} + +impl TraitInfo { + fn new(def_id: ast::DefId) -> TraitInfo { + TraitInfo { + def_id: def_id, + } + } +} +impl PartialEq for TraitInfo { + fn eq(&self, other: &TraitInfo) -> bool { + self.cmp(other) == Ordering::Equal + } +} +impl Eq for TraitInfo {} +impl PartialOrd for TraitInfo { + fn partial_cmp(&self, other: &TraitInfo) -> Option { Some(self.cmp(other)) } +} +impl Ord for TraitInfo { + fn cmp(&self, other: &TraitInfo) -> Ordering { + // accessible traits are more important/relevant than + // inaccessible ones, local crates are more important than + // remote ones (local: cnum == 0), and NodeIds just for + // totality. + + let lhs = (other.def_id.krate, other.def_id.node); + let rhs = (self.def_id.krate, self.def_id.node); + lhs.cmp(&rhs) + } +} + +/// Retrieve all traits in this crate and any dependent crates. +fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> { + if ccx.all_traits.borrow().is_none() { + use syntax::visit; + + let mut traits = vec![]; + + // Crate-local: + // + // meh. + struct Visitor<'a, 'b: 'a, 'tcx: 'a + 'b> { + traits: &'a mut AllTraitsVec, + } + impl<'v,'a, 'b, 'tcx> visit::Visitor<'v> for Visitor<'a, 'b, 'tcx> { + fn visit_item(&mut self, i: &'v ast::Item) { + match i.node { + ast::ItemTrait(..) => { + self.traits.push(TraitInfo::new(ast_util::local_def(i.id))); + } + _ => {} + } + visit::walk_item(self, i) + } + } + visit::walk_crate(&mut Visitor { + traits: &mut traits + }, ccx.tcx.map.krate()); + + // Cross-crate: + fn handle_external_def(traits: &mut AllTraitsVec, + ccx: &CrateCtxt, + cstore: &cstore::CStore, + dl: decoder::DefLike) { + match dl { + decoder::DlDef(def::DefTrait(did)) => { + traits.push(TraitInfo::new(did)); + } + decoder::DlDef(def::DefMod(did)) => { + csearch::each_child_of_item(cstore, did, |dl, _, _| { + handle_external_def(traits, ccx, cstore, dl) + }) + } + _ => {} + } + } + let cstore = &ccx.tcx.sess.cstore; + cstore.iter_crate_data(|&mut: cnum, _| { + csearch::each_top_level_item_of_crate(cstore, cnum, |dl, _, _| { + handle_external_def(&mut traits, ccx, cstore, dl) + }) + }); + + *ccx.all_traits.borrow_mut() = Some(traits); + } + + let borrow = ccx.all_traits.borrow(); + assert!(borrow.is_some()); + AllTraits { + borrow: borrow, + idx: 0 + } +} + +struct AllTraits<'a> { + borrow: cell::Ref<'a Option>, + idx: usize +} + +impl<'a> Iterator for AllTraits<'a> { + type Item = TraitInfo; + + fn next(&mut self) -> Option { + let AllTraits { ref borrow, ref mut idx } = *self; + // ugh. + borrow.as_ref().unwrap().get(*idx).map(|info| { + *idx += 1; + *info + }) + } +} diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs index 68b152dee23..88fe88bf265 100644 --- a/src/librustc_typeck/lib.rs +++ b/src/librustc_typeck/lib.rs @@ -108,6 +108,8 @@ use syntax::print::pprust::*; use syntax::{ast, ast_map, abi}; use syntax::ast_util::local_def; +use std::cell::RefCell; + mod check; mod rscope; mod astconv; @@ -123,6 +125,11 @@ struct TypeAndSubsts<'tcx> { struct CrateCtxt<'a, 'tcx: 'a> { // A mapping from method call sites to traits that have that method. trait_map: ty::TraitMap, + /// A vector of every trait accessible in the whole crate + /// (i.e. including those from subcrates). This is used only for + /// error reporting, and so is lazily initialised and generally + /// shouldn't taint the common path (hence the RefCell). + all_traits: RefCell>, tcx: &'a ty::ctxt<'tcx>, } @@ -320,6 +327,7 @@ pub fn check_crate(tcx: &ty::ctxt, trait_map: ty::TraitMap) { let time_passes = tcx.sess.time_passes(); let ccx = CrateCtxt { trait_map: trait_map, + all_traits: RefCell::new(None), tcx: tcx }; diff --git a/src/libsyntax/diagnostic.rs b/src/libsyntax/diagnostic.rs index 3f81dac2b0d..36058b694ea 100644 --- a/src/libsyntax/diagnostic.rs +++ b/src/libsyntax/diagnostic.rs @@ -118,6 +118,9 @@ impl SpanHandler { pub fn fileline_note(&self, sp: Span, msg: &str) { self.handler.custom_emit(&self.cm, FileLine(sp), msg, Note); } + pub fn fileline_help(&self, sp: Span, msg: &str) { + self.handler.custom_emit(&self.cm, FileLine(sp), msg, Help); + } pub fn span_bug(&self, sp: Span, msg: &str) -> ! { self.handler.emit(Some((&self.cm, sp)), msg, Bug); panic!(ExplicitBug); diff --git a/src/test/auxiliary/no_method_suggested_traits.rs b/src/test/auxiliary/no_method_suggested_traits.rs new file mode 100644 index 00000000000..fdeace00d4c --- /dev/null +++ b/src/test/auxiliary/no_method_suggested_traits.rs @@ -0,0 +1,38 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub use reexport::Reexported; + +pub mod foo { + pub trait PubPub { + fn method(&self); + } +} +pub mod bar { + trait PubPriv { + fn method(&self); + } +} +mod qux { + pub trait PrivPub { + fn method(&self); + } +} +mod quz { + trait PrivPriv { + fn method(&self); + } +} + +mod reexport { + pub trait Reexported { + fn method(&self); + } +} diff --git a/src/test/compile-fail/no-method-suggested-traits.rs b/src/test/compile-fail/no-method-suggested-traits.rs new file mode 100644 index 00000000000..2565d2b7302 --- /dev/null +++ b/src/test/compile-fail/no-method-suggested-traits.rs @@ -0,0 +1,30 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:no_method_suggested_traits.rs + +extern crate no_method_suggested_traits; + +mod foo { + trait Bar { //~ HELP `foo::Bar` + fn method(&self); + } +} + +fn main() { + 1u32.method(); + //~^ ERROR does not implement + //~^^ HELP the following traits define a method `method` + //~^^^ HELP `no_method_suggested_traits::foo::PubPub` + //~^^^^ HELP `no_method_suggested_traits::reexport::Reexported` + //~^^^^^ HELP `no_method_suggested_traits::bar::PubPriv` + //~^^^^^^ HELP `no_method_suggested_traits::qux::PrivPub` + //~^^^^^^^ HELP `no_method_suggested_traits::quz::PrivPriv` +} From 0a55aacc077f26f5703035f7c5395d083db3d355 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 16 Jan 2015 22:27:40 +1100 Subject: [PATCH 3/3] Prefer implemented traits in suggestions. If `a.method();` can't be resolved, we first look for implemented traits globally and suggest those. If there are no such traits found, we only then fall back to suggesting from the unfiltered list of traits. --- src/librustc_typeck/check/method/mod.rs | 7 +- src/librustc_typeck/check/method/probe.rs | 64 ++++++++++++++++++- src/librustc_typeck/check/method/suggest.rs | 57 +++++++++++------ .../auxiliary/no_method_suggested_traits.rs | 7 +- .../no-method-suggested-traits.rs | 46 +++++++++++-- 5 files changed, 149 insertions(+), 32 deletions(-) diff --git a/src/librustc_typeck/check/method/mod.rs b/src/librustc_typeck/check/method/mod.rs index a28b4ac3475..345bc5fd2aa 100644 --- a/src/librustc_typeck/check/method/mod.rs +++ b/src/librustc_typeck/check/method/mod.rs @@ -38,8 +38,9 @@ mod suggest; pub enum MethodError { // Did not find an applicable method, but we did find various - // static methods that may apply. - NoMatch(Vec), + // static methods that may apply, as well as a list of + // not-in-scope traits which may work. + NoMatch(Vec, Vec), // Multiple methods might apply. Ambiguity(Vec), @@ -65,7 +66,7 @@ pub fn exists<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, { match probe::probe(fcx, span, method_name, self_ty, call_expr_id) { Ok(_) => true, - Err(NoMatch(_)) => false, + Err(NoMatch(_, _)) => false, Err(Ambiguity(_)) => true, } } diff --git a/src/librustc_typeck/check/method/probe.rs b/src/librustc_typeck/check/method/probe.rs index dc4d6c9a826..9df8875152e 100644 --- a/src/librustc_typeck/check/method/probe.rs +++ b/src/librustc_typeck/check/method/probe.rs @@ -11,6 +11,7 @@ use super::{MethodError,Ambiguity,NoMatch}; use super::MethodIndex; use super::{CandidateSource,ImplSource,TraitSource}; +use super::suggest; use check; use check::{FnCtxt, NoPreference}; @@ -25,6 +26,7 @@ use middle::infer::InferCtxt; use syntax::ast; use syntax::codemap::{Span, DUMMY_SP}; use std::collections::HashSet; +use std::mem; use std::rc::Rc; use util::ppaux::Repr; @@ -42,6 +44,7 @@ struct ProbeContext<'a, 'tcx:'a> { extension_candidates: Vec>, impl_dups: HashSet, static_candidates: Vec, + all_traits_search: bool, } struct CandidateStep<'tcx> { @@ -127,7 +130,7 @@ pub fn probe<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, // take place in the `fcx.infcx().probe` below. let steps = match create_steps(fcx, span, self_ty) { Some(steps) => steps, - None => return Err(NoMatch(Vec::new())), + None => return Err(NoMatch(Vec::new(), Vec::new())), }; // Create a list of simplified self types, if we can. @@ -208,9 +211,17 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> { steps: Rc::new(steps), opt_simplified_steps: opt_simplified_steps, static_candidates: Vec::new(), + all_traits_search: false, } } + fn reset(&mut self) { + self.inherent_candidates.clear(); + self.extension_candidates.clear(); + self.impl_dups.clear(); + self.static_candidates.clear(); + } + fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.fcx.tcx() } @@ -446,6 +457,15 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> { } } + fn assemble_extension_candidates_for_all_traits(&mut self) { + let mut duplicates = HashSet::new(); + for trait_info in suggest::all_traits(self.fcx.ccx) { + if duplicates.insert(trait_info.def_id) { + self.assemble_extension_candidates_for_trait(trait_info.def_id) + } + } + } + fn assemble_extension_candidates_for_trait(&mut self, trait_def_id: ast::DefId) { debug!("assemble_extension_candidates_for_trait(trait_def_id={})", @@ -715,7 +735,47 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> { } } - Err(NoMatch(self.static_candidates)) + let static_candidates = mem::replace(&mut self.static_candidates, vec![]); + + let out_of_scope_traits = if !self.all_traits_search { + // things failed, and we haven't yet looked through all + // traits, so lets do that now: + self.reset(); + self.all_traits_search = true; + + let span = self.span; + let tcx = self.tcx(); + + self.assemble_extension_candidates_for_all_traits(); + + match self.pick() { + Ok(p) => vec![p.method_ty.container.id()], + Err(Ambiguity(v)) => v.into_iter().map(|source| { + match source { + TraitSource(id) => id, + ImplSource(impl_id) => { + match ty::trait_id_of_impl(tcx, impl_id) { + Some(id) => id, + None => tcx.sess.span_bug(span, + "found inherent method when looking \ + at traits") + } + } + } + }).collect(), + // it'd be really weird for this assertion to trigger, + // given the `vec![]` in the else branch below + Err(NoMatch(_, others)) => { + assert!(others.is_empty()); + vec![] + } + } + } else { + // we've just looked through all traits and didn't find + // anything at all. + vec![] + }; + Err(NoMatch(static_candidates, out_of_scope_traits)) } fn pick_step(&mut self, step: &CandidateStep<'tcx>) -> Option> { diff --git a/src/librustc_typeck/check/method/suggest.rs b/src/librustc_typeck/check/method/suggest.rs index aab1fa2a958..013c6e2f953 100644 --- a/src/librustc_typeck/check/method/suggest.rs +++ b/src/librustc_typeck/check/method/suggest.rs @@ -35,7 +35,7 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, error: MethodError) { match error { - MethodError::NoMatch(static_sources) => { + MethodError::NoMatch(static_sources, out_of_scope_traits) => { let cx = fcx.tcx(); let method_ustring = method_name.user_string(cx); @@ -75,7 +75,7 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, report_candidates(fcx, span, method_name, static_sources); } - suggest_traits_to_import(fcx, span, rcvr_ty, method_name) + suggest_traits_to_import(fcx, span, rcvr_ty, method_name, out_of_scope_traits) } MethodError::Ambiguity(sources) => { @@ -136,10 +136,35 @@ pub type AllTraitsVec = Vec; fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, span: Span, _rcvr_ty: Ty<'tcx>, - method_name: ast::Name) + method_name: ast::Name, + valid_out_of_scope_traits: Vec) { let tcx = fcx.tcx(); + let method_ustring = method_name.user_string(tcx); + if !valid_out_of_scope_traits.is_empty() { + let mut candidates = valid_out_of_scope_traits; + candidates.sort(); + let msg = format!( + "methods from traits can only be called if the trait is in scope; \ + the following {traits_are} implemented and {define} a method `{name}`:", + traits_are = if candidates.len() == 1 {"trait is"} else {"traits are"}, + define = if candidates.len() == 1 {"defines"} else {"define"}, + name = method_ustring); + + fcx.sess().fileline_help(span, &msg[]); + + for (i, trait_did) in candidates.iter().enumerate() { + fcx.sess().fileline_help(span, + &*format!("candidate #{}: `{}`", + i + 1, + ty::item_path_str(fcx.tcx(), *trait_did))) + + } + return + } + + // there's no implemented traits, so lets suggest some traits to implement let mut candidates = all_traits(fcx.ccx) .filter(|info| trait_method(tcx, info.def_id, method_name).is_some()) .collect::>(); @@ -148,22 +173,16 @@ fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, // sort from most relevant to least relevant candidates.sort_by(|a, b| a.cmp(b).reverse()); - let method_ustring = method_name.user_string(tcx); + let msg = format!( + "methods from traits can only be called if the trait is implemented and \ + in scope; no such traits are but the following {traits_define} a method `{name}`:", + traits_define = if candidates.len() == 1 {"trait defines"} else {"traits define"}, + name = method_ustring); - span_help!(fcx.sess(), span, - "methods from traits can only be called if the trait is implemented \ - and in scope; the following trait{s} define{inv_s} a method `{name}`:", - s = if candidates.len() == 1 {""} else {"s"}, - inv_s = if candidates.len() == 1 {"s"} else {""}, - name = method_ustring); + fcx.sess().fileline_help(span, &msg[]); for (i, trait_info) in candidates.iter().enumerate() { - // provide a good-as-possible span; the span of - // the trait if it is local, or the span of the - // method call itself if not - let trait_span = fcx.tcx().map.def_id_span(trait_info.def_id, span); - - fcx.sess().fileline_help(trait_span, + fcx.sess().fileline_help(span, &*format!("candidate #{}: `{}`", i + 1, ty::item_path_str(fcx.tcx(), trait_info.def_id))) @@ -173,7 +192,7 @@ fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, #[derive(Copy)] pub struct TraitInfo { - def_id: ast::DefId, + pub def_id: ast::DefId, } impl TraitInfo { @@ -206,7 +225,7 @@ impl Ord for TraitInfo { } /// Retrieve all traits in this crate and any dependent crates. -fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> { +pub fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> { if ccx.all_traits.borrow().is_none() { use syntax::visit; @@ -268,7 +287,7 @@ fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> { } } -struct AllTraits<'a> { +pub struct AllTraits<'a> { borrow: cell::Ref<'a Option>, idx: usize } diff --git a/src/test/auxiliary/no_method_suggested_traits.rs b/src/test/auxiliary/no_method_suggested_traits.rs index fdeace00d4c..328561495ee 100644 --- a/src/test/auxiliary/no_method_suggested_traits.rs +++ b/src/test/auxiliary/no_method_suggested_traits.rs @@ -12,8 +12,13 @@ pub use reexport::Reexported; pub mod foo { pub trait PubPub { - fn method(&self); + fn method(&self) {} + + fn method3(&self) {} } + + impl PubPub for u32 {} + impl PubPub for i32 {} } pub mod bar { trait PubPriv { diff --git a/src/test/compile-fail/no-method-suggested-traits.rs b/src/test/compile-fail/no-method-suggested-traits.rs index 2565d2b7302..277800778a8 100644 --- a/src/test/compile-fail/no-method-suggested-traits.rs +++ b/src/test/compile-fail/no-method-suggested-traits.rs @@ -13,18 +13,50 @@ extern crate no_method_suggested_traits; mod foo { - trait Bar { //~ HELP `foo::Bar` - fn method(&self); + trait Bar { + fn method(&self) {} + + fn method2(&self) {} } + + impl Bar for u32 {} + + impl Bar for char {} } fn main() { 1u32.method(); //~^ ERROR does not implement - //~^^ HELP the following traits define a method `method` + //~^^ HELP the following traits are implemented and define a method `method` + //~^^^ HELP `foo::Bar` + //~^^^^ HELP `no_method_suggested_traits::foo::PubPub` + + 'a'.method(); + //~^ ERROR does not implement + //~^^ HELP the following trait is implemented and defines a method `method` + //~^^^ HELP `foo::Bar` + + 1i32.method(); + //~^ ERROR does not implement + //~^^ HELP the following trait is implemented and defines a method `method` + //~^^^ HELP `no_method_suggested_traits::foo::PubPub` + + 1u64.method(); + //~^ ERROR does not implement + //~^^ HELP the following traits define a method `method` + //~^^^ HELP `foo::Bar` + //~^^^^ HELP `no_method_suggested_traits::foo::PubPub` + //~^^^^^ HELP `no_method_suggested_traits::reexport::Reexported` + //~^^^^^^ HELP `no_method_suggested_traits::bar::PubPriv` + //~^^^^^^^ HELP `no_method_suggested_traits::qux::PrivPub` + //~^^^^^^^^ HELP `no_method_suggested_traits::quz::PrivPriv` + + 1u64.method2(); + //~^ ERROR does not implement + //~^^ HELP the following trait defines a method `method2` + //~^^^ HELP `foo::Bar` + 1u64.method3(); + //~^ ERROR does not implement + //~^^ HELP the following trait defines a method `method3` //~^^^ HELP `no_method_suggested_traits::foo::PubPub` - //~^^^^ HELP `no_method_suggested_traits::reexport::Reexported` - //~^^^^^ HELP `no_method_suggested_traits::bar::PubPriv` - //~^^^^^^ HELP `no_method_suggested_traits::qux::PrivPub` - //~^^^^^^^ HELP `no_method_suggested_traits::quz::PrivPriv` }