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

Incorrect non-exaustive match statement (where the user may think that all case are covered) #92197

Closed
robinmoussu opened this issue Dec 22, 2021 · 6 comments
Assignees
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns D-papercut Diagnostics: An error or lint that needs small tweaks. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@robinmoussu
Copy link

robinmoussu commented Dec 22, 2021

Given the following code:

match (order(lhs), order(rhs)) {
    (lhs_, rhs_) if lhs_  < rhs_ => std::cmp::Ordering::Less,
    (lhs_, rhs_) if lhs_  > rhs_ => std::cmp::Ordering::Greater,
    (lhs_, rhs_) if lhs_ == rhs_ => lhs.cmp(rhs),
}

The current output is:

  2 src/main.rs|26 col 15 error   4| non-exhaustive patterns: `(_, _)` not covered                                                                                                                                                                            
  3 ||    |
  4 || 26 |         match (order(lhs), order(rhs)) {
  5 ||    |               ^^^^^^^^^^^^^^^^^^^^^^^^ pattern `(_, _)` not covered
  6 ||    |
  7 ||    = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
  8 ||    = note: the matched value is of type `(i32, i32)`

For more information about this error, try `rustc --explain E0004`.

At first, I thought it was a compiler bug (an understandable one since exhaustiveness checking is hard), but then I realized that an buggy (or adversarial) implementation of Ord could lead to lhs being neither less than, greater than nor equal to rhs. So the compiler is right, just confusing.

Ideally the output should look like (line 8 has been edited):

  2 src/main.rs|26 col 15 error   4| non-exhaustive patterns: `(_, _)` not covered                                                                                                                                                                            
  3 ||    |
  4 || 26 |         match (order(lhs), order(rhs)) {
  5 ||    |               ^^^^^^^^^^^^^^^^^^^^^^^^ pattern `(_, _)` not covered
  6 ||    |
  7 ||    = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
  8 ||    = help: a bad implementation of comparison operators of the matched type could lead to all cases not being handled
  9 ||    = 
 10 ||    = note: the matched value is of type `(i32, i32)`

For more information about this error, try `rustc --explain E0004`.

And the error E0004 could contain or more detailed explanation of why such code isn’t covering all cases.

It addition, but I’m less sure of this, a suggestion to be made to either change the last arm to a wildcard (_ => lhs.cmp(rhs) or to add an extra arm _ => unreachable!() with an explanation of why the user should choose one or the other.

Of course in this specific case (i32, i32) we could trust the implementation, but I’m not sure either that it would be a good idea to special-case them (since it would be even more confusing for a user type that is not special cased).

@robinmoussu robinmoussu 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. labels Dec 22, 2021
@PatchMixolydic
Copy link
Contributor

PatchMixolydic commented Dec 22, 2021

This doesn't necessarily require an adversarial implementation of the comparison operators; (f32::NAN, f32::NAN) will not match any of these arms. This code panics (playground):

fn main() {
    match (f32::NAN, f32::NAN) {
        (x, y) if x > y => {},
        (x, y) if x < y => {},
        (x, y) if x == y => {},
        _ => unreachable!("oh no"),
    }
}

This is a correct implementation of the comparison operators since f32 implements PartialOrd, but not Ord, thus certain values of f32 are allowed to not have a defined order. Ord provides stronger guarantees, but as you've already mentioned, an adversarial implementation can easily break them.

@rustbot modify labels: +A-exhaustiveness-checking +D-papercut +D-terse

@rustbot rustbot added A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns D-papercut Diagnostics: An error or lint that needs small tweaks. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. labels Dec 22, 2021
@workingjubilee
Copy link
Member

workingjubilee commented Dec 23, 2021

I do not believe if guards factor into exhaustiveness checking at all, do they? Aside from them not being considered equivalent to the bare pattern. I believe the help could be improved, but it should probably go in an entirely different direction and explain that as an if-guard is actually "arbitrary code that returns a bool", it is not considered logically related.

@BGR360
Copy link
Contributor

BGR360 commented Dec 25, 2021

I do not believe if guards factor into exhaustiveness checking at all, do they?

Looks like nope (playground):

fn foo(i: i32) {
    match i {
        i if i == 0 => {},
        i if i != 0 => {},
    }
}

fn main() {}
error[E0004]: non-exhaustive patterns: `_` not covered
 --> src/main.rs:2:11
  |
2 |     match i {
  |           ^ pattern `_` not covered
  |
  = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
  = note: the matched value is of type `i32`

@dtolnay dtolnay changed the title Incorect non-exaustive match statement (where the user may think that all case are covered) Incorrect non-exaustive match statement (where the user may think that all case are covered) Mar 11, 2022
@Nadrieril
Copy link
Member

Guards indeed don't and likely will never count into exhaustiveness checking. I think that's a common mistake though? It would be nice if we could detect this.

A potential good start would be: if every arm in a match has a guard it's almost surely a mistake, so we can remind the user that guards don't count. If someone want to have a go, you likely want to add a check somewhere in this function

@Nadrieril Nadrieril added the E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. label Apr 4, 2023
@ericmarkmartin
Copy link
Contributor

@rustbot claim

Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Jun 28, 2023
…on-exhaustion, r=fee1-dead

add note for non-exhaustive matches with guards

Associated issue: rust-lang#92197

When a match statement includes guards on every match arm (and is therefore necessarily non-exhaustive), add a note to the error E0004 diagnostic noting this.
@Nadrieril
Copy link
Member

A note was added in #113019, thank you @ericmarkmartin! I think that's the most we can do about this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns D-papercut Diagnostics: An error or lint that needs small tweaks. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. 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

7 participants