Skip to content

Commit 1e91163

Browse files
authored
Unrolled build for rust-lang#128201
Rollup merge of rust-lang#128201 - compiler-errors:closure-clone, r=oli-obk Implement `Copy`/`Clone` for async closures We can do so in the same cases that regular closures do. For the purposes of cloning, coroutine-closures are actually precisely the same as regular closures, specifically in the aspect that `Clone` impls care about which is the upvars. The only difference b/w coroutine-closures and regular closures is the type that they *return*, but this type has not been *created* yet, so we don't really have a problem. IDK why I didn't add this impl initially -- I went back and forth a bit on the internal representation for coroutine-closures before settling on a design which largely models regular closures. Previous (not published) iterations of coroutine-closures used to be represented as a special (read: cursed) kind of coroutine, which would probably suffer from the pitfalls that coroutines have that oli mentioned below in rust-lang#128201 (comment). r? oli-obk
2 parents 7c2012d + 5a9959f commit 1e91163

File tree

11 files changed

+120
-21
lines changed

11 files changed

+120
-21
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1442,9 +1442,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, '_, 'infcx, 'tcx> {
14421442
// See `tests/ui/moves/needs-clone-through-deref.rs`
14431443
return false;
14441444
}
1445+
// We don't want to suggest `.clone()` in a move closure, since the value has already been captured.
14451446
if self.in_move_closure(expr) {
14461447
return false;
14471448
}
1449+
// We also don't want to suggest cloning a closure itself, since the value has already been captured.
1450+
if let hir::ExprKind::Closure(_) = expr.kind {
1451+
return false;
1452+
}
14481453
// Try to find predicates on *generic params* that would allow copying `ty`
14491454
let mut suggestion =
14501455
if let Some(symbol) = tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {

compiler/rustc_mir_transform/src/shim.rs

+3
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,9 @@ fn build_clone_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -
435435
match self_ty.kind() {
436436
ty::FnDef(..) | ty::FnPtr(_) => builder.copy_shim(),
437437
ty::Closure(_, args) => builder.tuple_like_shim(dest, src, args.as_closure().upvar_tys()),
438+
ty::CoroutineClosure(_, args) => {
439+
builder.tuple_like_shim(dest, src, args.as_coroutine_closure().upvar_tys())
440+
}
438441
ty::Tuple(..) => builder.tuple_like_shim(dest, src, self_ty.tuple_fields()),
439442
ty::Coroutine(coroutine_def_id, args) => {
440443
assert_eq!(tcx.coroutine_movability(*coroutine_def_id), hir::Movability::Movable);

compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,10 @@ where
217217
// impl Copy/Clone for Closure where Self::TupledUpvars: Copy/Clone
218218
ty::Closure(_, args) => Ok(vec![ty::Binder::dummy(args.as_closure().tupled_upvars_ty())]),
219219

220-
ty::CoroutineClosure(..) => Err(NoSolution),
220+
// impl Copy/Clone for CoroutineClosure where Self::TupledUpvars: Copy/Clone
221+
ty::CoroutineClosure(_, args) => {
222+
Ok(vec![ty::Binder::dummy(args.as_coroutine_closure().tupled_upvars_ty())])
223+
}
221224

222225
// only when `coroutine_clone` is enabled and the coroutine is movable
223226
// impl Copy/Clone for Coroutine where T: Copy/Clone forall T in (upvars, witnesses)

compiler/rustc_trait_selection/src/traits/select/mod.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -2262,8 +2262,21 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
22622262
}
22632263
}
22642264

2265-
// FIXME(async_closures): These are never clone, for now.
2266-
ty::CoroutineClosure(_, _) => None,
2265+
ty::CoroutineClosure(_, args) => {
2266+
// (*) binder moved here
2267+
let ty = self.infcx.shallow_resolve(args.as_coroutine_closure().tupled_upvars_ty());
2268+
if let ty::Infer(ty::TyVar(_)) = ty.kind() {
2269+
// Not yet resolved.
2270+
Ambiguous
2271+
} else {
2272+
Where(
2273+
obligation
2274+
.predicate
2275+
.rebind(args.as_coroutine_closure().upvar_tys().to_vec()),
2276+
)
2277+
}
2278+
}
2279+
22672280
// `Copy` and `Clone` are automatically implemented for an anonymous adt
22682281
// if all of its fields are `Copy` and `Clone`
22692282
ty::Adt(adt, args) if adt.is_anonymous() => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//@ aux-build:block-on.rs
2+
//@ edition:2021
3+
//@ run-pass
4+
//@ check-run-results
5+
6+
#![feature(async_closure)]
7+
8+
extern crate block_on;
9+
10+
async fn for_each(f: impl async FnOnce(&str) + Clone) {
11+
f.clone()("world").await;
12+
f.clone()("world2").await;
13+
}
14+
15+
fn main() {
16+
block_on::block_on(async_main());
17+
}
18+
19+
async fn async_main() {
20+
let x = String::from("Hello,");
21+
for_each(async move |s| {
22+
println!("{x} {s}");
23+
}).await;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello, world
2+
Hello, world2

tests/ui/async-await/async-closures/move-consuming-capture.stderr

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ LL | x().await;
1111
|
1212
note: `async_call_once` takes ownership of the receiver `self`, which moves `x`
1313
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
14+
help: you could `clone` the value and consume it, if the `NoCopy: Clone` trait bound could be satisfied
15+
|
16+
LL | x.clone()().await;
17+
| ++++++++
18+
help: consider annotating `NoCopy` with `#[derive(Clone)]`
19+
|
20+
LL + #[derive(Clone)]
21+
LL | struct NoCopy;
22+
|
1423

1524
error: aborting due to 1 previous error
1625

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@ edition: 2021
2+
3+
#![feature(async_closure)]
4+
5+
struct NotClonableArg;
6+
#[derive(Default)]
7+
struct NotClonableReturnType;
8+
9+
// Verify that the only components that we care about are the upvars, not the signature.
10+
fn we_are_okay_with_not_clonable_signature() {
11+
let x = async |x: NotClonableArg| -> NotClonableReturnType { Default::default() };
12+
x.clone(); // Okay
13+
}
14+
15+
#[derive(Debug)]
16+
struct NotClonableUpvar;
17+
18+
fn we_only_care_about_clonable_upvars() {
19+
let x = NotClonableUpvar;
20+
// Notably, this is clone because we capture `&x`.
21+
let yes_clone = async || {
22+
println!("{x:?}");
23+
};
24+
yes_clone.clone(); // Okay
25+
26+
let z = NotClonableUpvar;
27+
// However, this is not because the closure captures `z` by move.
28+
// (Even though the future that is lent out captures `z by ref!)
29+
let not_clone = async move || {
30+
println!("{z:?}");
31+
};
32+
not_clone.clone();
33+
//~^ ERROR the trait bound `NotClonableUpvar: Clone` is not satisfied
34+
}
35+
36+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0277]: the trait bound `NotClonableUpvar: Clone` is not satisfied in `{async closure@$DIR/not-clone-closure.rs:29:21: 29:34}`
2+
--> $DIR/not-clone-closure.rs:32:15
3+
|
4+
LL | not_clone.clone();
5+
| ^^^^^ within `{async closure@$DIR/not-clone-closure.rs:29:21: 29:34}`, the trait `Clone` is not implemented for `NotClonableUpvar`, which is required by `{async closure@$DIR/not-clone-closure.rs:29:21: 29:34}: Clone`
6+
|
7+
note: required because it's used within this closure
8+
--> $DIR/not-clone-closure.rs:29:21
9+
|
10+
LL | let not_clone = async move || {
11+
| ^^^^^^^^^^^^^
12+
help: consider annotating `NotClonableUpvar` with `#[derive(Clone)]`
13+
|
14+
LL + #[derive(Clone)]
15+
LL | struct NotClonableUpvar;
16+
|
17+
18+
error: aborting due to 1 previous error
19+
20+
For more information about this error, try `rustc --explain E0277`.

tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn simple<'a>(x: &'a i32) {
1919

2020
let c = async move || { println!("{}", *x); };
2121
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
22-
outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
22+
outlives::<'a>(call_once(c));
2323
}
2424

2525
struct S<'a>(&'a i32);

tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr

+1-17
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,6 @@ LL | outlives::<'a>(call_once(c));
2929
LL | }
3030
| - `c` dropped here while still borrowed
3131

32-
error[E0505]: cannot move out of `c` because it is borrowed
33-
--> $DIR/without-precise-captures-we-are-powerless.rs:22:30
34-
|
35-
LL | fn simple<'a>(x: &'a i32) {
36-
| -- lifetime `'a` defined here
37-
...
38-
LL | let c = async move || { println!("{}", *x); };
39-
| - binding `c` declared here
40-
LL | outlives::<'a>(c());
41-
| ---
42-
| |
43-
| borrow of `c` occurs here
44-
| argument requires that `c` is borrowed for `'a`
45-
LL | outlives::<'a>(call_once(c));
46-
| ^ move out of `c` occurs here
47-
4832
error[E0597]: `x` does not live long enough
4933
--> $DIR/without-precise-captures-we-are-powerless.rs:28:13
5034
|
@@ -146,7 +130,7 @@ LL | // outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out w
146130
LL | }
147131
| - `c` dropped here while still borrowed
148132

149-
error: aborting due to 10 previous errors
133+
error: aborting due to 9 previous errors
150134

151135
Some errors have detailed explanations: E0505, E0597, E0621.
152136
For more information about an error, try `rustc --explain E0505`.

0 commit comments

Comments
 (0)