Rollup merge of #71948 - csmoe:issue-61076, r=oli-obk
Suggest to await future before ? operator Closes https://github.com/rust-lang/rust/issues/71811 cc #61076
This commit is contained in:
commit
badcf267df
13 changed files with 246 additions and 37 deletions
|
|
@ -390,11 +390,7 @@ impl<'hir> Map<'hir> {
|
|||
/// Given a `HirId`, returns the `BodyId` associated with it,
|
||||
/// if the node is a body owner, otherwise returns `None`.
|
||||
pub fn maybe_body_owned_by(&self, hir_id: HirId) -> Option<BodyId> {
|
||||
if let Some(node) = self.find(hir_id) {
|
||||
associated_body(node)
|
||||
} else {
|
||||
bug!("no entry for id `{}`", hir_id)
|
||||
}
|
||||
self.find(hir_id).map(associated_body).flatten()
|
||||
}
|
||||
|
||||
/// Given a body owner's id, returns the `BodyId` associated with it.
|
||||
|
|
|
|||
|
|
@ -1164,6 +1164,12 @@ rustc_queries! {
|
|||
desc { "evaluating trait selection obligation `{}`", goal.value }
|
||||
}
|
||||
|
||||
query type_implements_trait(
|
||||
key: (DefId, Ty<'tcx>, SubstsRef<'tcx>, ty::ParamEnv<'tcx>, )
|
||||
) -> bool {
|
||||
desc { "evaluating `type_implements_trait` `{:?}`", key }
|
||||
}
|
||||
|
||||
/// Do not call this query directly: part of the `Eq` type-op
|
||||
query type_op_ascribe_user_type(
|
||||
goal: CanonicalTypeOpAscribeUserTypeGoal<'tcx>
|
||||
|
|
|
|||
|
|
@ -295,3 +295,15 @@ impl Key for (Symbol, u32, u32) {
|
|||
DUMMY_SP
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Key for (DefId, Ty<'tcx>, SubstsRef<'tcx>, ty::ParamEnv<'tcx>) {
|
||||
type CacheSelector = DefaultCacheSelector;
|
||||
|
||||
fn query_crate(&self) -> CrateNum {
|
||||
LOCAL_CRATE
|
||||
}
|
||||
|
||||
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
|
||||
DUMMY_SP
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -402,6 +402,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
|
|||
self.suggest_remove_reference(&obligation, &mut err, &trait_ref);
|
||||
self.suggest_semicolon_removal(&obligation, &mut err, span, &trait_ref);
|
||||
self.note_version_mismatch(&mut err, &trait_ref);
|
||||
self.suggest_await_before_try(&mut err, &obligation, &trait_ref, span);
|
||||
if self.suggest_impl_trait(&mut err, span, &obligation, &trait_ref) {
|
||||
err.emit();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
use super::{
|
||||
EvaluationResult, Obligation, ObligationCause, ObligationCauseCode, PredicateObligation,
|
||||
SelectionContext,
|
||||
};
|
||||
|
||||
use crate::infer::InferCtxt;
|
||||
use crate::traits::normalize_projection_type;
|
||||
|
||||
use rustc_errors::{error_code, struct_span_err, Applicability, DiagnosticBuilder, Style};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
use rustc_hir::lang_items;
|
||||
use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
|
||||
use rustc_middle::ty::TypeckTables;
|
||||
use rustc_middle::ty::{
|
||||
|
|
@ -150,6 +153,15 @@ pub trait InferCtxtExt<'tcx> {
|
|||
T: fmt::Display;
|
||||
|
||||
fn suggest_new_overflow_limit(&self, err: &mut DiagnosticBuilder<'_>);
|
||||
|
||||
/// Suggest to await before try: future? => future.await?
|
||||
fn suggest_await_before_try(
|
||||
&self,
|
||||
err: &mut DiagnosticBuilder<'_>,
|
||||
obligation: &PredicateObligation<'tcx>,
|
||||
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
|
||||
span: Span,
|
||||
);
|
||||
}
|
||||
|
||||
fn predicate_constraint(generics: &hir::Generics<'_>, pred: String) -> (Span, String) {
|
||||
|
|
@ -1822,6 +1834,95 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
|
|||
suggested_limit, self.tcx.crate_name,
|
||||
));
|
||||
}
|
||||
|
||||
fn suggest_await_before_try(
|
||||
&self,
|
||||
err: &mut DiagnosticBuilder<'_>,
|
||||
obligation: &PredicateObligation<'tcx>,
|
||||
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
|
||||
span: Span,
|
||||
) {
|
||||
debug!(
|
||||
"suggest_await_befor_try: obligation={:?}, span={:?}, trait_ref={:?}, trait_ref_self_ty={:?}",
|
||||
obligation,
|
||||
span,
|
||||
trait_ref,
|
||||
trait_ref.self_ty()
|
||||
);
|
||||
let body_hir_id = obligation.cause.body_id;
|
||||
let item_id = self.tcx.hir().get_parent_node(body_hir_id);
|
||||
|
||||
if let Some(body_id) = self.tcx.hir().maybe_body_owned_by(item_id) {
|
||||
let body = self.tcx.hir().body(body_id);
|
||||
if let Some(hir::GeneratorKind::Async(_)) = body.generator_kind {
|
||||
let future_trait =
|
||||
self.tcx.require_lang_item(lang_items::FutureTraitLangItem, None);
|
||||
|
||||
let self_ty = self.resolve_vars_if_possible(&trait_ref.self_ty());
|
||||
|
||||
let impls_future = self.tcx.type_implements_trait((
|
||||
future_trait,
|
||||
self_ty,
|
||||
ty::List::empty(),
|
||||
obligation.param_env,
|
||||
));
|
||||
|
||||
let item_def_id = self
|
||||
.tcx
|
||||
.associated_items(future_trait)
|
||||
.in_definition_order()
|
||||
.next()
|
||||
.unwrap()
|
||||
.def_id;
|
||||
// `<T as Future>::Output`
|
||||
let projection_ty = ty::ProjectionTy {
|
||||
// `T`
|
||||
substs: self.tcx.mk_substs_trait(
|
||||
trait_ref.self_ty(),
|
||||
self.fresh_substs_for_item(span, item_def_id),
|
||||
),
|
||||
// `Future::Output`
|
||||
item_def_id,
|
||||
};
|
||||
|
||||
let mut selcx = SelectionContext::new(self);
|
||||
|
||||
let mut obligations = vec![];
|
||||
let normalized_ty = normalize_projection_type(
|
||||
&mut selcx,
|
||||
obligation.param_env,
|
||||
projection_ty,
|
||||
obligation.cause.clone(),
|
||||
0,
|
||||
&mut obligations,
|
||||
);
|
||||
|
||||
debug!(
|
||||
"suggest_await_befor_try: normalized_projection_type {:?}",
|
||||
self.resolve_vars_if_possible(&normalized_ty)
|
||||
);
|
||||
let try_obligation = self.mk_obligation_for_def_id(
|
||||
trait_ref.def_id(),
|
||||
normalized_ty,
|
||||
obligation.cause.clone(),
|
||||
obligation.param_env,
|
||||
);
|
||||
debug!("suggest_await_befor_try: try_trait_obligation {:?}", try_obligation);
|
||||
if self.predicate_may_hold(&try_obligation) && impls_future {
|
||||
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
|
||||
if snippet.ends_with('?') {
|
||||
err.span_suggestion(
|
||||
span,
|
||||
"consider using `.await` here",
|
||||
format!("{}.await?", snippet.trim_end_matches('?')),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all the returned expressions within the input expression.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ use rustc_hir::def_id::DefId;
|
|||
use rustc_middle::middle::region;
|
||||
use rustc_middle::ty::fold::TypeFoldable;
|
||||
use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
|
||||
use rustc_middle::ty::{self, GenericParamDefKind, ToPredicate, Ty, TyCtxt, WithConstness};
|
||||
use rustc_middle::ty::{
|
||||
self, GenericParamDefKind, ParamEnv, ToPredicate, Ty, TyCtxt, WithConstness,
|
||||
};
|
||||
use rustc_span::Span;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
|
@ -523,6 +525,43 @@ fn vtable_methods<'tcx>(
|
|||
}))
|
||||
}
|
||||
|
||||
/// Check whether a `ty` implements given trait(trait_def_id).
|
||||
///
|
||||
/// NOTE: Always return `false` for a type which needs inference.
|
||||
fn type_implements_trait<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
key: (
|
||||
DefId, // trait_def_id,
|
||||
Ty<'tcx>, // type
|
||||
SubstsRef<'tcx>,
|
||||
ParamEnv<'tcx>,
|
||||
),
|
||||
) -> bool {
|
||||
let (trait_def_id, ty, params, param_env) = key;
|
||||
|
||||
debug!(
|
||||
"type_implements_trait: trait_def_id={:?}, type={:?}, params={:?}, param_env={:?}",
|
||||
trait_def_id, ty, params, param_env
|
||||
);
|
||||
|
||||
// Do not check on infer_types to avoid panic in evaluate_obligation.
|
||||
if ty.has_infer_types() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ty = tcx.erase_regions(&ty);
|
||||
|
||||
let trait_ref = ty::TraitRef { def_id: trait_def_id, substs: tcx.mk_substs_trait(ty, params) };
|
||||
|
||||
let obligation = Obligation {
|
||||
cause: ObligationCause::dummy(),
|
||||
param_env,
|
||||
recursion_depth: 0,
|
||||
predicate: trait_ref.without_const().to_predicate(),
|
||||
};
|
||||
tcx.infer_ctxt().enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation))
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut ty::query::Providers<'_>) {
|
||||
object_safety::provide(providers);
|
||||
*providers = ty::query::Providers {
|
||||
|
|
@ -531,6 +570,7 @@ pub fn provide(providers: &mut ty::query::Providers<'_>) {
|
|||
codegen_fulfill_obligation: codegen::codegen_fulfill_obligation,
|
||||
vtable_methods,
|
||||
substitute_normalize_and_test_predicates,
|
||||
type_implements_trait,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5282,6 +5282,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
expected: Ty<'tcx>,
|
||||
found: Ty<'tcx>,
|
||||
) {
|
||||
debug!("suggest_missing_await: expr={:?} expected={:?}, found={:?}", expr, expected, found);
|
||||
// `.await` is not permitted outside of `async` bodies, so don't bother to suggest if the
|
||||
// body isn't `async`.
|
||||
let item_id = self.tcx().hir().get_parent_node(self.body_id);
|
||||
|
|
@ -5299,22 +5300,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
.next()
|
||||
.unwrap()
|
||||
.def_id;
|
||||
// `<T as Future>::Output`
|
||||
let projection_ty = ty::ProjectionTy {
|
||||
// `T`
|
||||
substs: self
|
||||
.tcx
|
||||
.mk_substs_trait(found, self.fresh_substs_for_item(sp, item_def_id)),
|
||||
// `Future::Output`
|
||||
item_def_id,
|
||||
};
|
||||
|
||||
let predicate =
|
||||
ty::Predicate::Projection(ty::Binder::bind(ty::ProjectionPredicate {
|
||||
// `<T as Future>::Output`
|
||||
projection_ty: ty::ProjectionTy {
|
||||
// `T`
|
||||
substs: self.tcx.mk_substs_trait(
|
||||
found,
|
||||
self.fresh_substs_for_item(sp, item_def_id),
|
||||
),
|
||||
// `Future::Output`
|
||||
item_def_id,
|
||||
},
|
||||
projection_ty,
|
||||
ty: expected,
|
||||
}));
|
||||
let obligation = traits::Obligation::new(self.misc(sp), self.param_env, predicate);
|
||||
|
||||
debug!("suggest_missing_await: trying obligation {:?}", obligation);
|
||||
|
||||
if self.infcx.predicate_may_hold(&obligation) {
|
||||
debug!("suggest_missing_await: obligation held: {:?}", obligation);
|
||||
if let Ok(code) = self.sess().source_map().span_to_snippet(sp) {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,10 @@ error[E0277]: the `?` operator can only be applied to values that implement `std
|
|||
--> $DIR/incorrect-syntax-suggestions.rs:16:19
|
||||
|
|
||||
LL | let _ = await bar()?;
|
||||
| ^^^^^^ the `?` operator cannot be applied to type `impl std::future::Future`
|
||||
| ^^^^^^
|
||||
| |
|
||||
| the `?` operator cannot be applied to type `impl std::future::Future`
|
||||
| help: consider using `.await` here: `bar().await?`
|
||||
|
|
||||
= help: the trait `std::ops::Try` is not implemented for `impl std::future::Future`
|
||||
= note: required by `std::ops::Try::into_result`
|
||||
|
|
|
|||
32
src/test/ui/async-await/issue-61076.rs
Normal file
32
src/test/ui/async-await/issue-61076.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// edition:2018
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
struct T;
|
||||
|
||||
impl Future for T {
|
||||
type Output = Result<(), ()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
async fn foo() -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn bar() -> Result<(), ()> {
|
||||
foo()?; //~ ERROR the `?` operator can only be applied to values that implement `std::ops::Try`
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn baz() -> Result<(), ()> {
|
||||
let t = T;
|
||||
t?; //~ ERROR the `?` operator can only be applied to values that implement `std::ops::Try`
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
27
src/test/ui/async-await/issue-61076.stderr
Normal file
27
src/test/ui/async-await/issue-61076.stderr
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try`
|
||||
--> $DIR/issue-61076.rs:22:5
|
||||
|
|
||||
LL | foo()?;
|
||||
| ^^^^^^
|
||||
| |
|
||||
| the `?` operator cannot be applied to type `impl std::future::Future`
|
||||
| help: consider using `.await` here: `foo().await?`
|
||||
|
|
||||
= help: the trait `std::ops::Try` is not implemented for `impl std::future::Future`
|
||||
= 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
|
||||
|
|
||||
LL | t?;
|
||||
| ^^
|
||||
| |
|
||||
| the `?` operator cannot be applied to type `T`
|
||||
| help: consider using `.await` here: `t.await?`
|
||||
|
|
||||
= 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
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
|
|
@ -7,7 +7,8 @@ async fn an_async_block() -> u32 {
|
|||
let x: Option<u32> = None;
|
||||
x?; //~ ERROR the `?` operator
|
||||
22
|
||||
}.await
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
async fn async_closure_containing_fn() -> u32 {
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ LL | | let x: Option<u32> = None;
|
|||
LL | | x?;
|
||||
| | ^^ cannot use the `?` operator in an async block that returns `{integer}`
|
||||
LL | | 22
|
||||
LL | | }.await
|
||||
LL | | }
|
||||
| |_____- this function should return `Result` or `Option` to accept `?`
|
||||
|
|
||||
= help: the trait `std::ops::Try` is not implemented for `{integer}`
|
||||
= note: required by `std::ops::Try::from_error`
|
||||
|
||||
error[E0277]: the `?` operator can only be used in an async closure that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
|
||||
--> $DIR/try-on-option-in-async.rs:16:9
|
||||
--> $DIR/try-on-option-in-async.rs:17:9
|
||||
|
|
||||
LL | let async_closure = async || {
|
||||
| __________________________________-
|
||||
|
|
@ -29,7 +29,7 @@ LL | | };
|
|||
= note: required by `std::ops::Try::from_error`
|
||||
|
||||
error[E0277]: the `?` operator can only be used in an async function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
|
||||
--> $DIR/try-on-option-in-async.rs:25:5
|
||||
--> $DIR/try-on-option-in-async.rs:26:5
|
||||
|
|
||||
LL | async fn an_async_function() -> u32 {
|
||||
| _____________________________________-
|
||||
|
|
|
|||
|
|
@ -40,15 +40,12 @@ use rustc_hir::{
|
|||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::traits;
|
||||
use rustc_middle::ty::{self, layout::IntegerExt, subst::GenericArg, Binder, Ty, TyCtxt, TypeFoldable};
|
||||
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
||||
use rustc_span::source_map::original_sp;
|
||||
use rustc_span::symbol::{self, kw, Symbol};
|
||||
use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
|
||||
use rustc_target::abi::Integer;
|
||||
use rustc_trait_selection::traits::predicate_for_trait_def;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::query::normalize::AtExt;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
|
@ -326,19 +323,8 @@ pub fn implements_trait<'a, 'tcx>(
|
|||
trait_id: DefId,
|
||||
ty_params: &[GenericArg<'tcx>],
|
||||
) -> bool {
|
||||
let ty = cx.tcx.erase_regions(&ty);
|
||||
let obligation = predicate_for_trait_def(
|
||||
cx.tcx,
|
||||
cx.param_env,
|
||||
traits::ObligationCause::dummy(),
|
||||
trait_id,
|
||||
0,
|
||||
ty,
|
||||
ty_params,
|
||||
);
|
||||
cx.tcx
|
||||
.infer_ctxt()
|
||||
.enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation))
|
||||
let ty_params = cx.tcx.mk_substs(ty_params.iter());
|
||||
cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env))
|
||||
}
|
||||
|
||||
/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue