Skip to content

Revert "Remove the witness type from coroutine args" #145193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Aug 10, 2025

Revert 63f6845 (last commit in #144458 -- the whole thing doesn't need to be reverted) to fix #145151.

r? lcnr

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Aug 10, 2025
@compiler-errors compiler-errors added the beta-nominated Nominated for backporting to the compiler in the beta channel. label Aug 10, 2025
@rust-log-analyzer
Copy link
Collaborator

The job aarch64-gnu-llvm-19-1 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
diff of stderr:

5    |                      -- the expected `async` closure body
6 LL |
7 LL |     let () = x();
-    |         ^^   --- this expression has type `{static main::{closure#0}::{closure#0}<?16t> upvar_tys=?15t resume_ty=ResumeTy yield_ty=() return_ty=() witness={main::{closure#0}::{closure#0}}}`
+    |         ^^   --- this expression has type `{static main::{closure#0}::{closure#0}<?16t> upvar_tys=?15t resume_ty=ResumeTy yield_ty=() return_ty=()}`
9    |         |
10    |         expected `async` closure body, found `()`
11    |

-    = note: expected `async` closure body `{static main::{closure#0}::{closure#0}<?16t> upvar_tys=?15t resume_ty=ResumeTy yield_ty=() return_ty=() witness={main::{closure#0}::{closure#0}}}`
+    = note: expected `async` closure body `{static main::{closure#0}::{closure#0}<?16t> upvar_tys=?15t resume_ty=ResumeTy yield_ty=() return_ty=()}`
13                          found unit type `()`
14 
15 error: aborting due to 1 previous error


The actual stderr differed from the expected stderr
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args async-await/async-closures/def-path.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/async-await/async-closures/def-path.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/async-await/async-closures/def-path" "-A" "unused" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers" "--edition=2021" "-Zverbose-internals"
stdout: none
--- stderr -------------------------------
error[E0308]: mismatched types
##[error]  --> /checkout/tests/ui/async-await/async-closures/def-path.rs:7:9
   |
LL |     let x = async || {};
   |                      -- the expected `async` closure body
LL |     //~^ NOTE the expected `async` closure body
LL |     let () = x();
   |         ^^   --- this expression has type `{static main::{closure#0}::{closure#0}<?16t> upvar_tys=?15t resume_ty=ResumeTy yield_ty=() return_ty=()}`
   |         |
   |         expected `async` closure body, found `()`
   |
   = note: expected `async` closure body `{static main::{closure#0}::{closure#0}<?16t> upvar_tys=?15t resume_ty=ResumeTy yield_ty=() return_ty=()}`
                         found unit type `()`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
---

9 LL | |     });
10    | |______^ coroutine is not `Sync`
11    |
-    = help: within `{main::{closure#0} upvar_tys=() resume_ty=() yield_ty=() return_ty=() witness={main::{closure#0}}}`, the trait `Sync` is not implemented for `NotSync`
+    = help: within `{main::{closure#0} upvar_tys=() resume_ty=() yield_ty=() return_ty=()}`, the trait `Sync` is not implemented for `NotSync`
13 note: coroutine is not `Sync` as this value is used across a yield
14   --> $DIR/coroutine-print-verbose-2.rs:20:9
15    |

34 LL | |     });
35    | |______^ coroutine is not `Send`
36    |
-    = help: within `{main::{closure#1} upvar_tys=() resume_ty=() yield_ty=() return_ty=() witness={main::{closure#1}}}`, the trait `Send` is not implemented for `NotSend`
+    = help: within `{main::{closure#1} upvar_tys=() resume_ty=() yield_ty=() return_ty=()}`, the trait `Send` is not implemented for `NotSend`
38 note: coroutine is not `Send` as this value is used across a yield
39   --> $DIR/coroutine-print-verbose-2.rs:27:9
40    |


The actual stderr differed from the expected stderr
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args coroutine/print/coroutine-print-verbose-2.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/coroutine/print/coroutine-print-verbose-2" "-A" "unused" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers" "-Zverbose-internals"
stdout: none
--- stderr -------------------------------
error: coroutine cannot be shared between threads safely
##[error]  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs:17:5
   |
LL | /     assert_sync(#[coroutine] || {
LL | |         //~^ ERROR: coroutine cannot be shared between threads safely
LL | |         let a = NotSync;
LL | |         yield;
LL | |         drop(a);
LL | |     });
   | |______^ coroutine is not `Sync`
   |
   = help: within `{main::{closure#0} upvar_tys=() resume_ty=() yield_ty=() return_ty=()}`, the trait `Sync` is not implemented for `NotSync`
