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.
This commit is contained in:
@@ -174,6 +174,9 @@ impl Session {
|
|||||||
pub fn fileline_note(&self, sp: Span, msg: &str) {
|
pub fn fileline_note(&self, sp: Span, msg: &str) {
|
||||||
self.diagnostic().fileline_note(sp, msg)
|
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) {
|
pub fn note(&self, msg: &str) {
|
||||||
self.diagnostic().handler().note(msg)
|
self.diagnostic().handler().note(msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use syntax::codemap::Span;
|
|||||||
pub use self::MethodError::*;
|
pub use self::MethodError::*;
|
||||||
pub use self::CandidateSource::*;
|
pub use self::CandidateSource::*;
|
||||||
|
|
||||||
pub use self::suggest::report_error;
|
pub use self::suggest::{report_error, AllTraitsVec};
|
||||||
|
|
||||||
mod confirm;
|
mod confirm;
|
||||||
mod doc;
|
mod doc;
|
||||||
|
|||||||
@@ -11,14 +11,21 @@
|
|||||||
//! Give useful errors and suggestions to users when a method can't be
|
//! Give useful errors and suggestions to users when a method can't be
|
||||||
//! found or is otherwise invalid.
|
//! found or is otherwise invalid.
|
||||||
|
|
||||||
|
use CrateCtxt;
|
||||||
|
|
||||||
use astconv::AstConv;
|
use astconv::AstConv;
|
||||||
use check::{self, FnCtxt};
|
use check::{self, FnCtxt};
|
||||||
use middle::ty::{self, Ty};
|
use middle::ty::{self, Ty};
|
||||||
|
use middle::def;
|
||||||
|
use metadata::{csearch, cstore, decoder};
|
||||||
use util::ppaux::UserString;
|
use util::ppaux::UserString;
|
||||||
|
|
||||||
use syntax::ast;
|
use syntax::{ast, ast_util};
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
|
|
||||||
|
use std::cell;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::{MethodError, CandidateSource, impl_method, trait_method};
|
use super::{MethodError, CandidateSource, impl_method, trait_method};
|
||||||
|
|
||||||
pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
|
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);
|
report_candidates(fcx, span, method_name, static_sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suggest_traits_to_import(fcx, span, rcvr_ty, method_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodError::Ambiguity(sources) => {
|
MethodError::Ambiguity(sources) => {
|
||||||
@@ -120,3 +129,159 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub type AllTraitsVec = Vec<TraitInfo>;
|
||||||
|
|
||||||
|
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::<Vec<_>>();
|
||||||
|
|
||||||
|
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<Ordering> { 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<AllTraitsVec>>,
|
||||||
|
idx: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for AllTraits<'a> {
|
||||||
|
type Item = TraitInfo;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<TraitInfo> {
|
||||||
|
let AllTraits { ref borrow, ref mut idx } = *self;
|
||||||
|
// ugh.
|
||||||
|
borrow.as_ref().unwrap().get(*idx).map(|info| {
|
||||||
|
*idx += 1;
|
||||||
|
*info
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ use syntax::print::pprust::*;
|
|||||||
use syntax::{ast, ast_map, abi};
|
use syntax::{ast, ast_map, abi};
|
||||||
use syntax::ast_util::local_def;
|
use syntax::ast_util::local_def;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
mod check;
|
mod check;
|
||||||
mod rscope;
|
mod rscope;
|
||||||
mod astconv;
|
mod astconv;
|
||||||
@@ -123,6 +125,11 @@ struct TypeAndSubsts<'tcx> {
|
|||||||
struct CrateCtxt<'a, 'tcx: 'a> {
|
struct CrateCtxt<'a, 'tcx: 'a> {
|
||||||
// A mapping from method call sites to traits that have that method.
|
// A mapping from method call sites to traits that have that method.
|
||||||
trait_map: ty::TraitMap,
|
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<Option<check::method::AllTraitsVec>>,
|
||||||
tcx: &'a ty::ctxt<'tcx>,
|
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 time_passes = tcx.sess.time_passes();
|
||||||
let ccx = CrateCtxt {
|
let ccx = CrateCtxt {
|
||||||
trait_map: trait_map,
|
trait_map: trait_map,
|
||||||
|
all_traits: RefCell::new(None),
|
||||||
tcx: tcx
|
tcx: tcx
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ impl SpanHandler {
|
|||||||
pub fn fileline_note(&self, sp: Span, msg: &str) {
|
pub fn fileline_note(&self, sp: Span, msg: &str) {
|
||||||
self.handler.custom_emit(&self.cm, FileLine(sp), msg, Note);
|
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) -> ! {
|
pub fn span_bug(&self, sp: Span, msg: &str) -> ! {
|
||||||
self.handler.emit(Some((&self.cm, sp)), msg, Bug);
|
self.handler.emit(Some((&self.cm, sp)), msg, Bug);
|
||||||
panic!(ExplicitBug);
|
panic!(ExplicitBug);
|
||||||
|
|||||||
38
src/test/auxiliary/no_method_suggested_traits.rs
Normal file
38
src/test/auxiliary/no_method_suggested_traits.rs
Normal file
@@ -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 <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.
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/test/compile-fail/no-method-suggested-traits.rs
Normal file
30
src/test/compile-fail/no-method-suggested-traits.rs
Normal file
@@ -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 <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.
|
||||||
|
|
||||||
|
// 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`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user