Skip to content

Commit 0a55aac

Browse files
committed
Prefer implemented traits in suggestions.
If `a.method();` can't be resolved, we first look for implemented traits globally and suggest those. If there are no such traits found, we only then fall back to suggesting from the unfiltered list of traits.
1 parent 06ad8bb commit 0a55aac

File tree

5 files changed

+148
-31
lines changed

5 files changed

+148
-31
lines changed

src/librustc_typeck/check/method/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ mod suggest;
3838

3939
pub enum MethodError {
4040
// Did not find an applicable method, but we did find various
41-
// static methods that may apply.
42-
NoMatch(Vec<CandidateSource>),
41+
// static methods that may apply, as well as a list of
42+
// not-in-scope traits which may work.
43+
NoMatch(Vec<CandidateSource>, Vec<ast::DefId>),
4344

4445
// Multiple methods might apply.
4546
Ambiguity(Vec<CandidateSource>),
@@ -65,7 +66,7 @@ pub fn exists<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
6566
{
6667
match probe::probe(fcx, span, method_name, self_ty, call_expr_id) {
6768
Ok(_) => true,
68-
Err(NoMatch(_)) => false,
69+
Err(NoMatch(_, _)) => false,
6970
Err(Ambiguity(_)) => true,
7071
}
7172
}

src/librustc_typeck/check/method/probe.rs

+62-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use super::{MethodError,Ambiguity,NoMatch};
1212
use super::MethodIndex;
1313
use super::{CandidateSource,ImplSource,TraitSource};
14+
use super::suggest;
1415

1516
use check;
1617
use check::{FnCtxt, NoPreference};
@@ -25,6 +26,7 @@ use middle::infer::InferCtxt;
2526
use syntax::ast;
2627
use syntax::codemap::{Span, DUMMY_SP};
2728
use std::collections::HashSet;
29+
use std::mem;
2830
use std::rc::Rc;
2931
use util::ppaux::Repr;
3032

@@ -42,6 +44,7 @@ struct ProbeContext<'a, 'tcx:'a> {
4244
extension_candidates: Vec<Candidate<'tcx>>,
4345
impl_dups: HashSet<ast::DefId>,
4446
static_candidates: Vec<CandidateSource>,
47+
all_traits_search: bool,
4548
}
4649

4750
struct CandidateStep<'tcx> {
@@ -127,7 +130,7 @@ pub fn probe<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
127130
// take place in the `fcx.infcx().probe` below.
128131
let steps = match create_steps(fcx, span, self_ty) {
129132
Some(steps) => steps,
130-
None => return Err(NoMatch(Vec::new())),
133+
None => return Err(NoMatch(Vec::new(), Vec::new())),
131134
};
132135

133136
// Create a list of simplified self types, if we can.
@@ -208,9 +211,17 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
208211
steps: Rc::new(steps),
209212
opt_simplified_steps: opt_simplified_steps,
210213
static_candidates: Vec::new(),
214+
all_traits_search: false,
211215
}
212216
}
213217

218+
fn reset(&mut self) {
219+
self.inherent_candidates.clear();
220+
self.extension_candidates.clear();
221+
self.impl_dups.clear();
222+
self.static_candidates.clear();
223+
}
224+
214225
fn tcx(&self) -> &'a ty::ctxt<'tcx> {
215226
self.fcx.tcx()
216227
}
@@ -446,6 +457,15 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
446457
}
447458
}
448459

