Skip to content

Suggest calling async closure when needed #66239

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

Merged
merged 3 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
195 changes: 133 additions & 62 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,60 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

fn mk_obligation_for_def_id(
&self,
def_id: DefId,
output_ty: Ty<'tcx>,
cause: ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> PredicateObligation<'tcx> {
let new_trait_ref = ty::TraitRef {
def_id,
substs: self.tcx.mk_substs_trait(output_ty, &[]),
};
Obligation::new(cause, param_env, new_trait_ref.to_predicate())
}

/// Given a closure's `DefId`, return the given name of the closure.
///
/// This doesn't account for reassignments, but it's only used for suggestions.
fn get_closure_name(
&self,
def_id: DefId,
err: &mut DiagnosticBuilder<'_>,
msg: &str,
) -> Option<String> {
let get_name = |err: &mut DiagnosticBuilder<'_>, kind: &hir::PatKind| -> Option<String> {
// Get the local name of this closure. This can be inaccurate because
// of the possibility of reassignment, but this should be good enough.
match &kind {
hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, name, None) => {
Some(format!("{}", name))
}
_ => {
err.note(&msg);
None
}
}
};

let hir = self.tcx.hir();
let hir_id = hir.as_local_hir_id(def_id)?;
let parent_node = hir.get_parent_node(hir_id);
match hir.find(parent_node) {
Some(hir::Node::Stmt(hir::Stmt {
kind: hir::StmtKind::Local(local), ..
})) => get_name(err, &local.pat.kind),
// Different to previous arm because one is `&hir::Local` and the other
// is `P<hir::Local>`.
Some(hir::Node::Local(local)) => get_name(err, &local.pat.kind),
_ => return None,
}
}

/// We tried to apply the bound to an `fn` or closure. Check whether calling it would
/// evaluate to a type that *would* satisfy the trait binding. If it would, suggest calling
/// it: `bar(foo)` → `bar(foo())`. This case is *very* likely to be hit if `foo` is `async`.
fn suggest_fn_call(
&self,
obligation: &PredicateObligation<'tcx>,
Expand All @@ -1238,63 +1292,82 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
points_at_arg: bool,
) {
let self_ty = trait_ref.self_ty();
match self_ty.kind {
let (def_id, output_ty, callable) = match self_ty.kind {
ty::Closure(def_id, substs) => {
(def_id, self.closure_sig(def_id, substs).output(), "closure")
}
ty::FnDef(def_id, _) => {
// We tried to apply the bound to an `fn`. Check whether calling it would evaluate
// to a type that *would* satisfy the trait binding. If it would, suggest calling
// it: `bar(foo)` -> `bar(foo)`. This case is *very* likely to be hit if `foo` is
// `async`.
let output_ty = self_ty.fn_sig(self.tcx).output();
let new_trait_ref = ty::TraitRef {
def_id: trait_ref.def_id(),
substs: self.tcx.mk_substs_trait(output_ty.skip_binder(), &[]),
(def_id, self_ty.fn_sig(self.tcx).output(), "function")
}
_ => return,
};
let msg = format!("use parentheses to call the {}", callable);

let obligation = self.mk_obligation_for_def_id(
trait_ref.def_id(),
output_ty.skip_binder(),
obligation.cause.clone(),
obligation.param_env,
);

match self.evaluate_obligation(&obligation) {
Ok(EvaluationResult::EvaluatedToOk) |
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
Ok(EvaluationResult::EvaluatedToAmbig) => {}
_ => return,
}
let hir = self.tcx.hir();
// Get the name of the callable and the arguments to be used in the suggestion.
let snippet = match hir.get_if_local(def_id) {
Some(hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Closure(_, decl, _, span, ..),
..
})) => {
err.span_label(*span, "consider calling this closure");
let name = match self.get_closure_name(def_id, err, &msg) {
Some(name) => name,
None => return,
};
let obligation = Obligation::new(
obligation.cause.clone(),
obligation.param_env,
new_trait_ref.to_predicate(),
);
match self.evaluate_obligation(&obligation) {
Ok(EvaluationResult::EvaluatedToOk) |
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
Ok(EvaluationResult::EvaluatedToAmbig) => {
if let Some(hir::Node::Item(hir::Item {
ident,
kind: hir::ItemKind::Fn(.., body_id),
..
})) = self.tcx.hir().get_if_local(def_id) {
let body = self.tcx.hir().body(*body_id);
let msg = "use parentheses to call the function";
let snippet = format!(
"{}({})",
ident,
body.params.iter()
.map(|arg| match &arg.pat.kind {
hir::PatKind::Binding(_, _, ident, None)
if ident.name != kw::SelfLower => ident.to_string(),
_ => "_".to_string(),
}).collect::<Vec<_>>().join(", "),
);
// When the obligation error has been ensured to have been caused by
// an argument, the `obligation.cause.span` points at the expression
// of the argument, so we can provide a suggestion. This is signaled
// by `points_at_arg`. Otherwise, we give a more general note.
if points_at_arg {
err.span_suggestion(
obligation.cause.span,
msg,
snippet,
Applicability::HasPlaceholders,
);
} else {
err.help(&format!("{}: `{}`", msg, snippet));
}
}
}
_ => {}
}
let args = decl.inputs.iter()
.map(|_| "_")
.collect::<Vec<_>>()
.join(", ");
format!("{}({})", name, args)
}
Some(hir::Node::Item(hir::Item {
ident,
kind: hir::ItemKind::Fn(.., body_id),
..
})) => {
err.span_label(ident.span, "consider calling this function");
let body = hir.body(*body_id);
let args = body.params.iter()
.map(|arg| match &arg.pat.kind {
hir::PatKind::Binding(_, _, ident, None)
// FIXME: provide a better suggestion when encountering `SelfLower`, it
// should suggest a method call.
if ident.name != kw::SelfLower => ident.to_string(),
_ => "_".to_string(),
})
.collect::<Vec<_>>()
.join(", ");
format!("{}({})", ident, args)
}
_ => {}
_ => return,
};
if points_at_arg {
// When the obligation error has been ensured to have been caused by
// an argument, the `obligation.cause.span` points at the expression
// of the argument, so we can provide a suggestion. This is signaled
// by `points_at_arg`. Otherwise, we give a more general note.
err.span_suggestion(
obligation.cause.span,
&msg,
snippet,
Applicability::HasPlaceholders,
);
} else {
err.help(&format!("{}: `{}`", msg, snippet));
}
}

Expand Down Expand Up @@ -1328,12 +1401,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
if let ty::Ref(_, t_type, _) = trait_type.kind {
trait_type = t_type;

let substs = self.tcx.mk_substs_trait(trait_type, &[]);
let new_trait_ref = ty::TraitRef::new(trait_ref.def_id, substs);
let new_obligation = Obligation::new(
let new_obligation = self.mk_obligation_for_def_id(
trait_ref.def_id,
trait_type,
ObligationCause::dummy(),
obligation.param_env,
new_trait_ref.to_predicate(),
);

if self.predicate_may_hold(&new_obligation) {
Expand Down Expand Up @@ -1391,12 +1463,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
hir::Mutability::Immutable => self.tcx.mk_mut_ref(region, t_type),
};

let substs = self.tcx.mk_substs_trait(&trait_type, &[]);
let new_trait_ref = ty::TraitRef::new(trait_ref.skip_binder().def_id, substs);
let new_obligation = Obligation::new(
let new_obligation = self.mk_obligation_for_def_id(
trait_ref.skip_binder().def_id,
trait_type,
ObligationCause::dummy(),
obligation.param_env,
new_trait_ref.to_predicate(),
);

if self.evaluate_obligation_no_overflow(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// edition:2018
#![feature(async_closure)]
use std::future::Future;

async fn foo() {}
Expand All @@ -7,4 +8,6 @@ fn bar(f: impl Future<Output=()>) {}

fn main() {
bar(foo); //~ERROR E0277
let async_closure = async || ();
bar(async_closure); //~ERROR E0277
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
error[E0277]: the trait bound `fn() -> impl std::future::Future {foo}: std::future::Future` is not satisfied
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:9:9
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:9
|
LL | async fn foo() {}
| --- consider calling this function
LL |
LL | fn bar(f: impl Future<Output=()>) {}
| --- ----------------- required by this bound in `bar`
...
Expand All @@ -10,6 +13,20 @@ LL | bar(foo);
| the trait `std::future::Future` is not implemented for `fn() -> impl std::future::Future {foo}`
| help: use parentheses to call the function: `foo()`

error: aborting due to previous error
error[E0277]: the trait bound `[closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:25: 11:36]: std::future::Future` is not satisfied
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:9
|
LL | fn bar(f: impl Future<Output=()>) {}
| --- ----------------- required by this bound in `bar`
...
LL | let async_closure = async || ();
| -------- consider calling this closure
LL | bar(async_closure);
| ^^^^^^^^^^^^^
| |
| the trait `std::future::Future` is not implemented for `[closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:25: 11:36]`
| help: use parentheses to call the closure: `async_closure()`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ fn bar(f: impl T<O=()>) {}

fn main() {
bar(foo); //~ERROR E0277
let closure = || S;
bar(closure); //~ERROR E0277
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
error[E0277]: the trait bound `fn() -> impl T {foo}: T` is not satisfied
--> $DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:17:9
|
LL | fn foo() -> impl T<O=()> { S }
| --- consider calling this function
LL |
LL | fn bar(f: impl T<O=()>) {}
| --- ------- required by this bound in `bar`
...
Expand All @@ -10,6 +13,20 @@ LL | bar(foo);
| the trait `T` is not implemented for `fn() -> impl T {foo}`
| help: use parentheses to call the function: `foo()`

error: aborting due to previous error
error[E0277]: the trait bound `[closure@$DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:18:19: 18:23]: T` is not satisfied
--> $DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:19:9
|
LL | fn bar(f: impl T<O=()>) {}
| --- ------- required by this bound in `bar`
...
LL | let closure = || S;
| -- consider calling this closure
LL | bar(closure);
| ^^^^^^^
| |
| the trait `T` is not implemented for `[closure@$DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:18:19: 18:23]`
| help: use parentheses to call the closure: `closure()`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.