Auto merge of #43442 - zackmdavis:note_available_field_names_if_levenshtein_fails, r=nikomatsakis

field does not exist error: note fields if Levenshtein suggestion fails

When trying to access or initialize a nonexistent field, if we can't infer what
field was meant (by virtue of the purported field in the source being a small
Levenshtein distance away from an actual field, suggestive of a typo), issue a
note listing all the available fields. To reduce terminal clutter, we don't
issue the note when we have a `find_best_match_for_name` Levenshtein
suggestion: the suggestion is probably right.

The third argument of the call to `find_best_match_for_name` is changed to
`None`, accepting the default maximum Levenshtein distance of one-third of the
identifier supplied for correction. The previous value of `Some(name.len())`
was overzealous, inappropriately very Levenshtein-distant suggestions when the
attempted field access could not plausibly be a mere typo. For example, if a
struct has fields `mule` and `phone`, but I type `.donkey`, I'd rather the
error have a note listing that the available fields are, in fact, `mule` and
`phone` (which is the behavior induced by this patch) rather than the error
asking "did you mean `phone`?" (which is the behavior on master). The "only
find fits with at least one matching letter" comment was accurate when it was
first introduced in 09d992471 (January 2015), but is a vicious lie in its
present context before a call to `find_best_match_for_name` and must be
destroyed (replacing every letter is within a Levenshtein distance of name.len()).

The present author claims that this suffices to resolve #42599.
This commit is contained in:
bors 2017-08-04 10:13:55 +00:00
commit f2a5af7a4c
11 changed files with 131 additions and 13 deletions

View File

