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

Await on mismatched future types #72784

Merged
merged 5 commits into from
Aug 27, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/librustc_infer/infer/error_reporting/mod.rs
Original file line number Diff line number Diff line change
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,72 @@ 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 mut projection_ty = None;
for (predicate, _) in self.tcx.predicates_of(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;
}
}
}
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: 2 additions & 2 deletions src/librustc_typeck/check/_match.rs
Original file line number Diff line number Diff line change
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
65 changes: 64 additions & 1 deletion src/librustc_typeck/check/expr.rs
Original file line number Diff line number Diff line change
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,73 @@ 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 mut projection_ty = None;
for (predicate, _) in self.tcx.predicates_of(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;
}
}
}
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,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a central place somewhere that we could define a helper to do this? I'd rather not duplicate it in all these places.

(If we can't extract the normalizing code that's fine, I'd prefer to, but we could just extract the parts before it.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a src/librustc_typeck/check/diagnostics.rs would make sense, and would follow the pattern in other parts of the compiler.

I made a quick search for SelectionContext::new( as I would imagine that any code doing this same thing would be using it, but I couldn't find any diagnostics use in typeck.

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 +1591,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
69 changes: 69 additions & 0 deletions src/librustc_typeck/check/method/suggest.rs
Original file line number Diff line number Diff line change
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,67 @@ 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, _substs) = 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 mut projection_ty = None;
for (predicate, _) in self.tcx.predicates_of(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;
}
}
}
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
41 changes: 41 additions & 0 deletions src/test/ui/async-await/issue-61076.rs
Original file line number Diff line number Diff line change
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() {}
Loading