From 3851fe0f53269c9af374af5ad3b92f2c0f04f4c7 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 25 Jul 2024 14:02:33 -0400 Subject: [PATCH] Make coroutine-closures possible to be cloned --- compiler/rustc_mir_transform/src/shim.rs | 3 +++ .../src/solve/assembly/structural_traits.rs | 5 +++- .../src/traits/select/mod.rs | 17 +++++++++++-- .../async-closures/clone-closure.rs | 24 +++++++++++++++++++ .../async-closures/clone-closure.run.stdout | 2 ++ .../move-consuming-capture.stderr | 9 +++++++ ...thout-precise-captures-we-are-powerless.rs | 2 +- ...t-precise-captures-we-are-powerless.stderr | 23 +++++------------- 8 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 tests/ui/async-await/async-closures/clone-closure.rs create mode 100644 tests/ui/async-await/async-closures/clone-closure.run.stdout diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index 6835a39cf3624..50fe33b5e4fa1 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -434,6 +434,9 @@ fn build_clone_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) - match self_ty.kind() { ty::FnDef(..) | ty::FnPtr(_) => builder.copy_shim(), ty::Closure(_, args) => builder.tuple_like_shim(dest, src, args.as_closure().upvar_tys()), + ty::CoroutineClosure(_, args) => { + builder.tuple_like_shim(dest, src, args.as_coroutine_closure().upvar_tys()) + } ty::Tuple(..) => builder.tuple_like_shim(dest, src, self_ty.tuple_fields()), ty::Coroutine(coroutine_def_id, args) => { assert_eq!(tcx.coroutine_movability(*coroutine_def_id), hir::Movability::Movable); diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 7df14e81ab5d4..bd4a754881c3f 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -216,7 +216,10 @@ where // impl Copy/Clone for Closure where Self::TupledUpvars: Copy/Clone ty::Closure(_, args) => Ok(vec![ty::Binder::dummy(args.as_closure().tupled_upvars_ty())]), - ty::CoroutineClosure(..) => Err(NoSolution), + // impl Copy/Clone for CoroutineClosure where Self::TupledUpvars: Copy/Clone + ty::CoroutineClosure(_, args) => { + Ok(vec![ty::Binder::dummy(args.as_coroutine_closure().tupled_upvars_ty())]) + } // only when `coroutine_clone` is enabled and the coroutine is movable // impl Copy/Clone for Coroutine where T: Copy/Clone forall T in (upvars, witnesses) diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index c007cd5314a86..699c05466bd5a 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -2262,8 +2262,21 @@ impl<'tcx> SelectionContext<'_, 'tcx> { } } - // FIXME(async_closures): These are never clone, for now. - ty::CoroutineClosure(_, _) => None, + ty::CoroutineClosure(_, args) => { + // (*) binder moved here + let ty = self.infcx.shallow_resolve(args.as_coroutine_closure().tupled_upvars_ty()); + if let ty::Infer(ty::TyVar(_)) = ty.kind() { + // Not yet resolved. + Ambiguous + } else { + Where( + obligation + .predicate + .rebind(args.as_coroutine_closure().upvar_tys().to_vec()), + ) + } + } + // `Copy` and `Clone` are automatically implemented for an anonymous adt // if all of its fields are `Copy` and `Clone` ty::Adt(adt, args) if adt.is_anonymous() => { diff --git a/tests/ui/async-await/async-closures/clone-closure.rs b/tests/ui/async-await/async-closures/clone-closure.rs new file mode 100644 index 0000000000000..807897e3e0314 --- /dev/null +++ b/tests/ui/async-await/async-closures/clone-closure.rs @@ -0,0 +1,24 @@ +//@ aux-build:block-on.rs +//@ edition:2021 +//@ run-pass +//@ check-run-results + +#![feature(async_closure)] + +extern crate block_on; + +async fn for_each(f: impl async FnOnce(&str) + Clone) { + f.clone()("world").await; + f.clone()("world2").await; +} + +fn main() { + block_on::block_on(async_main()); +} + +async fn async_main() { + let x = String::from("Hello,"); + for_each(async move |s| { + println!("{x} {s}"); + }).await; +} diff --git a/tests/ui/async-await/async-closures/clone-closure.run.stdout b/tests/ui/async-await/async-closures/clone-closure.run.stdout new file mode 100644 index 0000000000000..0cfcf1923da88 --- /dev/null +++ b/tests/ui/async-await/async-closures/clone-closure.run.stdout @@ -0,0 +1,2 @@ +Hello, world +Hello, world2 diff --git a/tests/ui/async-await/async-closures/move-consuming-capture.stderr b/tests/ui/async-await/async-closures/move-consuming-capture.stderr index 45c1eac8f8f56..4ce71ec49d612 100644 --- a/tests/ui/async-await/async-closures/move-consuming-capture.stderr +++ b/tests/ui/async-await/async-closures/move-consuming-capture.stderr @@ -11,6 +11,15 @@ LL | x().await; | note: `async_call_once` takes ownership of the receiver `self`, which moves `x` --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL +help: you could `clone` the value and consume it, if the `NoCopy: Clone` trait bound could be satisfied + | +LL | x.clone()().await; + | ++++++++ +help: consider annotating `NoCopy` with `#[derive(Clone)]` + | +LL + #[derive(Clone)] +LL | struct NoCopy; + | error: aborting due to 1 previous error diff --git a/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs index 17681161e20ed..18f16ca4b2d30 100644 --- a/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs +++ b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs @@ -19,7 +19,7 @@ fn simple<'a>(x: &'a i32) { let c = async move || { println!("{}", *x); }; outlives::<'a>(c()); //~ ERROR `c` does not live long enough - outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c` + outlives::<'a>(call_once(c)); } struct S<'a>(&'a i32); diff --git a/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr index 569028934cbac..ed32a53e80728 100644 --- a/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr +++ b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr @@ -29,22 +29,6 @@ LL | outlives::<'a>(call_once(c)); LL | } | - `c` dropped here while still borrowed -error[E0505]: cannot move out of `c` because it is borrowed - --> $DIR/without-precise-captures-we-are-powerless.rs:22:30 - | -LL | fn simple<'a>(x: &'a i32) { - | -- lifetime `'a` defined here -... -LL | let c = async move || { println!("{}", *x); }; - | - binding `c` declared here -LL | outlives::<'a>(c()); - | --- - | | - | borrow of `c` occurs here - | argument requires that `c` is borrowed for `'a` -LL | outlives::<'a>(call_once(c)); - | ^ move out of `c` occurs here - error[E0597]: `x` does not live long enough --> $DIR/without-precise-captures-we-are-powerless.rs:28:13 | @@ -72,6 +56,11 @@ LL | outlives::<'a>(call_once(c)); LL | LL | let c = async move || { println!("{}", *x.0); }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move out of `x` occurs here + | +help: consider cloning the value if the performance cost is acceptable + | +LL | let c = async || { println!("{}", *x.0); }.clone(); + | ++++++++ error[E0597]: `c` does not live long enough --> $DIR/without-precise-captures-we-are-powerless.rs:33:20 @@ -146,7 +135,7 @@ LL | // outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out w LL | } | - `c` dropped here while still borrowed -error: aborting due to 10 previous errors +error: aborting due to 9 previous errors Some errors have detailed explanations: E0505, E0597, E0621. For more information about an error, try `rustc --explain E0505`.