Change:
Trivial example: Output on current nightly: Ideally the error message would suggest using the 2018 edition.
Part of

Discussion:
use syntax::util::lev_distance::find_best_match_for_name; use rustc::hir; use rustc::hir::{ExprKind, QPath}; <ins> use rustc::hir::def_id::DefId; </ins> use rustc::hir::def::{CtorKind, Res, DefKind}; use rustc::hir::ptr::P; use rustc::infer;

autoderef.unambiguous_final_ty(self); if let Some((did, field_ty)) = private_candidate { <del> let struct_path = self.tcx().def_path_str(did); let mut err = struct_span_err!(self.tcx().sess, expr.span, E0616, "field `{}` of struct `{}` is private", field, struct_path); // Also check if an accessible method exists, which is often what is meant. if self.method_exists(field, expr_t, expr.hir_id, false) && !self.expr_in_place(expr.hir_id) { self.suggest_method_call( &mut err, &format!("a method `{}` also exists, call it with parentheses", field), field, expr_t, expr.hir_id, ); } err.emit(); field_ty } else if field.name == kw::Invalid { self.tcx().types.err </del> <ins> self.ban_private_field_access(expr, expr_t, field, did); return field_ty; } if field.name == kw::Invalid { </ins> } else if self.method_exists(field, expr_t, expr.hir_id, true) { <del> let mut err = type_error_struct!(self.tcx().sess, field.span, expr_t, E0615, "attempted to take value of method `{}` on type `{}`", field, expr_t); if !self.expr_in_place(expr.hir_id) { self.suggest_method_call( &mut err, "use parentheses to call the method", field, expr_t, expr.hir_id ); } else { err.help("methods are immutable and cannot be assigned to"); </del> <ins> self.ban_take_value_of_method(expr, expr_t, field); } else if !expr_t.is_primitive_ty() { let mut err = self.no_such_field_err(field.span, field, expr_t); match expr_t.sty { ty::Adt(def, _) if !def.is_enum() => { self.suggest_fields_on_recordish(&mut err, def, field); } ty::Array(_, len) => { self.maybe_suggest_array_indexing(&mut err, expr, base, field, len); } ty::RawPtr(..) => { self.suggest_first_deref_field(&mut err, expr, base, field); } _ => {} } if field.name == kw::Await { // We know by construction that `<expr>.await` is either on Rust 2015 // or results in `ExprKind::Await`. Suggest switching the edition to 2018. err.note("to `.await` a `Future`, switch to Rust 2018"); err.help("set `edition = "2018"` in `Cargo.toml`"); err.note("for more on editions, read https://doc.rust-lang.org/edition-guide"); </ins> } err.emit(); <del> self.tcx().types.err </del> } else { <del> if !expr_t.is_primitive_ty() { let mut err = self.no_such_field_err(field.span, field, expr_t); match expr_t.sty { ty::Adt(def, _) if !def.is_enum() => { if let Some(suggested_field_name) = Self::suggest_field_name(def.non_enum_variant(), &field.as_str(), vec![]) { err.span_suggestion( field.span, "a field with a similar name exists", suggested_field_name.to_string(), Applicability::MaybeIncorrect, ); } else { err.span_label(field.span, "unknown field"); let struct_variant_def = def.non_enum_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::Array(_, len) => { if let (Some(len), Ok(user_index)) = ( len.try_eval_usize(self.tcx, self.param_env), field.as_str().parse::<u64>() ) { let base = self.tcx.sess.source_map() .span_to_snippet(base.span) .unwrap_or_else(|_| self.tcx.hir().hir_to_pretty_string(base.hir_id)); let help = "instead of using tuple indexing, use array indexing"; let suggestion = format!("{}[{}]", base, field); let applicability = if len < user_index { Applicability::MachineApplicable } else { Applicability::MaybeIncorrect }; err.span_suggestion( expr.span, help, suggestion, applicability ); } } ty::RawPtr(..) => { let base = self.tcx.sess.source_map() .span_to_snippet(base.span) .unwrap_or_else(|_| self.tcx.hir().hir_to_pretty_string(base.hir_id)); let msg = format!("`{}` is a raw pointer; try dereferencing it", base); let suggestion = format!("(*{}).{}", base, field); err.span_suggestion( expr.span, &msg, suggestion, Applicability::MaybeIncorrect, ); } _ => {} } err </del> <ins> type_error_struct!( self.tcx().sess, field.span, expr_t, E0610, "`{}` is a primitive type and therefore doesn't have fields", expr_t ) .emit(); } self.tcx().types.err } fn ban_private_field_access( &self, expr: &hir::Expr, expr_t: Ty<'tcx>, field: ast::Ident, base_did: DefId, ) { let struct_path = self.tcx().def_path_str(base_did); let mut err = struct_span_err!( self.tcx().sess, expr.span, E0616, "field `{}` of struct `{}` is private", field, struct_path ); // Also check if an accessible method exists, which is often what is meant. if self.method_exists(field, expr_t, expr.hir_id, false) && !self.expr_in_place(expr.hir_id) { self.suggest_method_call( &mut err, &format!("a method `{}` also exists, call it with parentheses", field), field, expr_t, expr.hir_id, ); } err.emit(); } fn ban_take_value_of_method(&self, expr: &hir::Expr, expr_t: Ty<'tcx>, field: ast::Ident) { let mut err = type_error_struct!( self.tcx().sess, field.span, expr_t, E0615, "attempted to take value of method `{}` on type `{}`", field, expr_t ); if !self.expr_in_place(expr.hir_id) { self.suggest_method_call( &mut err, "use parentheses to call the method", field, expr_t, expr.hir_id ); } else { err.help("methods are immutable and cannot be assigned to"); } err.emit(); } fn suggest_fields_on_recordish( &self, err: &mut DiagnosticBuilder<'_>, def: &'tcx ty::AdtDef, field: ast::Ident, ) { if let Some(suggested_field_name) = Self::suggest_field_name(def.non_enum_variant(), &field.as_str(), vec![]) { err.span_suggestion( field.span, "a field with a similar name exists", suggested_field_name.to_string(), Applicability::MaybeIncorrect, ); } else { err.span_label(field.span, "unknown field"); let struct_variant_def = def.non_enum_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))); } } } fn maybe_suggest_array_indexing( &self, err: &mut DiagnosticBuilder<'_>, expr: &hir::Expr, base: &hir::Expr, field: ast::Ident, len: &ty::Const<'tcx>, ) { if let (Some(len), Ok(user_index)) = ( len.try_eval_usize(self.tcx, self.param_env), field.as_str().parse::<u64>() ) { let base = self.tcx.sess.source_map() .span_to_snippet(base.span) .unwrap_or_else(|_| self.tcx.hir().hir_to_pretty_string(base.hir_id)); let help = "instead of using tuple indexing, use array indexing"; let suggestion = format!("{}[{}]", base, field); let applicability = if len < user_index { Applicability::MachineApplicable </ins> } else { <del> type_error_struct!(self.tcx().sess, field.span, expr_t, E0610, "`{}` is a primitive type and therefore doesn't have fields", expr_t) }.emit(); self.tcx().types.err </del> <ins> Applicability::MaybeIncorrect }; err.span_suggestion(expr.span, help, suggestion, applicability); </ins> } } <ins> fn suggest_first_deref_field( &self, err: &mut DiagnosticBuilder<'_>, expr: &hir::Expr, base: &hir::Expr, field: ast::Ident, ) { let base = self.tcx.sess.source_map() .span_to_snippet(base.span) .unwrap_or_else(|_| self.tcx.hir().hir_to_pretty_string(base.hir_id)); let msg = format!("`{}` is a raw pointer; try dereferencing it", base); let suggestion = format!("(*{}).{}", base, field); err.span_suggestion( expr.span, &msg, suggestion, Applicability::MaybeIncorrect, ); } </ins> fn no_such_field_err<T: Display>(&self, span: Span, field: T, expr_t: &ty::TyS<'_>) -> DiagnosticBuilder<'_> { type_error_struct!(self.tcx().sess, span, expr_t, E0609,

<ins> use std::pin::Pin; use std::future::Future; fn main() {} fn await_on_struct_missing() { struct S; let x = S; x.await; //~^ ERROR no field `await` on type //~| NOTE unknown field //~| NOTE to `.await` a `Future`, switch to Rust 2018 //~| HELP set `edition = "2018"` in `Cargo.toml` //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide } fn await_on_struct_similar() { struct S { awai: u8, } let x = S { awai: 42 }; x.await; //~^ ERROR no field `await` on type //~| HELP a field with a similar name exists //~| NOTE to `.await` a `Future`, switch to Rust 2018 //~| HELP set `edition = "2018"` in `Cargo.toml` //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide } fn await_on_63533(x: Pin<&mut dyn Future<Output = ()>>) { x.await; //~^ ERROR no field `await` on type //~| NOTE unknown field //~| NOTE to `.await` a `Future`, switch to Rust 2018 //~| HELP set `edition = "2018"` in `Cargo.toml` //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide } fn await_on_apit(x: impl Future<Output = ()>) { x.await; //~^ ERROR no field `await` on type //~| NOTE to `.await` a `Future`, switch to Rust 2018 //~| HELP set `edition = "2018"` in `Cargo.toml` //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide } </ins>

<ins> error[E0609]: no field `await` on type `await_on_struct_missing::S` --> $DIR/suggest-switching-edition-on-await.rs:9:7 | LL |     x.await; |       ^^^^^ unknown field | = note: to `.await` a `Future`, switch to Rust 2018 = help: set `edition = "2018"` in `Cargo.toml` = note: for more on editions, read https://doc.rust-lang.org/edition-guide error[E0609]: no field `await` on type `await_on_struct_similar::S` --> $DIR/suggest-switching-edition-on-await.rs:22:7 | LL |     x.await; |       ^^^^^ help: a field with a similar name exists: `awai` | = note: to `.await` a `Future`, switch to Rust 2018 = help: set `edition = "2018"` in `Cargo.toml` = note: for more on editions, read https://doc.rust-lang.org/edition-guide error[E0609]: no field `await` on type `std::pin::Pin<&mut dyn std::future::Future<Output = ()>>` --> $DIR/suggest-switching-edition-on-await.rs:31:7 | LL |     x.await; |       ^^^^^ unknown field | = note: to `.await` a `Future`, switch to Rust 2018 = help: set `edition = "2018"` in `Cargo.toml` = note: for more on editions, read https://doc.rust-lang.org/edition-guide error[E0609]: no field `await` on type `impl Future<Output = ()>` --> $DIR/suggest-switching-edition-on-await.rs:40:7 | LL |     x.await; |       ^^^^^ | = note: to `.await` a `Future`, switch to Rust 2018 = help: set `edition = "2018"` in `Cargo.toml` = note: for more on editions, read https://doc.rust-lang.org/edition-guide error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0609`. </ins>