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

if and else have incompatible types in a let statement, where else block's evaluation will never be assigned #133316

Open
shanebishop opened this issue Nov 21, 2024 · 3 comments
Labels
A-control-flow Area: Control flow A-diagnostics Area: Messages for errors, warnings, and lints D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@shanebishop
Copy link

shanebishop commented Nov 21, 2024

I apologize if this was already reported in a separate issue, or if this is a known issue - I wasn't sure how to best search for previous issues like this.

I also realize this might not be a "bug" per se, but the other issue templates didn't seem to quite fit either.

I tried this code:

enum Cause { Cause1, Cause2 }
struct MyErr { x: Cause }

fn main() {
    _ = f();
}

fn f() -> Result<i32, MyErr> {
    let res = could_fail();
    let x = if let Ok(x) = res {
        x
    } else if let Err(e) = res {
        cleanup();
        return Err(e);
    };
    Ok(x)
}

fn could_fail() -> Result<i32, MyErr> {
    // ... code that could fail and return an Err ...
    Ok(0)
}

fn cleanup() {
    // ... cleanup code ...
}

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=60acf4e59e3c6403104e01aa409aa395

I expected the code to compile successfully, since the else if branch unconditionally returns. Because the else if branch always returns, x will always be an i32.

Instead, I get this compiler error:

error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:12:12
   |
