Rollup merge of #144156 - compiler-errors:dtorck-upvars, r=lcnr
Check coroutine upvars in dtorck constraint Fix rust-lang/rust#144155. This PR fixes an unsoundness where we were not considering coroutine upvars as drop-live if the coroutine interior types (witness types) had nothing which required drop. In the case that the coroutine does not have any interior types that need to be dropped, then we don't need to treat all of the upvars as use-live; instead, this PR uses the same logic as closures, and descends into the upvar types to collect anything that must be drop-live. The rest of this PR is reworking the comment to explain the behavior here. r? `@lcnr` or reassign 😸 --- Just some thoughts --- a proper fix for this whole situation would be to consider `TypingMode` in the `needs_drop` function, and just calling `coroutine_ty.needs_drop(tcx, typing_env)` in the dtorck constraint check. During MIR building, we should probably use a typing mode that stalls the local coroutines and considers them to be unconditionally drop, or perhaps just stall *all* coroutines in analysis mode. Then in borrowck mode, we can re-check `needs_drop` but descend into witness types properly. https://github.com/rust-lang/rust/pull/144158 implements this experimentally. This is a pretty involved fix, and conflicts with some in-flight changes (rust-lang/rust#144157) that I have around removing coroutine witnesses altogether. I'm happy to add a FIXME to rework this whole approach, but I don't want to block this quick fix since it's obviously more correct than the status-quo.
This commit is contained in:
@@ -319,39 +319,65 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
|
||||
}
|
||||
|
||||
ty::Coroutine(def_id, args) => {
|
||||
// rust-lang/rust#49918: types can be constructed, stored
|
||||
// in the interior, and sit idle when coroutine yields
|
||||
// (and is subsequently dropped).
|
||||
// rust-lang/rust#49918: Locals can be stored across await points in the coroutine,
|
||||
// called interior/witness types. Since we do not compute these witnesses until after
|
||||
// building MIR, we consider all coroutines to unconditionally require a drop during
|
||||
// MIR building. However, considering the coroutine to unconditionally require a drop
|
||||
// here may unnecessarily require its upvars' regions to be live when they don't need
|
||||
// to be, leading to borrowck errors: <https://github.com/rust-lang/rust/issues/116242>.
|
||||
//
|
||||
// It would be nice to descend into interior of a
|
||||
// coroutine to determine what effects dropping it might
|
||||
// have (by looking at any drop effects associated with
|
||||
// its interior).
|
||||
// Here, we implement a more precise approximation for the coroutine's dtorck constraint
|
||||
// by considering whether any of the interior types needs drop. Note that this is still
|
||||
// an approximation because the coroutine interior has its regions erased, so we must add
|
||||
// *all* of the upvars to live types set if we find that *any* interior type needs drop.
|
||||
// This is because any of the regions captured in the upvars may be stored in the interior,
|
||||
// which then has its regions replaced by a binder (conceptually erasing the regions),
|
||||
// so there's no way to enforce that the precise region in the interior type is live
|
||||
// since we've lost that information by this point.
|
||||
//
|
||||
// However, the interior's representation uses things like
|
||||
// CoroutineWitness that explicitly assume they are not
|
||||
// traversed in such a manner. So instead, we will
|
||||
// simplify things for now by treating all coroutines as
|
||||
// if they were like trait objects, where its upvars must
|
||||
// all be alive for the coroutine's (potential)
|
||||
// destructor.
|
||||
// Note also that this check requires that the coroutine's upvars are use-live, since
|
||||
// a region from a type that does not have a destructor that was captured in an upvar
|
||||
// may flow into an interior type with a destructor. This is stronger than requiring
|
||||
// the upvars are drop-live.
|
||||
//
|
||||
// In particular, skipping over `_interior` is safe
|
||||
// because any side-effects from dropping `_interior` can
|
||||
// only take place through references with lifetimes
|
||||
// derived from lifetimes attached to the upvars and resume
|
||||
// argument, and we *do* incorporate those here.
|
||||
// For example, if we capture two upvar references `&'1 (), &'2 ()` and have some type
|
||||
// in the interior, `for<'r> { NeedsDrop<'r> }`, we have no way to tell whether the
|
||||
// region `'r` came from the `'1` or `'2` region, so we require both are live. This
|
||||
// could even be unnecessary if `'r` was actually a `'static` region or some region
|
||||
// local to the coroutine! That's why it's an approximation.
|
||||
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.
|
||||
// Note that we don't care about whether the resume type has any drops since this is
|
||||
// redundant; there is no storage for the resume type, so if it is actually stored
|
||||
// in the interior, we'll already detect the need for a drop by checking the interior.
|
||||
let typing_env = tcx.erase_regions(typing_env);
|
||||
if tcx.mir_coroutine_witnesses(def_id).is_some_and(|witness| {
|
||||
let needs_drop = tcx.mir_coroutine_witnesses(def_id).is_some_and(|witness| {
|
||||
witness.field_tys.iter().any(|field| field.ty.needs_drop(tcx, typing_env))
|
||||
}) {
|
||||
});
|
||||
if needs_drop {
|
||||
// Pushing types directly to `constraints.outlives` is equivalent
|
||||
// to requiring them to be use-live, since if we were instead to
|
||||
// recurse on them like we do below, we only end up collecting the
|
||||
// types that are relevant for drop-liveness.
|
||||
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
|
||||
constraints.outlives.push(args.resume_ty().into());
|
||||
} else {
|
||||
// Even if a witness type doesn't need a drop, we still require that
|
||||
// the upvars are drop-live. This is only needed if we aren't already
|
||||
// counting *all* of the upvars as use-live above, since use-liveness
|
||||
// is a *stronger requirement* than drop-liveness. Recursing here
|
||||
// unconditionally would just be collecting duplicated types for no
|
||||
// reason.
|
||||
for ty in args.upvar_tys() {
|
||||
dtorck_constraint_for_ty_inner(
|
||||
tcx,
|
||||
typing_env,
|
||||
span,
|
||||
depth + 1,
|
||||
ty,
|
||||
constraints,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user