Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from Aug 16, 2019
269 changes: 168 additions & 101 deletions src/librustc_typeck/check/expr.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -1336,116 +1337,182 @@ 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
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) {
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");
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();
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
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 {
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
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<'_>)
-> DiagnosticBuilder<'_> {
type_error_struct!(self.tcx().sess, span, expr_t, E0609,
Expand Down
45 changes: 45 additions & 0 deletions src/test/ui/async-await/suggest-switching-edition-on-await.rs
@@ -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
}
43 changes: 43 additions & 0 deletions src/test/ui/async-await/suggest-switching-edition-on-await.stderr
@@ -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`.