Skip to content

Commit 09defbc

Browse files
author
Thomas Jespersen
committed
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 rust-lang#30197
1 parent ef227f5 commit 09defbc

File tree

6 files changed

+145
-10
lines changed

6 files changed

+145
-10
lines changed

src/librustc_typeck/check/method/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,22 @@ pub enum MethodError<'tcx> {
6868
// could lead to matches if satisfied, and a list of not-in-scope traits which may work.
6969
pub struct NoMatchData<'tcx> {
7070
pub static_candidates: Vec<CandidateSource>,
71+
pub lev_candidates: Vec<ty::AssociatedItem>,
7172
pub unsatisfied_predicates: Vec<TraitRef<'tcx>>,
7273
pub out_of_scope_traits: Vec<DefId>,
7374
pub mode: probe::Mode,
7475
}
7576

7677
impl<'tcx> NoMatchData<'tcx> {
7778
pub fn new(static_candidates: Vec<CandidateSource>,
79+
lev_candidates: Vec<ty::AssociatedItem>,
7880
unsatisfied_predicates: Vec<TraitRef<'tcx>>,
7981
out_of_scope_traits: Vec<DefId>,
8082
mode: probe::Mode)
8183
-> Self {
8284
NoMatchData {
8385
static_candidates,
86+
lev_candidates,
8487
unsatisfied_predicates,
8588
out_of_scope_traits,
8689
mode,

src/librustc_typeck/check/method/probe.rs

+61-10
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ use rustc::infer::type_variable::TypeVariableOrigin;
2323
use rustc::util::nodemap::FxHashSet;
2424
use rustc::infer::{self, InferOk};
2525
use syntax::ast;
26+
use syntax::util::lev_distance::lev_distance;
2627
use syntax_pos::Span;
2728
use rustc::hir;
2829
use std::mem;
2930
use std::ops::Deref;
3031
use std::rc::Rc;
32+
use std::cmp::max;
3133

3234
use self::CandidateKind::*;
3335
pub use self::PickKind::*;
@@ -51,6 +53,10 @@ struct ProbeContext<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
5153
/// used for error reporting
5254
static_candidates: Vec<CandidateSource>,
5355

56+
/// When probing for names, include names that are close to the
57+
/// requested name (by Levensthein distance)
58+
allow_similar_names: bool,
59+
5460
/// Some(candidate) if there is a private candidate
5561
private_candidate: Option<Def>,
5662

@@ -240,6 +246,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
240246
Some(steps) => steps,
241247
None => {
242248
return Err(MethodError::NoMatch(NoMatchData::new(Vec::new(),
249+
Vec::new(),
243250
Vec::new(),
244251
Vec::new(),
245252
mode)))
@@ -261,7 +268,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
261268
// that we create during the probe process are removed later
262269
self.probe(|_| {
263270
let mut probe_cx =
264-
ProbeContext::new(self, span, mode, method_name, return_type, steps);
271+
ProbeContext::new(self, span, mode, method_name, return_type, Rc::new(steps));
265272

266273
probe_cx.assemble_inherent_candidates();
267274
match scope {
@@ -333,7 +340,7 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
333340
mode: Mode,
334341
method_name: Option<ast::Name>,
335342
return_type: Option<Ty<'tcx>>,
336-
steps: Vec<CandidateStep<'tcx>>)
343+
steps: Rc<Vec<CandidateStep<'tcx>>>)
337344
-> ProbeContext<'a, 'gcx, 'tcx> {
338345
ProbeContext {
339346
fcx,
@@ -344,8 +351,9 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
344351
inherent_candidates: Vec::new(),
345352
extension_candidates: Vec::new(),
346353
impl_dups: FxHashSet(),
347-
steps: Rc::new(steps),
354+
steps: steps,
348355
static_candidates: Vec::new(),
356+
allow_similar_names: false,
349357
private_candidate: None,
350358
unsatisfied_predicates: Vec::new(),
351359
}
@@ -798,8 +806,10 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
798806
if let Some(def) = private_candidate {
799807
return Err(MethodError::PrivateMatch(def, out_of_scope_traits));
800808
}
809+
let lev_candidates = self.probe_for_lev_candidates()?;
801810

802811
Err(MethodError::NoMatch(NoMatchData::new(static_candidates,
812+
lev_candidates,
803813
unsatisfied_predicates,
804814
out_of_scope_traits,
805815
self.mode)))
@@ -913,11 +923,8 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
913923
debug!("applicable_candidates: {:?}", applicable_candidates);
914924

915925
if applicable_candidates.len() > 1 {
916-
match self.collapse_candidates_to_trait_pick(&applicable_candidates[..]) {
917-
Some(pick) => {
918-
return Some(Ok(pick));
919-
}
920-
None => {}
926+
if let Some(pick) = self.collapse_candidates_to_trait_pick(&applicable_candidates[..]) {
927+
return Some(Ok(pick));
921928
}
922929
}
923930

@@ -1126,6 +1133,39 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
11261133
})
11271134
}
11281135

1136+
/// Similarly to `probe_for_return_type`, this method attempts to find candidate methods where
1137+
/// the method name may have been misspelt.
1138+
fn probe_for_lev_candidates(&mut self) -> Result<Vec<ty::AssociatedItem>, MethodError<'tcx>> {
1139+
debug!("Probing for method names similar to {:?}",
1140+
self.method_name);
1141+
1142+
let steps = self.steps.clone();
1143+
self.probe(|_| {
1144+
let mut pcx = ProbeContext::new(self.fcx, self.span, self.mode, self.method_name,
1145+
self.return_type, steps);
1146+
pcx.allow_similar_names = true;
1147+
pcx.assemble_inherent_candidates();
1148+
pcx.assemble_extension_candidates_for_traits_in_scope(ast::DUMMY_NODE_ID)?;
1149+
1150+
let method_names = pcx.candidate_method_names();
1151+
pcx.allow_similar_names = false;
1152+
Ok(method_names
1153+
.iter()
1154+
.filter_map(|&method_name| {
1155+
pcx.reset();
1156+
pcx.method_name = Some(method_name);
1157+
pcx.assemble_inherent_candidates();
1158+
pcx.assemble_extension_candidates_for_traits_in_scope(ast::DUMMY_NODE_ID)
1159+
.ok().map_or(None, |_| {
1160+
pcx.pick_core()
1161+
.and_then(|pick| pick.ok())
1162+
.and_then(|pick| Some(pick.item))
1163+
})
1164+
})
1165+
.collect())
1166+
})
1167+
}
1168+
11291169
///////////////////////////////////////////////////////////////////////////
11301170
// MISCELLANY
11311171
fn has_applicable_self(&self, item: &ty::AssociatedItem) -> bool {
@@ -1253,10 +1293,21 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
12531293
self.tcx.erase_late_bound_regions(value)
12541294
}
12551295

1256-
/// Find the method with the appropriate name (or return type, as the case may be).
1296+
/// Find the method with the appropriate name (or return type, as the case may be). If
1297+
/// `allow_similar_names` is set, find methods with close-matching names.
12571298
fn impl_or_trait_item(&self, def_id: DefId) -> Vec<ty::AssociatedItem> {
12581299
if let Some(name) = self.method_name {
1259-
self.fcx.associated_item(def_id, name).map_or(Vec::new(), |x| vec![x])
1300+
if self.allow_similar_names {
1301+
let max_dist = max(name.as_str().len(), 3) / 3;
1302+
self.tcx.associated_items(def_id)
1303+
.filter(|x| {
1304+
let dist = lev_distance(&*name.as_str(), &x.name.as_str());
1305+
dist > 0 && dist <= max_dist
1306+
})
1307+
.collect()
1308+
} else {
1309+
self.fcx.associated_item(def_id, name).map_or(Vec::new(), |x| vec![x])
1310+
}
12601311
} else {
12611312
self.tcx.associated_items(def_id).collect()
12621313
}

src/librustc_typeck/check/method/suggest.rs

+7
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
162162

163163
match error {
164164
MethodError::NoMatch(NoMatchData { static_candidates: static_sources,
165+
lev_candidates,
165166
unsatisfied_predicates,
166167
out_of_scope_traits,
167168
mode,
@@ -282,6 +283,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
282283
item_name,
283284
rcvr_expr,
284285
out_of_scope_traits);
286+
287+
if !lev_candidates.is_empty() {
288+
for meth in lev_candidates.iter().take(5) {
289+
err.help(&format!("did you mean `{}`?", meth.name));
290+
}
291+
}
285292
err.emit();
286293
}
287294

src/test/ui/block-result/issue-3563.stderr

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ error[E0599]: no method named `b` found for type `&Self` in the current scope
33
|
44
13 | || self.b()
55
| ^
6+
|
7+
= help: did you mean `a`?
68

79
error[E0308]: mismatched types
810
--> $DIR/issue-3563.rs:13:9
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
struct Foo;
12+
13+
impl Foo {
14+
fn bar(self) {}
15+
fn baz(&self, x: f64) {}
16+
}
17+
18+
trait FooT {
19+
fn bag(&self);
20+
}
21+
22+
impl FooT for Foo {
23+
fn bag(&self) {}
24+
}
25+
26+
fn main() {
27+
let f = Foo;
28+
f.bat(1.0);
29+
30+
let s = "foo".to_string();
31+
let _ = s.is_emtpy();
32+
33+
// Generates a warning, both for count_ones and count_zeros
34+
let _ = 63u32.count_eos();
35+
let _ = 63u32.count_o(); // Does not generate a warning
36+
37+
}
38+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error[E0599]: no method named `bat` found for type `Foo` in the current scope
2+
--> $DIR/suggest-methods.rs:28:7
3+
|
4+
28 | f.bat(1.0);
5+
| ^^^
6+
|
7+
= help: did you mean `bar`?
8+
= help: did you mean `baz`?
9+
10+
error[E0599]: no method named `is_emtpy` found for type `std::string::String` in the current scope
11+
--> $DIR/suggest-methods.rs:31:15
12+
|
13+
31 | let _ = s.is_emtpy();
14+
| ^^^^^^^^
15+
|
16+
= help: did you mean `is_empty`?
17+
18+
error[E0599]: no method named `count_eos` found for type `u32` in the current scope
19+
--> $DIR/suggest-methods.rs:34:19
20+
|
21+
34 | let _ = 63u32.count_eos();
22+
| ^^^^^^^^^
23+
|
24+
= help: did you mean `count_ones`?
25+
= help: did you mean `count_zeros`?
26+
27+
error[E0599]: no method named `count_o` found for type `u32` in the current scope
28+
--> $DIR/suggest-methods.rs:35:19
29+
|
30+
35 | let _ = 63u32.count_o(); // Does not generate a warning
31+
| ^^^^^^^
32+
33+
error: aborting due to 4 previous errors
34+

0 commit comments

Comments
 (0)