Skip to content

Commit f2e14da

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

File tree

3 files changed

+72
-23
lines changed

3 files changed

+72
-23
lines changed

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

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -319,34 +319,38 @@ 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 the witness and upvar types need 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 the set of live types if we find that *any* interior type is live.
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).
330335
//
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.
336+
// Note also that we require that the coroutine's upvars are use-live, since a region
337+
// from a type that does not have a destructor that was captured in an upvar may flow
338+
// into an interior type with a destructor.
338339
//
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.
340+
// For example, if we capture two upvar references `&'1 (), &'2 ()` and have some type
341+
// in the interior, `for<'r> { NeedsDrop<'r> }`, we have no way to tell whether the
342+
// region `'r` came from the `'1` or `'2` region, so we require both are live. This
343+
// could even be unnecessary if `'r` was actually a `'static` region or some region
344+
// local to the coroutine! That's why it's an approximation.
344345
let args = args.as_coroutine();
345346

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)) {
347+
// Note that we don't care about whether the resume type has any drops since this is
348+
// redundant; there is no storage for the resume type, so if it is actually stored
349+
// in the interior, we'll already detect the need for a drop by checking the witness.
350+
let typing_env = tcx.erase_regions(typing_env);
351+
let needs_drop = args.witness().needs_drop(tcx, typing_env)
352+
|| args.upvar_tys().iter().any(|ty| ty.needs_drop(tcx, typing_env));
353+
if needs_drop {
350354
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
351355
constraints.outlives.push(args.resume_ty().into());
352356
}
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)