1 // FIXME(Centril): Move to rustc_span?
3 use rustc_span
::symbol
::Symbol
;
9 /// Finds the Levenshtein distance between two strings
10 pub fn lev_distance(a
: &str, b
: &str) -> usize {
11 // cases which don't require further computation
13 return b
.chars().count();
14 } else if b
.is_empty() {
15 return a
.chars().count();
18 let mut dcol
: Vec
<_
> = (0..=b
.len()).collect();
21 for (i
, sc
) in a
.chars().enumerate() {
23 dcol
[0] = current
+ 1;
25 for (j
, tc
) in b
.chars().enumerate() {
26 let next
= dcol
[j
+ 1];
28 dcol
[j
+ 1] = current
;
30 dcol
[j
+ 1] = cmp
::min(current
, next
);
31 dcol
[j
+ 1] = cmp
::min(dcol
[j
+ 1], dcol
[j
]) + 1;
40 /// Finds the best match for a given word in the given iterator
42 /// As a loose rule to avoid the obviously incorrect suggestions, it takes
43 /// an optional limit for the maximum allowable edit distance, which defaults
44 /// to one-third of the given word.
46 /// Besides Levenshtein, we use case insensitive comparison to improve accuracy on an edge case with
47 /// a lower(upper)case letters mismatch.
48 pub fn find_best_match_for_name
<'a
, T
>(
54 T
: Iterator
<Item
= &'a Symbol
>,
56 let lookup
= &lookup
.as_str();
57 let max_dist
= dist
.unwrap_or_else(|| cmp
::max(lookup
.len(), 3) / 3);
58 let name_vec
: Vec
<&Symbol
> = iter_names
.collect();
60 let (case_insensitive_match
, levenshtein_match
) = name_vec
63 let dist
= lev_distance(lookup
, &name
.as_str());
64 if dist
<= max_dist { Some((name, dist)) }
else { None }
66 // Here we are collecting the next structure:
67 // (case_insensitive_match, (levenshtein_match, levenshtein_distance))
68 .fold((None
, None
), |result
, (candidate
, dist
)| {
70 if candidate
.as_str().to_uppercase() == lookup
.to_uppercase() {
76 None
=> Some((candidate
, dist
)),
77 Some((c
, d
)) => Some(if dist
< d { (candidate, dist) }
else { (c, d) }
),
81 // Priority of matches:
82 // 1. Exact case insensitive match
83 // 2. Levenshtein distance match
84 // 3. Sorted word match
85 if let Some(candidate
) = case_insensitive_match
{
87 } else if levenshtein_match
.is_some() {
88 levenshtein_match
.map(|(candidate
, _
)| *candidate
)
90 find_match_by_sorted_words(name_vec
, lookup
)
94 fn find_match_by_sorted_words
<'a
>(iter_names
: Vec
<&'a Symbol
>, lookup
: &str) -> Option
<Symbol
> {
95 iter_names
.iter().fold(None
, |result
, candidate
| {
96 if sort_by_words(&candidate
.as_str()) == sort_by_words(lookup
) {
104 fn sort_by_words(name
: &str) -> String
{
105 let mut split_words
: Vec
<&str> = name
.split('_'
).collect();
106 // We are sorting primitive &strs and can use unstable sort here
107 split_words
.sort_unstable();
108 split_words
.join("_")