note: coroutine is not `Sync` as this value is used across a yield
  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs:20:9
   |
LL |         let a = NotSync;
   |             - has type `NotSync` which is not `Sync`
LL |         yield;
   |         ^^^^^ yield occurs here, with `a` maybe used later
note: required by a bound in `assert_sync`
  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs:14:23
   |
LL |     fn assert_sync<T: Sync>(_: T) {}
   |                       ^^^^ required by this bound in `assert_sync`

error: coroutine cannot be sent between threads safely
##[error]  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs:24:5
   |
LL | /     assert_send(#[coroutine] || {
LL | |         //~^ ERROR: coroutine cannot be sent between threads safely
LL | |         let a = NotSend;
LL | |         yield;
LL | |         drop(a);
LL | |     });
   | |______^ coroutine is not `Send`
   |
   = help: within `{main::{closure#1} upvar_tys=() resume_ty=() yield_ty=() return_ty=()}`, the trait `Send` is not implemented for `NotSend`
note: coroutine is not `Send` as this value is used across a yield
  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs:27:9
   |
LL |         let a = NotSend;
   |             - has type `NotSend` which is not `Send`
LL |         yield;
   |         ^^^^^ yield occurs here, with `a` maybe used later
note: required by a bound in `assert_send`
  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-2.rs:15:23
   |
LL |     fn assert_send<T: Send>(_: T) {}
   |                       ^^^^ required by this bound in `assert_send`

error: aborting due to 2 previous errors
------------------------------------------

---

11    | |_____^ expected `()`, found coroutine
12    |
13    = note: expected unit type `()`
-               found coroutine `{main::{closure#0} upvar_tys=?4t resume_ty=() yield_ty=i32 return_ty=&'?1 str witness={main::{closure#0}}}`
+               found coroutine `{main::{closure#0} upvar_tys=?4t resume_ty=() yield_ty=i32 return_ty=&'?1 str}`
15 
16 error: aborting due to 1 previous error
17 


The actual stderr differed from the expected stderr
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args coroutine/print/coroutine-print-verbose-3.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/coroutine/print/coroutine-print-verbose-3.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/coroutine/print/coroutine-print-verbose-3" "-A" "unused" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers" "-Zverbose-internals"
stdout: none
--- stderr -------------------------------
error[E0308]: mismatched types
##[error]  --> /checkout/tests/ui/coroutine/print/coroutine-print-verbose-3.rs:8:5
   |
LL |       let coroutine: () = #[coroutine]
   |                      -- expected due to this
LL | /     || {
LL | |         //~^ ERROR mismatched types
LL | |         yield 1i32;
LL | |         return x;
LL | |     };
   | |_____^ expected `()`, found coroutine
   |
   = note: expected unit type `()`
              found coroutine `{main::{closure#0} upvar_tys=?4t resume_ty=() yield_ty=i32 return_ty=&'?1 str}`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
------------------------------------------

@bors
Copy link
Collaborator

bors commented Aug 10, 2025

☔ The latest upstream changes (presumably #145210) made this pull request unmergeable. Please resolve the merge conflicts.


fn main() {
require_send(process());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did this error?

We've got

  • coroutine<'a>: Send
  • requires witness<'a>: Send
  • requires Box<Pin<coroutine<'!0>>: Send?
  • requires coroutine<'!0>: Send
  • requires witness<'!0>: Send
  • requires Box<Pin<coroutine<'!1>>: Send?
  • ....

But with this revert we get

  • coroutine<'a, witness<'a>>: Send
  • requires witness<'a>: Send
  • requires Box<Pin<coroutine<'!0, witness<'!1>>>>: Send?
  • requires coroutine<'!0, witness<'!1>>: Send
  • requires witness<'!1>: Send
  • requires Box<Pin<coroutine<'!2, witness<'!3>>>>: Send?
  • ....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, see #145194

Zalathar added a commit to Zalathar/rust that referenced this pull request Aug 11, 2025
…=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 rust-lang#145193, but then I'd like to un-revert that on master in this PR along with landing this this hack. Thoughts?

r? lcnr
rust-timer added a commit that referenced this pull request Aug 11, 2025
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
@compiler-errors
Copy link
Member Author

Closing in favor of the other fix

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 11, 2025
@apiraino apiraino removed the beta-nominated Nominated for backporting to the compiler in the beta channel. label Aug 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

E0275 is occuring in beta 1.90.0-beta.1 but not in stable 1.89.0
6 participants