Auto merge of #38927 - petrochenkov:leven, r=jseyfried
resolve: Levenshtein-based suggestions for non-import paths This patch addresses both items from https://github.com/rust-lang/rust/issues/30197#issuecomment-264846000 and therefore implements the largest part of https://github.com/rust-lang/rust/issues/30197. r? @jseyfried
This commit is contained in:
commit
7d82d95af9
@ -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
src/test/ui/resolve/levenshtein.rs
Normal file
33
src/test/ui/resolve/levenshtein.rs
Normal file
@ -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
src/test/ui/resolve/levenshtein.stderr
Normal file
56
src/test/ui/resolve/levenshtein.stderr
Normal file
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user