auto merge of #21008 : huonw/rust/trait-suggestions, r=nikomatsakis

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:
bors 2015-01-16 22:41:16 +00:00
commit ed530d7a3b
8 changed files with 495 additions and 106 deletions

View File

@ -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)
}

View File

@ -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<CandidateSource>),
// static methods that may apply, as well as a list of
// not-in-scope traits which may work.
NoMatch(Vec<CandidateSource>, Vec<ast::DefId>),
// Multiple methods might apply.
Ambiguity(Vec<CandidateSource>),
@ -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<CandidateSource>) {
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).

View File

@ -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<Candidate<'tcx>>,
impl_dups: HashSet<ast::DefId>,
static_candidates: Vec<CandidateSource>,
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<PickResult<'tcx>> {

View File

@ -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 <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.
//! 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<CandidateSource>) {
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<TraitInfo>;
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<ast::DefId>)
{
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::<Vec<_>>();
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<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.
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<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
})
}
}

View File

@ -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<Option<check::method::AllTraitsVec>>,
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
};

View File

@ -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);

View File

@ -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 <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) {}
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);
}
}

View File

@ -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 <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 {
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`
}