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