Auto merge of #72784 - csmoe:issue-61076, r=estebank

Await on mismatched future types

Closes #61076
This PR suggests to `await` on:
1. `async_fn().bar() => async_fn().await.bar()`
2. `async_fn().field => async_fn().await.field`
3. ` if let x = async() {} => if let x = async().await {}`

r? @tmandry @estebank
This commit is contained in:
bors 2020-08-27 07:26:32 +00:00
commit f7cbb7a594
8 changed files with 299 additions and 7 deletions

View File

@ -50,6 +50,7 @@ use super::region_constraints::GenericKind;
use super::{InferCtxt, RegionVariableOrigin, SubregionOrigin, TypeTrace, ValuePairs};
use crate::infer;
use crate::infer::OriginalQueryValues;
use crate::traits::error_reporting::report_object_safety_error;
use crate::traits::{
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
@ -60,8 +61,10 @@ use rustc_errors::{pluralize, struct_span_err};
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{Item, ItemKind, Node};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::ParamEnvAnd;
use rustc_middle::ty::{
self,
subst::{Subst, SubstsRef},
@ -1529,6 +1532,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
};
if let Some(exp_found) = exp_found {
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);
self.suggest_await_on_expect_found(cause, span, &exp_found, diag);
}
// In some (most?) cases cause.body_id points to actual body, but in some cases
@ -1547,6 +1551,62 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
self.note_error_origin(diag, cause, exp_found);
}
fn suggest_await_on_expect_found(
&self,
cause: &ObligationCause<'tcx>,
exp_span: Span,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut DiagnosticBuilder<'tcx>,
) {
debug!(
"suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
exp_span, exp_found.expected, exp_found.found
);
if let ty::Opaque(def_id, _) = exp_found.expected.kind {
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
// Future::Output
let item_def_id = self
.tcx
.associated_items(future_trait)
.in_definition_order()
.next()
.unwrap()
.def_id;
let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
if let Some(projection_ty) = projection_ty {
let projection_query = self.canonicalize_query(
&ParamEnvAnd { param_env: self.tcx.param_env(def_id), value: projection_ty },
&mut OriginalQueryValues::default(),
);
if let Ok(resp) = self.tcx.normalize_projection_ty(projection_query) {
let normalized_ty = resp.value.value.normalized_ty;
debug!("suggest_await_on_expect_found: normalized={:?}", normalized_ty);
if ty::TyS::same_type(normalized_ty, exp_found.found) {
let span = if let ObligationCauseCode::Pattern {
span,
origin_expr: _,
root_ty: _,
} = cause.code
{
// scrutinee's span
span.unwrap_or(exp_span)
} else {
exp_span
};
diag.span_suggestion_verbose(
span.shrink_to_hi(),
"consider awaiting on the future",
".await".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}
}
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
/// suggests it.
fn suggest_as_ref_where_appropriate(

View File

@ -173,6 +173,10 @@ rustc_queries! {
desc { |tcx| "finding projection predicates for `{}`", tcx.def_path_str(key) }
}
query projection_ty_from_predicates(key: (DefId, DefId)) -> Option<ty::ProjectionTy<'tcx>> {
desc { |tcx| "finding projection type inside predicates of `{}`", tcx.def_path_str(key.0) }
}
query native_libraries(_: CrateNum) -> Lrc<Vec<NativeLib>> {
desc { "looking up the native libraries of a linked crate" }
}

View File

@ -28,7 +28,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
};
// Type check the descriminant and get its type.
let scrut_ty = if force_scrutinee_bool {
let scrutinee_ty = if force_scrutinee_bool {
// Here we want to ensure:
//
// 1. That default match bindings are *not* accepted in the condition of an
@ -55,7 +55,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// #55810: Type check patterns first so we get types for all bindings.
for arm in arms {
self.check_pat_top(&arm.pat, scrut_ty, Some(scrut.span), true);
self.check_pat_top(&arm.pat, scrutinee_ty, Some(scrut.span), true);
}
// Now typecheck the blocks.

View File

@ -37,7 +37,7 @@ use rustc_middle::ty::{AdtKind, Visibility};
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_trait_selection::traits::{self, ObligationCauseCode};
use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext};
use std::fmt::Display;
@ -1509,6 +1509,54 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx().ty_error()
}
fn suggest_await_on_field_access(
&self,
err: &mut DiagnosticBuilder<'_>,
field_ident: Ident,
base: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
def_id: DefId,
) {
let param_env = self.tcx().param_env(def_id);
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
// Future::Output
let item_def_id =
self.tcx.associated_items(future_trait).in_definition_order().next().unwrap().def_id;
let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
debug!("suggest_await_on_field_access: projection_ty={:?}", projection_ty);
let cause = self.misc(expr.span);
let mut selcx = SelectionContext::new(&self.infcx);
let mut obligations = vec![];
if let Some(projection_ty) = projection_ty {
let normalized_ty = rustc_trait_selection::traits::normalize_projection_type(
&mut selcx,
param_env,
projection_ty,
cause,
0,
&mut obligations,
);
debug!(
"suggest_await_on_field_access: normalized_ty={:?}, ty_kind={:?}",
self.resolve_vars_if_possible(&normalized_ty),
normalized_ty.kind,
);
if let ty::Adt(def, _) = normalized_ty.kind {
if def.non_enum_variant().fields.iter().any(|field| field.ident == field_ident) {
err.span_suggestion_verbose(
base.span.shrink_to_hi(),
"consider awaiting before field access",
".await".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}
fn ban_nonexisting_field(
&self,
field: Ident,
@ -1516,6 +1564,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
expr_t: Ty<'tcx>,
) {
debug!(
"ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}",
field, base, expr, expr_t
);
let mut err = self.no_such_field_err(field.span, field, expr_t);
match expr_t.peel_refs().kind {
@ -1531,6 +1583,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::Param(param_ty) => {
self.point_at_param_definition(&mut err, param_ty);
}
ty::Opaque(def_id, _) => {
self.suggest_await_on_field_access(&mut err, field, base, expr, def_id);
}
_ => {}
}

View File

@ -21,6 +21,7 @@ use rustc_span::symbol::{kw, sym, Ident};
use rustc_span::{source_map, FileName, Span};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::Obligation;
use rustc_trait_selection::traits::SelectionContext;
use std::cmp::Ordering;
@ -392,6 +393,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
actual.prefix_string(),
ty_str,
);
if let Mode::MethodCall = mode {
if let SelfSource::MethodCall(call) = source {
self.suggest_await_before_method(
&mut err, item_name, actual, call, span,
);
}
}
if let Some(span) =
tcx.sess.confused_type_with_std_module.borrow().get(&span)
{
@ -854,6 +862,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
fn suggest_await_before_method(
&self,
err: &mut DiagnosticBuilder<'_>,
item_name: Ident,
ty: Ty<'tcx>,
call: &hir::Expr<'_>,
span: Span,
) {
if let ty::Opaque(def_id, _) = ty.kind {
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
// Future::Output
let item_def_id = self
.tcx
.associated_items(future_trait)
.in_definition_order()
.next()
.unwrap()
.def_id;
let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
let cause = self.misc(span);
let mut selcx = SelectionContext::new(&self.infcx);
let mut obligations = vec![];
if let Some(projection_ty) = projection_ty {
let normalized_ty = rustc_trait_selection::traits::normalize_projection_type(
&mut selcx,
self.param_env,
projection_ty,
cause,
0,
&mut obligations,
);
debug!(
"suggest_await_before_method: normalized_ty={:?}, ty_kind={:?}",
self.resolve_vars_if_possible(&normalized_ty),
normalized_ty.kind,
);
let method_exists = self.method_exists(item_name, normalized_ty, call.hir_id, true);
debug!("suggest_await_before_method: is_method_exist={}", method_exists);
if method_exists {
err.span_suggestion_verbose(
span.shrink_to_lo(),
"consider awaiting before this method call",
"await.".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}
fn suggest_use_candidates(
&self,
err: &mut DiagnosticBuilder<'_>,

View File

@ -70,6 +70,7 @@ pub fn provide(providers: &mut Providers) {
generics_of,
predicates_of,
predicates_defined_on,
projection_ty_from_predicates,
explicit_predicates_of,
super_predicates_of,
type_param_predicates,
@ -2051,6 +2052,28 @@ fn explicit_predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredicat
result
}
fn projection_ty_from_predicates(
tcx: TyCtxt<'tcx>,
key: (
// ty_def_id
DefId,
// def_id of `N` in `<T as Trait>::N`
DefId,
),
) -> Option<ty::ProjectionTy<'tcx>> {
let (ty_def_id, item_def_id) = key;
let mut projection_ty = None;
for (predicate, _) in tcx.predicates_of(ty_def_id).predicates {
if let ty::PredicateAtom::Projection(projection_predicate) = predicate.skip_binders() {
if item_def_id == projection_predicate.projection_ty.item_def_id {
projection_ty = Some(projection_predicate.projection_ty);
break;
}
}
}
projection_ty
}
fn trait_associated_item_predicates(
tcx: TyCtxt<'tcx>,
def_id: DefId,

View File

@ -6,6 +6,26 @@ use core::task::{Context, Poll};
struct T;
struct Tuple(i32);
struct Struct {
a: i32
}
impl Struct {
fn method(&self) {}
}
impl Future for Struct {
type Output = Struct;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> { Poll::Pending }
}
impl Future for Tuple {
type Output = Tuple;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> { Poll::Pending }
}
impl Future for T {
type Output = Result<(), ()>;
@ -23,10 +43,31 @@ async fn bar() -> Result<(), ()> {
Ok(())
}
async fn struct_() -> Struct {
Struct { a: 1 }
}
async fn tuple() -> Tuple {
Tuple(1i32)
}
async fn baz() -> Result<(), ()> {
let t = T;
t?; //~ ERROR the `?` operator can only be applied to values that implement `std::ops::Try`
let _: i32 = tuple().0; //~ ERROR no field `0`
let _: i32 = struct_().a; //~ ERROR no field `a`
struct_().method(); //~ ERROR no method named
Ok(())
}
async fn match_() {
match tuple() {
Tuple(_) => {} //~ ERROR mismatched types
}
}
fn main() {}

View File

@ -1,5 +1,5 @@
error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try`
--> $DIR/issue-61076.rs:22:5
--> $DIR/issue-61076.rs:42:5
|
LL | foo()?;
| ^^^^^^
@ -11,7 +11,7 @@ LL | foo()?;
= note: required by `std::ops::Try::into_result`
error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try`
--> $DIR/issue-61076.rs:28:5
--> $DIR/issue-61076.rs:56:5
|
LL | t?;
| ^^
@ -22,6 +22,56 @@ LL | t?;
= help: the trait `std::ops::Try` is not implemented for `T`
= note: required by `std::ops::Try::into_result`
error: aborting due to 2 previous errors
error[E0609]: no field `0` on type `impl std::future::Future`
--> $DIR/issue-61076.rs:58:26
|
LL | let _: i32 = tuple().0;
| ^
|
help: consider awaiting before field access
|
LL | let _: i32 = tuple().await.0;
| ^^^^^^
For more information about this error, try `rustc --explain E0277`.
error[E0609]: no field `a` on type `impl std::future::Future`
--> $DIR/issue-61076.rs:60:28
|
LL | let _: i32 = struct_().a;
| ^
|
help: consider awaiting before field access
|
LL | let _: i32 = struct_().await.a;
| ^^^^^^
error[E0599]: no method named `method` found for opaque type `impl std::future::Future` in the current scope
--> $DIR/issue-61076.rs:62:15
|
LL | struct_().method();
| ^^^^^^ method not found in `impl std::future::Future`
|
help: consider awaiting before this method call
|
LL | struct_().await.method();
| ^^^^^^
error[E0308]: mismatched types
--> $DIR/issue-61076.rs:69:9
|
LL | async fn tuple() -> Tuple {
| ----- the `Output` of this `async fn`'s expected opaque type
...
LL | Tuple(_) => {}
| ^^^^^^^^ expected opaque type, found struct `Tuple`
|
= note: expected opaque type `impl std::future::Future`
found struct `Tuple`
help: consider awaiting on the future
|
LL | match tuple().await {
| ^^^^^^
error: aborting due to 6 previous errors
Some errors have detailed explanations: E0277, E0308, E0599, E0609.
For more information about an error, try `rustc --explain E0277`.