460+
fn assemble_extension_candidates_for_all_traits(&mut self) {
461+
let mut duplicates = HashSet::new();
462+
for trait_info in suggest::all_traits(self.fcx.ccx) {
463+
if duplicates.insert(trait_info.def_id) {
464+
self.assemble_extension_candidates_for_trait(trait_info.def_id)
465+
}
466+
}
467+
}
468+
449469
fn assemble_extension_candidates_for_trait(&mut self,
450470
trait_def_id: ast::DefId) {
451471
debug!("assemble_extension_candidates_for_trait(trait_def_id={})",
@@ -715,7 +735,47 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> {
715735
}
716736
}
717737

718-
Err(NoMatch(self.static_candidates))
738+
let static_candidates = mem::replace(&mut self.static_candidates, vec![]);
739+
740+
let out_of_scope_traits = if !self.all_traits_search {
741+
// things failed, and we haven't yet looked through all
742+
// traits, so lets do that now:
743+
self.reset();
744+
self.all_traits_search = true;
745+
746+
let span = self.span;
747+
let tcx = self.tcx();
748+
749+
self.assemble_extension_candidates_for_all_traits();
750+
751+
match self.pick() {
752+
Ok(p) => vec![p.method_ty.container.id()],
753+
Err(Ambiguity(v)) => v.into_iter().map(|source| {
754+
match source {
755+
TraitSource(id) => id,
756+
ImplSource(impl_id) => {
757+
match ty::trait_id_of_impl(tcx, impl_id) {
758+
Some(id) => id,
759+
None => tcx.sess.span_bug(span,
760+
"found inherent method when looking \
761+
at traits")
762+
}
763+
}
764+
}
765+
}).collect(),
766+
// it'd be really weird for this assertion to trigger,
767+
// given the `vec![]` in the else branch below
768+
Err(NoMatch(_, others)) => {
769+
assert!(others.is_empty());
770+
vec![]
771+
}
772+
}
773+
} else {
774+
// we've just looked through all traits and didn't find
775+
// anything at all.
776+
vec![]
777+
};
778+
Err(NoMatch(static_candidates, out_of_scope_traits))
719779
}
720780

721781
fn pick_step(&mut self, step: &CandidateStep<'tcx>) -> Option<PickResult<'tcx>> {

src/librustc_typeck/check/method/suggest.rs

+38-19
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
3535
error: MethodError)
3636
{
3737
match error {
38-
MethodError::NoMatch(static_sources) => {
38+
MethodError::NoMatch(static_sources, out_of_scope_traits) => {
3939
let cx = fcx.tcx();
4040
let method_ustring = method_name.user_string(cx);
4141

@@ -75,7 +75,7 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
7575
report_candidates(fcx, span, method_name, static_sources);
7676
}
7777

78-
suggest_traits_to_import(fcx, span, rcvr_ty, method_name)
78+
suggest_traits_to_import(fcx, span, rcvr_ty, method_name, out_of_scope_traits)
7979
}
8080

8181
MethodError::Ambiguity(sources) => {
@@ -136,10 +136,35 @@ pub type AllTraitsVec = Vec<TraitInfo>;
136136
fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
137137
span: Span,
138138
_rcvr_ty: Ty<'tcx>,
139-
method_name: ast::Name)
139+
method_name: ast::Name,
140+
valid_out_of_scope_traits: Vec<ast::DefId>)
140141
{
141142
let tcx = fcx.tcx();
143+
let method_ustring = method_name.user_string(tcx);
144+
145+
if !valid_out_of_scope_traits.is_empty() {
146+
let mut candidates = valid_out_of_scope_traits;
147+
candidates.sort();
148+
let msg = format!(
149+
"methods from traits can only be called if the trait is in scope; \
150+
the following {traits_are} implemented and {define} a method `{name}`:",
151+
traits_are = if candidates.len() == 1 {"trait is"} else {"traits are"},
152+
define = if candidates.len() == 1 {"defines"} else {"define"},
153+
name = method_ustring);
154+
155+
fcx.sess().fileline_help(span, &msg[]);
156+
157+
for (i, trait_did) in candidates.iter().enumerate() {
158+
fcx.sess().fileline_help(span,
159+
&*format!("candidate #{}: `{}`",
160+
i + 1,
161+
ty::item_path_str(fcx.tcx(), *trait_did)))
162+
163+
}
164+
return
165+
}
142166

167+
// there's no implemented traits, so lets suggest some traits to implement
143168
let mut candidates = all_traits(fcx.ccx)
144169
.filter(|info| trait_method(tcx, info.def_id, method_name).is_some())
145170
.collect::<Vec<_>>();
@@ -148,22 +173,16 @@ fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
148173
// sort from most relevant to least relevant
149174
candidates.sort_by(|a, b| a.cmp(b).reverse());
150175

151-
let method_ustring = method_name.user_string(tcx);
176+
let msg = format!(
177+
"methods from traits can only be called if the trait is implemented and \
178+
in scope; no such traits are but the following {traits_define} a method `{name}`:",
179+
traits_define = if candidates.len() == 1 {"trait defines"} else {"traits define"},
180+
name = method_ustring);
152181

153-
span_help!(fcx.sess(), span,
154-
"methods from traits can only be called if the trait is implemented \
155-
and in scope; the following trait{s} define{inv_s} a method `{name}`:",
156-
s = if candidates.len() == 1 {""} else {"s"},
157-
inv_s = if candidates.len() == 1 {"s"} else {""},
158-
name = method_ustring);
182+
fcx.sess().fileline_help(span, &msg[]);
159183

160184
for (i, trait_info) in candidates.iter().enumerate() {
161-
// provide a good-as-possible span; the span of
162-
// the trait if it is local, or the span of the
163-
// method call itself if not
164-
let trait_span = fcx.tcx().map.def_id_span(trait_info.def_id, span);
165-
166-
fcx.sess().fileline_help(trait_span,
185+
fcx.sess().fileline_help(span,
167186
&*format!("candidate #{}: `{}`",
168187
i + 1,
169188
ty::item_path_str(fcx.tcx(), trait_info.def_id)))
@@ -173,7 +192,7 @@ fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
173192

174193
#[derive(Copy)]
175194
pub struct TraitInfo {
176-
def_id: ast::DefId,
195+
pub def_id: ast::DefId,
177196
}
178197

179198
impl TraitInfo {
@@ -206,7 +225,7 @@ impl Ord for TraitInfo {
206225
}
207226

208227
/// Retrieve all traits in this crate and any dependent crates.
209-
fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> {
228+
pub fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> {
210229
if ccx.all_traits.borrow().is_none() {
211230
use syntax::visit;
212231

@@ -268,7 +287,7 @@ fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> {
268287
}
269288
}
270289

271-
struct AllTraits<'a> {
290+
pub struct AllTraits<'a> {
272291
borrow: cell::Ref<'a Option<AllTraitsVec>>,
273292
idx: usize
274293
}

src/test/auxiliary/no_method_suggested_traits.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ pub use reexport::Reexported;
1212

1313
pub mod foo {
1414
pub trait PubPub {
15-
fn method(&self);
15+
fn method(&self) {}
16+
17+
fn method3(&self) {}
1618
}
19+
20+
impl PubPub for u32 {}
21+
impl PubPub for i32 {}
1722
}
1823
pub mod bar {
1924
trait PubPriv {

src/test/compile-fail/no-method-suggested-traits.rs

+38-6
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,50 @@
1313
extern crate no_method_suggested_traits;
1414

1515
mod foo {
16-
trait Bar { //~ HELP `foo::Bar`
17-
fn method(&self);
16+
trait Bar {
17+
fn method(&self) {}
18+
19+
fn method2(&self) {}
1820
}
21+
22+
impl Bar for u32 {}
23+
24+
impl Bar for char {}
1925
}
2026

2127
fn main() {
2228
1u32.method();
2329
//~^ ERROR does not implement
30+
//~^^ HELP the following traits are implemented and define a method `method`
31+
//~^^^ HELP `foo::Bar`
32+
//~^^^^ HELP `no_method_suggested_traits::foo::PubPub`
33+
34+
'a'.method();
35+
//~^ ERROR does not implement
36+
//~^^ HELP the following trait is implemented and defines a method `method`
37+
//~^^^ HELP `foo::Bar`
38+
39+
1i32.method();
40+
//~^ ERROR does not implement
41+
//~^^ HELP the following trait is implemented and defines a method `method`
42+
//~^^^ HELP `no_method_suggested_traits::foo::PubPub`
43+
44+
1u64.method();
45+
//~^ ERROR does not implement
2446
//~^^ HELP the following traits define a method `method`
47+
//~^^^ HELP `foo::Bar`
48+
//~^^^^ HELP `no_method_suggested_traits::foo::PubPub`
49+
//~^^^^^ HELP `no_method_suggested_traits::reexport::Reexported`
50+
//~^^^^^^ HELP `no_method_suggested_traits::bar::PubPriv`
51+
//~^^^^^^^ HELP `no_method_suggested_traits::qux::PrivPub`
52+
//~^^^^^^^^ HELP `no_method_suggested_traits::quz::PrivPriv`
53+
54+
1u64.method2();
55+
//~^ ERROR does not implement
56+
//~^^ HELP the following trait defines a method `method2`
57+
//~^^^ HELP `foo::Bar`
58+
1u64.method3();
59+
//~^ ERROR does not implement
60+
//~^^ HELP the following trait defines a method `method3`
2561
//~^^^ HELP `no_method_suggested_traits::foo::PubPub`
26-
//~^^^^ HELP `no_method_suggested_traits::reexport::Reexported`
27-
//~^^^^^ HELP `no_method_suggested_traits::bar::PubPriv`
28-
//~^^^^^^ HELP `no_method_suggested_traits::qux::PrivPub`
29-
//~^^^^^^^ HELP `no_method_suggested_traits::quz::PrivPriv`
3062
}

0 commit comments

Comments
 (0)