Skip to content

Commit

Permalink
Auto merge of #72784 - csmoe:issue-61076, r=estebank
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed Aug 27, 2020
2 parents 4e701af + 7cfcefd commit f7cbb7a
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 7 deletions.
60 changes: 60 additions & 0 deletions src/librustc_infer/infer/error_reporting/mod.rs
Expand Up @@ -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,
Expand All @@ -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},
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions src/librustc_middle/query/mod.rs
Expand Up @@ -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" }
}
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_typeck/check/_match.rs
Expand Up @@ -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
Expand All @@ -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.
Expand Down
57 changes: 56 additions & 1 deletion src/librustc_typeck/check/expr.rs
Expand Up @@ -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;

Expand Down Expand Up @@ -1509,13 +1509,65 @@ 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,
base: &'tcx hir::Expr<'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 {
Expand All @@ -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);
}
_ => {}
}

Expand Down
59 changes: 59 additions & 0 deletions src/librustc_typeck/check/method/suggest.rs
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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<'_>,
Expand Down
23 changes: 23 additions & 0 deletions src/librustc_typeck/collect.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
41 changes: 41 additions & 0 deletions src/test/ui/async-await/issue-61076.rs
Expand Up @@ -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<(), ()>;

Expand All @@ -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() {}

0 comments on commit f7cbb7a

Please sign in to comment.