Skip to content

Commit 0b0d683

Browse files
authored
Rollup merge of rust-lang#66239 - estebank:suggest-async-closure-call, r=Centril
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 rust-lang#65923.
2 parents 40deec8 + 614da98 commit 0b0d683

5 files changed

+175
-65
lines changed

src/librustc/traits/error_reporting.rs

Lines changed: 133 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,60 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
12451245
}
12461246
}
12471247

1248+
fn mk_obligation_for_def_id(
1249+
&self,
1250+
def_id: DefId,
1251+
output_ty: Ty<'tcx>,
1252+
cause: ObligationCause<'tcx>,
1253+
param_env: ty::ParamEnv<'tcx>,
1254+
) -> PredicateObligation<'tcx> {
1255+
let new_trait_ref = ty::TraitRef {
1256+
def_id,
1257+
substs: self.tcx.mk_substs_trait(output_ty, &[]),
1258+
};
1259+
Obligation::new(cause, param_env, new_trait_ref.to_predicate())
1260+
}
1261+
1262+
/// Given a closure's `DefId`, return the given name of the closure.
1263+
///
1264+
/// This doesn't account for reassignments, but it's only used for suggestions.
1265+
fn get_closure_name(
1266+
&self,
1267+
def_id: DefId,
1268+
err: &mut DiagnosticBuilder<'_>,
1269+
msg: &str,
1270+
) -> Option<String> {
1271+
let get_name = |err: &mut DiagnosticBuilder<'_>, kind: &hir::PatKind| -> Option<String> {
1272+
// Get the local name of this closure. This can be inaccurate because
1273+
// of the possibility of reassignment, but this should be good enough.
1274+
match &kind {
1275+
hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, name, None) => {
1276+
Some(format!("{}", name))
1277+
}
1278+
_ => {
1279+
err.note(&msg);
1280+
None
1281+
}
1282+
}
1283+
};
1284+
1285+
let hir = self.tcx.hir();
1286+
let hir_id = hir.as_local_hir_id(def_id)?;
1287+
let parent_node = hir.get_parent_node(hir_id);
1288+
match hir.find(parent_node) {
1289+
Some(hir::Node::Stmt(hir::Stmt {
1290+
kind: hir::StmtKind::Local(local), ..
1291+
})) => get_name(err, &local.pat.kind),
1292+
// Different to previous arm because one is `&hir::Local` and the other
1293+
// is `P<hir::Local>`.
1294+
Some(hir::Node::Local(local)) => get_name(err, &local.pat.kind),
1295+
_ => return None,
1296+
}
1297+
}
1298+
1299+
/// We tried to apply the bound to an `fn` or closure. Check whether calling it would
1300+
/// evaluate to a type that *would* satisfy the trait binding. If it would, suggest calling
1301+
/// it: `bar(foo)` → `bar(foo())`. This case is *very* likely to be hit if `foo` is `async`.
12481302
fn suggest_fn_call(
12491303
&self,
12501304
obligation: &PredicateObligation<'tcx>,
@@ -1253,63 +1307,82 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
12531307
points_at_arg: bool,
12541308
) {
12551309
let self_ty = trait_ref.self_ty();
1256-
match self_ty.kind {
1310+
let (def_id, output_ty, callable) = match self_ty.kind {
1311+
ty::Closure(def_id, substs) => {
1312+
(def_id, self.closure_sig(def_id, substs).output(), "closure")
1313+
}
12571314
ty::FnDef(def_id, _) => {
1258-
// We tried to apply the bound to an `fn`. Check whether calling it would evaluate
1259-
// to a type that *would* satisfy the trait binding. If it would, suggest calling
1260-
// it: `bar(foo)` -> `bar(foo)`. This case is *very* likely to be hit if `foo` is
1261-
// `async`.
1262-
let output_ty = self_ty.fn_sig(self.tcx).output();
1263-
let new_trait_ref = ty::TraitRef {
1264-
def_id: trait_ref.def_id(),
1265-
substs: self.tcx.mk_substs_trait(output_ty.skip_binder(), &[]),
1315+
(def_id, self_ty.fn_sig(self.tcx).output(), "function")
1316+
}
1317+
_ => return,
1318+
};
1319+
let msg = format!("use parentheses to call the {}", callable);
1320+
1321+
let obligation = self.mk_obligation_for_def_id(
1322+
trait_ref.def_id(),
1323+
output_ty.skip_binder(),
1324+
obligation.cause.clone(),
1325+
obligation.param_env,
1326+
);
1327+
1328+
match self.evaluate_obligation(&obligation) {
1329+
Ok(EvaluationResult::EvaluatedToOk) |
1330+
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
1331+
Ok(EvaluationResult::EvaluatedToAmbig) => {}
1332+
_ => return,
1333+
}
1334+
let hir = self.tcx.hir();
1335+
// Get the name of the callable and the arguments to be used in the suggestion.
1336+
let snippet = match hir.get_if_local(def_id) {
1337+
Some(hir::Node::Expr(hir::Expr {
1338+
kind: hir::ExprKind::Closure(_, decl, _, span, ..),
1339+
..
1340+
})) => {
1341+
err.span_label(*span, "consider calling this closure");
1342+
let name = match self.get_closure_name(def_id, err, &msg) {
1343+
Some(name) => name,
1344+
None => return,
12661345
};
1267-
let obligation = Obligation::new(
1268-
obligation.cause.clone(),
1269-
obligation.param_env,
1270-
new_trait_ref.to_predicate(),
1271-
);
1272-
match self.evaluate_obligation(&obligation) {
1273-
Ok(EvaluationResult::EvaluatedToOk) |
1274-
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
1275-
Ok(EvaluationResult::EvaluatedToAmbig) => {
1276-
if let Some(hir::Node::Item(hir::Item {
1277-
ident,
1278-
kind: hir::ItemKind::Fn(.., body_id),
1279-
..
1280-
})) = self.tcx.hir().get_if_local(def_id) {
1281-
let body = self.tcx.hir().body(*body_id);
1282-
let msg = "use parentheses to call the function";
1283-
let snippet = format!(
1284-
"{}({})",
1285-
ident,
1286-
body.params.iter()
1287-
.map(|arg| match &arg.pat.kind {
1288-
hir::PatKind::Binding(_, _, ident, None)
1289-
if ident.name != kw::SelfLower => ident.to_string(),
1290-
_ => "_".to_string(),
1291-
}).collect::<Vec<_>>().join(", "),
1292-
);
1293-
// When the obligation error has been ensured to have been caused by
1294-
// an argument, the `obligation.cause.span` points at the expression
1295-
// of the argument, so we can provide a suggestion. This is signaled
1296-
// by `points_at_arg`. Otherwise, we give a more general note.
1297-
if points_at_arg {
1298-
err.span_suggestion(
1299-
obligation.cause.span,
1300-
msg,
1301-
snippet,
1302-
Applicability::HasPlaceholders,
1303-
);
1304-
} else {
1305-
err.help(&format!("{}: `{}`", msg, snippet));
1306-
}
1307-
}
1308-
}
1309-
_ => {}
1310-
}
1346+
let args = decl.inputs.iter()
1347+
.map(|_| "_")
1348+
.collect::<Vec<_>>()
1349+
.join(", ");
1350+
format!("{}({})", name, args)
1351+
}
1352+
Some(hir::Node::Item(hir::Item {
1353+
ident,
1354+
kind: hir::ItemKind::Fn(.., body_id),
1355+
..
1356+
})) => {
1357+
err.span_label(ident.span, "consider calling this function");
1358+
let body = hir.body(*body_id);
1359+
let args = body.params.iter()
1360+
.map(|arg| match &arg.pat.kind {
1361+
hir::PatKind::Binding(_, _, ident, None)
1362+
// FIXME: provide a better suggestion when encountering `SelfLower`, it
1363+
// should suggest a method call.
1364+
if ident.name != kw::SelfLower => ident.to_string(),
1365+
_ => "_".to_string(),
1366+
})
1367+
.collect::<Vec<_>>()
1368+
.join(", ");
1369+
format!("{}({})", ident, args)
13111370
}
1312-
_ => {}
1371+
_ => return,
1372+
};
1373+
if points_at_arg {
1374+
// When the obligation error has been ensured to have been caused by
1375+
// an argument, the `obligation.cause.span` points at the expression
1376+
// of the argument, so we can provide a suggestion. This is signaled
1377+
// by `points_at_arg`. Otherwise, we give a more general note.
1378+
err.span_suggestion(
1379+
obligation.cause.span,
1380+
&msg,
1381+
snippet,
1382+
Applicability::HasPlaceholders,
1383+
);
1384+
} else {
1385+
err.help(&format!("{}: `{}`", msg, snippet));
13131386
}
13141387
}
13151388

@@ -1410,12 +1483,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
14101483
if let ty::Ref(_, t_type, _) = trait_type.kind {
14111484
trait_type = t_type;
14121485

1413-
let substs = self.tcx.mk_substs_trait(trait_type, &[]);
1414-
let new_trait_ref = ty::TraitRef::new(trait_ref.def_id, substs);
1415-
let new_obligation = Obligation::new(
1486+
let new_obligation = self.mk_obligation_for_def_id(
1487+
trait_ref.def_id,
1488+
trait_type,
14161489
ObligationCause::dummy(),
14171490
obligation.param_env,
1418-
new_trait_ref.to_predicate(),
14191491
);
14201492

14211493
if self.predicate_may_hold(&new_obligation) {
@@ -1473,12 +1545,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
14731545
hir::Mutability::Immutable => self.tcx.mk_mut_ref(region, t_type),
14741546
};
14751547

1476-
let substs = self.tcx.mk_substs_trait(&trait_type, &[]);
1477-
let new_trait_ref = ty::TraitRef::new(trait_ref.skip_binder().def_id, substs);
1478-
let new_obligation = Obligation::new(
1548+
let new_obligation = self.mk_obligation_for_def_id(
1549+
trait_ref.skip_binder().def_id,
1550+
trait_type,
14791551
ObligationCause::dummy(),
14801552
obligation.param_env,
1481-
new_trait_ref.to_predicate(),
14821553
);
14831554

14841555
if self.evaluate_obligation_no_overflow(

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

Lines changed: 3 additions & 0 deletions
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
}
Lines changed: 19 additions & 2 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 18 additions & 1 deletion
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)