Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3ef303b

Browse files
authoredJan 16, 2024
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
2 parents f990e22 + 04a5ee6 commit 3ef303b

File tree

13 files changed

+350
-320
lines changed

13 files changed

+350
-320
lines changed
 

‎compiler/rustc_ast_lowering/messages.ftl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ ast_lowering_assoc_ty_parentheses =
1414
ast_lowering_async_coroutines_not_supported =
1515
`async` coroutines are not yet supported
1616
17-
ast_lowering_async_non_move_closure_not_supported =
18-
`async` non-`move` closures with parameters are not currently supported
19-
.help = consider using `let` statements to manually capture variables by reference before entering an `async move` closure
20-
2117
ast_lowering_att_syntax_only_x86 =
2218
the `att_syntax` option is only supported on x86
2319

‎compiler/rustc_ast_lowering/src/errors.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,6 @@ pub struct ClosureCannotBeStatic {
145145
pub fn_decl_span: Span,
146146
}
147147

148-
#[derive(Diagnostic, Clone, Copy)]
149-
#[help]
150-
#[diag(ast_lowering_async_non_move_closure_not_supported, code = "E0708")]
151-
pub struct AsyncNonMoveClosureNotSupported {
152-
#[primary_span]
153-
pub fn_decl_span: Span,
154-
}
155-
156148
#[derive(Diagnostic, Clone, Copy)]
157149
#[diag(ast_lowering_functional_record_update_destructuring_assignment)]
158150
pub struct FunctionalRecordUpdateDestructuringAssignment {

‎compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::errors::{
2-
AsyncCoroutinesNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks,
3-
BaseExpressionDoubleDot, ClosureCannotBeStatic, CoroutineTooManyParameters,
2+
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot,
3+
ClosureCannotBeStatic, CoroutineTooManyParameters,
44
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody,
55
NeverPatternWithBody, NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure,
66
UnderscoreExprLhsAssign,
@@ -13,7 +13,6 @@ use rustc_ast::*;
1313
use rustc_data_structures::stack::ensure_sufficient_stack;
1414
use rustc_hir as hir;
1515
use rustc_hir::def::{DefKind, Res};
16-
use rustc_middle::span_bug;
1716
use rustc_session::errors::report_lit_error;
1817
use rustc_span::source_map::{respan, Spanned};
1918
use rustc_span::symbol::{kw, sym, Ident, Symbol};
@@ -1028,51 +1027,43 @@ impl<'hir> LoweringContext<'_, 'hir> {
10281027
fn_decl_span: Span,
10291028
fn_arg_span: Span,
10301029
) -> hir::ExprKind<'hir> {
1031-
let CoroutineKind::Async { closure_id: inner_closure_id, .. } = coroutine_kind else {
1032-
span_bug!(fn_decl_span, "`async gen` and `gen` closures are not supported, yet");
1033-
};
1034-
10351030
if let &ClosureBinder::For { span, .. } = binder {
10361031
self.dcx().emit_err(NotSupportedForLifetimeBinderAsyncClosure { span });
10371032
}
10381033

10391034
let (binder_clause, generic_params) = self.lower_closure_binder(binder);
10401035

1041-
let outer_decl =
1042-
FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
1043-
10441036
let body = self.with_new_scopes(fn_decl_span, |this| {
1045-
// FIXME(cramertj): allow `async` non-`move` closures with arguments.
1046-
if capture_clause == CaptureBy::Ref && !decl.inputs.is_empty() {
1047-
this.dcx().emit_err(AsyncNonMoveClosureNotSupported { fn_decl_span });
1048-
}
1049-
10501037
// Transform `async |x: u8| -> X { ... }` into
10511038
// `|x: u8| || -> X { ... }`.
1052-
let body_id = this.lower_fn_body(&outer_decl, |this| {
1039+
let body_id = this.lower_body(|this| {
10531040
let async_ret_ty = if let FnRetTy::Ty(ty) = &decl.output {
10541041
let itctx = ImplTraitContext::Disallowed(ImplTraitPosition::AsyncBlock);
10551042
Some(hir::FnRetTy::Return(this.lower_ty(ty, &itctx)))
10561043
} else {
10571044
None
10581045
};
10591046

1060-
let async_body = this.make_desugared_coroutine_expr(
1061-
capture_clause,
1062-
inner_closure_id,
1063-
async_ret_ty,
1047+
let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments(
1048+
decl,
1049+
|this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),
10641050
body.span,
1065-
hir::CoroutineDesugaring::Async,
1051+
coroutine_kind,
10661052
hir::CoroutineSource::Closure,
1067-
|this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),
1053+
async_ret_ty,
10681054
);
1069-
let hir_id = this.lower_node_id(inner_closure_id);
1055+
1056+
let hir_id = this.lower_node_id(coroutine_kind.closure_id());
10701057
this.maybe_forward_track_caller(body.span, closure_hir_id, hir_id);
1071-
hir::Expr { hir_id, kind: async_body, span: this.lower_span(body.span) }
1058+
1059+
(parameters, expr)
10721060
});
10731061
body_id
10741062
});
10751063

1064+
let outer_decl =
1065+
FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
1066+
10761067
let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
10771068
// We need to lower the declaration outside the new scope, because we
10781069
// have to conserve the state of being inside a loop condition for the

‎compiler/rustc_ast_lowering/src/item.rs

Lines changed: 200 additions & 170 deletions
Large diffs are not rendered by default.

‎compiler/rustc_error_codes/src/error_codes/E0708.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
#### Note: this error code is no longer emitted by the compiler.
2+
13
`async` non-`move` closures with parameters are currently not supported.
24

35
Erroneous code example:
46

5-
```compile_fail,edition2018,E0708
7+
```edition2018
68
#![feature(async_closure)]
79
810
fn main() {
9-
let add_one = async |num: u8| { // error!
11+
let add_one = async |num: u8| {
1012
num + 1
1113
};
1214
}

‎compiler/rustc_hir_typeck/src/callee.rs

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -293,49 +293,59 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
293293
callee_node: &hir::ExprKind<'_>,
294294
callee_span: Span,
295295
) {
296+
let hir::ExprKind::Block(..) = callee_node else {
297+
// Only calls on blocks suggested here.
298+
return;
299+
};
300+
296301
let hir = self.tcx.hir();
297-
let parent_hir_id = hir.parent_id(hir_id);
298-
let parent_node = self.tcx.hir_node(parent_hir_id);
299-
if let (
300-
hir::Node::Expr(hir::Expr {
301-
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, kind, .. }),
302+
let fn_decl_span = if let hir::Node::Expr(hir::Expr {
303+
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
304+
..
305+
}) = hir.get_parent(hir_id)
306+
{
307+
fn_decl_span
308+
} else if let Some((
309+
_,
310+
hir::Node::Expr(&hir::Expr {
311+
hir_id: parent_hir_id,
312+
kind:
313+
hir::ExprKind::Closure(&hir::Closure {
314+
kind:
315+
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
316+
hir::CoroutineDesugaring::Async,
317+
hir::CoroutineSource::Closure,
318+
)),
319+
..
320+
}),
302321
..
303322
}),
304-
hir::ExprKind::Block(..),
305-
) = (parent_node, callee_node)
323+
)) = hir.parent_iter(hir_id).nth(3)
306324
{
307-
let fn_decl_span = if matches!(
308-
kind,
309-
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
310-
hir::CoroutineDesugaring::Async,
311-
hir::CoroutineSource::Closure
312-
),)
313-
) {
314-
// Actually need to unwrap one more layer of HIR to get to
315-
// the _real_ closure...
316-
let async_closure = hir.parent_id(parent_hir_id);
317-
if let hir::Node::Expr(hir::Expr {
318-
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
319-
..
320-
}) = self.tcx.hir_node(async_closure)
321-
{
322-
fn_decl_span
323-
} else {
324-
return;
325-
}
326-
} else {
325+
// Actually need to unwrap one more layer of HIR to get to
326+
// the _real_ closure...
327+
let async_closure = hir.parent_id(parent_hir_id);
328+
if let hir::Node::Expr(hir::Expr {
329+
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
330+
..
331+
}) = self.tcx.hir_node(async_closure)
332+
{
327333
fn_decl_span
328-
};
334+
} else {
335+
return;
336+
}
337+
} else {
338+
return;
339+
};
329340

330-
let start = fn_decl_span.shrink_to_lo();
331-
let end = callee_span.shrink_to_hi();
332-
err.multipart_suggestion(
333-
"if you meant to create this closure and immediately call it, surround the \
341+
let start = fn_decl_span.shrink_to_lo();
342+
let end = callee_span.shrink_to_hi();
343+
err.multipart_suggestion(
344+
"if you meant to create this closure and immediately call it, surround the \
334345
closure with parentheses",
335-
vec![(start, "(".to_string()), (end, ")".to_string())],
336-
Applicability::MaybeIncorrect,
337-
);
338-
}
346+
vec![(start, "(".to_string()), (end, ")".to_string())],
347+
Applicability::MaybeIncorrect,
348+
);
339349
}
340350

341351
/// Give appropriate suggestion when encountering `[("a", 0) ("b", 1)]`, where the

‎src/tools/clippy/clippy_lints/src/async_yields_async.rs

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -45,50 +45,72 @@ declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]);
4545

4646
impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
4747
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
48-
// For functions, with explicitly defined types, don't warn.
49-
// XXXkhuey maybe we should?
50-
if let ExprKind::Closure(Closure {
51-
kind:
52-
ClosureKind::Coroutine(CoroutineKind::Desugared(
53-
CoroutineDesugaring::Async,
54-
CoroutineSource::Block | CoroutineSource::Closure,
55-
)),
48+
let ExprKind::Closure(Closure {
49+
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, kind)),
5650
body: body_id,
5751
..
5852
}) = expr.kind
59-
{
60-
if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() {
61-
let typeck_results = cx.tcx.typeck_body(*body_id);
62-
let body = cx.tcx.hir().body(*body_id);
63-
let expr_ty = typeck_results.expr_ty(body.value);
53+
else {
54+
return;
55+
};
6456

65-
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
66-
let return_expr_span = match &body.value.kind {
67-
// XXXkhuey there has to be a better way.
68-
ExprKind::Block(block, _) => block.expr.map(|e| e.span),
69-
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
70-
_ => None,
71-
};
72-
if let Some(return_expr_span) = return_expr_span {
73-
span_lint_hir_and_then(
74-
cx,
75-
ASYNC_YIELDS_ASYNC,
76-
body.value.hir_id,
57+
let body_expr = match kind {
58+
CoroutineSource::Fn => {
59+
// For functions, with explicitly defined types, don't warn.
60+
// XXXkhuey maybe we should?
61+
return;
62+
},
63+
CoroutineSource::Block => cx.tcx.hir().body(*body_id).value,
64+
CoroutineSource::Closure => {
65+
// Like `async fn`, async closures are wrapped in an additional block
66+
// to move all of the closure's arguments into the future.
67+
68+
let async_closure_body = cx.tcx.hir().body(*body_id).value;
69+
let ExprKind::Block(block, _) = async_closure_body.kind else {
70+
return;
71+
};
72+
let Some(block_expr) = block.expr else {
73+
return;
74+
};
75+
let ExprKind::DropTemps(body_expr) = block_expr.kind else {
76+
return;
77+
};
78+
body_expr
79+
},
80+
};
81+
82+
let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() else {
83+
return;
84+
};
85+
86+
let typeck_results = cx.tcx.typeck_body(*body_id);
87+
let expr_ty = typeck_results.expr_ty(body_expr);
88+
89+
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
90+
let return_expr_span = match &body_expr.kind {
91+
// XXXkhuey there has to be a better way.
92+
ExprKind::Block(block, _) => block.expr.map(|e| e.span),
93+
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
94+
_ => None,
95+
};
96+
if let Some(return_expr_span) = return_expr_span {
97+
span_lint_hir_and_then(
98+
cx,
99+
ASYNC_YIELDS_ASYNC,
100+
body_expr.hir_id,
101+
return_expr_span,
102+
"an async construct yields a type which is itself awaitable",
103+
|db| {
104+
db.span_label(body_expr.span, "outer async construct");
105+
db.span_label(return_expr_span, "awaitable value not awaited");
106+
db.span_suggestion(
77107
return_expr_span,
78-
"an async construct yields a type which is itself awaitable",
79-
|db| {
80-
db.span_label(body.value.span, "outer async construct");
81-
db.span_label(return_expr_span, "awaitable value not awaited");
82-
db.span_suggestion(
83-
return_expr_span,
84-
"consider awaiting this value",
85-
format!("{}.await", snippet(cx, return_expr_span, "..")),
86-
Applicability::MaybeIncorrect,
87-
);
88-
},
108+
"consider awaiting this value",
109+
format!("{}.await", snippet(cx, return_expr_span, "..")),
110+
Applicability::MaybeIncorrect,
89111
);
90-
}
91-
}
112+
},
113+
);
92114
}
93115
}
94116
}

‎src/tools/clippy/clippy_lints/src/redundant_closure_call.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use clippy_utils::sugg::Sugg;
55
use rustc_errors::Applicability;
66
use rustc_hir as hir;
77
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
8-
use rustc_hir::{intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Node};
8+
use rustc_hir::{
9+
intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, Node,
10+
};
911
use rustc_lint::{LateContext, LateLintPass};
1012
use rustc_middle::hir::nested_filter;
1113
use rustc_middle::lint::in_external_macro;
@@ -166,10 +168,22 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
166168
if coroutine_kind.is_async()
167169
&& let hir::ExprKind::Closure(closure) = body.kind
168170
{
169-
let async_closure_body = cx.tcx.hir().body(closure.body);
171+
// Like `async fn`, async closures are wrapped in an additional block
172+
// to move all of the closure's arguments into the future.
173+
174+
let async_closure_body = cx.tcx.hir().body(closure.body).value;
175+
let ExprKind::Block(block, _) = async_closure_body.kind else {
176+
return;
177+
};
178+
let Some(block_expr) = block.expr else {
179+
return;
180+
};
181+
let ExprKind::DropTemps(body_expr) = block_expr.kind else {
182+
return;
183+
};
170184

171185
// `async x` is a syntax error, so it becomes `async { x }`
172-
if !matches!(async_closure_body.value.kind, hir::ExprKind::Block(_, _)) {
186+
if !matches!(body_expr.kind, hir::ExprKind::Block(_, _)) {
173187
hint = hint.blockify();
174188
}
175189

‎src/tools/clippy/tests/ui/author/blocks.stdout

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ if let ExprKind::Closure { capture_clause: CaptureBy::Value { .. }, fn_decl: fn_
4848
&& expr2 = &cx.tcx.hir().body(body_id1).value
4949
&& let ExprKind::Block(block, None) = expr2.kind
5050
&& block.stmts.is_empty()
51-
&& block.expr.is_none()
51+
&& let Some(trailing_expr) = block.expr
52+
&& let ExprKind::DropTemps(expr3) = trailing_expr.kind
53+
&& let ExprKind::Block(block1, None) = expr3.kind
54+
&& block1.stmts.is_empty()
55+
&& block1.expr.is_none()
5256
{
5357
// report your lint here
5458
}

‎tests/ui/async-await/async-borrowck-escaping-closure-error.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// edition:2018
2+
// check-pass
3+
24
#![feature(async_closure)]
35
fn foo() -> Box<dyn std::future::Future<Output = u32>> {
46
let x = 0u32;
57
Box::new((async || x)())
6-
//~^ ERROR E0373
78
}
89

910
fn main() {

‎tests/ui/async-await/async-borrowck-escaping-closure-error.stderr

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// edition:2018
2+
// check-pass
23

34
#![feature(async_closure)]
45

56
fn main() {
67
let _ = async |x: u8| {};
7-
//~^ ERROR `async` non-`move` closures with parameters are not currently supported
88
}

‎tests/ui/async-await/no-params-non-move-async-closure.stderr

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.