Skip to content

Commit ac53f53

Browse files
authored
Unrolled build for rust-lang#123350
Rollup merge of rust-lang#123350 - compiler-errors:async-closure-by-move, r=oli-obk Actually use the inferred `ClosureKind` from signature inference in coroutine-closures A follow-up to rust-lang#123349, which fixes another subtle bug: We were not taking into account the async closure kind we infer during closure signature inference. When I pass a closure directly to an arg like `fn(x: impl async FnOnce())`, that should have the side-effect of artificially restricting the kind of the async closure to `ClosureKind::FnOnce`. We weren't doing this -- that's a quick fix; however, it uncovers a second, more subtle bug with the way that `move`, async closures, and `FnOnce` interact. Specifically, when we have an async closure like: ``` let x = Struct; let c = infer_as_fnonce(async move || { println!("{x:?}"); } ``` The outer closure captures `x` by move, but the inner coroutine still immutably borrows `x` from the outer closure. Since we've forced the closure to by `async FnOnce()`, we can't actually *do* a self borrow, since the signature of `AsyncFnOnce::call_once` doesn't have a borrowed lifetime. This means that all `async move` closures that are constrained to `FnOnce` will fail borrowck. We can fix that by detecting this case specifically, and making the *inner* async closure `move` as well. This is always beneficial to closure analysis, since if we have an `async FnOnce()` that's `move`, there's no reason to ever borrow anything, so `move` isn't artificially restrictive.
2 parents 5958f5e + 55e4661 commit ac53f53

File tree

9 files changed

+182
-55
lines changed

9 files changed

+182
-55
lines changed

compiler/rustc_hir_typeck/src/closure.rs

+23-9
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
227227
kind: TypeVariableOriginKind::ClosureSynthetic,
228228
span: expr_span,
229229
});
230-
let closure_kind_ty = self.next_ty_var(TypeVariableOrigin {
231-
// FIXME(eddyb) distinguish closure kind inference variables from the rest.
232-
kind: TypeVariableOriginKind::ClosureSynthetic,
233-
span: expr_span,
234-
});
230+
231+
let closure_kind_ty = match expected_kind {
232+
Some(kind) => Ty::from_closure_kind(tcx, kind),
233+
234+
// Create a type variable (for now) to represent the closure kind.
235+
// It will be unified during the upvar inference phase (`upvar.rs`)
236+
None => self.next_ty_var(TypeVariableOrigin {
237+
kind: TypeVariableOriginKind::ClosureSynthetic,
238+
span: expr_span,
239+
}),
240+
};
241+
235242
let coroutine_captures_by_ref_ty = self.next_ty_var(TypeVariableOrigin {
236243
kind: TypeVariableOriginKind::ClosureSynthetic,
237244
span: expr_span,
@@ -262,10 +269,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
262269
},
263270
);
264271

265-
let coroutine_kind_ty = self.next_ty_var(TypeVariableOrigin {
266-
kind: TypeVariableOriginKind::ClosureSynthetic,
267-
span: expr_span,
268-
});
272+
let coroutine_kind_ty = match expected_kind {
273+
Some(kind) => Ty::from_coroutine_closure_kind(tcx, kind),
274+
275+
// Create a type variable (for now) to represent the closure kind.
276+
// It will be unified during the upvar inference phase (`upvar.rs`)
277+
None => self.next_ty_var(TypeVariableOrigin {
278+
kind: TypeVariableOriginKind::ClosureSynthetic,
279+
span: expr_span,
280+
}),
281+
};
282+
269283
let coroutine_upvars_ty = self.next_ty_var(TypeVariableOrigin {
270284
kind: TypeVariableOriginKind::ClosureSynthetic,
271285
span: expr_span,

compiler/rustc_hir_typeck/src/upvar.rs

+39-11
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
166166
span: Span,
167167
body_id: hir::BodyId,
168168
body: &'tcx hir::Body<'tcx>,
169-
capture_clause: hir::CaptureBy,
169+
mut capture_clause: hir::CaptureBy,
170170
) {
171171
// Extract the type of the closure.
172172
let ty = self.node_ty(closure_hir_id);
@@ -259,6 +259,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
259259
)
260260
.consume_body(body);
261261

262+
// If a coroutine is comes from a coroutine-closure that is `move`, but
263+
// the coroutine-closure was inferred to be `FnOnce` during signature
264+
// inference, then it's still possible that we try to borrow upvars from
265+
// the coroutine-closure because they are not used by the coroutine body
266+
// in a way that forces a move.
267+
//
268+
// This would lead to an impossible to satisfy situation, since `AsyncFnOnce`
269+
// coroutine bodies can't borrow from their parent closure. To fix this,
270+
// we force the inner coroutine to also be `move`. This only matters for
271+
// coroutine-closures that are `move` since otherwise they themselves will
272+
// be borrowing from the outer environment, so there's no self-borrows occuring.
273+
if let UpvarArgs::Coroutine(..) = args
274+
&& let hir::CoroutineKind::Desugared(_, hir::CoroutineSource::Closure) =
275+
self.tcx.coroutine_kind(closure_def_id).expect("coroutine should have kind")
276+
&& let parent_hir_id =
277+
self.tcx.local_def_id_to_hir_id(self.tcx.local_parent(closure_def_id))
278+
&& let parent_ty = self.node_ty(parent_hir_id)
279+
&& let Some(ty::ClosureKind::FnOnce) = self.closure_kind(parent_ty)
280+
{
281+
capture_clause = self.tcx.hir_node(parent_hir_id).expect_closure().capture_clause;
282+
}
283+
262284
debug!(
263285
"For closure={:?}, capture_information={:#?}",
264286
closure_def_id, delegate.capture_information
@@ -399,16 +421,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
399421
);
400422

401423
// Additionally, we can now constrain the coroutine's kind type.
402-
let ty::Coroutine(_, coroutine_args) =
403-
*self.typeck_results.borrow().expr_ty(body.value).kind()
404-
else {
405-
bug!();
406-
};
407-
self.demand_eqtype(
408-
span,
409-
coroutine_args.as_coroutine().kind_ty(),
410-
Ty::from_coroutine_closure_kind(self.tcx, closure_kind),
411-
);
424+
//
425+
// We only do this if `infer_kind`, because if we have constrained
426+
// the kind from closure signature inference, the kind inferred
427+
// for the inner coroutine may actually be more restrictive.
428+
if infer_kind {
429+
let ty::Coroutine(_, coroutine_args) =
430+
*self.typeck_results.borrow().expr_ty(body.value).kind()
431+
else {
432+
bug!();
433+
};
434+
self.demand_eqtype(
435+
span,
436+
coroutine_args.as_coroutine().kind_ty(),
437+
Ty::from_coroutine_closure_kind(self.tcx, closure_kind),
438+
);
439+
}
412440
}
413441

414442
self.log_closure_min_capture_info(closure_def_id, span);

compiler/rustc_mir_transform/src/coroutine/by_move_body.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,17 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
9191
return;
9292
}
9393

94-
let ty::Coroutine(_, coroutine_args) = *coroutine_ty.kind() else { bug!("{body:#?}") };
95-
// We don't need to generate a by-move coroutine if the kind of the coroutine is
96-
// already `FnOnce` -- that means that any upvars that the closure consumes have
97-
// already been taken by-value.
98-
let coroutine_kind = coroutine_args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap();
99-
if coroutine_kind == ty::ClosureKind::FnOnce {
94+
// We don't need to generate a by-move coroutine if the coroutine body was
95+
// produced by the `CoroutineKindShim`, since it's already by-move.
96+
if matches!(body.source.instance, ty::InstanceDef::CoroutineKindShim { .. }) {
10097
return;
10198
}
10299

100+
let ty::Coroutine(_, args) = *coroutine_ty.kind() else { bug!("{body:#?}") };
101+
let args = args.as_coroutine();
102+
103+
let coroutine_kind = args.kind_ty().to_opt_closure_kind().unwrap();
104+
103105
let parent_def_id = tcx.local_parent(coroutine_def_id);
104106
let ty::CoroutineClosure(_, parent_args) =
105107
*tcx.type_of(parent_def_id).instantiate_identity().kind()
@@ -128,6 +130,12 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
128130
// the outer closure body -- we need to change the coroutine to take the
129131
// upvar by value.
130132
if coroutine_capture.is_by_ref() && !parent_capture.is_by_ref() {
133+
assert_ne!(
134+
coroutine_kind,
135+
ty::ClosureKind::FnOnce,
136+
"`FnOnce` coroutine-closures return coroutines that capture from \
137+
their body; it will always result in a borrowck error!"
138+
);
131139
by_ref_fields.insert(FieldIdx::from_usize(num_args + idx));
132140
}
133141

src/tools/miri/tests/pass/async-closure-captures.rs

+34
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,38 @@ async fn async_main() {
8888
};
8989
call_once(c).await;
9090
}
91+
92+
fn force_fnonce<T>(f: impl async FnOnce() -> T) -> impl async FnOnce() -> T {
93+
f
94+
}
95+
96+
// Capture something with `move`, but infer to `AsyncFnOnce`
97+
{
98+
let x = Hello(6);
99+
let c = force_fnonce(async move || {
100+
println!("{x:?}");
101+
});
102+
call_once(c).await;
103+
104+
let x = &Hello(7);
105+
let c = force_fnonce(async move || {
106+
println!("{x:?}");
107+
});
108+
call_once(c).await;
109+
}
110+
111+
// Capture something by-ref, but infer to `AsyncFnOnce`
112+
{
113+
let x = Hello(8);
114+
let c = force_fnonce(async || {
115+
println!("{x:?}");
116+
});
117+
call_once(c).await;
118+
119+
let x = &Hello(9);
120+
let c = force_fnonce(async || {
121+
println!("{x:?}");
122+
});
123+
call_once(c).await;
124+
}
91125
}

src/tools/miri/tests/pass/async-closure-captures.stdout

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ Hello(3)
88
Hello(4)
99
Hello(4)
1010
Hello(5)
11+
Hello(6)
12+
Hello(7)
13+
Hello(8)
14+
Hello(9)

tests/ui/async-await/async-closures/captures.rs

+34
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,38 @@ async fn async_main() {
7979
};
8080
call_once(c).await;
8181
}
82+
83+
fn force_fnonce<T>(f: impl async FnOnce() -> T) -> impl async FnOnce() -> T {
84+
f
85+
}
86+
87+
// Capture something with `move`, but infer to `AsyncFnOnce`
88+
{
89+
let x = Hello(6);
90+
let c = force_fnonce(async move || {
91+
println!("{x:?}");
92+
});
93+
call_once(c).await;
94+
95+
let x = &Hello(7);
96+
let c = force_fnonce(async move || {
97+
println!("{x:?}");
98+
});
99+
call_once(c).await;
100+
}
101+
102+
// Capture something by-ref, but infer to `AsyncFnOnce`
103+
{
104+
let x = Hello(8);
105+
let c = force_fnonce(async || {
106+
println!("{x:?}");
107+
});
108+
call_once(c).await;
109+
110+
let x = &Hello(9);
111+
let c = force_fnonce(async || {
112+
println!("{x:?}");
113+
});
114+
call_once(c).await;
115+
}
82116
}

tests/ui/async-await/async-closures/captures.run.stdout

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ Hello(3)
88
Hello(4)
99
Hello(4)
1010
Hello(5)
11+
Hello(6)
12+
Hello(7)
13+
Hello(8)
14+
Hello(9)

tests/ui/async-await/async-closures/wrong-fn-kind.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22

33
#![feature(async_closure)]
44

5-
fn main() {
6-
fn needs_async_fn(_: impl async Fn()) {}
5+
fn needs_async_fn(_: impl async Fn()) {}
76

7+
fn a() {
88
let mut x = 1;
99
needs_async_fn(async || {
10-
//~^ ERROR expected a closure that implements the `async Fn` trait, but this closure only implements `async FnMut`
10+
//~^ ERROR cannot borrow `x` as mutable, as it is a captured variable in a `Fn` closure
1111
x += 1;
1212
});
13+
}
1314

15+
fn b() {
1416
let x = String::new();
1517
needs_async_fn(move || async move {
1618
//~^ ERROR expected a closure that implements the `async Fn` trait, but this closure only implements `async FnOnce`
1719
println!("{x}");
1820
});
1921
}
22+
23+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,5 @@
1-
error[E0525]: expected a closure that implements the `async Fn` trait, but this closure only implements `async FnMut`
2-
--> $DIR/wrong-fn-kind.rs:9:20
3-
|
4-
LL | needs_async_fn(async || {
5-
| -------------- -^^^^^^^
6-
| | |
7-
| _____|______________this closure implements `async FnMut`, not `async Fn`
8-
| | |
9-
| | required by a bound introduced by this call
10-
LL | |
11-
LL | | x += 1;
12-
| | - closure is `async FnMut` because it mutates the variable `x` here
13-
LL | | });
14-
| |_____- the requirement to implement `async Fn` derives from here
15-
|
16-
note: required by a bound in `needs_async_fn`
17-
--> $DIR/wrong-fn-kind.rs:6:31
18-
|
19-
LL | fn needs_async_fn(_: impl async Fn()) {}
20-
| ^^^^^^^^^^ required by this bound in `needs_async_fn`
21-
221
error[E0525]: expected a closure that implements the `async Fn` trait, but this closure only implements `async FnOnce`
23-
--> $DIR/wrong-fn-kind.rs:15:20
2+
--> $DIR/wrong-fn-kind.rs:17:20
243
|
254
LL | needs_async_fn(move || async move {
265
| -------------- -^^^^^^
@@ -35,11 +14,29 @@ LL | | });
3514
| |_____- the requirement to implement `async Fn` derives from here
3615
|
3716
note: required by a bound in `needs_async_fn`
38-
--> $DIR/wrong-fn-kind.rs:6:31
17+
--> $DIR/wrong-fn-kind.rs:5:27
18+
|
19+
LL | fn needs_async_fn(_: impl async Fn()) {}
20+
| ^^^^^^^^^^ required by this bound in `needs_async_fn`
21+
22+
error[E0596]: cannot borrow `x` as mutable, as it is a captured variable in a `Fn` closure
23+
--> $DIR/wrong-fn-kind.rs:9:29
3924
|
40-
LL | fn needs_async_fn(_: impl async Fn()) {}
41-
| ^^^^^^^^^^ required by this bound in `needs_async_fn`
25+
LL | fn needs_async_fn(_: impl async Fn()) {}
26+
| --------------- change this to accept `FnMut` instead of `Fn`
27+
...
28+
LL | needs_async_fn(async || {
29+
| _____--------------_--------_^
30+
| | | |
31+
| | | in this closure
32+
| | expects `Fn` instead of `FnMut`
33+
LL | |
34+
LL | | x += 1;
35+
| | - mutable borrow occurs due to use of `x` in closure
36+
LL | | });
37+
| |_____^ cannot borrow as mutable
4238

4339
error: aborting due to 2 previous errors
4440

45-
For more information about this error, try `rustc --explain E0525`.
41+
Some errors have detailed explanations: E0525, E0596.
42+
For more information about an error, try `rustc --explain E0525`.

0 commit comments

Comments
 (0)