Skip to content

Commit bf7e91f

Browse files
committed
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 09d9924 (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 a Levenshtein distance of name.len()). The present author claims that this suffices to resolve #42599.
1 parent 6270257 commit bf7e91f

File tree

11 files changed

+114
-13
lines changed

11 files changed

+114
-13
lines changed

src/librustc_typeck/check/mod.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2956,6 +2956,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29562956
format!("did you mean `{}`?", suggested_field_name));
29572957
} else {
29582958
err.span_label(field.span, "unknown field");
2959+
let struct_variant_def = def.struct_variant();
2960+
let available_field_names = self.available_field_names(
2961+
struct_variant_def);
2962+
err.note(&format!("available fields are: {}",
2963+
available_field_names.join(", ")));
29592964
};
29602965
}
29612966
ty::TyRawPtr(..) => {
@@ -2979,7 +2984,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29792984
// Return an hint about the closest match in field names
29802985
fn suggest_field_name(variant: &'tcx ty::VariantDef,
29812986
field: &Spanned<ast::Name>,
2982-
skip : Vec<InternedString>)
2987+
skip: Vec<InternedString>)
29832988
-> Option<Symbol> {
29842989
let name = field.node.as_str();
29852990
let names = variant.fields.iter().filter_map(|field| {
@@ -2992,8 +2997,18 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29922997
}
29932998
});
29942999

2995-
// only find fits with at least one matching letter
2996-
find_best_match_for_name(names, &name, Some(name.len()))
3000+
find_best_match_for_name(names, &name, None)
3001+
}
3002+
3003+
fn available_field_names(&self, variant: &'tcx ty::VariantDef) -> Vec<String> {
3004+
let mut available = Vec::new();
3005+
for field in variant.fields.iter() {
3006+
let (_, def_scope) = self.tcx.adjust(field.name, variant.did, self.body_id);
3007+
if field.vis.is_accessible_from(def_scope, self.tcx) {
3008+
available.push(field.name.to_string());
3009+
}
3010+
}
3011+
available
29973012
}
29983013

29993014
// Check tuple index expressions
@@ -3107,14 +3122,22 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
31073122
format!("field does not exist - did you mean `{}`?", field_name));
31083123
} else {
31093124
match ty.sty {
3110-
ty::TyAdt(adt, ..) if adt.is_enum() => {
3111-
err.span_label(field.name.span, format!("`{}::{}` does not have this field",
3112-
ty, variant.name));
3113-
}
3114-
_ => {
3115-
err.span_label(field.name.span, format!("`{}` does not have this field", ty));
3125+
ty::TyAdt(adt, ..) => {
3126+
if adt.is_enum() {
3127+
err.span_label(field.name.span,
3128+
format!("`{}::{}` does not have this field",
3129+
ty, variant.name));
3130+
} else {
3131+
err.span_label(field.name.span,
3132+
format!("`{}` does not have this field", ty));
3133+
}
3134+
let available_field_names = self.available_field_names(variant);
3135+
err.note(&format!("available fields are: {}",
3136+
available_field_names.join(", ")));
31163137
}
3138+
_ => bug!("non-ADT passed to report_unknown_field")
31173139
}
3140+
31183141
};
31193142
err.emit();
31203143
}

