Skip to content

Commit

Permalink
resolve: Levenshtein-based suggestions for non-import paths
Browse files Browse the repository at this point in the history
  • Loading branch information
petrochenkov committed Jan 13, 2017
1 parent 9dedc81 commit 589bd64
Showing 4 changed files with 157 additions and 19 deletions.
81 changes: 65 additions & 16 deletions src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
@@ -2191,11 +2191,9 @@ impl<'a> Resolver<'a> {
}

// Try Levenshtein if nothing else worked.
if path.len() == 1 {
if let Some(candidate) = this.lookup_typo_candidate(name, ns, is_expected) {
err.span_label(span, &format!("did you mean `{}`?", candidate));
return err;
}
if let Some(candidate) = this.lookup_typo_candidate(path, ns, is_expected) {
err.span_label(span, &format!("did you mean `{}`?", candidate));
return err;
}

// Fallback label.
@@ -2649,21 +2647,72 @@ impl<'a> Resolver<'a> {
}

fn lookup_typo_candidate<FilterFn>(&mut self,
name: Name,
path: &[Ident],
ns: Namespace,
filter_fn: FilterFn)
-> Option<Name>
-> Option<String>
where FilterFn: Fn(Def) -> bool
{
// FIXME: bindings in ribs provide quite modest set of candidates,
// extend it with other names in scope.
let names = self.ribs[ns].iter().rev().flat_map(|rib| {
rib.bindings.iter().filter_map(|(ident, def)| {
if filter_fn(*def) { Some(&ident.name) } else { None }
})
});
match find_best_match_for_name(names, &name.as_str(), None) {
Some(found) if found != name => Some(found),
let add_module_candidates = |module: Module, names: &mut Vec<Name>| {
for (&(ident, _), resolution) in module.resolutions.borrow().iter() {
if let Some(binding) = resolution.borrow().binding {
if filter_fn(binding.def()) {
names.push(ident.name);
}
}
}
};

let mut names = Vec::new();
let prefix_str = if path.len() == 1 {
// Search in lexical scope.
// Walk backwards up the ribs in scope and collect candidates.
for rib in self.ribs[ns].iter().rev() {
// Locals and type parameters
for (ident, def) in &rib.bindings {
if filter_fn(*def) {
names.push(ident.name);
}
}
// Items in scope
if let ModuleRibKind(module) = rib.kind {
// Items from this module
add_module_candidates(module, &mut names);

if let ModuleKind::Block(..) = module.kind {
// We can see through blocks
} else {
// Items from the prelude
if let Some(prelude) = self.prelude {
if !module.no_implicit_prelude {
add_module_candidates(prelude, &mut names);
}
}
break;
}
}
}
// Add primitive types to the mix
if filter_fn(Def::PrimTy(TyBool)) {
for (name, _) in &self.primitive_type_table.primitive_types {
names.push(*name);
}
}
String::new()
} else {
// Search in module.
let mod_path = &path[..path.len() - 1];
if let PathResult::Module(module) = self.resolve_path(mod_path, Some(TypeNS), None) {
add_module_candidates(module, &mut names);
}
names_to_string(mod_path) + "::"
};

let name = path[path.len() - 1].name;
// Make sure error reporting is deterministic.
names.sort_by_key(|name| name.as_str());
match find_best_match_for_name(names.iter(), &name.as_str(), None) {
Some(found) if found != name => Some(format!("{}{}", prefix_str, found)),
_ => None,
}
}
33 changes: 33 additions & 0 deletions src/test/ui/resolve/levenshtein.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2016 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

const MAX_ITEM: usize = 10;

fn foo_bar() {}

fn foo(c: esize) {} // Misspelled primitive type name.

enum Bar { }

type A = Baz; // Misspelled type name.
type B = Opiton<u8>; // Misspelled type name from the prelude.

mod m {
type A = Baz; // No suggestion here, Bar is not visible

pub struct First;
pub struct Second;
}

fn main() {
let v = [0u32; MAXITEM]; // Misspelled constant name.
foobar(); // Misspelled function name.
let b: m::first = m::second; // Misspelled item in module.
}
56 changes: 56 additions & 0 deletions src/test/ui/resolve/levenshtein.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
error[E0412]: cannot find type `esize` in this scope
--> $DIR/levenshtein.rs:15:11
|
15 | fn foo(c: esize) {} // Misspelled primitive type name.
| ^^^^^ did you mean `isize`?

error[E0412]: cannot find type `Baz` in this scope
--> $DIR/levenshtein.rs:19:10
|
19 | type A = Baz; // Misspelled type name.
| ^^^ did you mean `Bar`?

error[E0412]: cannot find type `Opiton` in this scope
--> $DIR/levenshtein.rs:20:10
|
20 | type B = Opiton<u8>; // Misspelled type name from the prelude.
| ^^^^^^^^^^ did you mean `Option`?

error[E0412]: cannot find type `Baz` in this scope
--> $DIR/levenshtein.rs:23:14
|
23 | type A = Baz; // No suggestion here, Bar is not visible
| ^^^ not found in this scope

error[E0425]: cannot find value `MAXITEM` in this scope
--> $DIR/levenshtein.rs:30:20
|
30 | let v = [0u32; MAXITEM]; // Misspelled constant name.
| ^^^^^^^ did you mean `MAX_ITEM`?

error[E0425]: cannot find function `foobar` in this scope
--> $DIR/levenshtein.rs:31:5
|
31 | foobar(); // Misspelled function name.
| ^^^^^^ did you mean `foo_bar`?

error[E0412]: cannot find type `first` in module `m`
--> $DIR/levenshtein.rs:32:12
|
32 | let b: m::first = m::second; // Misspelled item in module.
| ^^^^^^^^ did you mean `m::First`?

error[E0425]: cannot find value `second` in module `m`
--> $DIR/levenshtein.rs:32:23
|
32 | let b: m::first = m::second; // Misspelled item in module.
| ^^^^^^^^^ did you mean `m::Second`?

error[E0080]: constant evaluation error
--> $DIR/levenshtein.rs:30:20
|
30 | let v = [0u32; MAXITEM]; // Misspelled constant name.
| ^^^^^^^ unresolved path in constant expression

error: aborting due to previous error

Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ error[E0423]: expected value, found module `a::b`
--> $DIR/suggest-path-instead-of-mod-dot-item.rs:55:12
|
55 | v.push(a::b);
| ^^^^ not a value
| ^^^^ did you mean `a::I`?

error[E0423]: expected value, found module `a::b`
--> $DIR/suggest-path-instead-of-mod-dot-item.rs:61:5
@@ -44,13 +44,13 @@ error[E0423]: expected value, found module `a::b`
--> $DIR/suggest-path-instead-of-mod-dot-item.rs:67:5
|
67 | a::b
| ^^^^ not a value
| ^^^^ did you mean `a::I`?

error[E0423]: expected function, found module `a::b`
--> $DIR/suggest-path-instead-of-mod-dot-item.rs:73:5
|
73 | a::b()
| ^^^^ not a function
| ^^^^ did you mean `a::I`?

error: main function not found

0 comments on commit 589bd64

Please sign in to comment.