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

Stabilize min_exhaustive_patterns #122792

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Nadrieril
Copy link
Contributor

@Nadrieril Nadrieril commented Mar 20, 2024

Stabilisation report

I propose we stabilize the min_exhaustive_patterns language feature.

With this feature, patterns of empty types are considered unreachable when matched by-value. This allows:

enum Void {}
fn foo() -> Result<u32, Void>;

fn main() {
  let Ok(x) = foo();
  // also
  match foo() {
    Ok(x) => ...,
  }
}

This is a subset of the long-unstable exhaustive_patterns feature. That feature is blocked because omitting empty patterns is tricky when not matched by-value. This PR stabilizes the by-value case, which is not tricky.

The not-by-value cases (behind references, pointers, and unions) stay as they are today, e.g.

enum Void {}
fn foo() -> Result<u32, &Void>;

fn main() {
  let Ok(x) = foo(); // ERROR: missing `Err(_)`
}

The consequence on existing code is some extra "unreachable pattern" warnings. This is fully backwards-compatible.

Comparison with today's rust

This proposal only affects match checking of empty types (i.e. types with no valid values). Non-empty types behave the same with or without this feature. Note that everything below is phrased in terms of match but applies equallly to if let and other pattern-matching expressions.

To be precise, a visibly empty type is:

  • an enum with no variants;
  • the never type !;
  • a struct with a visible field of a visibly empty type (and no #[non_exhaustive] annotation);
  • a tuple where one of the types is visibly empty;
  • en enum with all variants visibly empty (and no #[non_exhaustive] annotation);
  • a [T; N] with N != 0 and T visibly empty;
  • all other types are nonempty.

(An extra change was proposed below: that we ignore #[non_exhaustive] for structs since adding fields cannot turn an empty struct into a non-empty one)

For normal types, exhaustiveness checking requires that we list all variants (or use a wildcard). For empty types it's more subtle: in some cases we require a _ pattern even though there are no valid values that can match it. This is where the difference lies regarding this feature.

Today's rust

Under today's rust, a _ is required for all empty types, except specifically: if the matched expression is of type ! (the never type) or EmptyEnum (where EmptyEnum is an enum with no variants), then the _ is not required.

let foo: Result<u32, !> = ...;
match foo {
    Ok(x) => ...,
    Err(_) => ..., // required
}
let foo: Result<u32, &!> = ...;
match foo {
    Ok(x) => ...,
    Err(_) => ..., // required
}
let foo: &! = ...;
match foo {
    _ => ..., // required
}
fn blah(foo: (u32, !)) {
    match foo {
        _ => ..., // required
    }
}
unsafe {
    let ptr: *const ! = ...;
    match *ptr {} // allowed
    let ptr: *const (u32, !) = ...;
    match *ptr {
        (x, _) => { ... } // required
    }
    let ptr: *const Result<u32, !> = ...;
    match *ptr {
        Ok(x) => { ... }
        Err(_) => { ... } // required
    }
}

After this PR

After this PR, a pattern of an empty type can be omitted if (and only if):

  • the match scrutinee expression has type ! or EmptyEnum (like before);
  • or the empty type is matched by value (that's the new behavior).

In all other cases, a _ is required to match on an empty type.

let foo: Result<u32, !> = ...;
match foo {
    Ok(x) => ..., // `Err` not required
}
let foo: Result<u32, &!> = ...;
match foo {
    Ok(x) => ...,
    Err(_) => ..., // required because `!` is under a dereference
}
let foo: &! = ...;
match foo {
    _ => ..., // required because `!` is under a dereference
}
fn blah(foo: (u32, !)) {
    match foo {} // allowed
}
unsafe {
    let ptr: *const ! = ...;
    match *ptr {} // allowed
    let ptr: *const (u32, !) = ...;
    match *ptr {
        (x, _) => { ... } // required because the matched place is under a (pointer) dereference
    }
    let ptr: *const Result<u32, !> = ...;
    match *ptr {
        Ok(x) => { ... }
        Err(_) => { ... } // required because the matched place is under a (pointer) dereference
    }
}

Documentation

The reference does not say anything specific about exhaustiveness checking, hence there is nothing to update there. The nomicon does, I opened rust-lang/nomicon#445 to reflect the changes.

Tests

The relevant tests are in tests/ui/pattern/usefulness/empty-types.rs.

Unresolved Questions

None that I know of.

@rustbot
Copy link
Collaborator

rustbot commented Mar 20, 2024

r? @cjgillot

rustbot has assigned @cjgillot.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 20, 2024
@Nadrieril

This comment was marked as resolved.

@Nadrieril Nadrieril marked this pull request as draft March 20, 2024 20:10
@Nadrieril Nadrieril removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 20, 2024
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@Nadrieril Nadrieril added T-lang Relevant to the language team, which will review and decide on the PR/issue. F-exhaustive_patterns `#![feature(exhaustive_patterns)]` labels Mar 20, 2024
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Mar 21, 2024

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

Some changes occurred in compiler/rustc_codegen_gcc

cc @antoyo, @GuillaumeGomez

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

The Miri subtree was changed

cc @rust-lang/miri

@Nadrieril Nadrieril added the I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. label Mar 21, 2024
@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Mar 27, 2024
@RalfJung
Copy link
Member

RalfJung commented Mar 27, 2024 via email

@scottmcm
Copy link
Member

Thank you, @RalfJung, you're right that I misread it. Updated my FCP text to include not-non_exhaustive structs with a visibly-empty field. (I do think that's a good thing to have too, just had thought it wasn't implemented because I misread #122792 (comment) .)

@jplatte
Copy link
Contributor

jplatte commented Mar 27, 2024

Quick thought, clippy nowadays takes rust-version into account for many lints. Does the compiler do the same, in particular for the lint for unreachable match arms? Would this be an easy addition, if it's not a thing yet?

@Nadrieril
Copy link
Contributor Author

Nadrieril commented Mar 30, 2024

I didn't write it explicitly but arrays and tuples can also be empty, in the expected way. I'll add it to the OP.

@RalfJung
Copy link
Member

For arrays I hope that [!; 0] is considered non-empty as it should? Please make sure we have a test for that.

@Nadrieril
Copy link
Contributor Author

To my own shock I did not find a test for that :') There's one now.

@rust-log-analyzer

This comment has been minimized.

@Nadrieril Nadrieril force-pushed the stabilize-min-exh-pats2 branch 2 times, most recently from 696076a to b7198f2 Compare March 30, 2024 16:12
@bors
Copy link
Contributor

bors commented Apr 3, 2024

☔ The latest upstream changes (presumably #122225) made this pull request unmergeable. Please resolve the merge conflicts.

@rust-log-analyzer

This comment has been minimized.

@Nadrieril
Copy link
Contributor Author

Nadrieril commented Apr 7, 2024

I stumbled upon #108993 which changes how we compute inhabitedness. If I follow correctly, this would mean we don't look into inferred types to determine inhabitedness. So the following, which works today (play), would error instead:

#![feature(min_exhaustive_patterns)]
#![allow(unused)]

fn infer<F: FnOnce() -> R, R>(_: Result<bool, R>) {}

enum Void {}
fn with_void() {
    let mut x = Ok(true);
    match x {
        Ok(_) => {}
    }
    infer::<fn() -> Void, _>(x)
}

Hence #108993 becomes a breaking change. We thus need to decide whether to accept #108993 before we stabilize min_exhaustive_patterns.

@cjgillot
Copy link
Contributor

cjgillot commented Apr 7, 2024

I'd reason the other way around: stabilize this PR and adapt #108993 on those grounds. #108993 is a bit too eager to declare some code unreachable, which is an issue in itself.

@Nadrieril
Copy link
Contributor Author

Would it make sense to put #108993 under this feature gate?

@traviscross
Copy link
Contributor

It's interesting that we now have a cycle. #108993 was blocked on the stabilization of exhaustive patterns. But to stabilize exhaustive patterns, we need to land #108993 or decide not to do so (completing the cycle...).

@bors
Copy link
Contributor

bors commented Apr 11, 2024

☔ The latest upstream changes (presumably #123762) made this pull request unmergeable. Please resolve the merge conflicts.

@Nadrieril
Copy link
Contributor Author

Alright, having chatted with @cjgillot and @WaffleLapkin I no longer think it would make sense for exhaustiveness to infer less than it does today. Normalization is important to patterns behaving consistently. My concern is resolved and we don't need to wait for #108993.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-exhaustive_patterns `#![feature(exhaustive_patterns)]` I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet