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

Redundant conjunctive patterns (&, as) may trigger spurious exhaustiveness warning #18118

Open
brianrourkeboll opened this issue Dec 8, 2024 · 1 comment
Labels
Area-Compiler-PatternMatching pattern compilation, active patterns, performance, codegen Feature Improvement
Milestone

Comments

@brianrourkeboll
Copy link
Contributor

brianrourkeboll commented Dec 8, 2024

Redundant conjunctive patterns (&, as) may trigger a spurious exhaustiveness warning in some scenarios.

Repro steps

Minimal repro

A normal exhaustive pattern match does not trigger an exhaustiveness warning, as expected:

type T = A | B | C

let f x =
    match x with
    | A | B -> ()
    | C -> ()

However, given this example, adding a redundant match on case C with a conjunctive pattern like & or as will trigger a spurious exhaustiveness warning:

type T = A | B | C

let f x =
    match x with
    | A | B -> ()
    | C & C -> ()

//      match x with
//  ----------^

//stdin(4,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'A' may indicate a case not covered by the pattern(s).

More realistic/useful repro

Another, perhaps more realistic and useful example is this, where you might want to bind the first two elements of a list as well as the tail of the original list (i.e., including the second element but not the first):

let f xs =
    match xs with
    | [] | [_] -> ()
    | e1 :: e2 :: _ & _ :: tail -> ()

//      match xs with
//  ----------^^

//stdin(2,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value '[]' may indicate a case not covered by the pattern(s).

It is impossible to make the compiler happy in that scenario — we cannot turn e1 :: e2 :: _ & _ :: tail into (e1 :: e2 :: _ & _ :: tail | []), because we cannot bind e1, e2, or tail in the [] case which the compiler is (unnecessarily) asking us to add.

Note that no warning is emitted if one side of the conjunctive pattern is a simple named pattern, e.g.,

let f x =
    match x with // No warning.
    | A | B -> ()
    | C & c -> ()
let f x =
    match x with // No warning.
    | A | B -> ()
    | C as c -> ()

Codegen

This even affects codegen — an unreachable MatchFailureException branch is emitted:

type T = A | B | C

let f x =
    match x with // No warning.
    | A | B -> true
    | C -> false
    
let f' x =
    match x with // Warning, different codegen.
    | A | B -> true
    | C & C -> false

SharpLab

public static bool f(T x)
{
    switch (x._tag)
    {
        default:
            return true;
        case 2:
            return false;
    }
}

public static bool f'(T x)
{
    switch (x._tag)
    {
        default:
            return true;
        case 2:
            if (x._tag == 2)
            {
                return false;
            }
            throw new MatchFailureException("#mirrorsharp-virtual-fs\\f4765ba6-b1f5-44e9-b186-0abc7ae58e28\\_.fs", 10, 10);
    }
}

Expected behavior

No exhaustiveness warning should be emitted, and the codegen should be identical.

Actual behavior

An exhaustiveness warning and an unreachable MatchFailureException branch are emitted.

Known workarounds

N/A.

Related information

This behavior appears to be old — it reproduces for me in .NET 3.1, and it is still present in the latest .NET 9 SDK.

@T-Gro
Copy link
Member

T-Gro commented Dec 9, 2024

Hi Brian.

I think the compiler does go deep into intersection patterns and their logical relation. This is indeed a good feature improvement idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-PatternMatching pattern compilation, active patterns, performance, codegen Feature Improvement
Projects
Status: New
Development

No branches or pull requests

2 participants