10 |        let x = if let Ok(x) = res {
   |  ______________-
11 | |          x
   | |          - expected because of this
12 | |      } else if let Err(e) = res {
   | | ____________^
13 | ||         cleanup();
14 | ||         return Err(e);
15 | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`

Meta

rustc --version --verbose:

rustc 1.82.0 (f6e511eec 2024-10-15)
binary: rustc
commit-hash: f6e511eec7342f59a25f7c0534f1dbea00d01b14
commit-date: 2024-10-15
host: x86_64-unknown-linux-gnu
release: 1.82.0
LLVM version: 19.1.1
@shanebishop shanebishop added the C-bug Category: This is a bug. label Nov 21, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Nov 21, 2024
@fmease fmease 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. D-confusing Diagnostics: Confusing error or lint that should be reworked. A-control-flow Area: Control flow labels Nov 22, 2024
@fmease
Copy link
Member

fmease commented Nov 22, 2024

Unfortunately the diagnostic doesn't make this clear (we should fix that) but the culprit isn't the type of the else if branch, it's the type of the implicit else branch: There's an invisible else {} / else { () } at the end of the if-expression. As a result of that the type checker tries to unify three types:

  1. i32,
  2. /*unconstrained*/
  3. ()

which leads to an error because i32 and () aren't compatible.

@fmease
Copy link
Member

fmease commented Nov 22, 2024

You might wonder why there's an implicit else {} / else { () } at all if the two branches seemingly cover the whole input space (Ok(_) and Err(_) for Result<_, _>). Well, in this case the checks are two separate pattern matches from the perspective of the exhaustiveness checker and therefore the match on Err(_) doesn't count towards the exhaustiveness of the first match that contains the Ok(_)! Rust doesn't have flow-sensitive typing.

Consider turning the two 'matches' into a single one for the code to pass compilation:

let x = match res {
    Ok(x) => x,
    Err(e) =>{
        cleanup();
        return Err(e);
    }
};

You can even write it pretty concisely as:

let x = res.inspect_err(|_| cleanup())?;

@fmease
Copy link
Member

fmease commented Nov 22, 2024

For the sake of completeness, I will mention that you could theoretically adjust your if let … else if let the following way to make your code compile but that's ill-advised:

// Please DON'T! Harness the power of exhaustiveness checking instead!
let x = if let Ok(x) = res {
    x              // <-- i32
} else if let Err(e) = res {
    cleanup();
    return Err(e); // <-- /*unconstrained*/
} else {
    // We "suppress" the implicit `else {}` / `else { () }` branch by providing an explicit branch
    unreachable!() // <-- /*unconstrained*/
};

@fmease fmease added D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. C-bug Category: This is a bug. labels Nov 22, 2024
estebank added a commit to estebank/rust that referenced this issue Jan 15, 2025
```
error[E0308]: `if` and `else` have incompatible types
  --> $DIR/if-else-chain-missing-else.rs:12:12
   |
LL |        let x = if let Ok(x) = res {
   |  ______________-
LL | |          x
   | |          - expected because of this
LL | |      } else if let Err(e) = res {
   | | ____________^
LL | ||         return Err(e);
LL | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = note: consider adding an `else` block that evaluates to the expected type
```

We probably want a longer explanation and fewer spans on this case.

Partially address rust-lang#133316.
estebank added a commit to estebank/rust that referenced this issue Jan 15, 2025
```
error[E0308]: `if` and `else` have incompatible types
  --> $DIR/if-else-chain-missing-else.rs:12:12
   |
LL |        let x = if let Ok(x) = res {
   |  ______________-
LL | |          x
   | |          - expected because of this
LL | |      } else if let Err(e) = res {
   | | ____________^
LL | ||         return Err(e);
LL | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = note: consider adding an `else` block that evaluates to the expected type
```

We probably want a longer explanation and fewer spans on this case.

Partially address rust-lang#133316.
estebank added a commit to estebank/rust that referenced this issue Jan 16, 2025
```
error[E0308]: `if` and `else` have incompatible types
  --> $DIR/if-else-chain-missing-else.rs:12:12
   |
LL |        let x = if let Ok(x) = res {
   |  ______________-
LL | |          x
   | |          - expected because of this
LL | |      } else if let Err(e) = res {
   | | ____________^
LL | ||         return Err(e);
LL | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = note: consider adding an `else` block that evaluates to the expected type
```

We probably want a longer explanation and fewer spans on this case.

Partially address rust-lang#133316.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jan 17, 2025
Detect if-else chains with a missing final else in type errors

```
error[E0308]: `if` and `else` have incompatible types
  --> $DIR/if-else-chain-missing-else.rs:12:12
   |
LL |        let x = if let Ok(x) = res {
   |  ______________-
LL | |          x
   | |          - expected because of this
LL | |      } else if let Err(e) = res {
   | | ____________^
LL | ||         return Err(e);
LL | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = note: consider adding an `else` block that evaluates to the expected type
```

We probably want a longer explanation and fewer spans on this case.

Partially address rust-lang#133316.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jan 17, 2025
Detect if-else chains with a missing final else in type errors

```
error[E0308]: `if` and `else` have incompatible types
  --> $DIR/if-else-chain-missing-else.rs:12:12
   |
LL |        let x = if let Ok(x) = res {
   |  ______________-
LL | |          x
   | |          - expected because of this
LL | |      } else if let Err(e) = res {
   | | ____________^
LL | ||         return Err(e);
LL | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = note: consider adding an `else` block that evaluates to the expected type
```

We probably want a longer explanation and fewer spans on this case.

Partially address rust-lang#133316.
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Jan 17, 2025
Rollup merge of rust-lang#135558 - estebank:issue-133316, r=chenyukang

Detect if-else chains with a missing final else in type errors

```
error[E0308]: `if` and `else` have incompatible types
  --> $DIR/if-else-chain-missing-else.rs:12:12
   |
LL |        let x = if let Ok(x) = res {
   |  ______________-
LL | |          x
   | |          - expected because of this
LL | |      } else if let Err(e) = res {
   | | ____________^
LL | ||         return Err(e);
LL | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = note: consider adding an `else` block that evaluates to the expected type
```

We probably want a longer explanation and fewer spans on this case.

Partially address rust-lang#133316.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-control-flow Area: Control flow A-diagnostics Area: Messages for errors, warnings, and lints D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. 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

3 participants