From 6ed94fde79aa2ee424f2f72efedc76a9c362d27e Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Wed, 11 Jun 2025 16:26:09 +0200 Subject: [PATCH 01/16] 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 --- .../u/unreachable-match-patterns/bad.py | 13 ++++ .../u/unreachable-match-patterns/good.py | 15 +++++ .../u/unreachable-match-patterns/related.rst | 1 + doc/user_guide/checkers/features.rst | 12 ++++ doc/user_guide/messages/messages_overview.rst | 1 + doc/whatsnew/fragments/7128.new_check | 6 ++ pylint/checkers/match_statements_checker.py | 59 +++++++++++++++++++ .../u/unreachable_match_patterns.py | 15 +++++ .../u/unreachable_match_patterns.rc | 2 + .../u/unreachable_match_patterns.txt | 2 + 10 files changed, 126 insertions(+) create mode 100644 doc/data/messages/u/unreachable-match-patterns/bad.py create mode 100644 doc/data/messages/u/unreachable-match-patterns/good.py create mode 100644 doc/data/messages/u/unreachable-match-patterns/related.rst create mode 100644 doc/whatsnew/fragments/7128.new_check create mode 100644 pylint/checkers/match_statements_checker.py create mode 100644 tests/functional/u/unreachable_match_patterns.py create mode 100644 tests/functional/u/unreachable_match_patterns.rc create mode 100644 tests/functional/u/unreachable_match_patterns.txt 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..0441b06b24 --- /dev/null +++ b/doc/data/messages/u/unreachable-match-patterns/good.py @@ -0,0 +1,15 @@ +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..b588ce80d5 --- /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..06ec179125 --- /dev/null +++ b/pylint/checkers/match_statements_checker.py @@ -0,0 +1,59 @@ +# 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.", + ) + } + + def open(self) -> None: + py_version = self.linter.config.py_version + self._py310_plus = py_version >= (3, 10) + + @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 + and self._py310_plus + ): + 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.rc b/tests/functional/u/unreachable_match_patterns.rc new file mode 100644 index 0000000000..68a8c8ef15 --- /dev/null +++ b/tests/functional/u/unreachable_match_patterns.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.10 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 From 131fe47350d06d5a0e1a233a4ed7d352e30a6e93 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:56:05 +0000 Subject: [PATCH 02/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/data/messages/u/unreachable-match-patterns/good.py | 2 ++ pylint/checkers/match_statements_checker.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/data/messages/u/unreachable-match-patterns/good.py b/doc/data/messages/u/unreachable-match-patterns/good.py index 0441b06b24..0278641b89 100644 --- a/doc/data/messages/u/unreachable-match-patterns/good.py +++ b/doc/data/messages/u/unreachable-match-patterns/good.py @@ -1,4 +1,6 @@ from enum import Enum + + class Color(Enum): RED = 0 GREEN = 1 diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index 06ec179125..31ff11ff1c 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -37,7 +37,7 @@ def open(self) -> None: @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 + reached. """ for idx, case in enumerate(node.cases): if ( From af24ce1b04ba694e55d5cafea1aa03a9fd02a69e Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Thu, 12 Jun 2025 11:01:39 +0200 Subject: [PATCH 03/16] Remove the trailing `/` from the url. --- doc/data/messages/u/unreachable-match-patterns/related.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/data/messages/u/unreachable-match-patterns/related.rst b/doc/data/messages/u/unreachable-match-patterns/related.rst index b588ce80d5..bb0fe8f41e 100644 --- a/doc/data/messages/u/unreachable-match-patterns/related.rst +++ b/doc/data/messages/u/unreachable-match-patterns/related.rst @@ -1 +1 @@ -- `PEP 636 `_ +- `PEP 636 `_ From b3e8f2d87c5fb6bd5cc2c236f27da76d5bf8ecf1 Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Mon, 16 Jun 2025 23:05:08 +0200 Subject: [PATCH 04/16] Remove the unnecessary logic which checks if the version of Python is 3.10 or higher. --- pylint/checkers/match_statements_checker.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index 31ff11ff1c..dc7c7c41bd 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -30,10 +30,6 @@ class MatchStatementChecker(BaseChecker): ) } - def open(self) -> None: - py_version = self.linter.config.py_version - self._py310_plus = py_version >= (3, 10) - @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 @@ -45,7 +41,6 @@ def visit_match(self, node: nodes.Match) -> None: and case.pattern.pattern is None and isinstance(case.pattern.name, nodes.AssignName) and idx < len(node.cases) - 1 - and self._py310_plus ): self.add_message( "unreachable-match-patterns", From 50590cd826a1ad3f781295c48d278892a462d240 Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Mon, 16 Jun 2025 23:07:17 +0200 Subject: [PATCH 05/16] Remove unnecessary `.rc` file from the functional test. --- tests/functional/u/unreachable_match_patterns.rc | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 tests/functional/u/unreachable_match_patterns.rc diff --git a/tests/functional/u/unreachable_match_patterns.rc b/tests/functional/u/unreachable_match_patterns.rc deleted file mode 100644 index 68a8c8ef15..0000000000 --- a/tests/functional/u/unreachable_match_patterns.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.10 From aabe71c2856b864c8dced54d8b0a894324e19459 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:24:19 +0200 Subject: [PATCH 06/16] Update pylint/checkers/match_statements_checker.py Co-authored-by: Jacob Walls --- pylint/checkers/match_statements_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index dc7c7c41bd..0dec7829ef 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -23,7 +23,7 @@ class MatchStatementChecker(BaseChecker): msgs = { "E5000": ( "The name capture `case %s` makes the remaining patterns unreachable. " - "Use a dotted name(for example an enum) to fix this", + "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.", From e2c5ed9f2678f99517e79a9b8f9bfea8c94318d1 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:24:29 +0200 Subject: [PATCH 07/16] Update doc/user_guide/checkers/features.rst Co-authored-by: Jacob Walls --- doc/user_guide/checkers/features.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 5260c3676c..12d0efe494 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -681,7 +681,7 @@ 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 + Emitted when a name capture pattern is used in a match statement and there are case statements below it. From a62ca0f1f26a3bebfe3583822deb54f9a3a7005f Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Sat, 2 Aug 2025 15:44:33 +0200 Subject: [PATCH 08/16] Update the message ID using the script which finds the next available ID. (pylint/script/get_unused_message_id_category.py). --- doc/user_guide/checkers/features.rst | 4 ++-- pylint/checkers/match_statements_checker.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 12d0efe494..a4fd4f0faf 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -680,8 +680,8 @@ 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 is used in a match statement and there +:unreachable-match-patterns (E1901): *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. diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index 0dec7829ef..24c44c7de9 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -21,7 +21,7 @@ class MatchStatementChecker(BaseChecker): name = "match_statements" msgs = { - "E5000": ( + "E1901": ( "The name capture `case %s` makes the remaining patterns unreachable. " "Use a dotted name (for example an enum) to fix this.", "unreachable-match-patterns", From 7681772ede773f0e1633e6cd2cbbabcca605fca4 Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Sat, 2 Aug 2025 15:45:58 +0200 Subject: [PATCH 09/16] Update the functional test output in response to the modified message text in the checker code. --- tests/functional/u/unreachable_match_patterns.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/u/unreachable_match_patterns.txt b/tests/functional/u/unreachable_match_patterns.txt index c847ff79b4..201cd6c1fc 100644 --- a/tests/functional/u/unreachable_match_patterns.txt +++ b/tests/functional/u/unreachable_match_patterns.txt @@ -1,2 +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 +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 From cc1f227f34f384eafbd038b4e91c3b9403b111fd Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Sat, 2 Aug 2025 16:06:03 +0200 Subject: [PATCH 10/16] Exclude doc example from Ruff. The example intentionally contains a `SyntaxError` which will also raise a `SyntaxError` from Ruff. --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5df487e4f6..16396fb8ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,7 @@ repos: - id: ruff-check name: ruff-doc files: doc/data/messages + exclude: doc/data/messages/u/unreachable-match-patterns/bad.py - repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit rev: 0.1.2 hooks: From 68aaf3427d13798847b4a1a3ef233e48298d5748 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:27:32 +0200 Subject: [PATCH 11/16] Update .pre-commit-config.yaml Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16396fb8ca..f09ebec2ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,8 @@ repos: - id: ruff-check name: ruff-doc files: doc/data/messages - exclude: doc/data/messages/u/unreachable-match-patterns/bad.py + # Please exclude using doc/data/ruff.toml + # exclude: "" # Leave empty - repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit rev: 0.1.2 hooks: From 26a0f0ae913dee5f0957f6e34550e635b7599445 Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Sun, 3 Aug 2025 14:35:36 +0200 Subject: [PATCH 12/16] Exclude the example code documentation via ruff.toml instead. --- doc/data/ruff.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/data/ruff.toml b/doc/data/ruff.toml index 23fd5eec46..b15c3839f8 100644 --- a/doc/data/ruff.toml +++ b/doc/data/ruff.toml @@ -5,9 +5,10 @@ line-length = 103 extend-exclude = [ "messages/d/duplicate-argument-name/bad.py", "messages/s/syntax-error/bad.py", + "messages/u/unreachable-match-patterns/bad.py", # syntax error in newer python versions "messages/s/star-needs-assignment-target/bad.py", - "messages/i/invalid-star-assignment-target/bad.py" + "messages/i/invalid-star-assignment-target/bad.py", ] [lint] From 43b300abbcf2d3168bb0ebcb7e0f1842645e2f69 Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Mon, 4 Aug 2025 09:28:38 +0200 Subject: [PATCH 13/16] Change the name of the message. --- .../bare-name-capture-pattern}/bad.py | 4 ++-- .../bare-name-capture-pattern}/good.py | 0 .../bare-name-capture-pattern}/related.rst | 0 doc/data/ruff.toml | 2 +- doc/user_guide/checkers/features.rst | 2 +- doc/user_guide/messages/messages_overview.rst | 2 +- doc/whatsnew/fragments/7128.new_check | 2 +- pylint/checkers/match_statements_checker.py | 6 +++--- tests/functional/b/bare_name_capture_pattern.py | 15 +++++++++++++++ tests/functional/b/bare_name_capture_pattern.txt | 2 ++ tests/functional/u/unreachable_match_patterns.py | 15 --------------- tests/functional/u/unreachable_match_patterns.txt | 2 -- 12 files changed, 26 insertions(+), 26 deletions(-) rename doc/data/messages/{u/unreachable-match-patterns => b/bare-name-capture-pattern}/bad.py (64%) rename doc/data/messages/{u/unreachable-match-patterns => b/bare-name-capture-pattern}/good.py (100%) rename doc/data/messages/{u/unreachable-match-patterns => b/bare-name-capture-pattern}/related.rst (100%) create mode 100644 tests/functional/b/bare_name_capture_pattern.py create mode 100644 tests/functional/b/bare_name_capture_pattern.txt delete mode 100644 tests/functional/u/unreachable_match_patterns.py delete mode 100644 tests/functional/u/unreachable_match_patterns.txt diff --git a/doc/data/messages/u/unreachable-match-patterns/bad.py b/doc/data/messages/b/bare-name-capture-pattern/bad.py similarity index 64% rename from doc/data/messages/u/unreachable-match-patterns/bad.py rename to doc/data/messages/b/bare-name-capture-pattern/bad.py index deafc630a9..f5c73a6899 100644 --- a/doc/data/messages/u/unreachable-match-patterns/bad.py +++ b/doc/data/messages/b/bare-name-capture-pattern/bad.py @@ -5,9 +5,9 @@ color = blue match color: - case red: # [unreachable-match-patterns] + case red: # [bare-name-capture-pattern] print("I see red!") - case green: # [unreachable-match-patterns] + case green: # [bare-name-capture-pattern] 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/b/bare-name-capture-pattern/good.py similarity index 100% rename from doc/data/messages/u/unreachable-match-patterns/good.py rename to doc/data/messages/b/bare-name-capture-pattern/good.py diff --git a/doc/data/messages/u/unreachable-match-patterns/related.rst b/doc/data/messages/b/bare-name-capture-pattern/related.rst similarity index 100% rename from doc/data/messages/u/unreachable-match-patterns/related.rst rename to doc/data/messages/b/bare-name-capture-pattern/related.rst diff --git a/doc/data/ruff.toml b/doc/data/ruff.toml index b15c3839f8..569416c0c0 100644 --- a/doc/data/ruff.toml +++ b/doc/data/ruff.toml @@ -5,7 +5,7 @@ line-length = 103 extend-exclude = [ "messages/d/duplicate-argument-name/bad.py", "messages/s/syntax-error/bad.py", - "messages/u/unreachable-match-patterns/bad.py", + "messages/b/bare-name-capture-pattern/bad.py", # syntax error in newer python versions "messages/s/star-needs-assignment-target/bad.py", "messages/i/invalid-star-assignment-target/bad.py", diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index a4fd4f0faf..3ac494a21b 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -680,7 +680,7 @@ Verbatim name of the checker is ``match_statements``. Match Statements checker Messages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:unreachable-match-patterns (E1901): *The name capture `case %s` makes the remaining patterns unreachable. Use a dotted name (for example an enum) to fix this.* +:bare-name-capture-pattern (E1901): *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. diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index fb8c2f3aa7..2d068bcb76 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -63,6 +63,7 @@ All messages in the error category: error/bad-str-strip-call error/bad-string-format-type error/bad-super-call + error/bare-name-capture-pattern error/bidirectional-unicode error/broken-collections-callable error/broken-noreturn @@ -165,7 +166,6 @@ 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 index 5d8df3c427..4f39a588f8 100644 --- a/doc/whatsnew/fragments/7128.new_check +++ b/doc/whatsnew/fragments/7128.new_check @@ -1,5 +1,5 @@ Add ``match-statements`` checker and the following message: -``unreachable-match-patterns``. +``bare-name-capture-pattern``. 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. diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index 24c44c7de9..e132b9edd2 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -24,13 +24,13 @@ class MatchStatementChecker(BaseChecker): "E1901": ( "The name capture `case %s` makes the remaining patterns unreachable. " "Use a dotted name (for example an enum) to fix this.", - "unreachable-match-patterns", + "bare-name-capture-pattern", "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") + @only_required_for_messages("bare-name-capture-pattern") def visit_match(self, node: nodes.Match) -> None: """Check if a name capture pattern prevents the other cases from being reached. @@ -43,7 +43,7 @@ def visit_match(self, node: nodes.Match) -> None: and idx < len(node.cases) - 1 ): self.add_message( - "unreachable-match-patterns", + "bare-name-capture-pattern", node=case, args=case.pattern.name.name, confidence=HIGH, diff --git a/tests/functional/b/bare_name_capture_pattern.py b/tests/functional/b/bare_name_capture_pattern.py new file mode 100644 index 0000000000..d656da33d6 --- /dev/null +++ b/tests/functional/b/bare_name_capture_pattern.py @@ -0,0 +1,15 @@ +"""Functional tests for the ``bare-name-capture-pattern`` message""" + + +a = 'a' +b = 'b' +s = 'a' + + +match s: + case a: # [bare-name-capture-pattern] + pass + case b: # [bare-name-capture-pattern] + pass + case s: + pass diff --git a/tests/functional/b/bare_name_capture_pattern.txt b/tests/functional/b/bare_name_capture_pattern.txt new file mode 100644 index 0000000000..699672b8c9 --- /dev/null +++ b/tests/functional/b/bare_name_capture_pattern.txt @@ -0,0 +1,2 @@ +bare-name-capture-pattern: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 +bare-name-capture-pattern: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 diff --git a/tests/functional/u/unreachable_match_patterns.py b/tests/functional/u/unreachable_match_patterns.py deleted file mode 100644 index 19986fbccd..0000000000 --- a/tests/functional/u/unreachable_match_patterns.py +++ /dev/null @@ -1,15 +0,0 @@ -"""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 deleted file mode 100644 index 201cd6c1fc..0000000000 --- a/tests/functional/u/unreachable_match_patterns.txt +++ /dev/null @@ -1,2 +0,0 @@ -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 From e55b3a65c1d82ad43762f71bfc47fa0e9758205b Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Mon, 4 Aug 2025 09:35:39 +0200 Subject: [PATCH 14/16] Move the ruff config line underneath a helpful comment. Co-authored-by: Pierre Sassoulas --- doc/data/ruff.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/data/ruff.toml b/doc/data/ruff.toml index 569416c0c0..8011fc4ca5 100644 --- a/doc/data/ruff.toml +++ b/doc/data/ruff.toml @@ -5,8 +5,8 @@ line-length = 103 extend-exclude = [ "messages/d/duplicate-argument-name/bad.py", "messages/s/syntax-error/bad.py", - "messages/b/bare-name-capture-pattern/bad.py", # syntax error in newer python versions + "messages/b/bare-name-capture-pattern/bad.py", # python 3.10 "messages/s/star-needs-assignment-target/bad.py", "messages/i/invalid-star-assignment-target/bad.py", ] From 0d9c4b58935dbbbe2c94d543b420723062f28eb4 Mon Sep 17 00:00:00 2001 From: Mark Byrne Date: Fri, 8 Aug 2025 20:01:26 +0200 Subject: [PATCH 15/16] Update the wording of the new message. Co-authored-by: Jacob Walls --- doc/user_guide/checkers/features.rst | 2 +- pylint/checkers/match_statements_checker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 3ac494a21b..7f758a047c 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -681,7 +681,7 @@ Verbatim name of the checker is ``match_statements``. Match Statements checker Messages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bare-name-capture-pattern (E1901): *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 + Emitted when a name capture pattern is used in a match statement and there are case statements below it. diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index e132b9edd2..ed84fb7e1b 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -25,7 +25,7 @@ class MatchStatementChecker(BaseChecker): "The name capture `case %s` makes the remaining patterns unreachable. " "Use a dotted name (for example an enum) to fix this.", "bare-name-capture-pattern", - "Emitted when a name capture pattern in a match statement is used " + "Emitted when a name capture pattern is used in a match statement " "and there are case statements below it.", ) } From 034d78f4d077d7e4d99e25fb3626d771299be744 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:46:16 +0200 Subject: [PATCH 16/16] Apply suggestions from code review Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .../b/bare-name-capture-pattern/bad.py | 16 +++++++------- .../b/bare-name-capture-pattern/good.py | 16 +++++++------- doc/data/ruff.toml | 2 +- pylint/checkers/match_statements_checker.py | 22 +++++++++---------- .../functional/b/bare_name_capture_pattern.py | 8 ++++--- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/data/messages/b/bare-name-capture-pattern/bad.py b/doc/data/messages/b/bare-name-capture-pattern/bad.py index f5c73a6899..4fad74f30c 100644 --- a/doc/data/messages/b/bare-name-capture-pattern/bad.py +++ b/doc/data/messages/b/bare-name-capture-pattern/bad.py @@ -3,11 +3,11 @@ blue = 2 -color = blue -match color: - case red: # [bare-name-capture-pattern] - print("I see red!") - case green: # [bare-name-capture-pattern] - print("Grass is green") - case blue: - print("I'm feeling the blues :(") +def func(color): + match color: + case red: # [bare-name-capture-pattern] + print("I see red!") + case green: # [bare-name-capture-pattern] + print("Grass is green") + case blue: + print("I'm feeling the blues :(") diff --git a/doc/data/messages/b/bare-name-capture-pattern/good.py b/doc/data/messages/b/bare-name-capture-pattern/good.py index 0278641b89..70b18db60e 100644 --- a/doc/data/messages/b/bare-name-capture-pattern/good.py +++ b/doc/data/messages/b/bare-name-capture-pattern/good.py @@ -7,11 +7,11 @@ class Color(Enum): 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 :(") +def func(color: Color) -> None: + 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/ruff.toml b/doc/data/ruff.toml index 8011fc4ca5..f5832a9f76 100644 --- a/doc/data/ruff.toml +++ b/doc/data/ruff.toml @@ -6,7 +6,7 @@ extend-exclude = [ "messages/d/duplicate-argument-name/bad.py", "messages/s/syntax-error/bad.py", # syntax error in newer python versions - "messages/b/bare-name-capture-pattern/bad.py", # python 3.10 + "messages/b/bare-name-capture-pattern/bad.py", "messages/s/star-needs-assignment-target/bad.py", "messages/i/invalid-star-assignment-target/bad.py", ] diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py index ed84fb7e1b..3561ba6a4e 100644 --- a/pylint/checkers/match_statements_checker.py +++ b/pylint/checkers/match_statements_checker.py @@ -36,18 +36,16 @@ def visit_match(self, node: nodes.Match) -> None: 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( - "bare-name-capture-pattern", - node=case, - args=case.pattern.name.name, - confidence=HIGH, - ) + match case.pattern: + case nodes.MatchAs(pattern=None, name=nodes.AssignName()) if ( + idx < len(node.cases) - 1 + ): + self.add_message( + "bare-name-capture-pattern", + node=case, + args=case.pattern.name.name, + confidence=HIGH, + ) def register(linter: PyLinter) -> None: diff --git a/tests/functional/b/bare_name_capture_pattern.py b/tests/functional/b/bare_name_capture_pattern.py index d656da33d6..504e1ff5b2 100644 --- a/tests/functional/b/bare_name_capture_pattern.py +++ b/tests/functional/b/bare_name_capture_pattern.py @@ -1,9 +1,9 @@ """Functional tests for the ``bare-name-capture-pattern`` message""" -a = 'a' -b = 'b' -s = 'a' +a = "a" +b = "b" +s = "a" match s: @@ -11,5 +11,7 @@ pass case b: # [bare-name-capture-pattern] pass + case "a" as some_name: + pass case s: pass