Skip to content

Commit

Permalink
Suggest calling async closure when needed
Browse files Browse the repository at this point in the history
When using an async closure as a value in a place that expects a future,
suggest calling the closure.

Fix #65923.
  • Loading branch information
estebank committed Nov 17, 2019
1 parent 5c5b8af commit 0487f0c
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 55 deletions.
153 changes: 101 additions & 52 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,60 +1238,109 @@ 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(), &[]),
};
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));
}
}
(def_id, self_ty.fn_sig(self.tcx).output(), "function")
}
_ => return,
};
let msg = format!("use parentheses to call the {}", callable);
// 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`.

let new_trait_ref = ty::TraitRef {
def_id: trait_ref.def_id(),
substs: self.tcx.mk_substs_trait(output_ty.skip_binder(), &[]),
};
let obligation = Obligation::new(
obligation.cause.clone(),
obligation.param_env,
new_trait_ref.to_predicate(),
);
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
}
}
};
match self.evaluate_obligation(&obligation) {
Ok(EvaluationResult::EvaluatedToOk) |
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
Ok(EvaluationResult::EvaluatedToAmbig) => {
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 hir_id = match hir.as_local_hir_id(def_id) {
Some(hir_id) => hir_id,
None => return,
};
let parent_node = hir.get_parent_node(hir_id);
let name = match hir.find(parent_node) {
Some(hir::Node::Stmt(hir::Stmt {
kind: hir::StmtKind::Local(local), ..
})) => match get_name(err, &local.pat.kind) {
Some(name) => name,
None => return,
},
// Different to previous arm because one is `&hir::Local` and the other
// is `P<hir::Local>`.
Some(hir::Node::Local(local)) => match get_name(err, &local.pat.kind) {
Some(name) => name,
None => return,
},
_ => return,
};
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)
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
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`.

1 comment on commit 0487f0c

@gilescope
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks @estebank - that’s fantastic!

Please sign in to comment.