rust-lang / rust-lang/rust

`unreachable_patterns` fires on match arm that can't be removed.

Open
#129,352 17 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-lints D-invalid-suggestion F-never_type L-unreachable_patterns T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

playground

use core::future::Future;

async fn select<A: Future, B: Future>(_: A, _: B) -> Result<A::Output, B::Output> {
    todo!()
}

pub async fn wtf() -> ! {
    let a = async { loop {} };
    let b = async { loop {} };
    match select(a, b).await {
        Ok(x) => x,
        Err(x) => x,
    }
}

warns with unreachable_patterns on both arms

warning: unreachable pattern
  --> src/lib.rs:11:9
   |
11 |         Ok(x) => x,
   |         ^^^^^
   |
   = note: this pattern matches no values because `Result<!, !>` is uninhabited
   = note: `#[warn(unreachable_patterns)]` on by default

but removing either doesn't actually compile

error[E0004]: non-exhaustive patterns: `Ok(_)` not covered
   --> src/lib.rs:10:11
    |
10  |     match select(a, b).await {
    |           ^^^^^^^^^^^^^^^^^^ pattern `Ok(_)` not covered
    |
note: `Result<(), !>` defined here
   --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:527:1
    |
527 | pub enum Result<T, E> {
    | ^^^^^^^^^^^^^^^^^^^^^
...
531 |     Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
    |     -- not covered
    = note: the matched value is of type `Result<(), !>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
    |
11  ~         Err(x) => x,
12  ~         Ok(_) => todo!(),

So this code seems "unfixable" with the new lint.

What's happening is the match arm is unreachable, but it still has the "side effect" of making the async block be inferred to Future<Output=!> instead of Future<Output=()>. So, if you remove it the future now gets inferred to return (), and the arm is not unreachable anymore.

In edition 2024 it Just Works if you write match select(a, b).await {}, because the fallback is ! instead of ().

This seems a bit hard to mitigate in Edition 2021, it seems more like an unfortunate combination of the lint and the inference fallback more than a bug. I'm opening the issue anyway to share it, just in case anyone has an idea.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the linked Playground example in Rust editions 2021 and 2024. Investigate the interaction between the unreachable_patterns lint and async future output inference, including the fallback from ! to (). Done means establishing a decided compiler behavior or mitigation and covering the regression with an appropriate test.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.