Change Levensthein-based method to a single suggestion

The convention for suggesting close matches is to provide at most one match (the
closest one). Change the suggestions for misspelt method names to obey that.
This commit is contained in:
Thomas Jespersen 2017-09-21 13:04:11 +02:00
parent 09defbcb5b
commit 4963394f86
5 changed files with 39 additions and 26 deletions

View File

@ -68,24 +68,24 @@ pub enum MethodError<'tcx> {
// could lead to matches if satisfied, and a list of not-in-scope traits which may work. // could lead to matches if satisfied, and a list of not-in-scope traits which may work.
pub struct NoMatchData<'tcx> { pub struct NoMatchData<'tcx> {
pub static_candidates: Vec<CandidateSource>, pub static_candidates: Vec<CandidateSource>,
pub lev_candidates: Vec<ty::AssociatedItem>,
pub unsatisfied_predicates: Vec<TraitRef<'tcx>>, pub unsatisfied_predicates: Vec<TraitRef<'tcx>>,
pub out_of_scope_traits: Vec<DefId>, pub out_of_scope_traits: Vec<DefId>,
pub lev_candidate: Option<ty::AssociatedItem>,
pub mode: probe::Mode, pub mode: probe::Mode,
} }
impl<'tcx> NoMatchData<'tcx> { impl<'tcx> NoMatchData<'tcx> {
pub fn new(static_candidates: Vec<CandidateSource>, pub fn new(static_candidates: Vec<CandidateSource>,
lev_candidates: Vec<ty::AssociatedItem>,
unsatisfied_predicates: Vec<TraitRef<'tcx>>, unsatisfied_predicates: Vec<TraitRef<'tcx>>,
out_of_scope_traits: Vec<DefId>, out_of_scope_traits: Vec<DefId>,
lev_candidate: Option<ty::AssociatedItem>,
mode: probe::Mode) mode: probe::Mode)
-> Self { -> Self {
NoMatchData { NoMatchData {
static_candidates, static_candidates,
lev_candidates,
unsatisfied_predicates, unsatisfied_predicates,
out_of_scope_traits, out_of_scope_traits,
lev_candidate,
mode, mode,
} }
} }

View File

@ -23,7 +23,7 @@ use rustc::infer::type_variable::TypeVariableOrigin;
use rustc::util::nodemap::FxHashSet; use rustc::util::nodemap::FxHashSet;
use rustc::infer::{self, InferOk}; use rustc::infer::{self, InferOk};
use syntax::ast; use syntax::ast;
use syntax::util::lev_distance::lev_distance; use syntax::util::lev_distance::{lev_distance, find_best_match_for_name};
use syntax_pos::Span; use syntax_pos::Span;
use rustc::hir; use rustc::hir;
use std::mem; use std::mem;
@ -248,7 +248,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
return Err(MethodError::NoMatch(NoMatchData::new(Vec::new(), return Err(MethodError::NoMatch(NoMatchData::new(Vec::new(),
Vec::new(), Vec::new(),
Vec::new(), Vec::new(),
Vec::new(), None,
mode))) mode)))
} }
} }
@ -806,12 +806,12 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
if let Some(def) = private_candidate { if let Some(def) = private_candidate {
return Err(MethodError::PrivateMatch(def, out_of_scope_traits)); return Err(MethodError::PrivateMatch(def, out_of_scope_traits));
} }
let lev_candidates = self.probe_for_lev_candidates()?; let lev_candidate = self.probe_for_lev_candidate()?;
Err(MethodError::NoMatch(NoMatchData::new(static_candidates, Err(MethodError::NoMatch(NoMatchData::new(static_candidates,
lev_candidates,
unsatisfied_predicates, unsatisfied_predicates,
out_of_scope_traits, out_of_scope_traits,
lev_candidate,
self.mode))) self.mode)))
} }
@ -1133,9 +1133,10 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
}) })
} }
/// Similarly to `probe_for_return_type`, this method attempts to find candidate methods where /// Similarly to `probe_for_return_type`, this method attempts to find the best matching
/// the method name may have been misspelt. /// candidate method where the method name may have been misspelt. Similarly to other
fn probe_for_lev_candidates(&mut self) -> Result<Vec<ty::AssociatedItem>, MethodError<'tcx>> { /// Levenshtein based suggestions, we provide at most one such suggestion.
fn probe_for_lev_candidate(&mut self) -> Result<Option<ty::AssociatedItem>, MethodError<'tcx>> {
debug!("Probing for method names similar to {:?}", debug!("Probing for method names similar to {:?}",
self.method_name); self.method_name);
@ -1149,7 +1150,7 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
let method_names = pcx.candidate_method_names(); let method_names = pcx.candidate_method_names();
pcx.allow_similar_names = false; pcx.allow_similar_names = false;
Ok(method_names let applicable_close_candidates: Vec<ty::AssociatedItem> = method_names
.iter() .iter()
.filter_map(|&method_name| { .filter_map(|&method_name| {
pcx.reset(); pcx.reset();
@ -1162,7 +1163,21 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
.and_then(|pick| Some(pick.item)) .and_then(|pick| Some(pick.item))
}) })
}) })
.collect()) .collect();
if applicable_close_candidates.is_empty() {
Ok(None)
} else {
let best_name = {
let names = applicable_close_candidates.iter().map(|cand| &cand.name);
find_best_match_for_name(names,
&self.method_name.unwrap().as_str(),
None)
}.unwrap();
Ok(applicable_close_candidates
.into_iter()
.find(|method| method.name == best_name))
}
}) })
} }

View File

@ -162,9 +162,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
match error { match error {
MethodError::NoMatch(NoMatchData { static_candidates: static_sources, MethodError::NoMatch(NoMatchData { static_candidates: static_sources,
lev_candidates,
unsatisfied_predicates, unsatisfied_predicates,
out_of_scope_traits, out_of_scope_traits,
lev_candidate,
mode, mode,
.. }) => { .. }) => {
let tcx = self.tcx; let tcx = self.tcx;
@ -284,10 +284,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
rcvr_expr, rcvr_expr,
out_of_scope_traits); out_of_scope_traits);
if !lev_candidates.is_empty() { if let Some(lev_candidate) = lev_candidate {
for meth in lev_candidates.iter().take(5) { err.help(&format!("did you mean `{}`?", lev_candidate.name));
err.help(&format!("did you mean `{}`?", meth.name));
}
} }
err.emit(); err.emit();
} }

View File

@ -30,9 +30,11 @@ fn main() {
let s = "foo".to_string(); let s = "foo".to_string();
let _ = s.is_emtpy(); let _ = s.is_emtpy();
// Generates a warning, both for count_ones and count_zeros // Generates a warning for `count_zeros()`. `count_ones()` is also a close
// match, but the former is closer.
let _ = 63u32.count_eos(); let _ = 63u32.count_eos();
let _ = 63u32.count_o(); // Does not generate a warning
// Does not generate a warning
let _ = 63u32.count_o();
} }

View File

@ -5,7 +5,6 @@ error[E0599]: no method named `bat` found for type `Foo` in the current scope
| ^^^ | ^^^
| |
= help: did you mean `bar`? = 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 error[E0599]: no method named `is_emtpy` found for type `std::string::String` in the current scope
--> $DIR/suggest-methods.rs:31:15 --> $DIR/suggest-methods.rs:31:15
@ -16,18 +15,17 @@ error[E0599]: no method named `is_emtpy` found for type `std::string::String` in
= help: did you mean `is_empty`? = help: did you mean `is_empty`?
error[E0599]: no method named `count_eos` found for type `u32` in the current scope error[E0599]: no method named `count_eos` found for type `u32` in the current scope
--> $DIR/suggest-methods.rs:34:19 --> $DIR/suggest-methods.rs:35:19
| |
34 | let _ = 63u32.count_eos(); 35 | let _ = 63u32.count_eos();
| ^^^^^^^^^ | ^^^^^^^^^
| |
= help: did you mean `count_ones`?
= help: did you mean `count_zeros`? = help: did you mean `count_zeros`?
error[E0599]: no method named `count_o` found for type `u32` in the current scope error[E0599]: no method named `count_o` found for type `u32` in the current scope
--> $DIR/suggest-methods.rs:35:19 --> $DIR/suggest-methods.rs:38:19
| |
35 | let _ = 63u32.count_o(); // Does not generate a warning 38 | let _ = 63u32.count_o();
| ^^^^^^^ | ^^^^^^^
error: aborting due to 4 previous errors error: aborting due to 4 previous errors