Skip to content

Python match (PEP634) "or wildcard" pattern marked as missing branch #1421

@ljmc-github

Description

@ljmc-github

Describe the bug
Two equivalent function using the match/case construct with two different ways to use wildcard pattern are reported differently, with the "or wildcard" case marked as missing branch.

To Reproduce
Here is a minimal reproducible example, note testing is done pairwise to verify the functions as equivalent.

import os

import pytest


def wildcard_alone() -> str:

    match os.getenv("SOME_VAR", "default"):
        case "dev":
            return "development value"
        case "test" | "prod":
            return "production value"
        case "default":
            return "default value"
        case _:
            return "default value"


def wildcard_or() -> str:

    match os.getenv("SOME_VAR", "default"):
        case "dev":
            return "development value"
        case "test" | "prod":
            return "production value"
        case "default" | _:  # marked as missing
            return "default value"


@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone])
def test_match_env_dev(f, monkeypatch):
    monkeypatch.setenv("SOME_VAR", "dev")
    assert f() == "development value"


@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone])
def test_match_env_test(f, monkeypatch):
    monkeypatch.setenv("SOME_VAR", "test")
    assert f() == "production value"


@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone])
def test_match_env_prod(f, monkeypatch):
    monkeypatch.setenv("SOME_VAR", "prod")
    assert f() == "production value"


@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone])
def test_match_env_unset(f, monkeypatch):
    monkeypatch.delenv("SOME_VAR", raising=False)
    assert f() == "default value"


@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone])
def test_match_env_other(f, monkeypatch):
    monkeypatch.setenv("SOME_VAR", "unmatched")
    assert f() == "default value"

Here is the coverage.

% coverage run --branch -m pytest && coverage report -m
========================================================================================= test session starts =========================================================================================
platform darwin -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: /private/tmp/mre-coverage-case
collected 10 items

test_mre.py ..........                                                                                                                                                                          [100%]

========================================================================================= 10 passed in 0.05s ==========================================================================================
Name          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------
test_mre.py      40      0     12      1    98%   26->exit
---------------------------------------------------------
TOTAL            40      0     12      1    98%

And finally, the output of coverage debug sys.

-- sys -------------------------------------------------------
               coverage_version: 6.4.2
                coverage_module: /private/tmp/mre-coverage-case/.venv/lib/python3.10/site-packages/coverage/__init__.py
                         tracer: -none-
                        CTracer: available
           plugins.file_tracers: -none-
            plugins.configurers: -none-
      plugins.context_switchers: -none-
              configs_attempted: .coveragerc
                                 setup.cfg
                                 tox.ini
                                 pyproject.toml
                   configs_read: -none-
                    config_file: None
                config_contents: -none-
                      data_file: -none-
                         python: 3.10.5 (main, Jul  6 2022, 15:18:41) [Clang 13.1.6 (clang-1316.0.21.2.3)]
                       platform: macOS-12.2.1-x86_64-i386-64bit
                 implementation: CPython
                     executable: /private/tmp/mre-coverage-case/.venv/bin/python
                   def_encoding: utf-8
                    fs_encoding: utf-8
                            pid: 44594
                            cwd: /private/tmp/mre-coverage-case
                           path: /private/tmp/mre-coverage-case/.venv/bin
                                 /Users/redacted/.pyenv/versions/3.10.5/lib/python310.zip
                                 /Users/redacted/.pyenv/versions/3.10.5/lib/python3.10
                                 /Users/redacted/.pyenv/versions/3.10.5/lib/python3.10/lib-dynload
                                 /private/tmp/mre-coverage-case/.venv/lib/python3.10/site-packages
                    environment: HOME = /Users/redacted
                                 PYENV_DIR = /tmp/mre-coverage-case
                                 PYENV_HOOK_PATH = /Users/redacted/.pyenv/pyenv.d:/usr/local/Cellar/pyenv/2.3.2/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks
                                 PYENV_ROOT = /Users/redacted/.pyenv
                                 PYENV_SHELL = zsh
                                 PYENV_VERSION = 3.10.5
                   command_line: /private/tmp/mre-coverage-case/.venv/bin/coverage debug sys
                sqlite3_version: 2.6.0
         sqlite3_sqlite_version: 3.36.0
             sqlite3_temp_store: 0
        sqlite3_compile_options: BUG_COMPATIBLE_20160819, COMPILER=clang-13.0.0, DEFAULT_CACHE_SIZE=2000,
                                 DEFAULT_CKPTFULLFSYNC, DEFAULT_JOURNAL_SIZE_LIMIT=32768,
                                 DEFAULT_MEMSTATUS=0, DEFAULT_PAGE_SIZE=4096, DEFAULT_SYNCHRONOUS=2,
                                 DEFAULT_WAL_SYNCHRONOUS=1, ENABLE_API_ARMOR, ENABLE_BYTECODE_VTAB,
                                 ENABLE_COLUMN_METADATA, ENABLE_DBSTAT_VTAB, ENABLE_FTS3,
                                 ENABLE_FTS3_PARENTHESIS, ENABLE_FTS3_TOKENIZER, ENABLE_FTS4, ENABLE_FTS5,
                                 ENABLE_JSON1, ENABLE_LOCKING_STYLE=1, ENABLE_NORMALIZE,
                                 ENABLE_PREUPDATE_HOOK, ENABLE_RTREE, ENABLE_SESSION, ENABLE_SNAPSHOT,
                                 ENABLE_SQLLOG, ENABLE_STMT_SCANSTATUS, ENABLE_UNKNOWN_SQL_FUNCTION,
                                 ENABLE_UPDATE_DELETE_LIMIT, HAS_CODEC_RESTRICTED, HAVE_ISNAN,
                                 MAX_LENGTH=2147483645, MAX_MMAP_SIZE=1073741824,
                                 MAX_VARIABLE_NUMBER=500000, OMIT_AUTORESET, OMIT_LOAD_EXTENSION,
                                 STMTJRNL_SPILL=131072, SYSTEM_MALLOC, THREADSAFE=2, USE_URI

Expected behavior
All branches are covered and both function should be at 100% coverage.

Additional context
NA

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions