Skip to content

Class-pattern coverage is not combined across match arms #4752

Description

@JafarAbdi

Describe the Bug

Hi folks,

Thanks for the package!! I really like non-exhaustive-match and have been using it. One issue I have run into recently is that pyrefly reports an exhaustive match as non-exhaustive when separate class-pattern arms jointly cover a field union.

Here is an example:

from dataclasses import dataclass
from typing import final

@final
@dataclass
class A:
    pass

@final
@dataclass
class B:
    pass

@final
@dataclass
class Box:
    value: A | B

def f(box: Box) -> int:
    match box:
        case Box(A()):
            return 1
        case Box(B()):
            return 2

Actual:

Function declared to return `int`, but one or more paths are missing an explicit `return`
Match on `Box` is not exhaustive
Missing cases: Box

Expected: no errors. The two arms jointly cover Box.value.

Related: #3731 handles coverage within one arm, but not across arms.

Reproduces on current main. Enable non-exhaustive-match to see the second diagnostic (See sandbox link please)

I'm not familiar with the pyrefly codebase, so I asked a clanker to take a look. I reviewed the result, but it's easy for the clanker to convince me it's doing the right thing.

The patch does this:

before: check each arm on its own
after:  keep that check, then look across unguarded arms when needed

The extra check keeps field combinations together and only removes the class when every combination is covered. If it cannot prove that, it leaves the type alone.

Draft patch: https://github.com/JafarAbdi/pyrefly/tree/fix/class-pattern-coverage-across-arms

Happy to open a PR if this patch looks reasonable.

Sandbox Link

https://pyrefly.org/sandbox/?project=v2.q8ZXZOLL9tCSAbVUBYcSKIpi8hygbMxM64gS_vjVOpGiNr8CtSxwVKhRcILoAucrjaT8CiuQMnCyzMwrgaeskuQMhSS4dhBITixOBSnVcNTQRM4USBnDEItqJ5yqjcBxhpZiEMkiLz9PN7UiI7G0GBRRuhAngSIcrAIc4_iiEQA

(Only applicable for extension issues) IDE Information

No response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions