Skip to content

Commit 2a177c2

Browse files
authored
Rollup merge of #128520 - compiler-errors:more-precisely-force-move, r=BoxyUwU
Skip over args when determining if async-closure's inner coroutine consumes its upvars #125306 implements a strategy for when we have an `async move ||` async-closure that is inferred to be `async FnOnce`, it will force the inner coroutine to also be `move`, since we cannot borrow any upvars from the parent async-closure (since `FnOnce` is not lending): https://github.com/rust-lang/rust/blob/8e86c9567154dc5a9ada15ab196d23eae2bd7d89/compiler/rustc_hir_typeck/src/upvar.rs#L211-L229 However, when this strategy was implemented, it reused the `ExprUseVisitor` data from visiting the whole coroutine, which includes additional statements due to `async`-specific argument desugaring: https://github.com/rust-lang/rust/blob/8e86c9567154dc5a9ada15ab196d23eae2bd7d89/compiler/rustc_ast_lowering/src/item.rs#L1197-L1228 Well, it turns out that we don't care about these argument desugaring parameters, because arguments to the async-closure are not the *async-closure*'s captures -- they exist for only one invocation of the closure, and they're always consumed by construction (see the argument desugaring above), so they will force the coroutine's inferred kind to `FnOnce`. (Unless they're `Copy`, because we never consider `Copy` types to be consumed): https://github.com/rust-lang/rust/blob/8e86c9567154dc5a9ada15ab196d23eae2bd7d89/compiler/rustc_hir_typeck/src/expr_use_visitor.rs#L60-L66 However, since we *were* visiting these arg exprs, this resulted in us too-aggressively applying `move` to the inner coroutine, resulting in regressions. For example, this PR fixes #128516. Fascinatingly, the note above about how we never consume `Copy` types is why this only regressed when the argument types weren't all `Copy`. I tried to leave some comments inline to make this more clear :)
2 parents 86e7875 + 5138586 commit 2a177c2

File tree

2 files changed

+70
-13
lines changed

2 files changed

+70
-13
lines changed

compiler/rustc_hir_typeck/src/upvar.rs

+53-13
Original file line numberDiff line numberDiff line change
@@ -219,19 +219,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
219219
// `async-await/async-closures/force-move-due-to-inferred-kind.rs`.
220220
//
221221
// 2. If the coroutine-closure is forced to be `FnOnce` due to the way it
222-
// uses its upvars, but not *all* upvars would force the closure to `FnOnce`.
222+
// uses its upvars (e.g. it consumes a non-copy value), but not *all* upvars
223+
// would force the closure to `FnOnce`.
223224
// See the test: `async-await/async-closures/force-move-due-to-actually-fnonce.rs`.
224225
//
225226
// This would lead to an impossible to satisfy situation, since `AsyncFnOnce`
226227
// coroutine bodies can't borrow from their parent closure. To fix this,
227228
// we force the inner coroutine to also be `move`. This only matters for
228229
// coroutine-closures that are `move` since otherwise they themselves will
229230
// be borrowing from the outer environment, so there's no self-borrows occuring.
230-
//
231-
// One *important* note is that we do a call to `process_collected_capture_information`
232-
// to eagerly test whether the coroutine would end up `FnOnce`, but we do this
233-
// *before* capturing all the closure args by-value below, since that would always
234-
// cause the analysis to return `FnOnce`.
235231
if let UpvarArgs::Coroutine(..) = args
236232
&& let hir::CoroutineKind::Desugared(_, hir::CoroutineSource::Closure) =
237233
self.tcx.coroutine_kind(closure_def_id).expect("coroutine should have kind")
@@ -246,19 +242,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
246242
capture_clause = hir::CaptureBy::Value { move_kw };
247243
}
248244
// (2.) The way that the closure uses its upvars means it's `FnOnce`.
249-
else if let (_, ty::ClosureKind::FnOnce, _) = self
250-
.process_collected_capture_information(
251-
capture_clause,
252-
&delegate.capture_information,
253-
)
254-
{
245+
else if self.coroutine_body_consumes_upvars(closure_def_id, body) {
255246
capture_clause = hir::CaptureBy::Value { move_kw };
256247
}
257248
}
258249

259250
// As noted in `lower_coroutine_body_with_moved_arguments`, we default the capture mode
260251
// to `ByRef` for the `async {}` block internal to async fns/closure. This means
261-
// that we would *not* be moving all of the parameters into the async block by default.
252+
// that we would *not* be moving all of the parameters into the async block in all cases.
253+
// For example, when one of the arguments is `Copy`, we turn a consuming use into a copy of
254+
// a reference, so for `async fn x(t: i32) {}`, we'd only take a reference to `t`.
262255
//
263256
// We force all of these arguments to be captured by move before we do expr use analysis.
264257
//
@@ -535,6 +528,53 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
535528
}
536529
}
537530

531+
/// Determines whether the body of the coroutine uses its upvars in a way that
532+
/// consumes (i.e. moves) the value, which would force the coroutine to `FnOnce`.
533+
/// In a more detailed comment above, we care whether this happens, since if
534+
/// this happens, we want to force the coroutine to move all of the upvars it
535+
/// would've borrowed from the parent coroutine-closure.
536+
///
537+
/// This only really makes sense to be called on the child coroutine of a
538+
/// coroutine-closure.
539+
fn coroutine_body_consumes_upvars(
540+
&self,
541+
coroutine_def_id: LocalDefId,
542+
body: &'tcx hir::Body<'tcx>,
543+
) -> bool {
544+
// This block contains argument capturing details. Since arguments
545+
// aren't upvars, we do not care about them for determining if the
546+
// coroutine body actually consumes its upvars.
547+
let hir::ExprKind::Block(&hir::Block { expr: Some(body), .. }, None) = body.value.kind
548+
else {
549+
bug!();
550+
};
551+
// Specifically, we only care about the *real* body of the coroutine.
552+
// We skip out into the drop-temps within the block of the body in order
553+
// to skip over the args of the desugaring.
554+
let hir::ExprKind::DropTemps(body) = body.kind else {
555+
bug!();
556+
};
557+
558+
let mut delegate = InferBorrowKind {
559+
closure_def_id: coroutine_def_id,
560+
capture_information: Default::default(),
561+
fake_reads: Default::default(),
562+
};
563+
564+
let _ = euv::ExprUseVisitor::new(
565+
&FnCtxt::new(self, self.tcx.param_env(coroutine_def_id), coroutine_def_id),
566+
&mut delegate,
567+
)
568+
.consume_expr(body);
569+
570+
let (_, kind, _) = self.process_collected_capture_information(
571+
hir::CaptureBy::Ref,
572+
&delegate.capture_information,
573+
);
574+
575+
matches!(kind, ty::ClosureKind::FnOnce)
576+
}
577+
538578
// Returns a list of `Ty`s for each upvar.
539579
fn final_upvar_tys(&self, closure_id: LocalDefId) -> Vec<Ty<'tcx>> {
540580
self.typeck_results
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ aux-build:block-on.rs
2+
//@ edition:2021
3+
//@ build-pass
4+
5+
#![feature(async_closure)]
6+
7+
extern crate block_on;
8+
9+
fn wrapper(f: impl Fn(String)) -> impl async Fn(String) {
10+
async move |s| f(s)
11+
}
12+
13+
fn main() {
14+
block_on::block_on(async {
15+
wrapper(|who| println!("Hello, {who}!"))(String::from("world")).await;
16+
});
17+
}

0 commit comments

Comments
 (0)