Skip to content

Commit 0487f0c

Browse files
committed
Suggest calling async closure when needed
When using an async closure as a value in a place that expects a future, suggest calling the closure. Fix #65923.
1 parent 5c5b8af commit 0487f0c

5 files changed

+143
-55
lines changed

src/librustc/traits/error_reporting.rs

+101-52
Original file line numberDiff line numberDiff line change
@@ -1238,60 +1238,109 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
12381238
points_at_arg: bool,
12391239
) {
12401240
let self_ty = trait_ref.self_ty();
1241-
match self_ty.kind {
1241+
let (def_id, output_ty, callable) = match self_ty.kind {
1242+
ty::Closure(def_id, substs) => {
1243+
(def_id, self.closure_sig(def_id, substs).output(), "closure")
1244+
}
12421245
ty::FnDef(def_id, _) => {
1243-
// We tried to apply the bound to an `fn`. Check whether calling it would evaluate
1244-
// to a type that *would* satisfy the trait binding. If it would, suggest calling
1245-
// it: `bar(foo)` -> `bar(foo)`. This case is *very* likely to be hit if `foo` is
1246-
// `async`.
1247-
let output_ty = self_ty.fn_sig(self.tcx).output();
1248-
let new_trait_ref = ty::TraitRef {
1249-
def_id: trait_ref.def_id(),
1250-
substs: self.tcx.mk_substs_trait(output_ty.skip_binder(), &[]),
1251-
};
1252-
let obligation = Obligation::new(
1253-
obligation.cause.clone(),
1254-
obligation.param_env,
1255-
new_trait_ref.to_predicate(),
1256-
);
1257-
match self.evaluate_obligation(&obligation) {
1258-
Ok(EvaluationResult::EvaluatedToOk) |
1259-
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
1260-
Ok(EvaluationResult::EvaluatedToAmbig) => {
1261-
if let Some(hir::Node::Item(hir::Item {
1262-
ident,
1263-
kind: hir::ItemKind::Fn(.., body_id),
1264-
..
1265-
})) = self.tcx.hir().get_if_local(def_id) {
1266-
let body = self.tcx.hir().body(*body_id);
1267-
let msg = "use parentheses to call the function";
1268-
let snippet = format!(
1269-
"{}({})",
1270-
ident,
1271-
body.params.iter()
1272-
.map(|arg| match &arg.pat.kind {
1273-
hir::PatKind::Binding(_, _, ident, None)
1274-
if ident.name != kw::SelfLower => ident.to_string(),
1275-
_ => "_".to_string(),
1276-
}).collect::<Vec<_>>().join(", "),
1277-
);
1278-
// When the obligation error has been ensured to have been caused by
1279-
// an argument, the `obligation.cause.span` points at the expression
1280-
// of the argument, so we can provide a suggestion. This is signaled
1281-
// by `points_at_arg`. Otherwise, we give a more general note.
1282-
if points_at_arg {
1283-
err.span_suggestion(
1284-
obligation.cause.span,
1285-
msg,
1286-
snippet,
1287-
Applicability::HasPlaceholders,
1288-
);
1289-
} else {
1290-
err.help(&format!("{}: `{}`", msg, snippet));
1291-
}
1292-
}
1246+
(def_id, self_ty.fn_sig(self.tcx).output(), "function")
1247+
}
1248+
_ => return,
1249+
};
1250+
let msg = format!("use parentheses to call the {}", callable);
1251+
// We tried to apply the bound to an `fn` or closure. Check whether calling it would
1252+
// evaluate to a type that *would* satisfy the trait binding. If it would, suggest calling
1253+
// it: `bar(foo)` → `bar(foo())`. This case is *very* likely to be hit if `foo` is `async`.
1254+
1255+
let new_trait_ref = ty::TraitRef {
1256+
def_id: trait_ref.def_id(),
1257+
substs: self.tcx.mk_substs_trait(output_ty.skip_binder(), &[]),
1258+
};
1259+
let obligation = Obligation::new(
1260+
obligation.cause.clone(),
1261+
obligation.param_env,
1262+
new_trait_ref.to_predicate(),
1263+
);
1264+
let get_name = |err: &mut DiagnosticBuilder<'_>, kind: &hir::PatKind| -> Option<String> {
1265+
// Get the local name of this closure. This can be inaccurate because
1266+
// of the possibility of reassignment, but this should be good enough.
1267+
match &kind {
1268+
hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, name, None) => {
1269+
Some(format!("{}", name))
1270+
}
1271+
_ => {
1272+
err.note(&msg);
1273+
None
1274+
}
1275+
}
1276+
};
1277+
match self.evaluate_obligation(&obligation) {
1278+
Ok(EvaluationResult::EvaluatedToOk) |
1279+
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
1280+
Ok(EvaluationResult::EvaluatedToAmbig) => {
1281+
let hir = self.tcx.hir();
1282+
// Get the name of the callable and the arguments to be used in the suggestion.
1283+
let snippet = match hir.get_if_local(def_id) {
1284+
Some(hir::Node::Expr(hir::Expr {
1285+
kind: hir::ExprKind::Closure(_, decl, _, span, ..),
1286+
..
1287+
})) => {
1288+
err.span_label(*span, "consider calling this closure");
1289+
let hir_id = match hir.as_local_hir_id(def_id) {
1290+
Some(hir_id) => hir_id,
1291+
None => return,
1292+
};
1293+
let parent_node = hir.get_parent_node(hir_id);
1294+
let name = match hir.find(parent_node) {
1295+
Some(hir::Node::Stmt(hir::Stmt {
1296+
kind: hir::StmtKind::Local(local), ..
1297+
})) => match get_name(err, &local.pat.kind) {
1298+
Some(name) => name,
1299+
None => return,
1300+
},
1301+
// Different to previous arm because one is `&hir::Local` and the other
1302+
// is `P<hir::Local>`.
1303+
Some(hir::Node::Local(local)) => match get_name(err, &local.pat.kind) {
1304+
Some(name) => name,
1305+
None => return,
1306+
},
1307+
_ => return,
1308+
};
1309+
let args = decl.inputs.iter()
1310+
.map(|_| "_")
1311+
.collect::<Vec<_>>().join(", ");
1312+
format!("{}({})", name, args)
1313+
}
1314+
Some(hir::Node::Item(hir::Item {
1315+
ident,
1316+
kind: hir::ItemKind::Fn(.., body_id),
1317+
..
1318+
})) => {
1319+
err.span_label(ident.span, "consider calling this function");
1320+
let body = hir.body(*body_id);
1321+
let args = body.params.iter()
1322+
.map(|arg| match &arg.pat.kind {
1323+
hir::PatKind::Binding(_, _, ident, None)
1324+
if ident.name != kw::SelfLower => ident.to_string(),
1325+
_ => "_".to_string(),
1326+
}).collect::<Vec<_>>().join(", ");
1327+
format!("{}({})", ident, args)
12931328
}
1294-
_ => {}
1329+
_ => return,
1330+
};
1331+
if points_at_arg {
1332+
// When the obligation error has been ensured to have been caused by
1333+
// an argument, the `obligation.cause.span` points at the expression
1334+
// of the argument, so we can provide a suggestion. This is signaled
1335+
// by `points_at_arg`. Otherwise, we give a more general note.
1336+
err.span_suggestion(
1337+
obligation.cause.span,
1338+
&msg,
1339+
snippet,
1340+
Applicability::HasPlaceholders,
1341+
);
1342+
} else {
1343+
err.help(&format!("{}: `{}`", msg, snippet));
12951344
}
12961345
}
12971346
_ => {}

src/test/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// edition:2018
2+
#![feature(async_closure)]
23
use std::future::Future;
34

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

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

13-
error: aborting due to previous error
16+
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
17+
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:9
18+
|
19+
LL | fn bar(f: impl Future<Output=()>) {}
20+
| --- ----------------- required by this bound in `bar`
21+
...
22+
LL | let async_closure = async || ();
23+
| -------- consider calling this closure
24+
LL | bar(async_closure);
25+
| ^^^^^^^^^^^^^
26+
| |
27+
| 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]`
28+
| help: use parentheses to call the closure: `async_closure()`
29+
30+
error: aborting due to 2 previous errors
1431

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

src/test/ui/suggestions/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ fn bar(f: impl T<O=()>) {}
1515

1616
fn main() {
1717
bar(foo); //~ERROR E0277
18+
let closure = || S;
19+
bar(closure); //~ERROR E0277
1820
}

src/test/ui/suggestions/fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
error[E0277]: the trait bound `fn() -> impl T {foo}: T` is not satisfied
22
--> $DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:17:9
33
|
4+
LL | fn foo() -> impl T<O=()> { S }
5+
| --- consider calling this function
6+
LL |
47
LL | fn bar(f: impl T<O=()>) {}
58
| --- ------- required by this bound in `bar`
69
...
@@ -10,6 +13,20 @@ LL | bar(foo);
1013
| the trait `T` is not implemented for `fn() -> impl T {foo}`
1114
| help: use parentheses to call the function: `foo()`
1215

13-
error: aborting due to previous error
16+
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
17+
--> $DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:19:9
18+
|
19+
LL | fn bar(f: impl T<O=()>) {}
20+
| --- ------- required by this bound in `bar`
21+
...
22+
LL | let closure = || S;
23+
| -- consider calling this closure
24+
LL | bar(closure);
25+
| ^^^^^^^
26+
| |
27+
| 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]`
28+
| help: use parentheses to call the closure: `closure()`
29+
30+
error: aborting due to 2 previous errors
1431

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

0 commit comments

Comments
 (0)