From 6320b9e072e5404b8905974b8f0268b6a441680b Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 18 Jul 2025 19:48:53 +0000 Subject: [PATCH 1/2] Check coroutine upvars and resume ty in dtorck constraint --- .../src/traits/query/dropck_outlives.rs | 11 ++++++--- tests/ui/async-await/drop-live-upvar.rs | 23 +++++++++++++++++++ tests/ui/async-await/drop-live-upvar.stderr | 22 ++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/ui/async-await/drop-live-upvar.rs create mode 100644 tests/ui/async-await/drop-live-upvar.stderr diff --git a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs index 38cfdcdc22d33..5a62f60c9e287 100644 --- a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs +++ b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs @@ -344,9 +344,14 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>( let args = args.as_coroutine(); // While we conservatively assume that all coroutines require drop - // to avoid query cycles during MIR building, we can check the actual - // witness during borrowck to avoid unnecessary liveness constraints. - if args.witness().needs_drop(tcx, tcx.erase_regions(typing_env)) { + // to avoid query cycles during MIR building, we can be more precise + // here and check the specific components of the coroutines. This + // includes the witness types, upvars, *and* the resume ty. + let typing_env = tcx.erase_regions(typing_env); + let needs_drop = args.witness().needs_drop(tcx, typing_env) + || args.upvar_tys().iter().any(|ty| ty.needs_drop(tcx, typing_env)) + || args.resume_ty().needs_drop(tcx, typing_env); + if needs_drop { constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from)); constraints.outlives.push(args.resume_ty().into()); } diff --git a/tests/ui/async-await/drop-live-upvar.rs b/tests/ui/async-await/drop-live-upvar.rs new file mode 100644 index 0000000000000..8e881f729b910 --- /dev/null +++ b/tests/ui/async-await/drop-live-upvar.rs @@ -0,0 +1,23 @@ +//@ edition: 2018 +// Regression test for . + +struct NeedsDrop<'a>(&'a Vec); + +async fn await_point() {} + +impl Drop for NeedsDrop<'_> { + fn drop(&mut self) {} +} + +fn foo() { + let v = vec![1, 2, 3]; + let x = NeedsDrop(&v); + let c = async { + std::future::ready(()).await; + drop(x); + }; + drop(v); + //~^ ERROR cannot move out of `v` because it is borrowed +} + +fn main() {} diff --git a/tests/ui/async-await/drop-live-upvar.stderr b/tests/ui/async-await/drop-live-upvar.stderr new file mode 100644 index 0000000000000..f804484536baf --- /dev/null +++ b/tests/ui/async-await/drop-live-upvar.stderr @@ -0,0 +1,22 @@ +error[E0505]: cannot move out of `v` because it is borrowed + --> $DIR/drop-live-upvar.rs:19:10 + | +LL | let v = vec![1, 2, 3]; + | - binding `v` declared here +LL | let x = NeedsDrop(&v); + | -- borrow of `v` occurs here +... +LL | drop(v); + | ^ move out of `v` occurs here +LL | +LL | } + | - borrow might be used here, when `c` is dropped and runs the destructor for coroutine + | +help: consider cloning the value if the performance cost is acceptable + | +LL | let x = NeedsDrop(&v.clone()); + | ++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0505`. From f22316ce36a5ca3f37a2b46135fe1451b4185d35 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 18 Jul 2025 20:26:37 +0000 Subject: [PATCH 2/2] Experimental impl of a better way of handling coroutines in dtorck constraint --- compiler/rustc_infer/src/infer/mod.rs | 6 +++-- .../src/traits/query/dropck_outlives.rs | 10 +++---- compiler/rustc_traits/src/dropck_outlives.rs | 1 - compiler/rustc_ty_utils/src/needs_drop.rs | 27 ++++++++++++------- compiler/rustc_type_ir/src/infer_ctxt.rs | 6 ++--- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 2d269e320b64d..320d68190cc27 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1267,10 +1267,12 @@ impl<'tcx> InferCtxt<'tcx> { // to handle them without proper canonicalization. This means we may cause cycle // errors and fail to reveal opaques while inside of bodies. We should rename this // function and require explicit comments on all use-sites in the future. - ty::TypingMode::Analysis { defining_opaque_types_and_generators: _ } - | ty::TypingMode::Borrowck { defining_opaque_types: _ } => { + ty::TypingMode::Analysis { defining_opaque_types_and_generators: _ } => { TypingMode::non_body_analysis() } + ty::TypingMode::Borrowck { defining_opaque_types: _ } => { + TypingMode::Borrowck { defining_opaque_types: ty::List::empty() } + } mode @ (ty::TypingMode::Coherence | ty::TypingMode::PostBorrowckAnalysis { .. } | ty::TypingMode::PostAnalysis) => mode, diff --git a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs index 5a62f60c9e287..1fc144518402b 100644 --- a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs +++ b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs @@ -345,12 +345,10 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>( // While we conservatively assume that all coroutines require drop // to avoid query cycles during MIR building, we can be more precise - // here and check the specific components of the coroutines. This - // includes the witness types, upvars, *and* the resume ty. - let typing_env = tcx.erase_regions(typing_env); - let needs_drop = args.witness().needs_drop(tcx, typing_env) - || args.upvar_tys().iter().any(|ty| ty.needs_drop(tcx, typing_env)) - || args.resume_ty().needs_drop(tcx, typing_env); + // here by re-checking in a `TypingMode::Borrowck` environment. This + // will recurse into the coroutine witness (which we can now access + // without cycles). + let needs_drop = ty.needs_drop(tcx, tcx.erase_regions(typing_env)); if needs_drop { constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from)); constraints.outlives.push(args.resume_ty().into()); diff --git a/compiler/rustc_traits/src/dropck_outlives.rs b/compiler/rustc_traits/src/dropck_outlives.rs index 5eddad39e2be2..cf7873975929c 100644 --- a/compiler/rustc_traits/src/dropck_outlives.rs +++ b/compiler/rustc_traits/src/dropck_outlives.rs @@ -23,7 +23,6 @@ fn dropck_outlives<'tcx>( canonical_goal: CanonicalDropckOutlivesGoal<'tcx>, ) -> Result<&'tcx Canonical<'tcx, QueryResponse<'tcx, DropckOutlivesResult<'tcx>>>, NoSolution> { debug!("dropck_outlives(goal={:#?})", canonical_goal); - tcx.infer_ctxt().enter_canonical_trait_query(&canonical_goal, |ocx, goal| { compute_dropck_outlives_inner(ocx, goal, DUMMY_SP) }) diff --git a/compiler/rustc_ty_utils/src/needs_drop.rs b/compiler/rustc_ty_utils/src/needs_drop.rs index c3b04c20f4b67..d4eb0ab9c2e5a 100644 --- a/compiler/rustc_ty_utils/src/needs_drop.rs +++ b/compiler/rustc_ty_utils/src/needs_drop.rs @@ -101,9 +101,6 @@ fn has_significant_drop_raw<'tcx>( struct NeedsDropTypes<'tcx, F> { tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, - /// Whether to reveal coroutine witnesses, this is set - /// to `false` unless we compute `needs_drop` for a coroutine witness. - reveal_coroutine_witnesses: bool, query_ty: Ty<'tcx>, seen_tys: FxHashSet>, /// A stack of types left to process, and the recursion depth when we @@ -131,7 +128,6 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> { Self { tcx, typing_env, - reveal_coroutine_witnesses: exhaustive, seen_tys, query_ty: ty, unchecked_tys: vec![(ty, 0)], @@ -196,15 +192,28 @@ where // need to be dropped, and only require the captured types to be live // if they do. ty::Coroutine(_, args) => { - if self.reveal_coroutine_witnesses { - queue_type(self, args.as_coroutine().witness()); - } else { - return Some(self.always_drop_component(ty)); + for arg in args.as_coroutine().upvar_tys() { + queue_type(self, arg); + } + queue_type(self, args.as_coroutine().resume_ty()); + match self.typing_env.typing_mode { + ty::TypingMode::Coherence => { + unreachable!("coherence should not be considering drops") + } + ty::TypingMode::Analysis { .. } => { + return Some( + self.always_drop_component(args.as_coroutine().witness()), + ); + } + ty::TypingMode::Borrowck { .. } + | ty::TypingMode::PostBorrowckAnalysis { .. } + | ty::TypingMode::PostAnalysis => { + queue_type(self, args.as_coroutine().witness()); + } } } ty::CoroutineWitness(def_id, args) => { if let Some(witness) = tcx.mir_coroutine_witnesses(def_id) { - self.reveal_coroutine_witnesses = true; for field_ty in &witness.field_tys { queue_type( self, diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index e86a2305e2334..f3fc10c6e78dd 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -117,11 +117,11 @@ impl TypingMode { } pub fn borrowck(cx: I, body_def_id: I::LocalDefId) -> TypingMode { - let defining_opaque_types = cx.opaque_types_defined_by(body_def_id); - if defining_opaque_types.is_empty() { + // N.B. we can only use an analysis env if there are no coroutines defined. + if cx.opaque_types_and_coroutines_defined_by(body_def_id).is_empty() { TypingMode::non_body_analysis() } else { - TypingMode::Borrowck { defining_opaque_types } + TypingMode::Borrowck { defining_opaque_types: cx.opaque_types_defined_by(body_def_id) } } }