Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolve: Levenshtein-based suggestions for non-import paths #38927

Merged
merged 1 commit into from
Jan 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 65 additions & 16 deletions src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
}
}
Expand Down
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
Expand Up @@ -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
Expand All @@ -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

Expand Down