Skip to content

Commit f1410f8

Browse files
Check coroutine upvars and in dtorck constraint
1 parent 6781992 commit f1410f8

File tree

3 files changed

+88
-23
lines changed

3 files changed

+88
-23
lines changed

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

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -319,36 +319,56 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
319319
}
320320

321321
ty::Coroutine(_, args) => {
322-
// rust-lang/rust#49918: types can be constructed, stored
323-
// in the interior, and sit idle when coroutine yields
324-
// (and is subsequently dropped).
322+
// rust-lang/rust#49918: Locals can be stored across await points in the coroutine,
323+
// called interior/witness types. Since we do not compute these witnesses until after
324+
// building MIR, we consider all coroutines to unconditionally require a drop during
325+
// MIR building. However, considering the coroutine to unconditionally require a drop
326+
// here may unnecessarily require its upvars' regions to be live when they don't need
327+
// to be, leading to borrowck errors: <https://github.com/rust-lang/rust/issues/116242>.
325328
//
326-
// It would be nice to descend into interior of a
327-
// coroutine to determine what effects dropping it might
328-
// have (by looking at any drop effects associated with
329-
// its interior).
329+
// Here, we implement a more precise approximation for the coroutine's dtorck constraint
330+
// by considering whether any of the interior types needs drop. Note that this is still
331+
// an approximation because the coroutine interior has its regions erased, so we must add
332+
// *all* of the upvars to live types set if we find that *any* interior type needs drop.
333+
// This is because any of the regions captured in the upvars may be stored in the interior,
334+
// which then has its regions replaced by a binder (conceptually erasing the regions),
335+
// so there's no way to enforce that the precise region in the interior type is live
336+
// since we've lost that information by this point.
330337
//
331-
// However, the interior's representation uses things like
332-
// CoroutineWitness that explicitly assume they are not
333-
// traversed in such a manner. So instead, we will
334-
// simplify things for now by treating all coroutines as
335-
// if they were like trait objects, where its upvars must
336-
// all be alive for the coroutine's (potential)
337-
// destructor.
338+
// Note also that this check requires that the coroutine's upvars are use-live, since
339+
// a region from a type that does not have a destructor that was captured in an upvar
340+
// may flow into an interior type with a destructor. This is stronger than requiring
341+
// the upvars are drop-live.
338342
//
339-
// In particular, skipping over `_interior` is safe
340-
// because any side-effects from dropping `_interior` can
341-
// only take place through references with lifetimes
342-
// derived from lifetimes attached to the upvars and resume
343-
// argument, and we *do* incorporate those here.
343+
// For example, if we capture two upvar references `&'1 (), &'2 ()` and have some type
344+
// in the interior, `for<'r> { NeedsDrop<'r> }`, we have no way to tell whether the
345+
// region `'r` came from the `'1` or `'2` region, so we require both are live. This
346+
// could even be unnecessary if `'r` was actually a `'static` region or some region
347+
// local to the coroutine! That's why it's an approximation.
344348
let args = args.as_coroutine();
345349

346-
// While we conservatively assume that all coroutines require drop
347-
// to avoid query cycles during MIR building, we can check the actual
348-
// witness during borrowck to avoid unnecessary liveness constraints.
349-
if args.witness().needs_drop(tcx, tcx.erase_regions(typing_env)) {
350+
// Note that we don't care about whether the resume type has any drops since this is
351+
// redundant; there is no storage for the resume type, so if it is actually stored
352+
// in the interior, we'll already detect the need for a drop by checking the interior.
353+
let typing_env = tcx.erase_regions(typing_env);
354+
let needs_drop = args.witness().needs_drop(tcx, typing_env);
355+
if needs_drop {
350356
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
351357
constraints.outlives.push(args.resume_ty().into());
358+
} else {
359+
// Even if a witness type doesn't need a drop, we still require that
360+
// the upvars are drop-live. This is only needed if we aren't already
361+
// counting *all* of the upvars as use-live above.
362+
for ty in args.upvar_tys() {
363+
dtorck_constraint_for_ty_inner(
364+
tcx,
365+
typing_env,
366+
span,
367+
depth + 1,
368+
ty,
369+
constraints,
370+
);
371+
}
352372
}
353373
}
354374

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@ edition: 2018
2+
// Regression test for <https://github.com/rust-lang/rust/issues/144155>.
3+
4+
struct NeedsDrop<'a>(&'a Vec<i32>);
5+
6+
async fn await_point() {}
7+
8+
impl Drop for NeedsDrop<'_> {
9+
fn drop(&mut self) {}
10+
}
11+
12+
fn foo() {
13+
let v = vec![1, 2, 3];
14+
let x = NeedsDrop(&v);
15+
let c = async {
16+
std::future::ready(()).await;
17+
drop(x);
18+
};
19+
drop(v);
20+
//~^ ERROR cannot move out of `v` because it is borrowed
21+
}
22+
23+
fn main() {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error[E0505]: cannot move out of `v` because it is borrowed
2+
--> $DIR/drop-live-upvar.rs:19:10
3+
|
4+
LL | let v = vec![1, 2, 3];
5+
| - binding `v` declared here
6+
LL | let x = NeedsDrop(&v);
7+
| -- borrow of `v` occurs here
8+
...
9+
LL | drop(v);
10+
| ^ move out of `v` occurs here
11+
LL |
12+
LL | }
13+
| - borrow might be used here, when `c` is dropped and runs the destructor for coroutine
14+
|
15+
help: consider cloning the value if the performance cost is acceptable
16+
|
17+
LL | let x = NeedsDrop(&v.clone());
18+
| ++++++++
19+
20+
error: aborting due to 1 previous error
21+
22+
For more information about this error, try `rustc --explain E0505`.

0 commit comments

Comments
 (0)