Skip to content

Commit f2a5af7

Browse files
committed
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 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 within a Levenshtein distance of name.len()). The present author claims that this suffices to resolve #42599.
2 parents c523b3f + 2dbfa39 commit f2a5af7

11 files changed

+131
-13
lines changed

src/librustc_typeck/check/mod.rs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29602960
format!("did you mean `{}`?", suggested_field_name));
29612961
} else {
29622962
err.span_label(field.span, "unknown field");
2963+
let struct_variant_def = def.struct_variant();
2964+
let field_names = self.available_field_names(struct_variant_def);
2965+
if !field_names.is_empty() {
2966+
err.note(&format!("available fields are: {}",
2967+
self.name_series_display(field_names)));
2968+
}
29632969
};
29642970
}
29652971
ty::TyRawPtr(..) => {
@@ -2983,7 +2989,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29832989
// Return an hint about the closest match in field names
29842990
fn suggest_field_name(variant: &'tcx ty::VariantDef,
29852991
field: &Spanned<ast::Name>,
2986-
skip : Vec<InternedString>)
2992+
skip: Vec<InternedString>)
29872993
-> Option<Symbol> {
29882994
let name = field.node.as_str();
29892995
let names = variant.fields.iter().filter_map(|field| {
@@ -2996,8 +3002,29 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29963002
}
29973003
});
29983004

2999-
// only find fits with at least one matching letter
3000-
find_best_match_for_name(names, &name, Some(name.len()))
3005+
find_best_match_for_name(names, &name, None)
3006+
}
3007+
3008+
fn available_field_names(&self, variant: &'tcx ty::VariantDef) -> Vec<ast::Name> {
3009+
let mut available = Vec::new();
3010+
for field in variant.fields.iter() {
3011+
let (_, def_scope) = self.tcx.adjust(field.name, variant.did, self.body_id);
3012+
if field.vis.is_accessible_from(def_scope, self.tcx) {
3013+
available.push(field.name);
3014+
}
3015+
}
3016+
available
3017+
}
3018+
3019+
fn name_series_display(&self, names: Vec<ast::Name>) -> String {
3020+
// dynamic limit, to never omit just one field
3021+
let limit = if names.len() == 6 { 6 } else { 5 };
3022+
let mut display = names.iter().take(limit)
3023+
.map(|n| format!("`{}`", n)).collect::<Vec<_>>().join(", ");
3024+
if names.len() > limit {
3025+
display = format!("{} ... and {} others", display, names.len() - limit);
3026+
}
3027+
display
30013028
}
30023029

30033030
// Check tuple index expressions
@@ -3111,13 +3138,22 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
31113138
format!("field does not exist - did you mean `{}`?", field_name));
31123139
} else {
31133140
match ty.sty {
3114-
ty::TyAdt(adt, ..) if adt.is_enum() => {
3115-
err.span_label(field.name.span, format!("`{}::{}` does not have this field",
3116-
ty, variant.name));
3117-
}
3118-
_ => {
3119-
err.span_label(field.name.span, format!("`{}` does not have this field", ty));
3141+
ty::TyAdt(adt, ..) => {
3142+
if adt.is_enum() {
3143+
err.span_label(field.name.span,
3144+
format!("`{}::{}` does not have this field",
3145+
ty, variant.name));
3146+
} else {
3147+
err.span_label(field.name.span,
3148+
format!("`{}` does not have this field", ty));
3149+
}
3150+
let available_field_names = self.available_field_names(variant);
3151+
if !available_field_names.is_empty() {
3152+
err.note(&format!("available fields are: {}",
3153+
self.name_series_display(available_field_names)));
3154+
}
31203155
}
3156+
_ => bug!("non-ADT passed to report_unknown_field")
31213157
}
31223158
};
31233159
err.emit();

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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
another_field: bool,
19+
yet_another_field: bool,
20+
always_more_fields: bool,
21+
and_ever: bool,
22+
}
23+
24+
impl Demo {
25+
fn new_with_secret_two() -> Self {
26+
Self { secret_integer: 2, inocently_mispellable: () }
27+
}
28+
29+
fn new_with_secret_three() -> Self {
30+
Self { secret_integer: 3, egregiously_nonexistent_field: () }
31+
}
32+
}
33+
34+
}
35+
36+
fn main() {
37+
use submodule::Demo;
38+
39+
let demo = Demo::default();
40+
let innocent_field_misaccess = demo.inocently_mispellable;
41+
// note shouldn't suggest private fields
42+
let egregious_field_misaccess = demo.egregiously_nonexistent_field;
43+
}
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:26:39
3+
|
4+
26 | 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:30:39
9+
|
10+
30 | 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`, `another_field`, `yet_another_field` ... and 2 others
14+
15+
error[E0609]: no field `inocently_mispellable` on type `submodule::Demo`
16+
--> $DIR/issue-42599_available_fields_note.rs:40:41
17+
|
18+
40 | 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:42:42
23+
|
24+
42 | 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)