Skip to content
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

Universe errors involving an opaque type results in hallucinations and unused bound vars #124472

Open
WaffleLapkin opened this issue Apr 28, 2024 · 7 comments
Labels
A-async-await Area: Async & Await A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. A-lifetimes Area: lifetime related AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@WaffleLapkin
Copy link
Member

WaffleLapkin commented Apr 28, 2024

Code (Rust ≥2018)

use std::future::Future;

fn main() {
    consume_async(async_fn);
}

async fn async_fn(_: &u32) {}

fn consume_async<F>(_: impl FnOnce(&u32) -> F) where F: Future<Output = ()> {}

Current output

error[E0308]: mismatched types
 --> ./t.rs:4:5
  |
4 |     consume_async(async_fn);
  |     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected opaque type `impl for<'a> Future<Output = ()>`
             found opaque type `impl Future<Output = ()>`
  = help: consider `await`ing on both `Future`s
  = note: distinct uses of `impl Trait` result in different opaque types
note: the lifetime requirement is introduced here
 --> ./t.rs:9:45
  |
9 | fn consume_async<F>(_: impl FnOnce(&u32) -> F) where F: Future<Output = ()> {}
  |                                             ^

Desired output

error[E0308]: mismatched types
 --> ./t.rs:4:5
  |
4 |     consume_async(async_fn);
  |     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected `_`
             found some type `for<'a> T<'a>` which implements `Future<Output = ()>`
note: the lifetime requirement is introduced here
 --> ./t.rs:9:45
  |
9 | fn consume_async<F>(_: impl FnOnce(&u32) -> F) where F: Future<Output = ()> {}
  |                                             ^

Rationale and extra context

It's very confusing to see a second opaque type and suggestions "to await both futures" (???, there is only one). Ideally we would properly note that the generic can't name the lifetime or something.

Other cases

No response

Rust Version

rustc 1.79.0-nightly (ef8b9dcf2 2024-04-24)
binary: rustc
commit-hash: ef8b9dcf23700f2e2265317611460d3a65c19eff
commit-date: 2024-04-24
host: x86_64-unknown-linux-gnu
release: 1.79.0-nightly
LLVM version: 18.1.4

Anything else?

#124471 has a simiular test case/issue but without the opaque types.

@WaffleLapkin WaffleLapkin added A-diagnostics Area: Messages for errors, warnings, and lints A-lifetimes Area: lifetime related T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. A-async-await Area: Async & Await D-confusing Diagnostics: Confusing error or lint that should be reworked. T-types Relevant to the types team, which will review and decide on the PR/issue. labels Apr 28, 2024
@WaffleLapkin
Copy link
Member Author

Another option would to use syntax from rust-lang/rfcs#3617 and have

  • found for<'0> impl use<'0, ...> Future<Output = ()>
  • expected impl Future<Output = ()>

@fmease
Copy link
Member

fmease commented Apr 29, 2024

Wow, this diagnostic isn't great!

found some type `for<'a> T<'a>` which implements `Future<Output = ()>`

I don't think we should use for<'a> in places where it's not syntactically valid even if it makes sense conceptually. The user can't write for<'a> T<'a> where T is essentially a metavariable and stands for an arbitrary type. Sadly, in Rust you cannot introduce higher-ranked types in arbitrary places unlike in Haskell (forall a. a).

I don't think we use for<> in such a way in existing diagnostics, at least I hope so. Personally, I'd go for prose (which we already do in other diags) like ... for any lifetime `'a` / ... for arbitrary lifetime `'a`.

found `for<'0> impl use<'0, ...> Future<Output = ()>`

Similar situation: for<> impl isn't grammatical, only impl for<> is. Furthermore, it's still unclear if use<> will come before or after impl. Conceptually, this snippet makes sense, for sure. Again, I'd go for prose: ... for any lifetime `'0` and ... opaque type ... that captures `'0`.

@fmease
Copy link
Member

fmease commented Apr 29, 2024

= help: consider `await`ing on both `Future`s

If possible we should probably only suggest that if the failed region obligation/goal was caused by an expression.

@fmease
Copy link
Member

fmease commented Apr 29, 2024

Just to give more context for other readers, the user issue boils down to:

#![feature(precise_capturing)]

// `consume_async(async_fn)` passes compilation ✅
fn async_fn<'a>(_: &'a u32) -> impl use<> Future<Output = ()> { async {} }

// `consume_async(async_fn)` fails compilation ❌
fn async_fn<'a>(_: &'a u32) -> impl use<'a> Future<Output = ()> { async {} }

@BoxyUwU
Copy link
Member

BoxyUwU commented Apr 29, 2024

We do actually use for<> in weird places already. I'm not the biggest fan of it either. in general it's really inconsistent how we print out errors involving higher ranked lifetimes. I think I've seen like three or four different variations lol.

@traviscross
Copy link
Contributor

@rustbot labels +AsyncAwait-Triaged

We discussed this in async triage. This seems to be another one of those use cases for async closures.

cc @compiler-errors

@rustbot rustbot added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label May 6, 2024
@eholk
Copy link
Contributor

eholk commented May 6, 2024

If consume_async_fn takes an async closure, then it looks like things work. playground

#![feature(precise_capturing, async_closure)]
#![allow(incomplete_features)]

use std::future::Future;

fn main() {
    consume_async(async_fn);
}

fn async_fn<'a>(_: &'a u32) -> impl use<'a> Future<Output = ()> { async {} }

fn consume_async(_: impl async FnOnce(&u32) -> ()) {}

It seems to me a proper fix for this issue is to stabilize some of these features and then update the error messages to steer people towards those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. A-lifetimes Area: lifetime related AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants