pylint icon indicating copy to clipboard operation
pylint copied to clipboard

`possibly-used-before-assignment` does not handle `match` in nested `if`

Open noirbee opened this issue 1 year ago • 1 comments

Bug description

# pylint_used_before_assignment_nested_match.py

from enum import Enum
from typing import assert_never


class Example(Enum):
    FOO = 1
    BAR = 2


def check_value_only_match(example: Example) -> str | None:
    match example:
        case Example.FOO:
            result = "foo"
        case Example.BAR:
            result = "bar"
        case _:
            assert_never(example)

    return result


def check_value_if_then_if(example: Example, should_check: bool) -> str | None:
    if should_check:
        result = None
    else:
        if example == Example.FOO:
            result = "foo"
        elif example == Example.BAR:
            result = "bar"
        else:
            assert_never(example)

    return result


def check_value_if_then_match_return(example: Example, should_check: bool) -> str | None:
    if should_check:
        result = None
    else:
        match example:
            case Example.FOO:
                result = "foo"
            case Example.BAR:
                result = "bar"
            case _:
                return None

    return result


def check_value_if_then_match_raise(example: Example, should_check: bool) -> str | None:
    if should_check:
        result = None
    else:
        match example:
            case Example.FOO:
                result = "foo"
            case Example.BAR:
                result = "bar"
            case _:
                raise ValueError("Not a valid enum")

    return result


def check_value_if_then_match_assert_never(
    example: Example, should_check: bool
) -> str | None:
    if should_check:
        result = None
    else:
        match example:
            case Example.FOO:
                result = "foo"
            case Example.BAR:
                result = "bar"
            case _:
                assert_never(example)

    return result

Configuration

No response

Command used

% ./venv/bin/pylint --disable=all --enable=possibly-used-before-assignment pylint_used_before_assignment_nested_match.py

Pylint output

pylint_used_before_assignment_nested_match.py:50:11: E0606: Possibly using variable 'result' before assignment (possibly-used-before-assignment)
pylint_used_before_assignment_nested_match.py:65:11: E0606: Possibly using variable 'result' before assignment (possibly-used-before-assignment)
pylint_used_before_assignment_nested_match.py:82:11: E0606: Possibly using variable 'result' before assignment (possibly-used-before-assignment)

------------------------------------------------------------------
Your code has been rated at 6.59/10 (previous run: 7.22/10, -0.63)

Expected behavior

The first two functions are correctly handled by pylint, i.e.

  • a regular match works
  • two nested ifs work

I though this was a variant of https://github.com/pylint-dev/pylint/issues/9643 at first, with match not properly supported, but it's apparently a bit more complicated: it seems pylint has trouble understanding that the match nested under the first if is also "exhaustive". Note that this is triggered whatever "exit" type is used in the case _: fallthrough (return, raise or assert_never)

Pylint version

pylint 3.2.2
astroid 3.2.2
Python 3.11.2 (main, Feb 28 2023, 10:36:52) [GCC 12.2.0]

OS / Environment

% lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux trixie/sid
Release:        n/a
Codename:       trixie

Additional dependencies

No response

noirbee avatar May 22 '24 09:05 noirbee

I ran into the same issue after upgrading to pylint: in pylint 3.1.x the problem doesn't occur and since 3.2.0 it does.

Here is a more minimal test case: (although it's actually pretty similar to the fourth test case above)

def f(x):
    if x is None:
        y = 0
    else:
        match x:
            case int():
                y = x
            case _:
                raise TypeError(type(x))
    return y

Output:

testcase.py:10:11: E0606: Possibly using variable 'y' before assignment (possibly-used-before-assignment)

Versions used:

pylint 3.2.3
astroid 3.2.2
Python 3.11.9

mthuurne avatar Jun 16 '24 03:06 mthuurne