From 28f02fbf3e26738047135a577622a313a1c3bfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Oct 2020 10:54:34 -0700 Subject: [PATCH] Suggest calling await on method call and field access When encountering a failing method or field resolution on a `Future`, look at the `Output` and try the same operation on it. If successful, suggest calling `.await` on the `Future`. This had already been introduced in #72784, but at some point they stopped working. --- .../src/infer/error_reporting/mod.rs | 2 +- compiler/rustc_typeck/src/check/expr.rs | 71 +++++++------------ .../rustc_typeck/src/check/method/suggest.rs | 54 ++++---------- src/test/ui/async-await/issue-61076.rs | 29 +++++++- src/test/ui/async-await/issue-61076.stderr | 29 ++++++-- 5 files changed, 90 insertions(+), 95 deletions(-) diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index f7e4ace8fc5fc..1402f70c220dd 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -1669,7 +1669,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { self.note_error_origin(diag, cause, exp_found); } - fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option> { + pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option> { if let ty::Opaque(def_id, substs) = ty.kind() { let future_trait = self.tcx.require_lang_item(LangItem::Future, None); // Future::Output diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 03e448a00cc77..3f1edeb5108cb 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -42,7 +42,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, SelectionContext}; +use rustc_trait_selection::traits::{self, ObligationCauseCode}; use std::fmt::Display; @@ -1580,51 +1580,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err: &mut DiagnosticBuilder<'_>, field_ident: Ident, base: &'tcx hir::Expr<'tcx>, - expr: &'tcx hir::Expr<'tcx>, - def_id: DefId, + ty: Ty<'tcx>, ) { - 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() { - // no field access on enum type - if !def.is_enum() { - 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, - ); - } + let output_ty = match self.infcx.get_impl_future_output_ty(ty) { + Some(output_ty) => self.resolve_vars_if_possible(&output_ty), + _ => return, + }; + let mut add_label = true; + if let ty::Adt(def, _) = output_ty.kind() { + // no field access on enum type + if !def.is_enum() { + if def.non_enum_variant().fields.iter().any(|field| field.ident == field_ident) { + add_label = false; + err.span_label( + field_ident.span, + "field not available in `impl Future`, but it is available in its `Output`", + ); + err.span_suggestion_verbose( + base.span.shrink_to_hi(), + "consider `await`ing on the `Future` and access the field of its `Output`", + ".await".to_string(), + Applicability::MaybeIncorrect, + ); } } } + if add_label { + err.span_label(field_ident.span, &format!("field not found in `{}`", ty)); + } } fn ban_nonexisting_field( @@ -1653,8 +1636,8 @@ 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); + ty::Opaque(_, _) => { + self.suggest_await_on_field_access(&mut err, field, base, expr_t.peel_refs()); } _ => {} } diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs index 6d2ffadc20c27..46afe4892dbd7 100644 --- a/compiler/rustc_typeck/src/check/method/suggest.rs +++ b/compiler/rustc_typeck/src/check/method/suggest.rs @@ -21,7 +21,6 @@ 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; @@ -870,46 +869,19 @@ impl<'a, 'tcx> FnCtxt<'a, '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, - ); - } - } + let output_ty = match self.infcx.get_impl_future_output_ty(ty) { + Some(output_ty) => self.resolve_vars_if_possible(&output_ty), + _ => return, + }; + let method_exists = self.method_exists(item_name, output_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 `await`ing on the `Future` and calling the method on its `Output`", + "await.".to_string(), + Applicability::MaybeIncorrect, + ); } } diff --git a/src/test/ui/async-await/issue-61076.rs b/src/test/ui/async-await/issue-61076.rs index b1216ff4c4550..8a7b166cb15bd 100644 --- a/src/test/ui/async-await/issue-61076.rs +++ b/src/test/ui/async-await/issue-61076.rs @@ -40,6 +40,14 @@ async fn foo() -> Result<(), ()> { async fn bar() -> Result<(), ()> { foo()?; //~ ERROR the `?` operator can only be applied to values that implement `Try` + //~^ NOTE the `?` operator cannot be applied to type `impl Future` + //~| HELP the trait `Try` is not implemented for `impl Future` + //~| NOTE required by `into_result` + //~| HELP consider `await`ing on the `Future` + //~| NOTE in this expansion of desugaring of operator `?` + //~| NOTE in this expansion of desugaring of operator `?` + //~| NOTE in this expansion of desugaring of operator `?` + //~| NOTE in this expansion of desugaring of operator `?` Ok(()) } @@ -48,25 +56,42 @@ async fn struct_() -> Struct { } async fn tuple() -> Tuple { + //~^ NOTE the `Output` of this `async fn`'s expected opaque type Tuple(1i32) } async fn baz() -> Result<(), ()> { let t = T; t?; //~ ERROR the `?` operator can only be applied to values that implement `Try` + //~^ NOTE the `?` operator cannot be applied to type `T` + //~| HELP the trait `Try` is not implemented for `T` + //~| NOTE required by `into_result` + //~| HELP consider `await`ing on the `Future` + //~| NOTE in this expansion of desugaring of operator `?` + //~| NOTE in this expansion of desugaring of operator `?` + //~| NOTE in this expansion of desugaring of operator `?` + //~| NOTE in this expansion of desugaring of operator `?` + let _: i32 = tuple().0; //~ ERROR no field `0` + //~^ HELP consider `await`ing on the `Future` + //~| NOTE field not available in `impl Future` let _: i32 = struct_().a; //~ ERROR no field `a` + //~^ HELP consider `await`ing on the `Future` + //~| NOTE field not available in `impl Future` struct_().method(); //~ ERROR no method named - + //~^ NOTE method not found in `impl Future` + //~| HELP consider `await`ing on the `Future` Ok(()) } async fn match_() { - match tuple() { + match tuple() { //~ HELP consider `await`ing on the `Future` Tuple(_) => {} //~ ERROR mismatched types + //~^ NOTE expected opaque type, found struct `Tuple` + //~| NOTE expected opaque type `impl Future` } } diff --git a/src/test/ui/async-await/issue-61076.stderr b/src/test/ui/async-await/issue-61076.stderr index df54ac88acee1..db6dc3ea00a8d 100644 --- a/src/test/ui/async-await/issue-61076.stderr +++ b/src/test/ui/async-await/issue-61076.stderr @@ -12,7 +12,7 @@ LL | foo().await?; | ^^^^^^ error[E0277]: the `?` operator can only be applied to values that implement `Try` - --> $DIR/issue-61076.rs:56:5 + --> $DIR/issue-61076.rs:65:5 | LL | t?; | ^^ the `?` operator cannot be applied to type `T` @@ -25,25 +25,40 @@ LL | t.await?; | ^^^^^^ error[E0609]: no field `0` on type `impl Future` - --> $DIR/issue-61076.rs:58:26 + --> $DIR/issue-61076.rs:76:26 | LL | let _: i32 = tuple().0; - | ^ + | ^ field not available in `impl Future`, but it is available in its `Output` + | +help: consider `await`ing on the `Future` and access the field of its `Output` + | +LL | let _: i32 = tuple().await.0; + | ^^^^^^ error[E0609]: no field `a` on type `impl Future` - --> $DIR/issue-61076.rs:60:28 + --> $DIR/issue-61076.rs:80:28 | LL | let _: i32 = struct_().a; - | ^ + | ^ field not available in `impl Future`, but it is available in its `Output` + | +help: consider `await`ing on the `Future` and access the field of its `Output` + | +LL | let _: i32 = struct_().await.a; + | ^^^^^^ error[E0599]: no method named `method` found for opaque type `impl Future` in the current scope - --> $DIR/issue-61076.rs:62:15 + --> $DIR/issue-61076.rs:84:15 | LL | struct_().method(); | ^^^^^^ method not found in `impl Future` + | +help: consider `await`ing on the `Future` and calling the method on its `Output` + | +LL | struct_().await.method(); + | ^^^^^^ error[E0308]: mismatched types - --> $DIR/issue-61076.rs:69:9 + --> $DIR/issue-61076.rs:92:9 | LL | async fn tuple() -> Tuple { | ----- the `Output` of this `async fn`'s expected opaque type