about summary refs log tree commit diff
diff options
context:
space:
mode:
authorZack M. Davis <code@zackmdavis.net>2017-07-23 13:46:09 -0700
committerZack M. Davis <code@zackmdavis.net>2017-07-23 14:38:09 -0700
commitbf7e91f61da8a5ca74c7d97f6f2bc978c0366256 (patch)
treee9a3b982e3e658e607a2f033b605749c8c43ccca
parent6270257f4ef6e65213631d6b5a2a85807b8b2364 (diff)
downloadrust-bf7e91f61da8a5ca74c7d97f6f2bc978c0366256.tar.gz
rust-bf7e91f61da8a5ca74c7d97f6f2bc978c0366256.zip
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 a Levenshtein distance of name.len()).

The present author claims that this suffices to resolve #42599.
-rw-r--r--src/librustc_typeck/check/mod.rs41
-rw-r--r--src/test/compile-fail/E0559.rs3
-rw-r--r--src/test/compile-fail/E0560.rs1
-rw-r--r--src/test/compile-fail/issue-19922.rs3
-rw-r--r--src/test/compile-fail/numeric-fields.rs3
-rw-r--r--src/test/compile-fail/struct-fields-too-many.rs1
-rw-r--r--src/test/compile-fail/suggest-private-fields.rs3
-rw-r--r--src/test/compile-fail/union/union-fields.rs1
-rw-r--r--src/test/ui/did_you_mean/issue-36798_unknown_field.stderr2
-rw-r--r--src/test/ui/did_you_mean/issue-42599_available_fields_note.rs39
-rw-r--r--src/test/ui/did_you_mean/issue-42599_available_fields_note.stderr30
11 files changed, 114 insertions, 13 deletions
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index b086c427ba5..4b40a46f74e 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -2956,6 +2956,11 @@ 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 available_field_names = self.available_field_names(
+                                    struct_variant_def);
+                                err.note(&format!("available fields are: {}",
+                                                  available_field_names.join(", ")));
                             };
                     }
                     ty::TyRawPtr(..) => {
@@ -2979,7 +2984,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| {
@@ -2992,8 +2997,18 @@ 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<String> {
+        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.to_string());
+            }
+        }
+        available
     }
 
     // Check tuple index expressions
@@ -3107,14 +3122,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);
+                    err.note(&format!("available fields are: {}",
+                                      available_field_names.join(", ")));
                 }
+                _ => bug!("non-ADT passed to report_unknown_field")
             }
+
         };
         err.emit();
     }
diff --git a/src/test/compile-fail/E0559.rs b/src/test/compile-fail/E0559.rs
index fa6c885843e..21bb2dc7002 100644
--- a/src/test/compile-fail/E0559.rs
+++ b/src/test/compile-fail/E0559.rs
@@ -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
 }
diff --git a/src/test/compile-fail/E0560.rs b/src/test/compile-fail/E0560.rs
index c6326a0f977..7aa6b2e86d6 100644
--- a/src/test/compile-fail/E0560.rs
+++ b/src/test/compile-fail/E0560.rs
@@ -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
 }
diff --git a/src/test/compile-fail/issue-19922.rs b/src/test/compile-fail/issue-19922.rs
index d7b2f2b3f99..429c4384117 100644
--- a/src/test/compile-fail/issue-19922.rs
+++ b/src/test/compile-fail/issue-19922.rs
@@ -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
 }
diff --git a/src/test/compile-fail/numeric-fields.rs b/src/test/compile-fail/numeric-fields.rs
index 00fde3025a6..242c3a3a33d 100644
--- a/src/test/compile-fail/numeric-fields.rs
+++ b/src/test/compile-fail/numeric-fields.rs
@@ -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`
diff --git a/src/test/compile-fail/struct-fields-too-many.rs b/src/test/compile-fail/struct-fields-too-many.rs
index 0848ada731a..78ab94d5fb4 100644
--- a/src/test/compile-fail/struct-fields-too-many.rs
+++ b/src/test/compile-fail/struct-fields-too-many.rs
@@ -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
     };
 }
diff --git a/src/test/compile-fail/suggest-private-fields.rs b/src/test/compile-fail/suggest-private-fields.rs
index 3672e0e90c2..959932af9b1 100644
--- a/src/test/compile-fail/suggest-private-fields.rs
+++ b/src/test/compile-fail/suggest-private-fields.rs
@@ -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 {
diff --git a/src/test/compile-fail/union/union-fields.rs b/src/test/compile-fail/union/union-fields.rs
index b5d582a5746..2bcc2204e33 100644
--- a/src/test/compile-fail/union/union-fields.rs
+++ b/src/test/compile-fail/union/union-fields.rs
@@ -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
 
diff --git a/src/test/ui/did_you_mean/issue-36798_unknown_field.stderr b/src/test/ui/did_you_mean/issue-36798_unknown_field.stderr
index 82e3eab0836..610466c894a 100644
--- a/src/test/ui/did_you_mean/issue-36798_unknown_field.stderr
+++ b/src/test/ui/did_you_mean/issue-36798_unknown_field.stderr
@@ -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
 
diff --git a/src/test/ui/did_you_mean/issue-42599_available_fields_note.rs b/src/test/ui/did_you_mean/issue-42599_available_fields_note.rs
new file mode 100644
index 00000000000..4b0cc7b96a7
--- /dev/null
+++ b/src/test/ui/did_you_mean/issue-42599_available_fields_note.rs
@@ -0,0 +1,39 @@
+// 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: ()
+    }
+
+    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 `secret_integer` field
+    let egregious_field_misaccess = demo.egregiously_nonexistent_field;
+}
diff --git a/src/test/ui/did_you_mean/issue-42599_available_fields_note.stderr b/src/test/ui/did_you_mean/issue-42599_available_fields_note.stderr
new file mode 100644
index 00000000000..17edac92fd9
--- /dev/null
+++ b/src/test/ui/did_you_mean/issue-42599_available_fields_note.stderr
@@ -0,0 +1,30 @@
+error[E0560]: struct `submodule::Demo` has no field named `inocently_mispellable`
+  --> $DIR/issue-42599_available_fields_note.rs:22:39
+   |
+22 |             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:26:39
+   |
+26 |             Self { secret_integer: 3, egregiously_nonexistent_field: () }
+   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `submodule::Demo` does not have this field
+   |
+   = note: available fields are: favorite_integer, secret_integer, innocently_misspellable
+
+error[E0609]: no field `inocently_mispellable` on type `submodule::Demo`
+  --> $DIR/issue-42599_available_fields_note.rs:36:41
+   |
+36 |     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:38:42
+   |
+38 |     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
+