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

Confusing diagnostic for mismatched opaque types #102311

Open
domenicquirl opened this issue Sep 26, 2022 · 0 comments
Open

Confusing diagnostic for mismatched opaque types #102311

domenicquirl opened this issue Sep 26, 2022 · 0 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@domenicquirl
Copy link
Contributor

I just spent some time being quite confused by an error message I got when mistakenly using an async fn whose return type contains an impl Trait in multiple branches of a match statement. On my local toolchain, which is still on 1.62, the corresponding output is:

error[E0308]: `match` arms have incompatible types
   --> tools/src/bin/volt-test-device-comms.rs:243:35
    |
236 |                   let read_fut = match self.register {
    |                                  ------------------- `match` arms have incompatible types
237 |                       Discrete(reg) => with_timeout(client.read_discrete_inputs(reg, 1))
    |  ______________________________________-
238 | |                         .await
239 | |                         .map(|res| res.map(|_| ())),
    | |___________________________________________________- this is found to be of type `Result<Result<(), std::io::Error>, impl std::error::Error>`
240 |                       Coil(reg) => with_timeout(client.read_coils(reg, 1))
    |  __________________________________-
241 | |                         .await
242 | |                         .map(|res| res.map(|_| ())),
    | |___________________________________________________- this is found to be of type `Result<Result<(), std::io::Error>, impl std::error::Error>`
243 |                       Input(reg) => with_timeout(client.read_input_registers(reg, 1))
    |  ___________________________________^
244 | |                         .await
245 | |                         .map(|res| res.map(|_| ())),
    | |___________________________________________________^ expected `bool`, found `u16`
...
307 |   async fn with_timeout<O, F: Future<Output = O>>(future: F) -> Result<O, impl std::error::Error> {
    |                                                                           ----------------------
    |                                                                           |
    |                                                                           the expected opaque type
    |                                                                           the found opaque type
    |
    = note: expected enum `Result<_, impl std::error::Error>` (`bool`)
               found enum `Result<_, impl std::error::Error>` (`u16`)

(This is using tokio_modbus, there's a reduced repro below.) On current stable (1.64) and nightly (1.66.0-nightly (2022-09-25 f5193a9)), the message seems to have improved with some additional information about what other types the compiler has looked at while trying to match the opaque type. So currently, given the following code (playground):

use std::fmt;
use std::future::Future;

#[derive(Debug, PartialEq)]
struct MyError(());

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "my message")
    }
}

impl std::error::Error for MyError {}

fn main() {}

async fn test() {
    let _ = match 0u16 {
        0 => maybe(one(), true).await.map(|res| res.map(|_| ())),
        _ => maybe(r#true(), true).await.map(|res| res.map(|_| ())),
    };
}

async fn maybe<O, F: Future<Output = O>>(future: F, run: bool) -> Result<O, impl std::error::Error> { 
    if !run {
        Err(MyError(()))
    } else {
        Ok(future.await)
    }
}

async fn one() -> Result<usize, std::io::Error> {
    Ok(1)
}
async fn r#true() -> Result<bool, std::io::Error> {
    Ok(true)
}

The output is:

