Skip to content

Commit

Permalink
auto merge of #21008 : huonw/rust/trait-suggestions, r=nikomatsakis
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Jan 16, 2015
2 parents 653e688 + 0a55aac commit ed530d7
Show file tree
Hide file tree
Showing 8 changed files with 495 additions and 106 deletions.
3 changes: 3 additions & 0 deletions src/librustc/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
112 changes: 8 additions & 104 deletions src/librustc_typeck/check/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@
use astconv::AstConv;
use check::{FnCtxt};
use check::{impl_self_ty};
use check::vtable;
use check::vtable::select_new_fcx_obligations;
use middle::subst;
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};
Expand All @@ -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>),
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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).
Expand Down
64 changes: 62 additions & 2 deletions src/librustc_typeck/check/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;

Expand All @@ -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> {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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={})",
Expand Down Expand Up @@ -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>> {
Expand Down
Loading

0 comments on commit ed530d7

Please sign in to comment.