diff --git a/src/librustc_typeck/check/method/mod.rs b/src/librustc_typeck/check/method/mod.rs index 0afc482cb79d..f344b6362203 100644 --- a/src/librustc_typeck/check/method/mod.rs +++ b/src/librustc_typeck/check/method/mod.rs @@ -68,6 +68,7 @@ pub enum MethodError<'tcx> { // could lead to matches if satisfied, and a list of not-in-scope traits which may work. pub struct NoMatchData<'tcx> { pub static_candidates: Vec, + pub lev_candidates: Vec, pub unsatisfied_predicates: Vec>, pub out_of_scope_traits: Vec, pub mode: probe::Mode, @@ -75,12 +76,14 @@ pub struct NoMatchData<'tcx> { impl<'tcx> NoMatchData<'tcx> { pub fn new(static_candidates: Vec, + lev_candidates: Vec, unsatisfied_predicates: Vec>, out_of_scope_traits: Vec, mode: probe::Mode) -> Self { NoMatchData { static_candidates, + lev_candidates, unsatisfied_predicates, out_of_scope_traits, mode, diff --git a/src/librustc_typeck/check/method/probe.rs b/src/librustc_typeck/check/method/probe.rs index 7b9478183251..2041ff58861e 100644 --- a/src/librustc_typeck/check/method/probe.rs +++ b/src/librustc_typeck/check/method/probe.rs @@ -23,11 +23,13 @@ use rustc::infer::type_variable::TypeVariableOrigin; use rustc::util::nodemap::FxHashSet; use rustc::infer::{self, InferOk}; use syntax::ast; +use syntax::util::lev_distance::lev_distance; use syntax_pos::Span; use rustc::hir; use std::mem; use std::ops::Deref; use std::rc::Rc; +use std::cmp::max; use self::CandidateKind::*; pub use self::PickKind::*; @@ -51,6 +53,10 @@ struct ProbeContext<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { /// used for error reporting static_candidates: Vec, + /// When probing for names, include names that are close to the + /// requested name (by Levensthein distance) + allow_similar_names: bool, + /// Some(candidate) if there is a private candidate private_candidate: Option, @@ -240,6 +246,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { Some(steps) => steps, None => { return Err(MethodError::NoMatch(NoMatchData::new(Vec::new(), + Vec::new(), Vec::new(), Vec::new(), mode))) @@ -261,7 +268,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { // that we create during the probe process are removed later self.probe(|_| { let mut probe_cx = - ProbeContext::new(self, span, mode, method_name, return_type, steps); + ProbeContext::new(self, span, mode, method_name, return_type, Rc::new(steps)); probe_cx.assemble_inherent_candidates(); match scope { @@ -333,7 +340,7 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { mode: Mode, method_name: Option, return_type: Option>, - steps: Vec>) + steps: Rc>>) -> ProbeContext<'a, 'gcx, 'tcx> { ProbeContext { fcx, @@ -344,8 +351,9 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { inherent_candidates: Vec::new(), extension_candidates: Vec::new(), impl_dups: FxHashSet(), - steps: Rc::new(steps), + steps: steps, static_candidates: Vec::new(), + allow_similar_names: false, private_candidate: None, unsatisfied_predicates: Vec::new(), } @@ -798,8 +806,10 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { if let Some(def) = private_candidate { return Err(MethodError::PrivateMatch(def, out_of_scope_traits)); } + let lev_candidates = self.probe_for_lev_candidates()?; Err(MethodError::NoMatch(NoMatchData::new(static_candidates, + lev_candidates, unsatisfied_predicates, out_of_scope_traits, self.mode))) @@ -913,11 +923,8 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { debug!("applicable_candidates: {:?}", applicable_candidates); if applicable_candidates.len() > 1 { - match self.collapse_candidates_to_trait_pick(&applicable_candidates[..]) { - Some(pick) => { - return Some(Ok(pick)); - } - None => {} + if let Some(pick) = self.collapse_candidates_to_trait_pick(&applicable_candidates[..]) { + return Some(Ok(pick)); } } @@ -1126,6 +1133,39 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { }) } + /// Similarly to `probe_for_return_type`, this method attempts to find candidate methods where + /// the method name may have been misspelt. + fn probe_for_lev_candidates(&mut self) -> Result, MethodError<'tcx>> { + debug!("Probing for method names similar to {:?}", + self.method_name); + + let steps = self.steps.clone(); + self.probe(|_| { + let mut pcx = ProbeContext::new(self.fcx, self.span, self.mode, self.method_name, + self.return_type, steps); + pcx.allow_similar_names = true; + pcx.assemble_inherent_candidates(); + pcx.assemble_extension_candidates_for_traits_in_scope(ast::DUMMY_NODE_ID)?; + + let method_names = pcx.candidate_method_names(); + pcx.allow_similar_names = false; + Ok(method_names + .iter() + .filter_map(|&method_name| { + pcx.reset(); + pcx.method_name = Some(method_name); + pcx.assemble_inherent_candidates(); + pcx.assemble_extension_candidates_for_traits_in_scope(ast::DUMMY_NODE_ID) + .ok().map_or(None, |_| { + pcx.pick_core() + .and_then(|pick| pick.ok()) + .and_then(|pick| Some(pick.item)) + }) + }) + .collect()) + }) + } + /////////////////////////////////////////////////////////////////////////// // MISCELLANY fn has_applicable_self(&self, item: &ty::AssociatedItem) -> bool { @@ -1253,10 +1293,21 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> { self.tcx.erase_late_bound_regions(value) } - /// Find the method with the appropriate name (or return type, as the case may be). + /// Find the method with the appropriate name (or return type, as the case may be). If + /// `allow_similar_names` is set, find methods with close-matching names. fn impl_or_trait_item(&self, def_id: DefId) -> Vec { if let Some(name) = self.method_name { - self.fcx.associated_item(def_id, name).map_or(Vec::new(), |x| vec![x]) + if self.allow_similar_names { + let max_dist = max(name.as_str().len(), 3) / 3; + self.tcx.associated_items(def_id) + .filter(|x| { + let dist = lev_distance(&*name.as_str(), &x.name.as_str()); + dist > 0 && dist <= max_dist + }) + .collect() + } else { + self.fcx.associated_item(def_id, name).map_or(Vec::new(), |x| vec![x]) + } } else { self.tcx.associated_items(def_id).collect() } diff --git a/src/librustc_typeck/check/method/suggest.rs b/src/librustc_typeck/check/method/suggest.rs index 7fa3dd7472db..d65ea5f7fb5c 100644 --- a/src/librustc_typeck/check/method/suggest.rs +++ b/src/librustc_typeck/check/method/suggest.rs @@ -162,6 +162,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { match error { MethodError::NoMatch(NoMatchData { static_candidates: static_sources, + lev_candidates, unsatisfied_predicates, out_of_scope_traits, mode, @@ -282,6 +283,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { item_name, rcvr_expr, out_of_scope_traits); + + if !lev_candidates.is_empty() { + for meth in lev_candidates.iter().take(5) { + err.help(&format!("did you mean `{}`?", meth.name)); + } + } err.emit(); } diff --git a/src/test/ui/block-result/issue-3563.stderr b/src/test/ui/block-result/issue-3563.stderr index 4b1f8b032b74..e3f0df6fb5f1 100644 --- a/src/test/ui/block-result/issue-3563.stderr +++ b/src/test/ui/block-result/issue-3563.stderr @@ -3,6 +3,8 @@ error[E0599]: no method named `b` found for type `&Self` in the current scope | 13 | || self.b() | ^ + | + = help: did you mean `a`? error[E0308]: mismatched types --> $DIR/issue-3563.rs:13:9 diff --git a/src/test/ui/suggestions/suggest-methods.rs b/src/test/ui/suggestions/suggest-methods.rs new file mode 100644 index 000000000000..36b9976ae56f --- /dev/null +++ b/src/test/ui/suggestions/suggest-methods.rs @@ -0,0 +1,38 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +struct Foo; + +impl Foo { + fn bar(self) {} + fn baz(&self, x: f64) {} +} + +trait FooT { + fn bag(&self); +} + +impl FooT for Foo { + fn bag(&self) {} +} + +fn main() { + let f = Foo; + f.bat(1.0); + + let s = "foo".to_string(); + let _ = s.is_emtpy(); + + // Generates a warning, both for count_ones and count_zeros + let _ = 63u32.count_eos(); + let _ = 63u32.count_o(); // Does not generate a warning + +} + diff --git a/src/test/ui/suggestions/suggest-methods.stderr b/src/test/ui/suggestions/suggest-methods.stderr new file mode 100644 index 000000000000..d1a5ebcdef46 --- /dev/null +++ b/src/test/ui/suggestions/suggest-methods.stderr @@ -0,0 +1,34 @@ +error[E0599]: no method named `bat` found for type `Foo` in the current scope + --> $DIR/suggest-methods.rs:28:7 + | +28 | f.bat(1.0); + | ^^^ + | + = help: did you mean `bar`? + = help: did you mean `baz`? + +error[E0599]: no method named `is_emtpy` found for type `std::string::String` in the current scope + --> $DIR/suggest-methods.rs:31:15 + | +31 | let _ = s.is_emtpy(); + | ^^^^^^^^ + | + = help: did you mean `is_empty`? + +error[E0599]: no method named `count_eos` found for type `u32` in the current scope + --> $DIR/suggest-methods.rs:34:19 + | +34 | let _ = 63u32.count_eos(); + | ^^^^^^^^^ + | + = help: did you mean `count_ones`? + = help: did you mean `count_zeros`? + +error[E0599]: no method named `count_o` found for type `u32` in the current scope + --> $DIR/suggest-methods.rs:35:19 + | +35 | let _ = 63u32.count_o(); // Does not generate a warning + | ^^^^^^^ + +error: aborting due to 4 previous errors +