]> git.lizzy.rs Git - rust.git/commitdiff
Modify the Levenshtein-based suggestions to include imports
authorRavi Shankar <wafflespeanut@gmail.com>
Mon, 14 Dec 2015 17:06:31 +0000 (22:36 +0530)
committerRavi Shankar <wafflespeanut@gmail.com>
Wed, 16 Dec 2015 11:03:24 +0000 (16:33 +0530)
src/librustc_resolve/lib.rs
src/librustc_resolve/resolve_imports.rs
src/librustc_typeck/check/mod.rs
src/libsyntax/ext/base.rs
src/libsyntax/util/lev_distance.rs
src/test/compile-fail/unresolved-import.rs

index c32acb7bb269f36f51c3c0aec8c4fec6dd0df251..5fc24d613750621d9acb548577415edfe4591937 100644 (file)
@@ -33,7 +33,6 @@
 #[no_link]
 extern crate rustc_bitflags;
 extern crate rustc_front;
-
 extern crate rustc;
 
 use self::PatternBindingMode::*;
@@ -69,7 +68,7 @@
 use syntax::attr::AttrMetaMethods;
 use syntax::parse::token::{self, special_names, special_idents};
 use syntax::codemap::{self, Span, Pos};
-use syntax::util::lev_distance::{lev_distance, max_suggestion_distance};
+use syntax::util::lev_distance::find_best_match_for_name;
 
 use rustc_front::intravisit::{self, FnKind, Visitor};
 use rustc_front::hir;
@@ -94,7 +93,6 @@
 use std::fmt;
 use std::mem::replace;
 use std::rc::{Rc, Weak};
-use std::usize;
 
 use resolve_imports::{Target, ImportDirective, ImportResolutionPerNamespace};
 use resolve_imports::Shadowable;
