diff --git a/doc/data/messages/u/unreachable-match-patterns/bad.py b/doc/data/messages/u/unreachable-match-patterns/bad.py new file mode 100644 index 0000000000..deafc630a9 --- /dev/null +++ b/doc/data/messages/u/unreachable-match-patterns/bad.py @@ -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 :(") diff --git a/doc/data/messages/u/unreachable-match-patterns/good.py b/doc/data/messages/u/unreachable-match-patterns/good.py new file mode 100644 index 0000000000..0278641b89 --- /dev/null +++ b/doc/data/messages/u/unreachable-match-patterns/good.py @@ -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 :(") diff --git a/doc/data/messages/u/unreachable-match-patterns/related.rst b/doc/data/messages/u/unreachable-match-patterns/related.rst new file mode 100644 index 0000000000..bb0fe8f41e --- /dev/null +++ b/doc/data/messages/u/unreachable-match-patterns/related.rst @@ -0,0 +1 @@ +- `PEP 636 `_ diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 670b7d67ec..5260c3676c 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -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 ~~~~~~~~~~~~~~~~~~~ diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index fc487fc25c..fb8c2f3aa7 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -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 diff --git a/doc/whatsnew/fragments/7128.new_check b/doc/whatsnew/fragments/7128.new_check new file mode 100644 index 0000000000..5d8df3c427 --- /dev/null +++ b/doc/whatsnew/fragments/7128.new_check @@ -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 diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py new file mode 100644 index 0000000000..dc7c7c41bd --- /dev/null +++ b/pylint/checkers/match_statements_checker.py @@ -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)) diff --git a/tests/functional/u/unreachable_match_patterns.py b/tests/functional/u/unreachable_match_patterns.py new file mode 100644 index 0000000000..19986fbccd --- /dev/null +++ b/tests/functional/u/unreachable_match_patterns.py @@ -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 diff --git a/tests/functional/u/unreachable_match_patterns.txt b/tests/functional/u/unreachable_match_patterns.txt new file mode 100644 index 0000000000..c847ff79b4 --- /dev/null +++ b/tests/functional/u/unreachable_match_patterns.txt @@ -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