error[[E0308]](https://doc.rust-lang.org/stable/error-index.html#E0308): `match` arms have incompatible types
  --> src/main.rs:20:14
   |
18 |       let _ = match 0u16 {
   |  _____________-
19 | |         0 => maybe(one(), true).await.map(|res| res.map(|_| ())),
   | |              --------------------------------------------------- this is found to be of type `Result<Result<(), std::io::Error>, impl std::error::Error>`
20 | |         _ => maybe(r#true(), true).await.map(|res| res.map(|_| ())),
   | |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `bool`
21 | |     };
   | |_____- `match` arms have incompatible types
...
24 |   async fn maybe<O, F: Future<Output = O>>(future: F, run: bool) -> Result<O, impl std::error::Error> { 
   |                                                                               ----------------------
   |                                                                               |
   |                                                                               one of the expected opaque types
   |                                                                               one of the found opaque types
   |
note: while checking the return type of the `async fn`
  --> src/main.rs:32:19
   |
32 | async fn one() -> Result<usize, std::io::Error> {
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ checked the `Output` of this `async fn`, one of the expected opaque types
note: while checking the return type of the `async fn`
  --> src/main.rs:35:22
   |
35 | async fn r#true() -> Result<bool, std::io::Error> {
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ checked the `Output` of this `async fn`, one of the found opaque types
   = note: expected enum `Result<_, impl std::error::Error>` (`usize`)
              found enum `Result<_, impl std::error::Error>` (`bool`)

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

While this gives more context on what code might be involved in the error,

  • the primary diagnostic still says "expected usize, found bool", which might be true in some sense, but the actual mismatched types are different instances of impl Error whose only relation to the mentioned primitive types is that the latter occur in the type parameters to the function that produces the impl Error,
  • the source location for "one of the expected opaque types" and "one of the found opaque types" is identical. Both point to the code that declares the impl Error return type, which is always confusing because it makes it seem that expected and found are the same (in which case, why is there an error),
  • the notes claim that the Future::Output parameters of the async fns used to invoke maybe are expected/found opaque types. This is still confusing to me, because (as in the case above) the error is about the impl Error, and in this case Output = Result<usize, std::io::Error>, which is neither opaque nor the problem (the only thing that would somewhat make sense to me here is that async fn desugars to impl Future, which is opaque. But that's not present in the diagnostic at all),
  • the (`bool`) and (`usize`) in the final note were confusing to me, because they don't appear anywhere in the type (neither the printed abbreviation nor the real type, which at the point of the error is aready Result<Result<(), io::Error>, impl Error>) and the message doesn't say what they mean and what they've got to do with the error, and
  • nowhere in the message is it mentioned that impl Trait return types are different when instantiating generic functions with different parameters
    • with my actual issue, this was even more confusing to me because the first two branches did not produce an error, even though the futures, also a generic parameter of the function returning the opaque type, were different. So I would have expected that to already cause the error if that was the problem. This seems better on current stable, where adding a second branch with an async fn two() -> Result<usize, io::Error> produces an error which points to the locations of the two functions (playground)

Possible output that would have helped me better identify the issue:

error[[E0308]](https://doc.rust-lang.org/stable/error-index.html#E0308): `match` arms have incompatible types
  --> src/main.rs:20:14
   |
18 |       let _ = match 0u16 {
   |  _____________-
19 | |         0 => maybe(one(), true).await.map(|res| res.map(|_| ())),
   | |              --------------------------------------------------- this is found to be of type `Result<Result<(), std::io::Error>, impl std::error::Error>`
20 | |         _ => maybe(r#true(), true).await.map(|res| res.map(|_| ())),
   | |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `impl std::error::Error` is different here
21 | |     };
   | |_____- `match` arms have incompatible types
...
24 |   async fn maybe<O, F: Future<Output = O>>(future: F, run: bool) -> Result<O, impl std::error::Error> { 
   |                                                                               ----------------------
   |                                                                               |
   |                                                                               opaque type defined here
   |
   = note: when returning a type containing `impl Trait` from a generic function, its concrete type is different if the function is called with different generic arguments
note: while checking the return type of the `async fn`
  --> src/main.rs:32:19
   |
32 | async fn one() -> Result<usize, std::io::Error> {
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ checked the `Output` of this `async fn`, which is `O` in <src/main.rs:19:20>
note: while checking the return type of the `async fn`
  --> src/main.rs:35:22
   |
35 | async fn r#true() -> Result<bool, std::io::Error> {
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ checked the `Output` of this `async fn`, which is `O` in <src/main.rs:20:20>
   = note: mismatch between `usize` and `bool`

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

For completeness, the error is resolved as expected by replacing the opaque type with its concrete type.

The latest change to the message for this error that I could find is #63167, but that doesn't yet include the (`bool`) (it just says "opaque type" and adds a source location). #93519 might be similar.

@domenicquirl domenicquirl added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

1 participant