Skip to content

Add unreachable-match-patterns message and new checker. #10424

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/data/messages/u/unreachable-match-patterns/bad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
red = 0
green = 1
blue = 2


color = blue
match color:
case red: # [unreachable-match-patterns]
print("I see red!")
case green: # [unreachable-match-patterns]
print("Grass is green")
case blue:
print("I'm feeling the blues :(")
17 changes: 17 additions & 0 deletions doc/data/messages/u/unreachable-match-patterns/good.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from enum import Enum


class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2


color = Color.BLUE
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
1 change: 1 addition & 0 deletions doc/data/messages/u/unreachable-match-patterns/related.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `PEP 636 <https://peps.python.org/pep-0636/#matching-against-constants-and-enums>`_
12 changes: 12 additions & 0 deletions doc/user_guide/checkers/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,18 @@ Logging checker Messages
format-interpolation is disabled then you can use str.format.


Match Statements checker
~~~~~~~~~~~~~~~~~~~~~~~~

Verbatim name of the checker is ``match_statements``.

Match Statements checker Messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:unreachable-match-patterns (E5000): *The name capture `case %s` makes the remaining patterns unreachable. Use a dotted name(for example an enum) to fix this*
Emitted when a name capture pattern in a match statement is used and there
are case statements below it.


Method Args checker
~~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions doc/user_guide/messages/messages_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ All messages in the error category:
error/unexpected-special-method-signature
error/unhashable-member
error/unpacking-non-sequence
error/unreachable-match-patterns
error/unrecognized-inline-option
error/unrecognized-option
error/unsubscriptable-object
Expand Down
6 changes: 6 additions & 0 deletions doc/whatsnew/fragments/7128.new_check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Add ``match-statements`` checker and the following message:
``unreachable-match-patterns``.
This will emit an error message when a name capture pattern is used in a match statement which would make the remaining patterns unreachable.
This code is a SyntaxError at runtime.

Closes #7128
54 changes: 54 additions & 0 deletions pylint/checkers/match_statements_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt

"""Match statement checker for Python code."""

from __future__ import annotations

from typing import TYPE_CHECKING

from astroid import nodes

from pylint.checkers import BaseChecker
from pylint.checkers.utils import only_required_for_messages
from pylint.interfaces import HIGH

if TYPE_CHECKING:
from pylint.lint import PyLinter


class MatchStatementChecker(BaseChecker):
name = "match_statements"
msgs = {
"E5000": (
"The name capture `case %s` makes the remaining patterns unreachable. "
"Use a dotted name(for example an enum) to fix this",
"unreachable-match-patterns",
"Emitted when a name capture pattern in a match statement is used "
"and there are case statements below it.",
)
}

@only_required_for_messages("unreachable-match-patterns")
def visit_match(self, node: nodes.Match) -> None:
"""Check if a name capture pattern prevents the other cases from being
reached.
"""
for idx, case in enumerate(node.cases):
if (
isinstance(case.pattern, nodes.MatchAs)
and case.pattern.pattern is None
and isinstance(case.pattern.name, nodes.AssignName)
and idx < len(node.cases) - 1
):
self.add_message(
"unreachable-match-patterns",
node=case,
args=case.pattern.name.name,
confidence=HIGH,
)


def register(linter: PyLinter) -> None:
linter.register_checker(MatchStatementChecker(linter))
15 changes: 15 additions & 0 deletions tests/functional/u/unreachable_match_patterns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Functional tests for the ``unreachable-match-patterns`` message"""


a = 'a'
b = 'b'
s = 'a'


match s:
case a: # [unreachable-match-patterns]
pass
case b: # [unreachable-match-patterns]
pass
case s:
pass
2 changes: 2 additions & 0 deletions tests/functional/u/unreachable_match_patterns.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
unreachable-match-patterns:10:0:None:None::The name capture `case a` makes the remaining patterns unreachable. Use a dotted name(for example an enum) to fix this:HIGH
unreachable-match-patterns:12:0:None:None::The name capture `case b` makes the remaining patterns unreachable. Use a dotted name(for example an enum) to fix this:HIGH
Loading