From 06ad8bb872c93ed0aa1db00cf02b31b0f30d57b3 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Wed, 14 Jan 2015 10:49:17 +1100 Subject: [PATCH] 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 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 8637a915305db..a28b4ac34753b 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 b6d97d2df9dec..aab1fa2a95817 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 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..fdeace00d4ca2 --- /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 0000000000000..2565d2b730267 --- /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` +}