mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-28 02:57:37 +00:00
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:
commit
f7cbb7a594
@ -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(
|
||||
|
@ -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" }
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -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<'_>,
|
||||
|
@ -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,
|
||||
|
@ -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() {}
|
||||
|
@ -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`.
|
||||
|
Loading…
Reference in New Issue
Block a user