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:
commit
d9a429a1eb
3 changed files with 262 additions and 107 deletions
|
@ -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,114 +1337,180 @@ 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,
|
||||
"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
|
||||
} 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,
|
||||
"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();
|
||||
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() => {
|
||||
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
|
||||
} 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
|
||||
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
|
||||
);
|
||||
// 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
|
||||
} else {
|
||||
Applicability::MaybeIncorrect
|
||||
};
|
||||
err.span_suggestion(expr.span, help, suggestion, applicability);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
fn no_such_field_err<T: Display>(&self, span: Span, field: T, expr_t: &ty::TyS<'_>)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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`.
|
Loading…
Add table
Reference in a new issue