src/test/compile-fail/E0559.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ enum Field {
1515
fn main() {
1616
let s = Field::Fool { joke: 0 };
1717
//~^ ERROR E0559
18-
//~| NOTE field does not exist - did you mean `x`?
18+
//~| NOTE `Field::Fool` does not have this field
19+
//~| NOTE available fields are: x
1920
}

src/test/compile-fail/E0560.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ fn main() {
1616
let s = Simba { mother: 1, father: 0 };
1717
//~^ ERROR E0560
1818
//~| NOTE `Simba` does not have this field
19+
//~| NOTE available fields are: mother
1920
}

src/test/compile-fail/issue-19922.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ enum Homura {
1515
fn main() {
1616
let homura = Homura::Akemi { kaname: () };
1717
//~^ ERROR variant `Homura::Akemi` has no field named `kaname`
18-
//~| NOTE field does not exist - did you mean `madoka`?
18+
//~| NOTE `Homura::Akemi` does not have this field
19+
//~| NOTE available fields are: madoka
1920
}

src/test/compile-fail/numeric-fields.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ struct S(u8, u16);
1313
fn main() {
1414
let s = S{0b1: 10, 0: 11};
1515
//~^ ERROR struct `S` has no field named `0b1`
16-
//~| NOTE field does not exist - did you mean `1`?
16+
//~| NOTE `S` does not have this field
17+
//~| NOTE available fields are: 0, 1
1718
match s {
1819
S{0: a, 0x1: b, ..} => {}
1920
//~^ ERROR does not have a field named `0x1`

src/test/compile-fail/struct-fields-too-many.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ fn main() {
1818
bar: 0
1919
//~^ ERROR struct `BuildData` has no field named `bar`
2020
//~| NOTE `BuildData` does not have this field
21+
//~| NOTE available fields are: foo
2122
};
2223
}

src/test/compile-fail/suggest-private-fields.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ fn main () {
2727
//~| NOTE field does not exist - did you mean `a`?
2828
bb: 20,
2929
//~^ ERROR struct `xc::B` has no field named `bb`
30-
//~| NOTE field does not exist - did you mean `a`?
30+
//~| NOTE `xc::B` does not have this field
31+
//~| NOTE available fields are: a
3132
};
3233
// local crate struct
3334
let l = A {

src/test/compile-fail/union/union-fields.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn main() {
2020
let u = U { a: 0, b: 1, c: 2 }; //~ ERROR union expressions should have exactly one field
2121
//~^ ERROR union `U` has no field named `c`
2222
//~| NOTE `U` does not have this field
23+
//~| NOTE available fields are: a, b
2324
let u = U { ..u }; //~ ERROR union expressions should have exactly one field
2425
//~^ ERROR functional record update syntax requires a struct
2526

src/test/ui/did_you_mean/issue-36798_unknown_field.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ error[E0609]: no field `zz` on type `Foo`
33
|
44
17 | f.zz;
55
| ^^ unknown field
6+
|
7+
= note: available fields are: bar
68

79
error: aborting due to previous error
810

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
mod submodule {
12+
13+
#[derive(Default)]
14+
pub struct Demo {
15+
pub favorite_integer: isize,
16+
secret_integer: isize,
17+
pub innocently_misspellable: ()
18+
}
19+
20+
impl Demo {
21+
fn new_with_secret_two() -> Self {
22+
Self { secret_integer: 2, inocently_mispellable: () }
23+
}
24+
25+
fn new_with_secret_three() -> Self {
26+
Self { secret_integer: 3, egregiously_nonexistent_field: () }
27+
}
28+
}
29+
30+
}
31+
32+
fn main() {
33+
use submodule::Demo;
34+
35+
let demo = Demo::default();
36+
let innocent_field_misaccess = demo.inocently_mispellable;
37+
// note shouldn't suggest private `secret_integer` field
38+
let egregious_field_misaccess = demo.egregiously_nonexistent_field;
39+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error[E0560]: struct `submodule::Demo` has no field named `inocently_mispellable`
2+
--> $DIR/issue-42599_available_fields_note.rs:22:39
3+
|
4+
22 | Self { secret_integer: 2, inocently_mispellable: () }
5+
| ^^^^^^^^^^^^^^^^^^^^^^ field does not exist - did you mean `innocently_misspellable`?
6+
7+
error[E0560]: struct `submodule::Demo` has no field named `egregiously_nonexistent_field`
8+
--> $DIR/issue-42599_available_fields_note.rs:26:39
9+
|
10+
26 | Self { secret_integer: 3, egregiously_nonexistent_field: () }
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `submodule::Demo` does not have this field
12+
|
13+
= note: available fields are: favorite_integer, secret_integer, innocently_misspellable
14+
15+
error[E0609]: no field `inocently_mispellable` on type `submodule::Demo`
16+
--> $DIR/issue-42599_available_fields_note.rs:36:41
17+
|
18+
36 | let innocent_field_misaccess = demo.inocently_mispellable;
19+
| ^^^^^^^^^^^^^^^^^^^^^ did you mean `innocently_misspellable`?
20+
21+
error[E0609]: no field `egregiously_nonexistent_field` on type `submodule::Demo`
22+
--> $DIR/issue-42599_available_fields_note.rs:38:42
23+
|
24+
38 | let egregious_field_misaccess = demo.egregiously_nonexistent_field;
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown field
26+
|
27+
= note: available fields are: favorite_integer, innocently_misspellable
28+
29+
error: aborting due to 4 previous errors
30+

0 commit comments

Comments
 (0)