Skip to content

Commit d0f3f08

Browse files
authored
Unrolled build for #145194
Rollup merge of #145194 - compiler-errors:coro-witness-re, r=lcnr Ignore coroutine witness type region args in auto trait confirmation ## The problem Consider code like: ``` async fn process<'a>() { Box::pin(process()).await; } fn require_send(_: impl Send) {} fn main() { require_send(process()); } ``` When proving that the coroutine `{coroutine@process}::<'?0>: Send`, we end up instantiating a nested goal `{witness@process}::<'?0>: Send` by synthesizing a witness type from the coroutine's args: Proving a coroutine witness type implements an auto trait requires looking up the coroutine's witness types. The witness types are a binder that look like `for<'r> { Pin<Box<{coroutine@process}::<'r>>> }`. We instantiate this binder with placeholders and prove `Send` on the witness types. This ends up eventually needing to prove something like `{coroutine@process}::<'!1>: Send`. Repeat this process, and we end up in an overflow during fulfillment, since fulfillment does not use freshening. This can be visualized with a trait stack that ends up looking like: * `{coroutine@process}::<'?0>: Send` * `{witness@process}::<'?0>: Send` * `Pin<Box<{coroutine@process}::<'!1>>>: Send` * `{coroutine@process}::<'!1>: Send` * ... * `{coroutine@process}::<'!2>: Send` * `{witness@process}::<'!2>: Send` * ... * overflow! The problem here specifically comes from the first step: synthesizing a witness type from the coroutine's args. ## Why wasn't this an issue before? Specifically, before 63f6845, this wasn't an issue because we were instead extracting the witness from the coroutine type itself. It turns out that given some `{coroutine@process}::<'?0>`, the witness type was actually something like `{witness@process}::<'erased>`! So why do we end up with a witness type with `'erased` in its args? This is due to the fact that opaque type inference erases all regions from the witness. This is actually explicitly part of opaque type inference -- changing this to actually visit the witness types actually replicates this overflow even with 63f6845 reverted: https://github.com/rust-lang/rust/blob/ca77504943887037504c7fc0b9bf06dab3910373/compiler/rustc_borrowck/src/type_check/opaque_types.rs#L303-L313 To better understand this difference and how it avoids a cycle, if you look at the trait stack before 63f6845, we end up with something like: * `{coroutine@process}::<'?0>: Send` * `{witness@process}::<'erased>: Send` **<-- THIS CHANGED** * `Pin<Box<{coroutine@process}::<'!1>>>: Send` * `{coroutine@process}::<'!1>: Send` * ... * `{coroutine@process}::<'erased>: Send` **<-- THIS CHANGED** * `{witness@process}::<'erased>: Send` **<-- THIS CHANGED** * coinductive cycle! 🎉 ## So what's the fix? This hack replicates the behavior in opaque type inference to erase regions from the witness type, but instead erasing the regions during auto trait confirmation. This is kinda a hack, but is sound. It does not need to be replicated in the new trait solver, of course. --- I hope this explanation makes sense. We could beta backport this instead of the revert #145193, but then I'd like to un-revert that on master in this PR along with landing this this hack. Thoughts? r? lcnr
2 parents a6620a4 + b4aa629 commit d0f3f08

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

compiler/rustc_trait_selection/src/traits/select/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,10 +2333,23 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
23332333

23342334
ty::Coroutine(def_id, args) => {
23352335
let ty = self.infcx.shallow_resolve(args.as_coroutine().tupled_upvars_ty());
2336+
let tcx = self.tcx();
23362337
let witness = Ty::new_coroutine_witness(
2337-
self.tcx(),
2338+
tcx,
23382339
def_id,
2339-
self.tcx().mk_args(args.as_coroutine().parent_args()),
2340+
ty::GenericArgs::for_item(tcx, def_id, |def, _| match def.kind {
2341+
// HACK: Coroutine witnesse types are lifetime erased, so they
2342+
// never reference any lifetime args from the coroutine. We erase
2343+
// the regions here since we may get into situations where a
2344+
// coroutine is recursively contained within itself, leading to
2345+
// witness types that differ by region args. This means that
2346+
// cycle detection in fulfillment will not kick in, which leads
2347+
// to unnecessary overflows in async code. See the issue:
2348+
// <https://github.com/rust-lang/rust/issues/145151>.
2349+
ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
2350+
ty::GenericParamDefKind::Type { .. }
2351+
| ty::GenericParamDefKind::Const { .. } => args[def.index as usize],
2352+
}),
23402353
);
23412354
ty::Binder::dummy(AutoImplConstituents {
23422355
types: [ty].into_iter().chain(iter::once(witness)).collect(),
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Regression test for <https://github.com/rust-lang/rust/issues/145151>.
2+
3+
//@ edition: 2024
4+
//@ check-pass
5+
6+
async fn process<'a>() {
7+
Box::pin(process()).await;
8+
}
9+
10+
fn require_send(_: impl Send) {}
11+
12+
fn main() {
13+
require_send(process());
14+
}

0 commit comments

Comments
 (0)