Skip to content

Irrefutable if let on a single-variant enum only produces () value #61788

New issue

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

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

Already on GitHub? # to your account

Closed
moxian opened this issue Jun 12, 2019 · 8 comments · Fixed by #120479
Closed

Irrefutable if let on a single-variant enum only produces () value #61788

moxian opened this issue Jun 12, 2019 · 8 comments · Fixed by #120479
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-suggestion-diagnostics Area: Suggestions generated by the compiler applied by `cargo fix` C-enhancement Category: An issue proposing an enhancement or a PR with one. P-low Low priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@moxian
Copy link
Contributor

moxian commented Jun 12, 2019

The following code (playground)

enum Enum{
    Variant(i32),
}

fn stuff(x: Enum) -> i32{
    if let Enum::Variant(value) = x {
        value
    }
}

gives the following error:

   Compiling playground v0.0.1 (/playground)
error[E0317]: if may be missing an else clause
 --> src/lib.rs:6:5
  |
6 | /     if let Enum::Variant(value) = x {
7 | |         value
8 | |     }
  | |_____^ expected (), found i32
  |
  = note: expected type `()`
             found type `i32`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0317`.
error: Could not compile `playground`.

which is weird, since the else clause would never be visited even if it was present.

The equivalent one-arm match

enum Enum{
    Variant(i32),
}

fn stuff(x: Enum) -> i32{
    match x {
        Enum::Variant(value) => value,
    }
}

compiles just fine (expectedly).

Adding an else { unreachable!() } branch to the if let is, of course, possible, but unelegant, and really shouldn't be needed, as it can be inferred at compile time.

@varkor
Copy link
Member

varkor commented Jun 12, 2019

You can match irrefutably without if in this case:

fn stuff(x: Enum) -> i32 {
    let Enum::Variant(value) = x;
    value
}

though maybe we should give this special diagnostics.

@varkor varkor added the A-suggestion-diagnostics Area: Suggestions generated by the compiler applied by `cargo fix` label Jun 12, 2019
@Centril
Copy link
Contributor

Centril commented Jun 12, 2019

I think this is too niche to deserve a special diagnostics (and I don't think we should substantially work on diagnostics for if until let_chains is implemented).

which is weird, since the else clause would never be visited even if it was present.

if let is defined by a desugaring:

if let PAT = EXPR BLOCK_IF [else BLOCK_ELSE]

becomes:

match EXPR {
    PAT => BLOCK_IF,
    _ => [ {} | BLOCK_ELSE ],
}

so what you really wrote is:

match x {
    Enum::Variant(value) => value,
    _ => {},
}

The block {} is typed at () which does not unify with value unless it is also typed at ().

@estebank estebank added A-diagnostics Area: Messages for errors, warnings, and lints P-low Low priority labels Jun 13, 2019
@moxian
Copy link
Contributor Author

moxian commented Jun 14, 2019

I didn't know let Enum::Variant(value) = x; was a valid syntax. If I did, I probably wouldn't have filed the issue, because of course, in hindsight, that's how you write an infallible if let.

(With that knowledge in mind) I agree that it's only a diagnostics issue and no language change is needed here.
And I fully agree that it's a niche low-priority quirk that should maybe be fixed "someday". (I don't know what let_chains but even then I'd guess that this one is lower priority than that).

@estebank estebank added the A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. label Jul 25, 2019
@estebank
Copy link
Contributor

I think the appropriate thing to do here would be to lint on if let Irrefutable = foo {}, which would have clarified things.

@Centril
Copy link
Contributor

Centril commented Jul 25, 2019

@estebank
Copy link
Contributor

But we do only after typechecking, and we don't if there are any errors before then. Quite a few lints have similar issues related to not happening early enough.

@Centril
Copy link
Contributor

Centril commented Jul 25, 2019

@estebank How do you know that the pattern is irrefutable before match checking in HAIR?

@estebank
Copy link
Contributor

You can't, but you can write the lint in such a way that it triggers regardless. We're getting to typechecking, we should be able to detect irrefutability by then. It just can't use the standard linting machinery (like a few other lints already do to avoid being silenced by accident).

@jonas-schievink jonas-schievink added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 27, 2019
estebank added a commit to estebank/rust that referenced this issue Jan 29, 2024
When encountering an `if let` tail expression without an `else` arm for an enum with a single variant, suggest writing an irrefutable `let` binding instead.

```
error[E0317]: `if` may be missing an `else` clause
  --> $DIR/irrefutable-if-let-without-else.rs:8:5
   |
