Rollup merge of #119978 - compiler-errors:async-closure-captures, r=oli-obk

Move async closure parameters into the resultant closure's future eagerly

Move async closure parameters into the closure's resultant future eagerly.

Before, we used to desugar `async |p1, p2, ..| { body }` as `|p1, p2, ..| { || async { body } }`. Now, we desugar the above like `|p1, p2, ..| { async move { let p1 = p1; let p2 = p2; ... body } }`. This mirrors the same desugaring that `async fn` does with its parameter types, and the compiler literally uses the same code via a shared helper function.

This removes the necessity for E0708, since now expressions like `async |x: i32| { x }` will not give you confusing borrow errors.

This does *not* fix the case where async closures have self-borrows. This will come with a general implementation of async closures, which is still in the works.

r? oli-obk
This commit is contained in:
Matthias Krüger 2024-01-18 10:34:18 +01:00 committed by GitHub
commit cc629f0a1e
3 changed files with 82 additions and 42 deletions

View File

@ -45,25 +45,49 @@ declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]);
impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// For functions, with explicitly defined types, don't warn. let ExprKind::Closure(Closure {
// XXXkhuey maybe we should? kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, kind)),
if let ExprKind::Closure(Closure {
kind:
ClosureKind::Coroutine(CoroutineKind::Desugared(
CoroutineDesugaring::Async,
CoroutineSource::Block | CoroutineSource::Closure,
)),
body: body_id, body: body_id,
.. ..
}) = expr.kind }) = expr.kind
{ else {
if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() { return;
};
let body_expr = match kind {
CoroutineSource::Fn => {
// For functions, with explicitly defined types, don't warn.
// XXXkhuey maybe we should?
return;
},
CoroutineSource::Block => cx.tcx.hir().body(*body_id).value,
CoroutineSource::Closure => {
// Like `async fn`, async closures are wrapped in an additional block
// to move all of the closure's arguments into the future.
let async_closure_body = cx.tcx.hir().body(*body_id).value;
let ExprKind::Block(block, _) = async_closure_body.kind else {
return;
};
let Some(block_expr) = block.expr else {
return;
};
let ExprKind::DropTemps(body_expr) = block_expr.kind else {
return;
};
body_expr
},
};
let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() else {
return;
};
let typeck_results = cx.tcx.typeck_body(*body_id); let typeck_results = cx.tcx.typeck_body(*body_id);
let body = cx.tcx.hir().body(*body_id); let expr_ty = typeck_results.expr_ty(body_expr);
let expr_ty = typeck_results.expr_ty(body.value);
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
let return_expr_span = match &body.value.kind { let return_expr_span = match &body_expr.kind {
// XXXkhuey there has to be a better way. // XXXkhuey there has to be a better way.
ExprKind::Block(block, _) => block.expr.map(|e| e.span), ExprKind::Block(block, _) => block.expr.map(|e| e.span),
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span), ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
@ -73,11 +97,11 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
ASYNC_YIELDS_ASYNC, ASYNC_YIELDS_ASYNC,
body.value.hir_id, body_expr.hir_id,
return_expr_span, return_expr_span,
"an async construct yields a type which is itself awaitable", "an async construct yields a type which is itself awaitable",
|db| { |db| {
db.span_label(body.value.span, "outer async construct"); db.span_label(body_expr.span, "outer async construct");
db.span_label(return_expr_span, "awaitable value not awaited"); db.span_label(return_expr_span, "awaitable value not awaited");
db.span_suggestion( db.span_suggestion(
return_expr_span, return_expr_span,
@ -91,5 +115,3 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
} }
} }
} }
}
}

View File

@ -5,7 +5,9 @@ use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor}; use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
use rustc_hir::{intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Node}; use rustc_hir::{
intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, Node,
};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -166,10 +168,22 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
if coroutine_kind.is_async() if coroutine_kind.is_async()
&& let hir::ExprKind::Closure(closure) = body.kind && let hir::ExprKind::Closure(closure) = body.kind
{ {
let async_closure_body = cx.tcx.hir().body(closure.body); // Like `async fn`, async closures are wrapped in an additional block
// to move all of the closure's arguments into the future.
let async_closure_body = cx.tcx.hir().body(closure.body).value;
let ExprKind::Block(block, _) = async_closure_body.kind else {
return;
};
let Some(block_expr) = block.expr else {
return;
};
let ExprKind::DropTemps(body_expr) = block_expr.kind else {
return;
};
// `async x` is a syntax error, so it becomes `async { x }` // `async x` is a syntax error, so it becomes `async { x }`
if !matches!(async_closure_body.value.kind, hir::ExprKind::Block(_, _)) { if !matches!(body_expr.kind, hir::ExprKind::Block(_, _)) {
hint = hint.blockify(); hint = hint.blockify();
} }

View File

@ -48,7 +48,11 @@ if let ExprKind::Closure { capture_clause: CaptureBy::Value { .. }, fn_decl: fn_
&& expr2 = &cx.tcx.hir().body(body_id1).value && expr2 = &cx.tcx.hir().body(body_id1).value
&& let ExprKind::Block(block, None) = expr2.kind && let ExprKind::Block(block, None) = expr2.kind
&& block.stmts.is_empty() && block.stmts.is_empty()
&& block.expr.is_none() && let Some(trailing_expr) = block.expr
&& let ExprKind::DropTemps(expr3) = trailing_expr.kind
&& let ExprKind::Block(block1, None) = expr3.kind
&& block1.stmts.is_empty()
&& block1.expr.is_none()
{ {
// report your lint here // report your lint here
} }