@ -2960,6 +2960,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
format!("did you mean `{}`?", suggested_field_name));
} else {
err.span_label(field.span, "unknown field");
let struct_variant_def = def.struct_variant();
let field_names = self.available_field_names(struct_variant_def);
if !field_names.is_empty() {
err.note(&format!("available fields are: {}",
self.name_series_display(field_names)));
}
};
}
ty::TyRawPtr(..) => {
@ -2983,7 +2989,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
// Return an hint about the closest match in field names
fn suggest_field_name(variant: &'tcx ty::VariantDef,
field: &Spanned<ast::Name>,
skip : Vec<InternedString>)
skip: Vec<InternedString>)
-> Option<Symbol> {
let name = field.node.as_str();
let names = variant.fields.iter().filter_map(|field| {
@ -2996,8 +3002,29 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
}
});
// only find fits with at least one matching letter
find_best_match_for_name(names, &name, Some(name.len()))
find_best_match_for_name(names, &name, None)
}
fn available_field_names(&self, variant: &'tcx ty::VariantDef) -> Vec<ast::Name> {
let mut available = Vec::new();
for field in variant.fields.iter() {
let (_, def_scope) = self.tcx.adjust(field.name, variant.did, self.body_id);
if field.vis.is_accessible_from(def_scope, self.tcx) {
available.push(field.name);
}
}
available
}
fn name_series_display(&self, names: Vec<ast::Name>) -> String {
// dynamic limit, to never omit just one field
let limit = if names.len() == 6 { 6 } else { 5 };
let mut display = names.iter().take(limit)
.map(|n| format!("`{}`", n)).collect::<Vec<_>>().join(", ");
if names.len() > limit {
display = format!("{} ... and {} others", display, names.len() - limit);
}
display
}
// Check tuple index expressions
@ -3111,13 +3138,22 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
format!("field does not exist - did you mean `{}`?", field_name));
} else {
match ty.sty {
ty::TyAdt(adt, ..) if adt.is_enum() => {
err.span_label(field.name.span, format!("`{}::{}` does not have this field",
ty, variant.name));
}
_ => {
err.span_label(field.name.span, format!("`{}` does not have this field", ty));
ty::TyAdt(adt, ..) => {
if adt.is_enum() {
err.span_label(field.name.span,
format!("`{}::{}` does not have this field",
ty, variant.name));
} else {
err.span_label(field.name.span,
format!("`{}` does not have this field", ty));
}
let available_field_names = self.available_field_names(variant);
if !available_field_names.is_empty() {
err.note(&format!("available fields are: {}",
self.name_series_display(available_field_names)));
}
}
_ => bug!("non-ADT passed to report_unknown_field")
}
};
err.emit();

View File

@ -15,5 +15,6 @@ enum Field {
fn main() {
let s = Field::Fool { joke: 0 };
//~^ ERROR E0559
//~| NOTE field does not exist - did you mean `x`?
//~| NOTE `Field::Fool` does not have this field
//~| NOTE available fields are: `x`
}

View File

@ -16,4 +16,5 @@ fn main() {
let s = Simba { mother: 1, father: 0 };
//~^ ERROR E0560
//~| NOTE `Simba` does not have this field
//~| NOTE available fields are: `mother`
}

View File

@ -15,5 +15,6 @@ enum Homura {
fn main() {
let homura = Homura::Akemi { kaname: () };
//~^ ERROR variant `Homura::Akemi` has no field named `kaname`
//~| NOTE field does not exist - did you mean `madoka`?
//~| NOTE `Homura::Akemi` does not have this field
//~| NOTE available fields are: `madoka`
}

View File

@ -13,7 +13,8 @@ struct S(u8, u16);
fn main() {
let s = S{0b1: 10, 0: 11};
//~^ ERROR struct `S` has no field named `0b1`
//~| NOTE field does not exist - did you mean `1`?
//~| NOTE `S` does not have this field
//~| NOTE available fields are: `0`, `1`
match s {
S{0: a, 0x1: b, ..} => {}
//~^ ERROR does not have a field named `0x1`

View File

@ -18,5 +18,6 @@ fn main() {
bar: 0
//~^ ERROR struct `BuildData` has no field named `bar`
//~| NOTE `BuildData` does not have this field
//~| NOTE available fields are: `foo`
};
}

View File

@ -27,7 +27,8 @@ fn main () {
//~| NOTE field does not exist - did you mean `a`?
bb: 20,
//~^ ERROR struct `xc::B` has no field named `bb`
//~| NOTE field does not exist - did you mean `a`?
//~| NOTE `xc::B` does not have this field
//~| NOTE available fields are: `a`
};
// local crate struct
let l = A {

View File

@ -20,6 +20,7 @@ fn main() {
let u = U { a: 0, b: 1, c: 2 }; //~ ERROR union expressions should have exactly one field
//~^ ERROR union `U` has no field named `c`
//~| NOTE `U` does not have this field
//~| NOTE available fields are: `a`, `b`
let u = U { ..u }; //~ ERROR union expressions should have exactly one field
//~^ ERROR functional record update syntax requires a struct

View File

@ -3,6 +3,8 @@ error[E0609]: no field `zz` on type `Foo`
|
17 | f.zz;
| ^^ unknown field
|
= note: available fields are: `bar`
error: aborting due to previous error

View File

@ -0,0 +1,43 @@
// 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.
mod submodule {
#[derive(Default)]
pub struct Demo {
pub favorite_integer: isize,
secret_integer: isize,
pub innocently_misspellable: (),
another_field: bool,
yet_another_field: bool,
always_more_fields: bool,
and_ever: bool,
}
impl Demo {
fn new_with_secret_two() -> Self {
Self { secret_integer: 2, inocently_mispellable: () }
}
fn new_with_secret_three() -> Self {
Self { secret_integer: 3, egregiously_nonexistent_field: () }
}
}
}
fn main() {
use submodule::Demo;
let demo = Demo::default();
let innocent_field_misaccess = demo.inocently_mispellable;
// note shouldn't suggest private fields
let egregious_field_misaccess = demo.egregiously_nonexistent_field;
}

View File

@ -0,0 +1,30 @@
error[E0560]: struct `submodule::Demo` has no field named `inocently_mispellable`
--> $DIR/issue-42599_available_fields_note.rs:26:39
|
26 | Self { secret_integer: 2, inocently_mispellable: () }
| ^^^^^^^^^^^^^^^^^^^^^^ field does not exist - did you mean `innocently_misspellable`?
error[E0560]: struct `submodule::Demo` has no field named `egregiously_nonexistent_field`
--> $DIR/issue-42599_available_fields_note.rs:30:39
|
30 | Self { secret_integer: 3, egregiously_nonexistent_field: () }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `submodule::Demo` does not have this field
|
= note: available fields are: `favorite_integer`, `secret_integer`, `innocently_misspellable`, `another_field`, `yet_another_field` ... and 2 others
error[E0609]: no field `inocently_mispellable` on type `submodule::Demo`
--> $DIR/issue-42599_available_fields_note.rs:40:41
|
40 | let innocent_field_misaccess = demo.inocently_mispellable;
| ^^^^^^^^^^^^^^^^^^^^^ did you mean `innocently_misspellable`?
error[E0609]: no field `egregiously_nonexistent_field` on type `submodule::Demo`
--> $DIR/issue-42599_available_fields_note.rs:42:42
|
42 | let egregious_field_misaccess = demo.egregiously_nonexistent_field;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `favorite_integer`, `innocently_misspellable`
error: aborting due to 4 previous errors