LL |   fn foo(x: Enum) -> i32 {
   |                      --- expected `i32` because of this return type
LL | /     if let Enum::Variant(value) = x {
LL | |         value
LL | |     }
   | |_____^ expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = help: consider adding an `else` block that evaluates to the expected type
help: consider using an irrefutable `let` binding instead
   |
LL ~     let Enum::Variant(value) = x;
LL ~         value
   |
```

Fix rust-lang#61788.
estebank added a commit to estebank/rust that referenced this issue Jan 29, 2024
When encountering an `if let` tail expression without an `else` arm for an enum with a single variant, suggest writing an irrefutable `let` binding instead.

```
error[E0317]: `if` may be missing an `else` clause
  --> $DIR/irrefutable-if-let-without-else.rs:8:5
   |
LL |   fn foo(x: Enum) -> i32 {
   |                      --- expected `i32` because of this return type
LL | /     if let Enum::Variant(value) = x {
LL | |         value
LL | |     }
   | |_____^ expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = help: consider adding an `else` block that evaluates to the expected type
help: consider using an irrefutable `let` binding instead
   |
LL ~     let Enum::Variant(value) = x;
LL ~         value
   |
```

Fix rust-lang#61788.
Nadrieril added a commit to Nadrieril/rust that referenced this issue Feb 7, 2024
Suggest turning `if let` into irrefutable `let` if appropriate

When encountering an `if let` tail expression without an `else` arm for an enum with a single variant, suggest writing an irrefutable `let` binding instead.

```
error[E0317]: `if` may be missing an `else` clause
  --> $DIR/irrefutable-if-let-without-else.rs:8:5
   |
LL |   fn foo(x: Enum) -> i32 {
   |                      --- expected `i32` because of this return type
LL | /     if let Enum::Variant(value) = x {
LL | |         value
LL | |     }
   | |_____^ expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = help: consider adding an `else` block that evaluates to the expected type
help: consider using an irrefutable `let` binding instead
   |
LL ~     let Enum::Variant(value) = x;
LL ~         value
   |
```

Fix rust-lang#61788.
Nadrieril added a commit to Nadrieril/rust that referenced this issue Feb 7, 2024
Suggest turning `if let` into irrefutable `let` if appropriate

When encountering an `if let` tail expression without an `else` arm for an enum with a single variant, suggest writing an irrefutable `let` binding instead.

```
error[E0317]: `if` may be missing an `else` clause
  --> $DIR/irrefutable-if-let-without-else.rs:8:5
   |
LL |   fn foo(x: Enum) -> i32 {
   |                      --- expected `i32` because of this return type
LL | /     if let Enum::Variant(value) = x {
LL | |         value
LL | |     }
   | |_____^ expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = help: consider adding an `else` block that evaluates to the expected type
help: consider using an irrefutable `let` binding instead
   |
LL ~     let Enum::Variant(value) = x;
LL ~         value
   |
```

Fix rust-lang#61788.
@bors bors closed this as completed in a939bad Feb 7, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Feb 7, 2024
Rollup merge of rust-lang#120479 - estebank:issue-61788, r=wesleywiser

Suggest turning `if let` into irrefutable `let` if appropriate

When encountering an `if let` tail expression without an `else` arm for an enum with a single variant, suggest writing an irrefutable `let` binding instead.

```
error[E0317]: `if` may be missing an `else` clause
  --> $DIR/irrefutable-if-let-without-else.rs:8:5
   |
LL |   fn foo(x: Enum) -> i32 {
   |                      --- expected `i32` because of this return type
LL | /     if let Enum::Variant(value) = x {
LL | |         value
LL | |     }
   | |_____^ expected `i32`, found `()`
   |
   = note: `if` expressions without `else` evaluate to `()`
   = help: consider adding an `else` block that evaluates to the expected type
help: consider using an irrefutable `let` binding instead
   |
LL ~     let Enum::Variant(value) = x;
LL ~         value
   |
```

Fix rust-lang#61788.
@fmease fmease added A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. and removed A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. labels Dec 21, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-suggestion-diagnostics Area: Suggestions generated by the compiler applied by `cargo fix` C-enhancement Category: An issue proposing an enhancement or a PR with one. P-low Low priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants