Skip to content

Pattern reachability algorithm fails in the presence of uninhabited types #12609

Closed

Description

@glaebhoerl

From check_match.rs:

// Algorithm from http://moscova.inria.fr/~maranget/papers/warn/index.html
//
// Whether a vector `v` of patterns is 'useful' in relation to a set of such
// vectors `m` is defined as there being a set of inputs that will match `v`
// but not any of the sets in `m`.
//
// This is used both for reachability checking (if a pattern isn't useful in
// relation to preceding patterns, it is not reachable) and exhaustiveness
// checking (if a wildcard pattern is useful in relation to a matrix, the
// matrix isn't exhaustive).

// Note: is_useful doesn't work on empty types, as the paper notes.
// So it assumes that v is non-empty.

This results in, most prominently (#4499), a match on an empty enum with a wildcard pattern being accepted:

enum X { }
fn x() -> X { unimplemented!() }
match x() {
    _ => println!("hi")
}

while if you add a variant to the enum and a corresponding arm to the match:

enum X { X }
fn x() -> X { unimplemented!() }
match x() {
    X => println!("hello"),
    _ => println!("hi")
}

rustc rejects the program with error: unreachable pattern.

Clearly a trailing wildcard pattern after you've already matched all possible values in preceding arms should always be considered unreachable, and this is true even when the number of potential values, and hence preceding arms, are both zero.

As the comment notes the algorithm we use relies on "no empty types" as an assumption, so this may not be an easy bug, but it's still a bug.

Activity

  1. glaebhoerl commented on Jun 28, 2014

    @glaebhoerl
    ContributorAuthor

    cc @brson @nikomatsakis @pcwalton (not really sure who I should be cc-ing): I think this is a backwards compatibility issue in the respect that fixing this bug will cause fewer programs to be accepted. We should make it clear that pattern matching uninhabited types with wildcard patterns is not legal according to the language spec, that it being currently accepted is a compiler bug, and that we reserve the right to fix the bug in the future (if/when we figure out how to).

    See examples above if the rationale isn't clear.

  2. glaebhoerl commented on Jul 1, 2014

    @glaebhoerl
    ContributorAuthor

    Yes, this is implied by the term "uninhabited type". (Or do you mean that there's a definition of an "empty type" in some official spec that needs updating?)

  3. nikomatsakis commented on Jul 3, 2014

    @nikomatsakis
    Contributor

    I agree with @glaebhoerl, though this is clearly a corner case. I wonder if we should remove disallow uninhabited enums and make our lives easier?

  4. pnkfelix commented on Jul 3, 2014

    @pnkfelix
    Contributor

    /me cannot help but point at #4499 (comment) (though arguably feature-gating is quite different from outright removal...)

  5. pnkfelix commented on Jul 3, 2014

    @pnkfelix
    Contributor

    Nominating, P-backcompat-lang. See #12609 (comment) for the basis.

  6. pnkfelix commented on Jul 3, 2014

    @pnkfelix
    Contributor

    Assigning P-backcompat-lang, 1.0 milestone.

    (There are many interesting ways to resolve this, and some not-so-interesting ways.)

  7. added this to the 1.0 milestone on Jul 3, 2014
  8. sfackler commented on Jul 3, 2014

    @sfackler
    Member

    Uninhabited enums are useful as phantom type parameters.

  9. pcwalton commented on Jul 4, 2014

    @pcwalton
    Contributor

    OCaml has the same behavior as Rust today:

    type foo;;
    
    let x: foo = Obj.magic 0 in
    match x with
    | _ -> print_endline "hello darkness my old friend";; (* OK *)
    
  10. pcwalton commented on Jul 4, 2014

    @pcwalton
    Contributor

    SML/NJ ICE's:

    datatype foo = FOO of foo;
    
    val x: foo = Unsafe.cast 0;
    case x of _ => print "hello darkness my old friend";
    
    Error: Compiler bug: PPObj: switch: none of the datacons matched
    
  11. brson commented on Jul 4, 2014

    @brson
    Contributor

    @pcwalton that's hilarious

  12. 38 remaining items

  13. canndrew commented on Sep 14, 2016

    @canndrew
    Contributor

    I've had a crack at implementing this here: #36476
    It seems to work but definitely needs review from someone who actually understands the code I hacked on.

    Also, the change I've implemented allows people to omit patterns that are unreachable due to uninhabited types but doesn't force them to. This is important for backwards-compatibility but also because I think code like this:

    let res: Result<T, E> = ...;
    match res {
        Ok(t) => ...,
        Err(e) => ...,
    }

    should be allowed to compile even when E (or T) is uninhabited. This might be important for someone who's (eg.) generating code with a macro.

  14. added a commit that references this issue on Nov 30, 2016
  15. glaebhoerl commented on Jan 6, 2017

    @glaebhoerl
    ContributorAuthor

    So should this be closed, or do we wait for it to hit stable?

  16. nikomatsakis commented on Jan 7, 2017

    @nikomatsakis
    Contributor

    We can close.

  17. added a commit that references this issue on Apr 18, 2024
  18. added and removed on Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-type-systemArea: Type systemP-lowLow priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions