Skip to content
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

Suggest calling async closure when needed #66239

Merged
merged 3 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
183 changes: 121 additions & 62 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,24 @@ 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())
}


/// 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 +1256,106 @@ 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);

let obligation = self.mk_obligation_for_def_id(
trait_ref.def_id(),
output_ty.skip_binder(),
obligation.cause.clone(),
obligation.param_env,
);
estebank marked this conversation as resolved.
Show resolved Hide resolved

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) => {}
_ => 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 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(", ");
estebank marked this conversation as resolved.
Show resolved Hide resolved
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(", ");
estebank marked this conversation as resolved.
Show resolved Hide resolved
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 +1389,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 +1451,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`.