diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 27acc39c77863..8a7c7b38287ec 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 0c53a16a8118b..345bc5fd2aa60 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,14 +29,18 @@ use syntax::codemap::Span; pub use self::MethodError::*; pub use self::CandidateSource::*; +pub use self::suggest::{report_error, AllTraitsVec}; + mod confirm; mod doc; mod probe; +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), @@ -63,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, } } @@ -294,105 +297,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/probe.rs b/src/librustc_typeck/check/method/probe.rs index dc4d6c9a826c9..9df8875152e3b 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 new file mode 100644 index 0000000000000..013c6e2f953a0 --- /dev/null +++ b/src/librustc_typeck/check/method/suggest.rs @@ -0,0 +1,306 @@ +// 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 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, 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>, + span: Span, + rcvr_ty: Ty<'tcx>, + method_name: ast::Name, + error: MethodError) +{ + match error { + MethodError::NoMatch(static_sources, out_of_scope_traits) => { + 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); + } + + suggest_traits_to_import(fcx, span, rcvr_ty, method_name, out_of_scope_traits) + } + + 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)); + } + } + } + } +} + + +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, + 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::>(); + + if candidates.len() > 0 { + // sort from most relevant to least relevant + candidates.sort_by(|a, b| a.cmp(b).reverse()); + + 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); + + fcx.sess().fileline_help(span, &msg[]); + + for (i, trait_info) in candidates.iter().enumerate() { + fcx.sess().fileline_help(span, + &*format!("candidate #{}: `{}`", + i + 1, + ty::item_path_str(fcx.tcx(), trait_info.def_id))) + } + } +} + +#[derive(Copy)] +pub struct TraitInfo { + pub 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. +pub 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 + } +} + +pub 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 68b152dee233b..88fe88bf26540 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 3f81dac2b0d62..36058b694eac4 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 0000000000000..328561495eef0 --- /dev/null +++ b/src/test/auxiliary/no_method_suggested_traits.rs @@ -0,0 +1,43 @@ +// 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) {} + + fn method3(&self) {} + } + + impl PubPub for u32 {} + impl PubPub for i32 {} +} +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 0000000000000..277800778a87e --- /dev/null +++ b/src/test/compile-fail/no-method-suggested-traits.rs @@ -0,0 +1,62 @@ +// 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 { + 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 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` +}