]> git.proxmox.com Git - rustc.git/blob - src/librustc_typeck/check/method/suggest.rs
2325b9852c73dc4c62b1835e64e2765fa4ba8c4f
[rustc.git] / src / librustc_typeck / check / method / suggest.rs
1 // Copyright 2014 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 //! Give useful errors and suggestions to users when an item can't be
12 //! found or is otherwise invalid.
13
14 use CrateCtxt;
15
16 use astconv::AstConv;
17 use check::{self, FnCtxt};
18 use middle::ty::{self, Ty, ToPolyTraitRef, AsPredicate};
19 use middle::def;
20 use middle::lang_items::FnOnceTraitLangItem;
21 use middle::subst::Substs;
22 use middle::traits::{Obligation, SelectionContext};
23 use metadata::{csearch, cstore, decoder};
24
25 use syntax::{ast, ast_util};
26 use syntax::codemap::Span;
27 use syntax::print::pprust;
28
29 use std::cell;
30 use std::cmp::Ordering;
31
32 use super::{MethodError, NoMatchData, CandidateSource, impl_item, trait_item};
33 use super::probe::Mode;
34
35 pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
36 span: Span,
37 rcvr_ty: Ty<'tcx>,
38 item_name: ast::Name,
39 rcvr_expr: Option<&ast::Expr>,
40 error: MethodError<'tcx>)
41 {
42 // avoid suggestions when we don't know what's going on.
43 if ty::type_is_error(rcvr_ty) {
44 return
45 }
46
47 match error {
48 MethodError::NoMatch(NoMatchData { static_candidates: static_sources,
49 unsatisfied_predicates,
50 out_of_scope_traits,
51 mode }) => {
52 let cx = fcx.tcx();
53
54 fcx.type_error_message(
55 span,
56 |actual| {
57 format!("no {} named `{}` found for type `{}` \
58 in the current scope",
59 if mode == Mode::MethodCall { "method" }
60 else { "associated item" },
61 item_name,
62 actual)
63 },
64 rcvr_ty,
65 None);
66
67 // If the item has the name of a field, give a help note
68 if let (&ty::TyStruct(did, substs), Some(expr)) = (&rcvr_ty.sty, rcvr_expr) {
69 let fields = ty::lookup_struct_fields(cx, did);
70
71 if let Some(field) = fields.iter().find(|f| f.name == item_name) {
72 let expr_string = match cx.sess.codemap().span_to_snippet(expr.span) {
73 Ok(expr_string) => expr_string,
74 _ => "s".into() // Default to a generic placeholder for the
75 // expression when we can't generate a string
76 // snippet
77 };
78
79 let span_stored_function = || {
80 cx.sess.span_note(span,
81 &format!("use `({0}.{1})(...)` if you meant to call \
82 the function stored in the `{1}` field",
83 expr_string, item_name));
84 };
85
86 let span_did_you_mean = || {
87 cx.sess.span_note(span, &format!("did you mean to write `{0}.{1}`?",
88 expr_string, item_name));
89 };
90
91 // Determine if the field can be used as a function in some way
92 let field_ty = ty::lookup_field_type(cx, did, field.id, substs);
93 if let Ok(fn_once_trait_did) = cx.lang_items.require(FnOnceTraitLangItem) {
94 let infcx = fcx.infcx();
95 infcx.probe(|_| {
96 let fn_once_substs = Substs::new_trait(vec![infcx.next_ty_var()],
97 Vec::new(),
98 field_ty);
99 let trait_ref = ty::TraitRef::new(fn_once_trait_did,
100 cx.mk_substs(fn_once_substs));
101 let poly_trait_ref = trait_ref.to_poly_trait_ref();
102 let obligation = Obligation::misc(span,
103 fcx.body_id,
104 poly_trait_ref.as_predicate());
105 let mut selcx = SelectionContext::new(infcx, fcx);
106
107 if selcx.evaluate_obligation(&obligation) {
108 span_stored_function();
109 } else {
110 span_did_you_mean();
111 }
112 });
113 } else {
114 match field_ty.sty {
115 // fallback to matching a closure or function pointer
116 ty::TyClosure(..) | ty::TyBareFn(..) => span_stored_function(),
117 _ => span_did_you_mean(),
118 }
119 }
120 }
121 }
122
123 if !static_sources.is_empty() {
124 cx.sess.fileline_note(
125 span,
126 "found defined static methods, maybe a `self` is missing?");
127
128 report_candidates(fcx, span, item_name, static_sources);
129 }
130
131 if !unsatisfied_predicates.is_empty() {
132 let bound_list = unsatisfied_predicates.iter()
133 .map(|p| format!("`{} : {}`",
134 p.self_ty(),
135 p))
136 .collect::<Vec<_>>()
137 .connect(", ");
138 cx.sess.fileline_note(
139 span,
140 &format!("the method `{}` exists but the \
141 following trait bounds were not satisfied: {}",
142 item_name,
143 bound_list));
144 }
145
146 suggest_traits_to_import(fcx, span, rcvr_ty, item_name,
147 rcvr_expr, out_of_scope_traits)
148 }
149
150 MethodError::Ambiguity(sources) => {
151 span_err!(fcx.sess(), span, E0034,
152 "multiple applicable items in scope");
153
154 report_candidates(fcx, span, item_name, sources);
155 }
156
157 MethodError::ClosureAmbiguity(trait_def_id) => {
158 let msg = format!("the `{}` method from the `{}` trait cannot be explicitly \
159 invoked on this closure as we have not yet inferred what \
160 kind of closure it is",
161 item_name,
162 ty::item_path_str(fcx.tcx(), trait_def_id));
163 let msg = if let Some(callee) = rcvr_expr {
164 format!("{}; use overloaded call notation instead (e.g., `{}()`)",
165 msg, pprust::expr_to_string(callee))
166 } else {
167 msg
168 };
169 fcx.sess().span_err(span, &msg);
170 }
171 }
172
173 fn report_candidates(fcx: &FnCtxt,
174 span: Span,
175 item_name: ast::Name,
176 mut sources: Vec<CandidateSource>) {
177 sources.sort();
178 sources.dedup();
179
180 for (idx, source) in sources.iter().enumerate() {
181 match *source {
182 CandidateSource::ImplSource(impl_did) => {
183 // Provide the best span we can. Use the item, if local to crate, else
184 // the impl, if local to crate (item may be defaulted), else the call site.
185 let item = impl_item(fcx.tcx(), impl_did, item_name).unwrap();
186 let impl_span = fcx.tcx().map.def_id_span(impl_did, span);
187 let item_span = fcx.tcx().map.def_id_span(item.def_id(), impl_span);
188
189 let impl_ty = check::impl_self_ty(fcx, span, impl_did).ty;
190
191 let insertion = match ty::impl_trait_ref(fcx.tcx(), impl_did) {
192 None => format!(""),
193 Some(trait_ref) => format!(" of the trait `{}`",
194 ty::item_path_str(fcx.tcx(),
195 trait_ref.def_id)),
196 };
197
198 span_note!(fcx.sess(), item_span,
199 "candidate #{} is defined in an impl{} for the type `{}`",
200 idx + 1,
201 insertion,
202 impl_ty);
203 }
204 CandidateSource::TraitSource(trait_did) => {
205 let (_, item) = trait_item(fcx.tcx(), trait_did, item_name).unwrap();
206 let item_span = fcx.tcx().map.def_id_span(item.def_id(), span);
207 span_note!(fcx.sess(), item_span,
208 "candidate #{} is defined in the trait `{}`",
209 idx + 1,
210 ty::item_path_str(fcx.tcx(), trait_did));
211 }
212 }
213 }
214 }
215 }
216
217
218 pub type AllTraitsVec = Vec<TraitInfo>;
219
220 fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
221 span: Span,
222 rcvr_ty: Ty<'tcx>,
223 item_name: ast::Name,
224 rcvr_expr: Option<&ast::Expr>,
225 valid_out_of_scope_traits: Vec<ast::DefId>)
226 {
227 let tcx = fcx.tcx();
228
229 if !valid_out_of_scope_traits.is_empty() {
230 let mut candidates = valid_out_of_scope_traits;
231 candidates.sort();
232 candidates.dedup();
233 let msg = format!(
234 "items from traits can only be used if the trait is in scope; \
235 the following {traits_are} implemented but not in scope, \
236 perhaps add a `use` for {one_of_them}:",
237 traits_are = if candidates.len() == 1 {"trait is"} else {"traits are"},
238 one_of_them = if candidates.len() == 1 {"it"} else {"one of them"});
239
240 fcx.sess().fileline_help(span, &msg[..]);
241
242 for (i, trait_did) in candidates.iter().enumerate() {
243 fcx.sess().fileline_help(span,
244 &*format!("candidate #{}: use `{}`",
245 i + 1,
246 ty::item_path_str(fcx.tcx(), *trait_did)))
247
248 }
249 return
250 }
251
252 let type_is_local = type_derefs_to_local(fcx, span, rcvr_ty, rcvr_expr);
253
254 // there's no implemented traits, so lets suggest some traits to
255 // implement, by finding ones that have the item name, and are
256 // legal to implement.
257 let mut candidates = all_traits(fcx.ccx)
258 .filter(|info| {
259 // we approximate the coherence rules to only suggest
260 // traits that are legal to implement by requiring that
261 // either the type or trait is local. Multidispatch means
262 // this isn't perfect (that is, there are cases when
263 // implementing a trait would be legal but is rejected
264 // here).
265 (type_is_local || ast_util::is_local(info.def_id))
266 && trait_item(tcx, info.def_id, item_name).is_some()
267 })
268 .collect::<Vec<_>>();
269
270 if !candidates.is_empty() {
271 // sort from most relevant to least relevant
272 candidates.sort_by(|a, b| a.cmp(b).reverse());
273 candidates.dedup();
274
275 // FIXME #21673 this help message could be tuned to the case
276 // of a type parameter: suggest adding a trait bound rather
277 // than implementing.
278 let msg = format!(
279 "items from traits can only be used if the trait is implemented and in scope; \
280 the following {traits_define} an item `{name}`, \
281 perhaps you need to implement {one_of_them}:",
282 traits_define = if candidates.len() == 1 {"trait defines"} else {"traits define"},
283 one_of_them = if candidates.len() == 1 {"it"} else {"one of them"},
284 name = item_name);
285
286 fcx.sess().fileline_help(span, &msg[..]);
287
288 for (i, trait_info) in candidates.iter().enumerate() {
289 fcx.sess().fileline_help(span,
290 &*format!("candidate #{}: `{}`",
291 i + 1,
292 ty::item_path_str(fcx.tcx(), trait_info.def_id)))
293 }
294 }
295 }
296
297 /// Checks whether there is a local type somewhere in the chain of
298 /// autoderefs of `rcvr_ty`.
299 fn type_derefs_to_local<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
300 span: Span,
301 rcvr_ty: Ty<'tcx>,
302 rcvr_expr: Option<&ast::Expr>) -> bool {
303 fn is_local(ty: Ty) -> bool {
304 match ty.sty {
305 ty::TyEnum(did, _) | ty::TyStruct(did, _) => ast_util::is_local(did),
306
307 ty::TyTrait(ref tr) => ast_util::is_local(tr.principal_def_id()),
308
309 ty::TyParam(_) => true,
310
311 // everything else (primitive types etc.) is effectively
312 // non-local (there are "edge" cases, e.g. (LocalType,), but
313 // the noise from these sort of types is usually just really
314 // annoying, rather than any sort of help).
315 _ => false
316 }
317 }
318
319 // This occurs for UFCS desugaring of `T::method`, where there is no
320 // receiver expression for the method call, and thus no autoderef.
321 if rcvr_expr.is_none() {
322 return is_local(fcx.resolve_type_vars_if_possible(rcvr_ty));
323 }
324
325 check::autoderef(fcx, span, rcvr_ty, None,
326 check::UnresolvedTypeAction::Ignore, check::NoPreference,
327 |ty, _| {
328 if is_local(ty) {
329 Some(())
330 } else {
331 None
332 }
333 }).2.is_some()
334 }
335
336 #[derive(Copy, Clone)]
337 pub struct TraitInfo {
338 pub def_id: ast::DefId,
339 }
340
341 impl TraitInfo {
342 fn new(def_id: ast::DefId) -> TraitInfo {
343 TraitInfo {
344 def_id: def_id,
345 }
346 }
347 }
348 impl PartialEq for TraitInfo {
349 fn eq(&self, other: &TraitInfo) -> bool {
350 self.cmp(other) == Ordering::Equal
351 }
352 }
353 impl Eq for TraitInfo {}
354 impl PartialOrd for TraitInfo {
355 fn partial_cmp(&self, other: &TraitInfo) -> Option<Ordering> { Some(self.cmp(other)) }
356 }
357 impl Ord for TraitInfo {
358 fn cmp(&self, other: &TraitInfo) -> Ordering {
359 // accessible traits are more important/relevant than
360 // inaccessible ones, local crates are more important than
361 // remote ones (local: cnum == 0), and NodeIds just for
362 // totality.
363
364 let lhs = (other.def_id.krate, other.def_id.node);
365 let rhs = (self.def_id.krate, self.def_id.node);
366 lhs.cmp(&rhs)
367 }
368 }
369
370 /// Retrieve all traits in this crate and any dependent crates.
371 pub fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> {
372 if ccx.all_traits.borrow().is_none() {
373 use syntax::visit;
374
375 let mut traits = vec![];
376
377 // Crate-local:
378 //
379 // meh.
380 struct Visitor<'a> {
381 traits: &'a mut AllTraitsVec,
382 }
383 impl<'v, 'a> visit::Visitor<'v> for Visitor<'a> {
384 fn visit_item(&mut self, i: &'v ast::Item) {
385 match i.node {
386 ast::ItemTrait(..) => {
387 self.traits.push(TraitInfo::new(ast_util::local_def(i.id)));
388 }
389 _ => {}
390 }
391 visit::walk_item(self, i)
392 }
393 }
394 visit::walk_crate(&mut Visitor {
395 traits: &mut traits
396 }, ccx.tcx.map.krate());
397
398 // Cross-crate:
399 fn handle_external_def(traits: &mut AllTraitsVec,
400 ccx: &CrateCtxt,
401 cstore: &cstore::CStore,
402 dl: decoder::DefLike) {
403 match dl {
404 decoder::DlDef(def::DefTrait(did)) => {
405 traits.push(TraitInfo::new(did));
406 }
407 decoder::DlDef(def::DefMod(did)) => {
408 csearch::each_child_of_item(cstore, did, |dl, _, _| {
409 handle_external_def(traits, ccx, cstore, dl)
410 })
411 }
412 _ => {}
413 }
414 }
415 let cstore = &ccx.tcx.sess.cstore;
416 cstore.iter_crate_data(|cnum, _| {
417 csearch::each_top_level_item_of_crate(cstore, cnum, |dl, _, _| {
418 handle_external_def(&mut traits, ccx, cstore, dl)
419 })
420 });
421
422 *ccx.all_traits.borrow_mut() = Some(traits);
423 }
424
425 let borrow = ccx.all_traits.borrow();
426 assert!(borrow.is_some());
427 AllTraits {
428 borrow: borrow,
429 idx: 0
430 }
431 }
432
433 pub struct AllTraits<'a> {
434 borrow: cell::Ref<'a, Option<AllTraitsVec>>,
435 idx: usize
436 }
437
438 impl<'a> Iterator for AllTraits<'a> {
439 type Item = TraitInfo;
440
441 fn next(&mut self) -> Option<TraitInfo> {
442 let AllTraits { ref borrow, ref mut idx } = *self;
443 // ugh.
444 borrow.as_ref().unwrap().get(*idx).map(|info| {
445 *idx += 1;
446 *info
447 })
448 }
449 }