Skip to content

Commit a2f5f96

Browse files
committed
Auto merge of #117134 - lcnr:dropck_outlives-coroutine, r=compiler-errors
dropck_outlives check whether generator witness needs_drop see https://rust-lang.zulipchat.com/#narrow/stream/326866-t-types.2Fnominated/topic/.23116242.3A.20Code.20no.20longer.20compiles.20after.20-Zdrop-tracking-mir.20.E2.80.A6/near/398311627 for an explanation. Fixes #116242 (or well, the repro by `@jamuraa` in #116242 (comment)). I did not add a regression test as it depends on other crates. We do have 1 test going from fail to pass, showing the intended behavior. r? types
2 parents e8418e0 + dda5e32 commit a2f5f96

File tree

11 files changed

+110
-59
lines changed

11 files changed

+110
-59
lines changed

compiler/rustc_middle/src/ty/util.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1107,8 +1107,10 @@ impl<'tcx> Ty<'tcx> {
11071107
// This doesn't depend on regions, so try to minimize distinct
11081108
// query keys used.
11091109
// If normalization fails, we just use `query_ty`.
1110-
let query_ty =
1111-
tcx.try_normalize_erasing_regions(param_env, query_ty).unwrap_or(query_ty);
1110+
debug_assert!(!param_env.has_infer());
1111+
let query_ty = tcx
1112+
.try_normalize_erasing_regions(param_env, query_ty)
1113+
.unwrap_or_else(|_| tcx.erase_regions(query_ty));
11121114

11131115
tcx.needs_drop_raw(param_env.and(query_ty))
11141116
}
@@ -1297,7 +1299,6 @@ pub fn needs_drop_components<'tcx>(
12971299
| ty::FnDef(..)
12981300
| ty::FnPtr(_)
12991301
| ty::Char
1300-
| ty::CoroutineWitness(..)
13011302
| ty::RawPtr(_)
13021303
| ty::Ref(..)
13031304
| ty::Str => Ok(SmallVec::new()),
@@ -1337,7 +1338,8 @@ pub fn needs_drop_components<'tcx>(
13371338
| ty::Placeholder(..)
13381339
| ty::Infer(_)
13391340
| ty::Closure(..)
1340-
| ty::Coroutine(..) => Ok(smallvec![ty]),
1341+
| ty::Coroutine(..)
1342+
| ty::CoroutineWitness(..) => Ok(smallvec![ty]),
13411343
}
13421344
}
13431345

compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs

+22-13
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub fn compute_dropck_outlives_inner<'tcx>(
133133
result.overflows.len(),
134134
ty_stack.len()
135135
);
136-
dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?;
136+
dtorck_constraint_for_ty_inner(tcx, param_env, DUMMY_SP, depth, ty, &mut constraints)?;
137137

138138
// "outlives" represent types/regions that may be touched
139139
// by a destructor.
@@ -185,16 +185,15 @@ pub fn compute_dropck_outlives_inner<'tcx>(
185185

186186
/// Returns a set of constraints that needs to be satisfied in
187187
/// order for `ty` to be valid for destruction.
188+
#[instrument(level = "debug", skip(tcx, param_env, span, constraints))]
188189
pub fn dtorck_constraint_for_ty_inner<'tcx>(
189190
tcx: TyCtxt<'tcx>,
191+
param_env: ty::ParamEnv<'tcx>,
190192
span: Span,
191-
for_ty: Ty<'tcx>,
192193
depth: usize,
193194
ty: Ty<'tcx>,
194195
constraints: &mut DropckConstraint<'tcx>,
195196
) -> Result<(), NoSolution> {
196-
debug!("dtorck_constraint_for_ty_inner({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);
197-
198197
if !tcx.recursion_limit().value_within_limit(depth) {
199198
constraints.overflows.push(ty);
200199
return Ok(());
@@ -224,13 +223,13 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
224223
ty::Array(ety, _) | ty::Slice(ety) => {
225224
// single-element containers, behave like their element
226225
rustc_data_structures::stack::ensure_sufficient_stack(|| {
227-
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, *ety, constraints)
226+
dtorck_constraint_for_ty_inner(tcx, param_env, span, depth + 1, *ety, constraints)
228227
})?;
229228
}
230229

231230
ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
232231
for ty in tys.iter() {
233-
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
232+
dtorck_constraint_for_ty_inner(tcx, param_env, span, depth + 1, ty, constraints)?;
234233
}
235234
Ok::<_, NoSolution>(())
236235
})?,
@@ -249,7 +248,14 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
249248

250249
rustc_data_structures::stack::ensure_sufficient_stack(|| {
251250
for ty in args.as_closure().upvar_tys() {
252-
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
251+
dtorck_constraint_for_ty_inner(
252+
tcx,
253+
param_env,
254+
span,
255+
depth + 1,
256+
ty,
257+
constraints,
258+
)?;
253259
}
254260
Ok::<_, NoSolution>(())
255261
})?
@@ -278,8 +284,8 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
278284
// only take place through references with lifetimes
279285
// derived from lifetimes attached to the upvars and resume
280286
// argument, and we *do* incorporate those here.
281-
282-
if !args.as_coroutine().is_valid() {
287+
let args = args.as_coroutine();
288+
if !args.is_valid() {
283289
// By the time this code runs, all type variables ought to
284290
// be fully resolved.
285291
tcx.sess.delay_span_bug(
@@ -289,10 +295,13 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
289295
return Err(NoSolution);
290296
}
291297

292-
constraints
293-
.outlives
294-
.extend(args.as_coroutine().upvar_tys().iter().map(ty::GenericArg::from));
295-
constraints.outlives.push(args.as_coroutine().resume_ty().into());
298+
// While we conservatively assume that all coroutines require drop
299+
// to avoid query cycles during MIR building, we can check the actual
300+
// witness during borrowck to avoid unnecessary liveness constraints.
301+
if args.witness().needs_drop(tcx, tcx.erase_regions(param_env)) {
302+
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
303+
constraints.outlives.push(args.resume_ty().into());
304+
}
296305
}
297306

298307
ty::Adt(def, args) => {

compiler/rustc_traits/src/dropck_outlives.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub(crate) fn adt_dtorck_constraint(
3434
) -> Result<&DropckConstraint<'_>, NoSolution> {
3535
let def = tcx.adt_def(def_id);
3636
let span = tcx.def_span(def_id);
37+
let param_env = tcx.param_env(def_id);
3738
debug!("dtorck_constraint: {:?}", def);
3839

3940
if def.is_manually_drop() {
@@ -55,7 +56,7 @@ pub(crate) fn adt_dtorck_constraint(
5556
let mut result = DropckConstraint::empty();
5657
for field in def.all_fields() {
5758
let fty = tcx.type_of(field.did).instantiate_identity();
58-
dtorck_constraint_for_ty_inner(tcx, span, fty, 0, fty, &mut result)?;
59+
dtorck_constraint_for_ty_inner(tcx, param_env, span, 0, fty, &mut result)?;
5960
}
6061
result.outlives.extend(tcx.destructor_constraints(def));
6162
dedup_dtorck_constraint(&mut result);

compiler/rustc_ty_utils/src/needs_drop.rs

+29-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ fn has_significant_drop_raw<'tcx>(
6666
struct NeedsDropTypes<'tcx, F> {
6767
tcx: TyCtxt<'tcx>,
6868
param_env: ty::ParamEnv<'tcx>,
69+
// Whether to reveal coroutine witnesses, this is set
70+
// to `false` unless we compute `needs_drop` for a coroutine witness.
71+
reveal_coroutine_witnesses: bool,
6972
query_ty: Ty<'tcx>,
7073
seen_tys: FxHashSet<Ty<'tcx>>,
7174
/// A stack of types left to process, and the recursion depth when we
@@ -89,6 +92,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
8992
Self {
9093
tcx,
9194
param_env,
95+
reveal_coroutine_witnesses: false,
9296
seen_tys,
9397
query_ty: ty,
9498
unchecked_tys: vec![(ty, 0)],
@@ -133,8 +137,31 @@ where
133137
// The information required to determine whether a coroutine has drop is
134138
// computed on MIR, while this very method is used to build MIR.
135139
// To avoid cycles, we consider that coroutines always require drop.
136-
ty::Coroutine(..) => {
137-
return Some(Err(AlwaysRequiresDrop));
140+
//
141+
// HACK: Because we erase regions contained in the coroutine witness, we
142+
// have to conservatively assume that every region captured by the
143+
// coroutine has to be live when dropped. This results in a lot of
144+
// undesirable borrowck errors. During borrowck, we call `needs_drop`
145+
// for the coroutine witness and check whether any of the contained types
146+
// need to be dropped, and only require the captured types to be live
147+
// if they do.
148+
ty::Coroutine(_, args, _) => {
149+
if self.reveal_coroutine_witnesses {
150+
queue_type(self, args.as_coroutine().witness());
151+
} else {
152+
return Some(Err(AlwaysRequiresDrop));
153+
}
154+
}
155+
ty::CoroutineWitness(def_id, args) => {
156+
if let Some(witness) = tcx.mir_coroutine_witnesses(def_id) {
157+
self.reveal_coroutine_witnesses = true;
158+
for field_ty in &witness.field_tys {
159+
queue_type(
160+
self,
161+
EarlyBinder::bind(field_ty.ty).instantiate(tcx, args),
162+
);
163+
}
164+
}
138165
}
139166

140167
_ if component.is_copy_modulo_regions(tcx, self.param_env) => (),
@@ -191,7 +218,6 @@ where
191218
| ty::FnPtr(..)
192219
| ty::Tuple(_)
193220
| ty::Bound(..)
194-
| ty::CoroutineWitness(..)
195221
| ty::Never
196222
| ty::Infer(_)
197223
| ty::Error(_) => {

compiler/rustc_type_ir/src/ty_kind.rs

-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ pub enum TyKind<I: Interner> {
218218
/// the type of the coroutine, we convert them to higher ranked
219219
/// lifetimes bound by the witness itself.
220220
///
221-
/// This variant is only using when `drop_tracking_mir` is set.
222221
/// This contains the `DefId` and the `GenericArgsRef` of the coroutine.
223222
/// The actual witness types are computed on MIR by the `mir_coroutine_witnesses` query.
224223
///

tests/ui/coroutine/borrowing.stderr

+6-14
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
error[E0597]: `a` does not live long enough
22
--> $DIR/borrowing.rs:9:33
33
|
4+
LL | let _b = {
5+
| -- borrow later stored here
6+
LL | let a = 3;
47
LL | Pin::new(&mut || yield &a).resume(())
5-
| ----------^
6-
| | |
7-
| | borrowed value does not live long enough
8+
| -- ^ borrowed value does not live long enough
9+
| |
810
| value captured here by coroutine
9-
| a temporary with access to the borrow is created here ...
1011
LL |
1112
LL | };
12-
| -- ... and the borrow might be used here, when that temporary is dropped and runs the destructor for coroutine
13-
| |
14-
| `a` dropped here while still borrowed
15-
|
16-
= note: the temporary is part of an expression at the end of a block;
17-
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
18-
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
19-
|
20-
LL | let x = Pin::new(&mut || yield &a).resume(()); x
21-
| +++++++ +++
13+
| - `a` dropped here while still borrowed
2214

2315
error[E0597]: `a` does not live long enough
2416
--> $DIR/borrowing.rs:16:20
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// edition:2021
2+
// check-pass
23
#![feature(coroutines)]
34

45
fn main() {
56
let x = &mut ();
67
|| {
78
let _c = || yield *&mut *x;
89
|| _ = &mut *x;
9-
//~^ cannot borrow `*x` as mutable more than once at a time
1010
};
1111
}

tests/ui/coroutine/issue-110929-coroutine-conflict-error-ice.stderr

-18
This file was deleted.

tests/ui/coroutine/retain-resume-ref.stderr

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ error[E0499]: cannot borrow `thing` as mutable more than once at a time
44
LL | gen.as_mut().resume(&mut thing);
55
| ---------- first mutable borrow occurs here
66
LL | gen.as_mut().resume(&mut thing);
7-
| ^^^^^^^^^^ second mutable borrow occurs here
8-
LL |
9-
LL | }
10-
| - first borrow might be used here, when `gen` is dropped and runs the destructor for coroutine
7+
| ------ ^^^^^^^^^^ second mutable borrow occurs here
8+
| |
9+
| first borrow later used by call
1110

1211
error: aborting due to previous error
1312

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// check-pass
2+
// edition: 2021
3+
4+
// regression test for #116242.
5+
use std::future;
6+
7+
fn main() {
8+
let mut recv = future::ready(());
9+
let _combined_fut = async {
10+
let _ = || read(&mut recv);
11+
};
12+
13+
drop(recv);
14+
}
15+
16+
fn read<F: future::Future>(_: &mut F) -> F::Output {
17+
todo!()
18+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// check-pass
2+
// edition: 2021
3+
4+
// regression test found while working on #117134.
5+
use std::future;
6+
7+
fn main() {
8+
let mut recv = future::ready(());
9+
let _combined_fut = async {
10+
let _ = || read(&mut recv);
11+
};
12+
13+
let _uwu = (String::new(), _combined_fut);
14+
// Dropping a coroutine as part of a more complex
15+
// types should not add unnecessary liveness
16+
// constraints.
17+
18+
drop(recv);
19+
}
20+
21+
fn read<F: future::Future>(_: &mut F) -> F::Output {
22+
todo!()
23+
}

0 commit comments

Comments
 (0)