Add suggestions for misspelled method names
Use the syntax::util::lev_distance module to provide suggestions when a named method cannot be found. Part of #30197
This commit is contained in:
parent
ef227f5ffe
commit
09defbcb5b
6 changed files with 145 additions and 10 deletions
|
|
@ -68,6 +68,7 @@ pub enum MethodError<'tcx> {
|
|||
// could lead to matches if satisfied, and a list of not-in-scope traits which may work.
|
||||
pub struct NoMatchData<'tcx> {
|
||||
pub static_candidates: Vec<CandidateSource>,
|
||||
pub lev_candidates: Vec<ty::AssociatedItem>,
|
||||
pub unsatisfied_predicates: Vec<TraitRef<'tcx>>,
|
||||
pub out_of_scope_traits: Vec<DefId>,
|
||||
pub mode: probe::Mode,
|
||||
|
|
@ -75,12 +76,14 @@ pub struct NoMatchData<'tcx> {
|
|||
|
||||
impl<'tcx> NoMatchData<'tcx> {
|
||||
pub fn new(static_candidates: Vec<CandidateSource>,
|
||||
lev_candidates: Vec<ty::AssociatedItem>,
|
||||
unsatisfied_predicates: Vec<TraitRef<'tcx>>,
|
||||
out_of_scope_traits: Vec<DefId>,
|
||||
mode: probe::Mode)
|
||||
-> Self {
|
||||
NoMatchData {
|
||||
static_candidates,
|
||||
lev_candidates,
|
||||
unsatisfied_predicates,
|
||||
out_of_scope_traits,
|
||||
mode,
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ use rustc::infer::type_variable::TypeVariableOrigin;
|
|||
use rustc::util::nodemap::FxHashSet;
|
||||
use rustc::infer::{self, InferOk};
|
||||
use syntax::ast;
|
||||
use syntax::util::lev_distance::lev_distance;
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::cmp::max;
|
||||
|
||||
use self::CandidateKind::*;
|
||||
pub use self::PickKind::*;
|
||||
|
|
@ -51,6 +53,10 @@ struct ProbeContext<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
|
|||
/// used for error reporting
|
||||
static_candidates: Vec<CandidateSource>,
|
||||
|
||||
/// When probing for names, include names that are close to the
|
||||
/// requested name (by Levensthein distance)
|
||||
allow_similar_names: bool,
|
||||
|
||||
/// Some(candidate) if there is a private candidate
|
||||
private_candidate: Option<Def>,
|
||||
|
||||
|
|
@ -240,6 +246,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
Some(steps) => steps,
|
||||
None => {
|
||||
return Err(MethodError::NoMatch(NoMatchData::new(Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
mode)))
|
||||
|
|
@ -261,7 +268,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
// that we create during the probe process are removed later
|
||||
self.probe(|_| {
|
||||
let mut probe_cx =
|
||||
ProbeContext::new(self, span, mode, method_name, return_type, steps);
|
||||
ProbeContext::new(self, span, mode, method_name, return_type, Rc::new(steps));
|
||||
|
||||
probe_cx.assemble_inherent_candidates();
|
||||
match scope {
|
||||
|
|
@ -333,7 +340,7 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
|||
mode: Mode,
|
||||
method_name: Option<ast::Name>,
|
||||
return_type: Option<Ty<'tcx>>,
|
||||
steps: Vec<CandidateStep<'tcx>>)
|
||||
steps: Rc<Vec<CandidateStep<'tcx>>>)
|
||||
-> ProbeContext<'a, 'gcx, 'tcx> {
|
||||
ProbeContext {
|
||||
fcx,
|
||||
|
|
@ -344,8 +351,9 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
|||
inherent_candidates: Vec::new(),
|
||||
extension_candidates: Vec::new(),
|
||||
impl_dups: FxHashSet(),
|
||||
steps: Rc::new(steps),
|
||||
steps: steps,
|
||||
static_candidates: Vec::new(),
|
||||
allow_similar_names: false,
|
||||
private_candidate: None,
|
||||
unsatisfied_predicates: Vec::new(),
|
||||
}
|
||||
|
|
@ -798,8 +806,10 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
|||
if let Some(def) = private_candidate {
|
||||
return Err(MethodError::PrivateMatch(def, out_of_scope_traits));
|
||||
}
|
||||
let lev_candidates = self.probe_for_lev_candidates()?;
|
||||
|
||||
Err(MethodError::NoMatch(NoMatchData::new(static_candidates,
|
||||
lev_candidates,
|
||||
unsatisfied_predicates,
|
||||
out_of_scope_traits,
|
||||
self.mode)))
|
||||
|
|
@ -913,11 +923,8 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
|||
debug!("applicable_candidates: {:?}", applicable_candidates);
|
||||
|
||||
if applicable_candidates.len() > 1 {
|
||||
match self.collapse_candidates_to_trait_pick(&applicable_candidates[..]) {
|
||||
Some(pick) => {
|
||||
return Some(Ok(pick));
|
||||
}
|
||||
None => {}
|
||||
if let Some(pick) = self.collapse_candidates_to_trait_pick(&applicable_candidates[..]) {
|
||||
return Some(Ok(pick));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1126,6 +1133,39 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Similarly to `probe_for_return_type`, this method attempts to find candidate methods where
|
||||
/// the method name may have been misspelt.
|
||||
fn probe_for_lev_candidates(&mut self) -> Result<Vec<ty::AssociatedItem>, MethodError<'tcx>> {
|
||||
debug!("Probing for method names similar to {:?}",
|
||||
self.method_name);
|
||||
|
||||
let steps = self.steps.clone();
|
||||
self.probe(|_| {
|
||||
let mut pcx = ProbeContext::new(self.fcx, self.span, self.mode, self.method_name,
|
||||
self.return_type, steps);
|
||||
pcx.allow_similar_names = true;
|
||||
pcx.assemble_inherent_candidates();
|
||||
pcx.assemble_extension_candidates_for_traits_in_scope(ast::DUMMY_NODE_ID)?;
|
||||
|
||||
let method_names = pcx.candidate_method_names();
|
||||
pcx.allow_similar_names = false;
|
||||
Ok(method_names
|
||||
.iter()
|
||||
.filter_map(|&method_name| {
|
||||
pcx.reset();
|
||||
pcx.method_name = Some(method_name);
|
||||
pcx.assemble_inherent_candidates();
|
||||
pcx.assemble_extension_candidates_for_traits_in_scope(ast::DUMMY_NODE_ID)
|
||||
.ok().map_or(None, |_| {
|
||||
pcx.pick_core()
|
||||
.and_then(|pick| pick.ok())
|
||||
.and_then(|pick| Some(pick.item))
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// MISCELLANY
|
||||
fn has_applicable_self(&self, item: &ty::AssociatedItem) -> bool {
|
||||
|
|
@ -1253,10 +1293,21 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
|||
self.tcx.erase_late_bound_regions(value)
|
||||
}
|
||||
|
||||
/// Find the method with the appropriate name (or return type, as the case may be).
|
||||
/// Find the method with the appropriate name (or return type, as the case may be). If
|
||||
/// `allow_similar_names` is set, find methods with close-matching names.
|
||||
fn impl_or_trait_item(&self, def_id: DefId) -> Vec<ty::AssociatedItem> {
|
||||
if let Some(name) = self.method_name {
|
||||
self.fcx.associated_item(def_id, name).map_or(Vec::new(), |x| vec![x])
|
||||
if self.allow_similar_names {
|
||||
let max_dist = max(name.as_str().len(), 3) / 3;
|
||||
self.tcx.associated_items(def_id)
|
||||
.filter(|x| {
|
||||
let dist = lev_distance(&*name.as_str(), &x.name.as_str());
|
||||
dist > 0 && dist <= max_dist
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
self.fcx.associated_item(def_id, name).map_or(Vec::new(), |x| vec![x])
|
||||
}
|
||||
} else {
|
||||
self.tcx.associated_items(def_id).collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
|
||||
match error {
|
||||
MethodError::NoMatch(NoMatchData { static_candidates: static_sources,
|
||||
lev_candidates,
|
||||
unsatisfied_predicates,
|
||||
out_of_scope_traits,
|
||||
mode,
|
||||
|
|
@ -282,6 +283,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
item_name,
|
||||
rcvr_expr,
|
||||
out_of_scope_traits);
|
||||
|
||||
if !lev_candidates.is_empty() {
|
||||
for meth in lev_candidates.iter().take(5) {
|
||||
err.help(&format!("did you mean `{}`?", meth.name));
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ error[E0599]: no method named `b` found for type `&Self` in the current scope
|
|||
|
|
||||
13 | || self.b()
|
||||
| ^
|
||||
|
|
||||
= help: did you mean `a`?
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/issue-3563.rs:13:9
|
||||
|
|
|
|||
38
src/test/ui/suggestions/suggest-methods.rs
Normal file
38
src/test/ui/suggestions/suggest-methods.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
struct Foo;
|
||||
|
||||
impl Foo {
|
||||
fn bar(self) {}
|
||||
fn baz(&self, x: f64) {}
|
||||
}
|
||||
|
||||
trait FooT {
|
||||
fn bag(&self);
|
||||
}
|
||||
|
||||
impl FooT for Foo {
|
||||
fn bag(&self) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let f = Foo;
|
||||
f.bat(1.0);
|
||||
|
||||
let s = "foo".to_string();
|
||||
let _ = s.is_emtpy();
|
||||
|
||||
// Generates a warning, both for count_ones and count_zeros
|
||||
let _ = 63u32.count_eos();
|
||||
let _ = 63u32.count_o(); // Does not generate a warning
|
||||
|
||||
}
|
||||
|
||||
34
src/test/ui/suggestions/suggest-methods.stderr
Normal file
34
src/test/ui/suggestions/suggest-methods.stderr
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
error[E0599]: no method named `bat` found for type `Foo` in the current scope
|
||||
--> $DIR/suggest-methods.rs:28:7
|
||||
|
|
||||
28 | f.bat(1.0);
|
||||
| ^^^
|
||||
|
|
||||
= help: did you mean `bar`?
|
||||
= help: did you mean `baz`?
|
||||
|
||||
error[E0599]: no method named `is_emtpy` found for type `std::string::String` in the current scope
|
||||
--> $DIR/suggest-methods.rs:31:15
|
||||
|
|
||||
31 | let _ = s.is_emtpy();
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: did you mean `is_empty`?
|
||||
|
||||
error[E0599]: no method named `count_eos` found for type `u32` in the current scope
|
||||
--> $DIR/suggest-methods.rs:34:19
|
||||
|
|
||||
34 | let _ = 63u32.count_eos();
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= help: did you mean `count_ones`?
|
||||
= help: did you mean `count_zeros`?
|
||||
|
||||
error[E0599]: no method named `count_o` found for type `u32` in the current scope
|
||||
--> $DIR/suggest-methods.rs:35:19
|
||||
|
|
||||
35 | let _ = 63u32.count_o(); // Does not generate a warning
|
||||
| ^^^^^^^
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue