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 "unreachable pattern" warning. #70372

Closed
steffahn opened this issue Mar 24, 2020 · 12 comments · Fixed by #70413
Closed

Incorrect "unreachable pattern" warning. #70372

steffahn opened this issue Mar 24, 2020 · 12 comments · Fixed by #70413
Assignees
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. C-bug Category: This is a bug. E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. F-or_patterns `#![feature(or_patterns)]` P-medium Medium priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@steffahn
Copy link
Member

steffahn commented Mar 24, 2020

Not much to comment about. The supposedly “unreachable” pattern is obviously reached.

fn main() {
    match (3,42) {
        (a,_) | (_,a) if a > 10 => {println!("{}", a)}
        _ => ()
    }
}

(Playground)

Output:

42

Errors:

   Compiling playground v0.0.1 (/playground)
warning: unreachable pattern
 --> src/main.rs:4:17
  |
4 |         (a,_) | (_,a) if a > 10 => {println!("{}", a)}
  |                 ^^^^^
  |
  = note: `#[warn(unreachable_patterns)]` on by default

    Finished release [optimized] target(s) in 0.78s
     Running `target/release/playground`

This issue has been assigned to @AminArria via this comment.

@Centril Centril added E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. C-bug Category: This is a bug. A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. I-nominated P-medium Medium priority labels Mar 24, 2020
@Centril
Copy link
Contributor

Centril commented Mar 24, 2020

Looks like this was injected in 1.41.0, ostensibly in the exhaustiveness checking refactorings.
To find out, let's cc @rustbot ping cleanup

cc @varkor @Nadrieril

@rustbot
Copy link
Collaborator

rustbot commented Mar 24, 2020

Hey Cleanup Crew ICE-breakers! This bug has been identified as a good
"Cleanup ICE-breaking candidate". In case it's useful, here are some
instructions for tackling these sorts of bugs. Maybe take a look?
Thanks! <3

cc @AminArria @chrissimpkins @contrun @DutchGhost @elshize @ethanboxx @h-michael @HallerPatrick @hdhoang @hellow554 @imtsuki @jakevossen5 @kanru @KarlK90 @LeSeulArtichaut @MAdrianMattocks @matheus-consoli @mental32 @nmccarty @Noah-Kennedy @pard68 @PeytonT @pierreN @Redblueflame @RobbieClarken @RobertoSnap @robjtede @SarthakSingh31 @senden9 @shekohex @sinato @spastorino @turboladen @woshilapin @yerke

@rustbot rustbot added the ICEBreaker-Cleanup-Crew Helping to "clean up" bugs with minimal examples and bisections label Mar 24, 2020
@Centril
Copy link
Contributor

Centril commented Mar 24, 2020

The relevant code is in check_match::check_arms:

fn check_arms<'p, 'tcx>(
    cx: &mut MatchCheckCtxt<'p, 'tcx>,
    arms: &[(&'p super::Pat<'tcx>, HirId, bool)],
) -> Matrix<'p, 'tcx> {
    let mut seen = Matrix::empty();
    let mut catchall = None;
    for (arm_index, (pat, id, has_guard)) in arms.iter().copied().enumerate() {
        let v = PatStack::from_pattern(pat);
        match is_useful(cx, &seen, &v, LeaveOutWitness, id, true) {
            NotUseful => { ... } // Elided, this branch should not be taken in this case.
            Useful(unreachable_subpatterns) => {
                for pat in unreachable_subpatterns {
                    unreachable_pattern(cx.tcx, pat.span, id, None);
                }
            }
            UsefulWithWitness(_) => bug!(),
        }
        if !has_guard {
            seen.push(v);
            if catchall.is_none() && pat_is_catchall(pat) {
                catchall = Some(pat.span);
            }
        }
    }
    seen
}

From the point of exhaustiveness checking this algorithm is correct, but not from a linting POV in the presence of guards. Previously, before the or-patterns reform, we merely interpreted each | as a separate arm, so this was correctly handled from a linting perspective in the NotUseful => { ... } arm. Now however, the linting will happen in the Useful(unreachable_subpatterns) => { ... } arm, as according to is_useful(...), unaware of whether the arm has_guard, the pattern itself has an unreachable alternative.

To fix this, presumably we have to propagate has_guard into is_useful, while being careful not to declare anything exhaustive which isn't for the soundness sensitive bits.

@Centril Centril added the F-or_patterns `#![feature(or_patterns)]` label Mar 24, 2020
@steffahn
Copy link
Member Author

