Rollup merge of #63539 - Centril:2015.await, r=oli-obk

Suggest Rust 2018 on `<expr>.await` with no such field

When type checking a field projection (`fn check_field`) to `<expr>.await` where `<expr>: τ` and `τ` is not a primitive type, suggest switching to Rust 2018. E.g.

```
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
```

Fixes https://github.com/rust-lang/rust/issues/63533

This PR also performs some preparatory cleanups in `fn check_field`; the last 2 commits are where the suggestion is introduced and tested respectively.

r? @varkor
This commit is contained in:
Mazdak Farrokhzad 2019-08-16 08:26:38 +02:00 committed by GitHub
commit d9a429a1eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 262 additions and 107 deletions

View file

@ -24,6 +24,7 @@ use syntax::source_map::Span;
use syntax::util::lev_distance::find_best_match_for_name;
use rustc::hir;
use rustc::hir::{ExprKind, QPath};
use rustc::hir::def_id::DefId;
use rustc::hir::def::{CtorKind, Res, DefKind};
use rustc::hir::ptr::P;
use rustc::infer;
@ -1336,10 +1337,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
autoderef.unambiguous_final_ty(self);
if let Some((did, field_ty)) = private_candidate {
let struct_path = self.tcx().def_path_str(did);
let mut err = struct_span_err!(self.tcx().sess, expr.span, E0616,
self.ban_private_field_access(expr, expr_t, field, did);
return field_ty;
}
if field.name == kw::Invalid {
} else if self.method_exists(field, expr_t, expr.hir_id, true) {
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");
}
err.emit();
} else {
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);
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)
@ -1353,13 +1413,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
}
err.emit();
field_ty
} else if field.name == kw::Invalid {
self.tcx().types.err
} else if self.method_exists(field, expr_t, expr.hir_id, true) {
let mut err = type_error_struct!(self.tcx().sess, field.span, expr_t, E0615,
}
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);
field,
expr_t
);
if !self.expr_in_place(expr.hir_id) {
self.suggest_method_call(
@ -1374,16 +1439,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
err.emit();
self.tcx().types.err
} 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() => {
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![]) {
Self::suggest_field_name(def.non_enum_variant(), &field.as_str(), vec![])
{
err.span_suggestion(
field.span,
"a field with a similar name exists",
@ -1398,17 +1464,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err.note(&format!("available fields are: {}",
self.name_series_display(field_names)));
}
};
}
ty::Array(_, len) => {
}
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));
.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 {
@ -1416,12 +1489,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else {
Applicability::MaybeIncorrect
};
err.span_suggestion(
expr.span, help, suggestion, applicability
);
err.span_suggestion(expr.span, help, suggestion, applicability);
}
}
ty::RawPtr(..) => {
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));
@ -1434,17 +1512,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MaybeIncorrect,
);
}
_ => {}
}
err
} else {
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 no_such_field_err<T: Display>(&self, span: Span, field: T, expr_t: &ty::TyS<'_>)
-> DiagnosticBuilder<'_> {

View file

@ -0,0 +1,45 @@
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
}

View file

@ -0,0 +1,43 @@
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`.