]> git.lizzy.rs Git - rust.git/blob - src/librustc_typeck/check/method/suggest.rs
Auto merge of #22517 - brson:relnotes, r=Gankro
[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 a method 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};
19 use middle::def;
20 use metadata::{csearch, cstore, decoder};
21 use util::ppaux::UserString;
22
23 use syntax::{ast, ast_util};
24 use syntax::codemap::Span;
25 use syntax::print::pprust;
26
27 use std::cell;
28 use std::cmp::Ordering;
29
30 use super::{MethodError, CandidateSource, impl_method, trait_method};
31
32 pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
33                               span: Span,
34                               rcvr_ty: Ty<'tcx>,
35                               method_name: ast::Name,
36                               callee_expr: &ast::Expr,
37                               error: MethodError)
38 {
39     // avoid suggestions when we don't know what's going on.
40     if ty::type_is_error(rcvr_ty) {
41         return
42     }
43
44     match error {
45         MethodError::NoMatch(static_sources, out_of_scope_traits) => {
46             let cx = fcx.tcx();
47             let method_ustring = method_name.user_string(cx);
48
49             // True if the type is a struct and contains a field with
50             // the same name as the not-found method
51             let is_field = match rcvr_ty.sty {
52                 ty::ty_struct(did, _) =>
53                     ty::lookup_struct_fields(cx, did)
54                         .iter()
55                         .any(|f| f.name.user_string(cx) == method_ustring),
56                 _ => false
57             };
58
59             fcx.type_error_message(
60                 span,
61                 |actual| {
62                     format!("type `{}` does not implement any \
63                              method in scope named `{}`",
64                             actual,
65                             method_ustring)
66                 },
67                 rcvr_ty,
68                 None);
69
70             // If the method has the name of a field, give a help note
71             if is_field {
72                 cx.sess.span_note(span,
73                     &format!("use `(s.{0})(...)` if you meant to call the \
74                             function stored in the `{0}` field", method_ustring)[]);
75             }
76
77             if static_sources.len() > 0 {
78                 fcx.tcx().sess.fileline_note(
79                     span,
80                     "found defined static methods, maybe a `self` is missing?");
81
82                 report_candidates(fcx, span, method_name, static_sources);
83             }
84
85             suggest_traits_to_import(fcx, span, rcvr_ty, method_name, out_of_scope_traits)
86         }
87
88         MethodError::Ambiguity(sources) => {
89             span_err!(fcx.sess(), span, E0034,
90                       "multiple applicable methods in scope");
91
92             report_candidates(fcx, span, method_name, sources);
93         }
94
95         MethodError::ClosureAmbiguity(trait_def_id) => {
96             fcx.sess().span_err(
97                 span,
98                 &*format!("the `{}` method from the `{}` trait cannot be explicitly \
99                            invoked on this closure as we have not yet inferred what \
100                            kind of closure it is; use overloaded call notation instead \
101                            (e.g., `{}()`)",
102                           method_name.user_string(fcx.tcx()),
103                           ty::item_path_str(fcx.tcx(), trait_def_id),
104                           pprust::expr_to_string(callee_expr)));
105         }
106     }
107
108     fn report_candidates(fcx: &FnCtxt,
109                          span: Span,
110                          method_name: ast::Name,
111                          mut sources: Vec<CandidateSource>) {
112         sources.sort();
113         sources.dedup();
114
115         for (idx, source) in sources.iter().enumerate() {
116             match *source {
117                 CandidateSource::ImplSource(impl_did) => {
118                     // Provide the best span we can. Use the method, if local to crate, else
119                     // the impl, if local to crate (method may be defaulted), else the call site.
120                     let method = impl_method(fcx.tcx(), impl_did, method_name).unwrap();
121                     let impl_span = fcx.tcx().map.def_id_span(impl_did, span);
122                     let method_span = fcx.tcx().map.def_id_span(method.def_id, impl_span);
123
124                     let impl_ty = check::impl_self_ty(fcx, span, impl_did).ty;
125
126                     let insertion = match ty::impl_trait_ref(fcx.tcx(), impl_did) {
127                         None => format!(""),
128                         Some(trait_ref) => format!(" of the trait `{}`",
129                                                    ty::item_path_str(fcx.tcx(),
130                                                                      trait_ref.def_id)),
131                     };
132
133                     span_note!(fcx.sess(), method_span,
134                                "candidate #{} is defined in an impl{} for the type `{}`",
135                                idx + 1,
136                                insertion,
137                                impl_ty.user_string(fcx.tcx()));
138                 }
139                 CandidateSource::TraitSource(trait_did) => {
140                     let (_, method) = trait_method(fcx.tcx(), trait_did, method_name).unwrap();
141                     let method_span = fcx.tcx().map.def_id_span(method.def_id, span);
142                     span_note!(fcx.sess(), method_span,
143                                "candidate #{} is defined in the trait `{}`",
144                                idx + 1,
145                                ty::item_path_str(fcx.tcx(), trait_did));
146                 }
147             }
148         }
149     }
150 }
151
152
153 pub type AllTraitsVec = Vec<TraitInfo>;
154
155 fn suggest_traits_to_import<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
156                                       span: Span,
157                                       rcvr_ty: Ty<'tcx>,
158                                       method_name: ast::Name,
159                                       valid_out_of_scope_traits: Vec<ast::DefId>)
160 {
161     let tcx = fcx.tcx();
162     let method_ustring = method_name.user_string(tcx);
163
164     if !valid_out_of_scope_traits.is_empty() {
165         let mut candidates = valid_out_of_scope_traits;
166         candidates.sort();
167         candidates.dedup();
168         let msg = format!(
169             "methods from traits can only be called if the trait is in scope; \
170              the following {traits_are} implemented but not in scope, \
171              perhaps add a `use` for {one_of_them}:",
172             traits_are = if candidates.len() == 1 {"trait is"} else {"traits are"},
173             one_of_them = if candidates.len() == 1 {"it"} else {"one of them"});
174
175         fcx.sess().fileline_help(span, &msg[]);
176
177         for (i, trait_did) in candidates.iter().enumerate() {
178             fcx.sess().fileline_help(span,
179                                      &*format!("candidate #{}: use `{}`",
180                                                i + 1,
181                                                ty::item_path_str(fcx.tcx(), *trait_did)))
182
183         }
184         return
185     }
186
187     let type_is_local = type_derefs_to_local(fcx, span, rcvr_ty);
188
189     // there's no implemented traits, so lets suggest some traits to
190     // implement, by finding ones that have the method name, and are
191     // legal to implement.
192     let mut candidates = all_traits(fcx.ccx)
193         .filter(|info| {
194             // we approximate the coherence rules to only suggest
195             // traits that are legal to implement by requiring that
196             // either the type or trait is local. Multidispatch means
197             // this isn't perfect (that is, there are cases when
198             // implementing a trait would be legal but is rejected
199             // here).
200             (type_is_local || ast_util::is_local(info.def_id))
201                 && trait_method(tcx, info.def_id, method_name).is_some()
202         })
203         .collect::<Vec<_>>();
204
205     if candidates.len() > 0 {
206         // sort from most relevant to least relevant
207         candidates.sort_by(|a, b| a.cmp(b).reverse());
208         candidates.dedup();
209
210         // FIXME #21673 this help message could be tuned to the case
211         // of a type parameter: suggest adding a trait bound rather
212         // than implementing.
213         let msg = format!(
214             "methods from traits can only be called if the trait is implemented and in scope; \
215              the following {traits_define} a method `{name}`, \
216              perhaps you need to implement {one_of_them}:",
217             traits_define = if candidates.len() == 1 {"trait defines"} else {"traits define"},
218             one_of_them = if candidates.len() == 1 {"it"} else {"one of them"},
219             name = method_ustring);
220
221         fcx.sess().fileline_help(span, &msg[]);
222
223         for (i, trait_info) in candidates.iter().enumerate() {
224             fcx.sess().fileline_help(span,
225                                      &*format!("candidate #{}: `{}`",
226                                                i + 1,
227                                                ty::item_path_str(fcx.tcx(), trait_info.def_id)))
228         }
229     }
230 }
231
232 /// Checks whether there is a local type somewhere in the chain of
233 /// autoderefs of `rcvr_ty`.
234 fn type_derefs_to_local<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
235                                   span: Span,
236                                   rcvr_ty: Ty<'tcx>) -> bool {
237     check::autoderef(fcx, span, rcvr_ty, None,
238                      check::UnresolvedTypeAction::Ignore, check::NoPreference,
239                      |&: ty, _| {
240         let is_local = match ty.sty {
241             ty::ty_enum(did, _) | ty::ty_struct(did, _) => ast_util::is_local(did),
242
243             ty::ty_trait(ref tr) => ast_util::is_local(tr.principal_def_id()),
244
245             ty::ty_param(_) => true,
246
247             // the user cannot implement traits for unboxed closures, so
248             // there's no point suggesting anything at all, local or not.
249             ty::ty_closure(..) => return Some(false),
250
251             // everything else (primitive types etc.) is effectively
252             // non-local (there are "edge" cases, e.g. (LocalType,), but
253             // the noise from these sort of types is usually just really
254             // annoying, rather than any sort of help).
255             _ => false
256         };
257         if is_local {
258             Some(true)
259         } else {
260             None
261         }
262     }).2.unwrap_or(false)
263 }
264
265 #[derive(Copy)]
266 pub struct TraitInfo {
267     pub def_id: ast::DefId,
268 }
269
270 impl TraitInfo {
271     fn new(def_id: ast::DefId) -> TraitInfo {
272         TraitInfo {
273             def_id: def_id,
274         }
275     }
276 }
277 impl PartialEq for TraitInfo {
278     fn eq(&self, other: &TraitInfo) -> bool {
279         self.cmp(other) == Ordering::Equal
280     }
281 }
282 impl Eq for TraitInfo {}
283 impl PartialOrd for TraitInfo {
284     fn partial_cmp(&self, other: &TraitInfo) -> Option<Ordering> { Some(self.cmp(other)) }
285 }
286 impl Ord for TraitInfo {
287     fn cmp(&self, other: &TraitInfo) -> Ordering {
288         // accessible traits are more important/relevant than
289         // inaccessible ones, local crates are more important than
290         // remote ones (local: cnum == 0), and NodeIds just for
291         // totality.
292
293         let lhs = (other.def_id.krate, other.def_id.node);
294         let rhs = (self.def_id.krate, self.def_id.node);
295         lhs.cmp(&rhs)
296     }
297 }
298
299 /// Retrieve all traits in this crate and any dependent crates.
300 pub fn all_traits<'a>(ccx: &'a CrateCtxt) -> AllTraits<'a> {
301     if ccx.all_traits.borrow().is_none() {
302         use syntax::visit;
303
304         let mut traits = vec![];
305
306         // Crate-local:
307         //
308         // meh.
309         struct Visitor<'a, 'b: 'a, 'tcx: 'a + 'b> {
310             traits: &'a mut AllTraitsVec,
311         }
312         impl<'v,'a, 'b, 'tcx> visit::Visitor<'v> for Visitor<'a, 'b, 'tcx> {
313             fn visit_item(&mut self, i: &'v ast::Item) {
314                 match i.node {
315                     ast::ItemTrait(..) => {
316                         self.traits.push(TraitInfo::new(ast_util::local_def(i.id)));
317                     }
318                     _ => {}
319                 }
320                 visit::walk_item(self, i)
321             }
322         }
323         visit::walk_crate(&mut Visitor {
324             traits: &mut traits
325         }, ccx.tcx.map.krate());
326
327         // Cross-crate:
328         fn handle_external_def(traits: &mut AllTraitsVec,
329                                ccx: &CrateCtxt,
330                                cstore: &cstore::CStore,
331                                dl: decoder::DefLike) {
332             match dl {
333                 decoder::DlDef(def::DefTrait(did)) => {
334                     traits.push(TraitInfo::new(did));
335                 }
336                 decoder::DlDef(def::DefMod(did)) => {
337                     csearch::each_child_of_item(cstore, did, |dl, _, _| {
338                         handle_external_def(traits, ccx, cstore, dl)
339                     })
340                 }
341                 _ => {}
342             }
343         }
344         let cstore = &ccx.tcx.sess.cstore;
345         cstore.iter_crate_data(|cnum, _| {
346             csearch::each_top_level_item_of_crate(cstore, cnum, |dl, _, _| {
347                 handle_external_def(&mut traits, ccx, cstore, dl)
348             })
349         });
350
351         *ccx.all_traits.borrow_mut() = Some(traits);
352     }
353
354     let borrow = ccx.all_traits.borrow();
355     assert!(borrow.is_some());
356     AllTraits {
357         borrow: borrow,
358         idx: 0
359     }
360 }
361
362 pub struct AllTraits<'a> {
363     borrow: cell::Ref<'a Option<AllTraitsVec>>,
364     idx: usize
365 }
366
367 impl<'a> Iterator for AllTraits<'a> {
368     type Item = TraitInfo;
369
370     fn next(&mut self) -> Option<TraitInfo> {
371         let AllTraits { ref borrow, ref mut idx } = *self;
372         // ugh.
373         borrow.as_ref().unwrap().get(*idx).map(|info| {
374             *idx += 1;
375             *info
376         })
377     }
378 }