In case this is still relevant.. I went trying out cargo-bisect-rustc and it gave me:

regressed nightly: nightly-2019-12-04
regressed commit: f577b0e

@Centril Centril removed E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc ICEBreaker-Cleanup-Crew Helping to "clean up" bugs with minimal examples and bisections labels Mar 24, 2020
@Nadrieril
Copy link
Member

I'm going to assume we're ok with not detecting

match (3,42) {
    (a, _) | (a, _) if a > 10 => {println!("{}", a)}
    _ => ()
}

as redundant, otherwise we violate one of the assumptions of match checking, namely that variables and wildcards behave identically, and that's a whole other can of worms.

To fix this, presumably we have to propagate has_guard into is_useful, while being careful not to declare anything exhaustive which isn't for the soundness sensitive bits.

Agreed. Specifically, what we want to avoid is adding to the matrix any pattern that is under a match guard. The code in check_match does that correctly, but the or-pattern-handling code in _match doesn't. Once again I can't fix that myself right now, but I suspect it should be enough to disable this line:

when we are under a match guard. So passing a is_under_guard bool to is_useful (recursively) should do the trick.
@Centril (or anyone really) would you mind trying that ? 👼

Two more test cases to be sure we're doing it right:

match Some((3,42)) {
    Some((a, _)) | Some((_, a)) if a > 10 => {println!("{}", a)}
    _ => ()
}
match Some((3,42)) {
    Some((a, _) | (_, a)) if a > 10 => {println!("{}", a)}
    _ => ()
}

@varkor
Copy link
Member

varkor commented Mar 25, 2020

With such clear instructions, this looks like a good issue for beginners! I'll add the labels.

@varkor varkor added E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. labels Mar 25, 2020
@AminArria
Copy link
Contributor

Hi, I want to take this

I understand the solution explained by @Nadrieril, but have one question regarding check_not_useful because it also calls is_useful. From what I gather it generates a wildcard pattern and check it isn't useful, so for this task I can treat it as having no guard right?

@spastorino
Copy link
Member

@rustbot assign @AminArria

@rustbot rustbot self-assigned this Mar 25, 2020
@Nadrieril
Copy link
Member

From what I gather it generates a wildcard pattern and check it isn't useful, so for this task I can treat it as having no guard right?

Absolutely !

@Centril
Copy link
Contributor

Centril commented Mar 25, 2020

@AminArria Thanks for tackling this. :)

I think it would also be neat to add some comments on fn is_useful around what it means to pass in "yes it has a guard", specifically that it shouldn't be relied on for soundness (i.e. in check_not_useful). Perhaps also consider an enum HasGuard { Yes, No } or some such for added robustness (consider also turning is_top_level: bool into enum TopLevel { Yes, No } for stronger typing).

Best of luck!

@Centril
Copy link
Contributor

Centril commented Mar 25, 2020

I'm going to assume we're ok with not detecting

match (3,42) {
    (a, _) | (a, _) if a > 10 => {println!("{}", a)}
    _ => ()
}

as redundant, otherwise we violate one of the assumptions of match checking, namely that variables and wildcards behave identically, and that's a whole other can of worms.

👍 -- that seems like quite a reasonable tradeoff for what is a rare occurrence. :)

@varkor
Copy link
Member

varkor commented Mar 25, 2020

Perhaps also consider an enum HasGuard { Yes, No } or some such for added robustness (consider also turning is_top_level: bool into enum TopLevel { Yes, No } for stronger typing).

I don't think this is any more readable, or safe, than a bool. The variable name makes it very clear in the method, and we can comment the call site if that seems ambiguous.

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Mar 26, 2020
…arning, r=Centril,Nadrieril,varkor

Fix incorrect pattern warning "unreachable pattern"

Fixes rust-lang#70372

Added `is_under_guard` parameter to `_match::is_useful` and only add it to the matrix if `false`

Tested with:
```rust
#![feature(or_patterns)]
fn main() {
    match (3,42) {
        (a,_) | (_,a) if a > 10 => {println!("{}", a)}
        _ => ()
    }

    match Some((3,42)) {
        Some((a, _)) | Some((_, a)) if a > 10 => {println!("{}", a)}
        _ => ()

    }

    match Some((3,42)) {
        Some((a, _) | (_, a)) if a > 10 => {println!("{}", a)}
        _ => ()
    }
}
```
@bors bors closed this as completed in c640b95 Mar 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. C-bug Category: This is a bug. E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. F-or_patterns `#![feature(or_patterns)]` P-medium Medium priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. 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.

7 participants