Skip to content

Commit 06ad8bb

Browse files
committed
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.
1 parent af506fa commit 06ad8bb

File tree

7 files changed

+249
-2
lines changed

7 files changed

+249
-2
lines changed

src/librustc/session/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ impl Session {
174174
pub fn fileline_note(&self, sp: Span, msg: &str) {
175175
self.diagnostic().fileline_note(sp, msg)
176176
}
177+
pub fn fileline_help(&self, sp: Span, msg: &str) {
178+
self.diagnostic().fileline_help(sp, msg)
179+
}
177180
pub fn note(&self, msg: &str) {
178181
self.diagnostic().handler().note(msg)
179182
}

src/librustc_typeck/check/method/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use syntax::codemap::Span;
2929
pub use self::MethodError::*;
3030
pub use self::CandidateSource::*;
3131

32-
pub use self::suggest::report_error;
32+
pub use self::suggest::{report_error, AllTraitsVec};
3333

3434
mod confirm;
3535
mod doc;

src/librustc_typeck/check/method/suggest.rs

+166-1
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@
1111
//! Give useful errors and suggestions to users when a method can't be
1212
//! found or is otherwise invalid.
1313
14+
use CrateCtxt;
15+
1416
use astconv::AstConv;
1517
use check::{self, FnCtxt};
1618
use middle::ty::{self, Ty};
19+
use middle::def;
20+
use metadata::{csearch, cstore, decoder};
1721
use util::ppaux::UserString;
1822

19-
use syntax::ast;
23+
use syntax::{ast, ast_util};
2024
use syntax::codemap::Span;
2125

26+
use std::cell;
27+
use std::cmp::Ordering;
28+
2229
use super::{MethodError, CandidateSource, impl_method, trait_method};
2330

2431
pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
@@ -67,6 +74,8 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
6774

6875
report_candidates(fcx, span, method_name, static_sources);
6976
}
77+
78+
suggest_traits_to_import(fcx, span, rcvr_ty, method_name)
7079
}
7180

7281
MethodError::Ambiguity(sources) => {
@@ -120,3 +129,159 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
120129
}
121130
}
122131
}
132+
133+
134+
pub type AllTraitsVec = Vec<TraitInfo>;
135+
136+
fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
137+
span: Span,
138+
_rcvr_ty: Ty<'tcx>,
139+
method_name: ast::Name)
140+
{
141+
let tcx = fcx.tcx();
142+
143+
let mut candidates = all_traits(fcx.ccx)
144+
.filter(|info| trait_method(tcx, info.def_id, method_name).is_some())
145+
.collect::<Vec<_>>();
146+
147+
if candidates.len() > 0 {
148+
// sort from most relevant to least relevant
149+
candidates.sort_by(|a, b| a.cmp(b).reverse());
150+
151+
let method_ustring = method_name.user_string(tcx);
152+
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);
159+
160+
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,
167+
&*format!("candidate #{}: `{}`",
168+
i + 1,
169+
ty::item_path_str(fcx.tcx(), trait_info.def_id)))
170+
}
171+
}
172+
}
173+
174+
#[derive(Copy)]
175+
pub struct TraitInfo {
176+
def_id: ast::DefId,
177+
}
178+
179+
impl TraitInfo {
180+
fn new(def_id: ast::DefId) -> TraitInfo {
181+
TraitInfo {
182+
def_id: def_id,
183+
}
184+
}
185+
}
186+
impl PartialEq for TraitInfo {
187+
fn eq(&self, other: &TraitInfo) -> bool {
188+
self.cmp(other) == Ordering::Equal
189+
}
190+
}
191+
impl Eq for TraitInfo {}
192+
impl PartialOrd for TraitInfo {
193+
fn partial_cmp(&self, other: &TraitInfo) -> Option<Ordering> { Some(self.cmp(other)) }
194+
}
195+
impl Ord for TraitInfo {
196+
fn cmp(&self, other: &TraitInfo) -> Ordering {
197+
// accessible traits are more important/relevant than
198+
// inaccessible ones, local crates are more important than
199+
// remote ones (local: cnum == 0), and NodeIds just for
200+
// totality.
201+
202+
let lhs = (other.def_id.krate, other.def_id.node);
203+
let rhs = (self.def_id.krate, self.def_id.node);
204+
lhs.cmp(&rhs)
205+
}
206+
}
207+
208+
/// Retrieve all traits in this crate and any dependent crates.
209+
fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> {
210+
if ccx.all_traits.borrow().is_none() {
211+
use syntax::visit;
212+
213+
let mut traits = vec![];
214+
215+
// Crate-local:
216+
//
217+
// meh.
218+
struct Visitor<'a, 'b: 'a, 'tcx: 'a + 'b> {
219+
traits: &'a mut AllTraitsVec,
220+
}
221+
impl<'v,'a, 'b, 'tcx> visit::Visitor<'v> for Visitor<'a, 'b, 'tcx> {
222+
fn visit_item(&mut self, i: &'v ast::Item) {
223+
match i.node {
224+
ast::ItemTrait(..) => {
225+
self.traits.push(TraitInfo::new(ast_util::local_def(i.id)));
226+
}
227+
_ => {}
228+
}
229+
visit::walk_item(self, i)
230+
}
231+
}
232+
visit::walk_crate(&mut Visitor {
233+
traits: &mut traits
234+
}, ccx.tcx.map.krate());
235+
236+
// Cross-crate:
237+
fn handle_external_def(traits: &mut AllTraitsVec,
238+
ccx: &CrateCtxt,
239+
cstore: &cstore::CStore,
240+
dl: decoder::DefLike) {
241+
match dl {
242+
decoder::DlDef(def::DefTrait(did)) => {
243+
traits.push(TraitInfo::new(did));
244+
}
245+
decoder::DlDef(def::DefMod(did)) => {
246+
csearch::each_child_of_item(cstore, did, |dl, _, _| {
247+
handle_external_def(traits, ccx, cstore, dl)
248+
})
249+
}
250+
_ => {}
251+
}
252+
}
253+
let cstore = &ccx.tcx.sess.cstore;
254+
cstore.iter_crate_data(|&mut: cnum, _| {
255+
csearch::each_top_level_item_of_crate(cstore, cnum, |dl, _, _| {
256+
handle_external_def(&mut traits, ccx, cstore, dl)
257+
})
258+
});
259+
260+
*ccx.all_traits.borrow_mut() = Some(traits);
261+
}
262+
263+
let borrow = ccx.all_traits.borrow();
264+
assert!(borrow.is_some());
265+
AllTraits {
266+
borrow: borrow,
267+
idx: 0
268+
}
269+
}
270+
271+
struct AllTraits<'a> {
272+
borrow: cell::Ref<'a Option<AllTraitsVec>>,
273+
idx: usize
274+
}
275+
276+
impl<'a> Iterator for AllTraits<'a> {
277+
type Item = TraitInfo;
278+
279+
fn next(&mut self) -> Option<TraitInfo> {
280+
let AllTraits { ref borrow, ref mut idx } = *self;
281+
// ugh.
282+
borrow.as_ref().unwrap().get(*idx).map(|info| {
283+
*idx += 1;
284+
*info
285+
})
286+
}
287+
}

src/librustc_typeck/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ use syntax::print::pprust::*;
108108
use syntax::{ast, ast_map, abi};
109109
use syntax::ast_util::local_def;
110110

111+
use std::cell::RefCell;
112+
111113
mod check;
112114
mod rscope;
113115
mod astconv;
@@ -123,6 +125,11 @@ struct TypeAndSubsts<'tcx> {
123125
struct CrateCtxt<'a, 'tcx: 'a> {
124126
// A mapping from method call sites to traits that have that method.
125127
trait_map: ty::TraitMap,
128+
/// A vector of every trait accessible in the whole crate
129+
/// (i.e. including those from subcrates). This is used only for
130+
/// error reporting, and so is lazily initialised and generally
131+
/// shouldn't taint the common path (hence the RefCell).
132+
all_traits: RefCell<Option<check::method::AllTraitsVec>>,
126133
tcx: &'a ty::ctxt<'tcx>,
127134
}
128135

@@ -320,6 +327,7 @@ pub fn check_crate(tcx: &ty::ctxt, trait_map: ty::TraitMap) {
320327
let time_passes = tcx.sess.time_passes();
321328
let ccx = CrateCtxt {
322329
trait_map: trait_map,
330+
all_traits: RefCell::new(None),
323331
tcx: tcx
324332
};
325333

src/libsyntax/diagnostic.rs

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ impl SpanHandler {
118118
pub fn fileline_note(&self, sp: Span, msg: &str) {
119119
self.handler.custom_emit(&self.cm, FileLine(sp), msg, Note);
120120
}
121+
pub fn fileline_help(&self, sp: Span, msg: &str) {
122+
self.handler.custom_emit(&self.cm, FileLine(sp), msg, Help);
123+
}
121124
pub fn span_bug(&self, sp: Span, msg: &str) -> ! {
122125
self.handler.emit(Some((&self.cm, sp)), msg, Bug);
123126
panic!(ExplicitBug);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
pub use reexport::Reexported;
12+
13+
pub mod foo {
14+
pub trait PubPub {
15+
fn method(&self);
16+
}
17+
}
18+
pub mod bar {
19+
trait PubPriv {
20+
fn method(&self);
21+
}
22+
}
23+
mod qux {
24+
pub trait PrivPub {
25+
fn method(&self);
26+
}
27+
}
28+
mod quz {
29+
trait PrivPriv {
30+
fn method(&self);
31+
}
32+
}
33+
34+
mod reexport {
35+
pub trait Reexported {
36+
fn method(&self);
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// aux-build:no_method_suggested_traits.rs
12+
13+
extern crate no_method_suggested_traits;
14+
15+
mod foo {
16+
trait Bar { //~ HELP `foo::Bar`
17+
fn method(&self);
18+
}
19+
}
20+
21+
fn main() {
22+
1u32.method();
23+
//~^ ERROR does not implement
24+
//~^^ HELP the following traits define a method `method`
25+
//~^^^ 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`
30+
}

0 commit comments

Comments
 (0)