@@ -121,7 +119,7 @@ macro_rules! execute_callback {
 
 enum SuggestionType {
     Macro(String),
-    Function(String),
+    Function(token::InternedString),
     NotFound,
 }
 
@@ -3352,39 +3350,22 @@ fn is_static_method(this: &Resolver, did: DefId) -> bool {
         NoSuggestion
     }
 
-    fn find_best_match_for_name(&mut self, name: &str) -> SuggestionType {
-        let mut maybes: Vec<token::InternedString> = Vec::new();
-        let mut values: Vec<usize> = Vec::new();
-
+    fn find_best_match(&mut self, name: &str) -> SuggestionType {
         if let Some(macro_name) = self.session.available_macros
-                                 .borrow().iter().find(|n| n.as_str() == name) {
+                                  .borrow().iter().find(|n| n.as_str() == name) {
             return SuggestionType::Macro(format!("{}!", macro_name));
         }
 
-        for rib in self.value_ribs.iter().rev() {
-            for (&k, _) in &rib.bindings {
-                maybes.push(k.as_str());
-                values.push(usize::MAX);
-            }
-        }
-
-        let mut smallest = 0;
-        for (i, other) in maybes.iter().enumerate() {
-            values[i] = lev_distance(name, &other);
+        let names = self.value_ribs
+                    .iter()
+                    .rev()
+                    .flat_map(|rib| rib.bindings.keys());
 
-            if values[i] <= values[smallest] {
-                smallest = i;
+        if let Some(found) = find_best_match_for_name(names, name, None) {
+            if name != &*found {
+                return SuggestionType::Function(found);
             }
-        }
-
-        let max_distance = max_suggestion_distance(name);
-        if !values.is_empty() && values[smallest] <= max_distance && name != &maybes[smallest][..] {
-
-            SuggestionType::Function(maybes[smallest].to_string())
-
-        } else {
-            SuggestionType::NotFound
-        }
+        } SuggestionType::NotFound
     }
 
     fn resolve_expr(&mut self, expr: &Expr) {
@@ -3495,7 +3476,7 @@ fn resolve_expr(&mut self, expr: &Expr) {
                                     NoSuggestion => {
                                         // limit search to 5 to reduce the number
                                         // of stupid suggestions
-                                        match self.find_best_match_for_name(&path_name) {
+                                        match self.find_best_match(&path_name) {
                                             SuggestionType::Macro(s) => {
                                                 format!("the macro `{}`", s)
                                             }
index fd471893acd53c5517328ae67e73597b8457d293..446b092ad5aaab25e05e71b741e673aabc863925 100644 (file)
 use syntax::ast::{NodeId, Name};
 use syntax::attr::AttrMetaMethods;
 use syntax::codemap::Span;
+use syntax::util::lev_distance::find_best_match_for_name;
 
 use std::mem::replace;
 use std::rc::Rc;
 
-
 /// Contains data for specific types of import directives.
 #[derive(Copy, Clone,Debug)]
 pub enum ImportDirectiveSubclass {
@@ -424,17 +424,22 @@ fn resolve_single_import(&mut self,
         };
 
         // We need to resolve both namespaces for this to succeed.
-        //
 
         let mut value_result = UnknownResult;
         let mut type_result = UnknownResult;
+        let mut lev_suggestion = "".to_owned();
 
         // Search for direct children of the containing module.
         build_reduced_graph::populate_module_if_necessary(self.resolver, &target_module);
 
         match target_module.children.borrow().get(&source) {
             None => {
-                // Continue.
+                let names = target_module.children.borrow();
+                if let Some(name) = find_best_match_for_name(names.keys(),
+                                                             &source.as_str(),
+                                                             None) {
+                    lev_suggestion = format!(". Did you mean to use `{}`?", name);
+                }
             }
             Some(ref child_name_bindings) => {
                 // pub_err makes sure we don't give the same error twice.
@@ -494,6 +499,17 @@ fn resolve_single_import(&mut self,
                         // therefore accurately report that the names are
                         // unbound.
 
+                        if lev_suggestion.is_empty() {  // skip if we already have a suggestion
+                            let names = target_module.import_resolutions.borrow();
+                            if let Some(name) = find_best_match_for_name(names.keys(),
+                                                                         &source.as_str(),
+                                                                         None) {
+                                lev_suggestion =
+                                    format!(". Did you mean to use the re-exported import `{}`?",
+                                            name);
+                            }
+                        }
+
                         if value_result.is_unknown() {
                             value_result = UnboundResult;
                         }
@@ -671,9 +687,9 @@ fn get_binding(this: &mut Resolver,
                                                            target);
 
         if value_result.is_unbound() && type_result.is_unbound() {
-            let msg = format!("There is no `{}` in `{}`",
+            let msg = format!("There is no `{}` in `{}`{}",
                               source,
-                              module_to_string(&target_module));
+                              module_to_string(&target_module), lev_suggestion);
             return ResolveResult::Failed(Some((directive.span, msg)));
         }
         let value_used_public = value_used_reexport || value_used_public;
index a50213202b82c8cb94905f2ac3c65aa0c5f53683..84f48111a9efea62fa08431a87757de92c2708e3 100644 (file)
 use syntax::owned_slice::OwnedSlice;
 use syntax::parse::token::{self, InternedString};
 use syntax::ptr::P;
-use syntax::util::lev_distance::lev_distance;
+use syntax::util::lev_distance::find_best_match_for_name;
 
 use rustc_front::intravisit::{self, Visitor};
 use rustc_front::hir;
@@ -2996,28 +2996,22 @@ fn suggest_field_names<'tcx>(variant: ty::VariantDef<'tcx>,
                                  tcx: &ty::ctxt<'tcx>,
                                  skip : Vec<InternedString>) {
         let name = field.node.as_str();
+        let names = variant.fields
+                    .iter()
+                    .filter_map(|ref field| {
+                        // ignore already set fields and private fields from non-local crates
+                        if skip.iter().any(|x| *x == field.name.as_str()) ||
+                           (variant.did.krate != LOCAL_CRATE && field.vis != Visibility::Public) {
+                               None
+                        } else {
+                            Some(&field.name)
+                        }
+                    });
+
         // only find fits with at least one matching letter
-        let mut best_dist = name.len();
-        let mut best = None;
-        for elem in &variant.fields {
-            let n = elem.name.as_str();
-            // ignore already set fields
-            if skip.iter().any(|x| *x == n) {
-                continue;
-            }
-            // ignore private fields from non-local crates
-            if variant.did.krate != LOCAL_CRATE && elem.vis != Visibility::Public {
-                continue;
-            }
-            let dist = lev_distance(&n, &name);
-            if dist < best_dist {
-                best = Some(n);
-                best_dist = dist;
-            }
-        }
-        if let Some(n) = best {
+        if let Some(name) = find_best_match_for_name(names, &name, Some(name.len())) {
             tcx.sess.span_help(field.span,
-                &format!("did you mean `{}`?", n));
+                &format!("did you mean `{}`?", name));
         }
     }
 
index 3b613922bc947f89a7081ad79513aefc5fca0850..6c13d09e56c290a929c1299b72844daef5ebae17 100644 (file)
@@ -24,7 +24,7 @@
 use parse::token::{InternedString, intern, str_to_ident};
 use ptr::P;
 use util::small_vector::SmallVector;
-use util::lev_distance::{lev_distance, max_suggestion_distance};
+use util::lev_distance::find_best_match_for_name;
 use ext::mtwt;
 use fold::Folder;
 
@@ -780,15 +780,8 @@ pub fn name_of(&self, st: &str) -> ast::Name {
     }
 
     pub fn suggest_macro_name(&mut self, name: &str, span: Span) {
-        let mut min: Option<(Name, usize)> = None;
-        let max_dist = max_suggestion_distance(name);
-        for macro_name in self.syntax_env.names.iter() {
-            let dist = lev_distance(name, &macro_name.as_str());
-            if dist <= max_dist && (min.is_none() || min.unwrap().1 > dist) {
-                min = Some((*macro_name, dist));
-            }
-        }
-        if let Some((suggestion, _)) = min {
+        let names = &self.syntax_env.names;
+        if let Some(suggestion) = find_best_match_for_name(names.iter(), name, None) {
             self.fileline_help(span, &format!("did you mean `{}!`?", suggestion));
         }
     }
index 9bf96311122e0e3c51a891ec349afedc2012cbfe..e0796c34e57efde74e2e0e046780785547279196 100644 (file)
@@ -8,50 +8,64 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use ast::Name;
 use std::cmp;
+use parse::token::InternedString;
 
-pub fn lev_distance(me: &str, t: &str) -> usize {
-    if me.is_empty() { return t.chars().count(); }
-    if t.is_empty() { return me.chars().count(); }
+/// To find the Levenshtein distance between two strings
+pub fn lev_distance(a: &str, b: &str) -> usize {
+    // cases which don't require further computation
+    if a.is_empty() {
+        return b.chars().count();
+    } else if b.is_empty() {
+        return a.chars().count();
+    }
 
-    let mut dcol: Vec<_> = (0..t.len() + 1).collect();
+    let mut dcol: Vec<_> = (0..b.len() + 1).collect();
     let mut t_last = 0;
 
-    for (i, sc) in me.chars().enumerate() {
-
+    for (i, sc) in a.chars().enumerate() {
         let mut current = i;
         dcol[0] = current + 1;
 
-        for (j, tc) in t.chars().enumerate() {
-
+        for (j, tc) in b.chars().enumerate() {
             let next = dcol[j + 1];
-
             if sc == tc {
                 dcol[j + 1] = current;
             } else {
                 dcol[j + 1] = cmp::min(current, next);
                 dcol[j + 1] = cmp::min(dcol[j + 1], dcol[j]) + 1;
             }
-
             current = next;
             t_last = j;
         }
-    }
-
-    dcol[t_last + 1]
+    } dcol[t_last + 1]
 }
 
-pub fn max_suggestion_distance(name: &str) -> usize {
-    use std::cmp::max;
-    // As a loose rule to avoid obviously incorrect suggestions, clamp the
-    // maximum edit distance we will accept for a suggestion to one third of
-    // the typo'd name's length.
-    max(name.len(), 3) / 3
+/// To find the best match for a given string from an iterator of names
+/// As a loose rule to avoid the obviously incorrect suggestions, it takes
+/// an optional limit for the maximum allowable edit distance, which defaults
+/// to one-third of the given word
+pub fn find_best_match_for_name<'a, T>(iter_names: T,
+                                       lookup: &str,
+                                       dist: Option<usize>) -> Option<InternedString>
+    where T: Iterator<Item = &'a Name> {
+    let max_dist = dist.map_or_else(|| cmp::max(lookup.len(), 3) / 3, |d| d);
+    iter_names
+    .filter_map(|name| {
+        let dist = lev_distance(lookup, &name.as_str());
+        match dist <= max_dist {    // filter the unwanted cases
+            true => Some((name.as_str(), dist)),
+            false => None,
+        }
+    })
+    .min_by_key(|&(_, val)| val)    // extract the tuple containing the minimum edit distance
+    .map(|(s, _)| s)                // and return only the string
 }
 
 #[test]
 fn test_lev_distance() {
-    use std::char::{ from_u32, MAX };
+    use std::char::{from_u32, MAX};
     // Test bytelength agnosticity
     for c in (0..MAX as u32)
              .filter_map(|i| from_u32(i))
index 5edcf1d99a02bda2285b092177f6a65d4fdf793d..b6207450d983594ffe1c8a4c9127cbe67d2b15d0 100644 (file)
@@ -8,24 +8,26 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+// ignore-tidy-linelength
+
 use foo::bar; //~ ERROR unresolved import `foo::bar`. Maybe a missing `extern crate foo`?
 
-use bar::baz as x; //~ ERROR unresolved import `bar::baz`. There is no `baz` in `bar`
+use bar::Baz as x; //~ ERROR unresolved import `bar::Baz`. There is no `Baz` in `bar`. Did you mean to use `Bar`?
 
-use food::baz; //~ ERROR unresolved import `food::baz`. There is no `baz` in `food`
+use food::baz; //~ ERROR unresolved import `food::baz`. There is no `baz` in `food`. Did you mean to use the re-exported import `bag`?
 
-use food::{quux as beans}; //~ ERROR unresolved import `food::quux`. There is no `quux` in `food`
+use food::{beens as Foo}; //~ ERROR unresolved import `food::beens`. There is no `beens` in `food`. Did you mean to use the re-exported import `beans`?
 
 mod bar {
-    struct bar;
+    pub struct Bar;
 }
 
 mod food {
-    pub use self::zug::baz::{self as bag, quux as beans};
+    pub use self::zug::baz::{self as bag, foobar as beans};
 
     mod zug {
         pub mod baz {
-            pub struct quux;
+            pub struct foobar;
         }
     }
 }