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:
commit
f2a5af7a4c
@ -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();
|
||||
|
@ -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`
|
||||
}
|
||||
|
@ -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`
|
||||
}
|
||||
|
@ -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`
|
||||
}
|
||||
|
@ -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`
|
||||
|
@ -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`
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user