From 2971fe3167a80f0f52600ae14952e30b1ef84cff Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 6 Aug 2025 02:26:49 +0200 Subject: [PATCH] Update checks for PEP 765 - return/break/continue in finally block --- doc/data/messages/b/break-in-finally/bad.py | 5 +++ doc/data/messages/b/break-in-finally/good.py | 7 ++++ .../messages/b/break-in-finally/related.rst | 2 ++ .../c/continue-in-finally/details.rst | 1 - .../messages/c/continue-in-finally/pylintrc | 2 -- .../c/continue-in-finally/related.rst | 2 ++ .../messages/r/return-in-finally/related.rst | 1 + doc/user_guide/checkers/features.rst | 9 +++-- doc/user_guide/messages/messages_overview.rst | 3 +- doc/whatsnew/fragments/10480.breaking | 5 +++ doc/whatsnew/fragments/10480.new_check | 5 +++ pylint/checkers/base/basic_error_checker.py | 33 ++++++++++++------- pyproject.toml | 8 ++++- tests/functional/c/continue_in_finally.py | 2 +- tests/functional/c/continue_in_finally.rc | 2 -- tests/functional/c/continue_in_finally.txt | 3 +- 16 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 doc/data/messages/b/break-in-finally/bad.py create mode 100644 doc/data/messages/b/break-in-finally/good.py create mode 100644 doc/data/messages/b/break-in-finally/related.rst delete mode 100644 doc/data/messages/c/continue-in-finally/details.rst delete mode 100644 doc/data/messages/c/continue-in-finally/pylintrc create mode 100644 doc/data/messages/c/continue-in-finally/related.rst create mode 100644 doc/whatsnew/fragments/10480.breaking create mode 100644 doc/whatsnew/fragments/10480.new_check delete mode 100644 tests/functional/c/continue_in_finally.rc diff --git a/doc/data/messages/b/break-in-finally/bad.py b/doc/data/messages/b/break-in-finally/bad.py new file mode 100644 index 0000000000..6d36006ad9 --- /dev/null +++ b/doc/data/messages/b/break-in-finally/bad.py @@ -0,0 +1,5 @@ +while True: + try: + pass + finally: + break # [break-in-finally] diff --git a/doc/data/messages/b/break-in-finally/good.py b/doc/data/messages/b/break-in-finally/good.py new file mode 100644 index 0000000000..87189de69e --- /dev/null +++ b/doc/data/messages/b/break-in-finally/good.py @@ -0,0 +1,7 @@ +while True: + try: + pass + except ValueError: + pass + else: + break diff --git a/doc/data/messages/b/break-in-finally/related.rst b/doc/data/messages/b/break-in-finally/related.rst new file mode 100644 index 0000000000..6ebcdf496b --- /dev/null +++ b/doc/data/messages/b/break-in-finally/related.rst @@ -0,0 +1,2 @@ +- `Python 3 docs 'finally' clause `_ +- `PEP 765 - Disallow return/break/continue that exit a finally block `_ diff --git a/doc/data/messages/c/continue-in-finally/details.rst b/doc/data/messages/c/continue-in-finally/details.rst deleted file mode 100644 index 2d9044ee1a..0000000000 --- a/doc/data/messages/c/continue-in-finally/details.rst +++ /dev/null @@ -1 +0,0 @@ -Note this message can't be emitted when using Python version 3.8 or greater. diff --git a/doc/data/messages/c/continue-in-finally/pylintrc b/doc/data/messages/c/continue-in-finally/pylintrc deleted file mode 100644 index b7a429f3d4..0000000000 --- a/doc/data/messages/c/continue-in-finally/pylintrc +++ /dev/null @@ -1,2 +0,0 @@ -[MAIN] -py-version=3.7 diff --git a/doc/data/messages/c/continue-in-finally/related.rst b/doc/data/messages/c/continue-in-finally/related.rst new file mode 100644 index 0000000000..6ebcdf496b --- /dev/null +++ b/doc/data/messages/c/continue-in-finally/related.rst @@ -0,0 +1,2 @@ +- `Python 3 docs 'finally' clause `_ +- `PEP 765 - Disallow return/break/continue that exit a finally block `_ diff --git a/doc/data/messages/r/return-in-finally/related.rst b/doc/data/messages/r/return-in-finally/related.rst index f11b6809e6..6ebcdf496b 100644 --- a/doc/data/messages/r/return-in-finally/related.rst +++ b/doc/data/messages/r/return-in-finally/related.rst @@ -1 +1,2 @@ - `Python 3 docs 'finally' clause `_ +- `PEP 765 - Disallow return/break/continue that exit a finally block `_ diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 670b7d67ec..e44425f10d 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -57,9 +57,6 @@ Basic checker Messages Used when break or continue keywords are used outside a loop. :function-redefined (E0102): *%s already defined line %s* Used when a function / class / method is redefined. -:continue-in-finally (E0116): *'continue' not supported inside 'finally' clause* - Emitted when the `continue` keyword is found inside a finally clause, which - is a SyntaxError. :abstract-class-instantiated (E0110): *Abstract class %r with abstract methods instantiated* Used when an abstract class with `abc.ABCMeta` as metaclass has abstract methods and is instantiated. @@ -108,6 +105,12 @@ Basic checker Messages Used when a break or a return statement is found inside the finally clause of a try...finally block: the exceptions raised in the try clause will be silently swallowed instead of being re-raised. +:break-in-finally (W0137): *'break' discouraged inside 'finally' clause* + Emitted when the `break` keyword is found inside a finally clause. This + will raise a SyntaxWarning starting in Python 3.14. +:continue-in-finally (W0136): *'continue' discouraged inside 'finally' clause* + Emitted when the `continue` keyword is found inside a finally clause. This + will raise a SyntaxWarning starting in Python 3.14. :return-in-finally (W0134): *'return' shadowed by the 'finally' clause.* Emitted when a 'return' statement is found in a 'finally' block. This will overwrite the return value of a function and should be avoided. diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index fc487fc25c..1c2b9147b7 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -68,7 +68,6 @@ All messages in the error category: error/broken-noreturn error/catching-non-exception error/class-variable-slots-conflict - error/continue-in-finally error/declare-non-slot error/dict-iter-missing-items error/duplicate-argument-name @@ -223,6 +222,7 @@ All messages in the warning category: warning/bare-except warning/binary-op-exception warning/boolean-datetime + warning/break-in-finally warning/broad-exception-caught warning/broad-exception-raised warning/cell-var-from-loop @@ -230,6 +230,7 @@ All messages in the warning category: warning/confusing-with-statement warning/consider-ternary-expression warning/contextmanager-generator-missing-cleanup + warning/continue-in-finally warning/dangerous-default-value warning/deprecated-argument warning/deprecated-attribute diff --git a/doc/whatsnew/fragments/10480.breaking b/doc/whatsnew/fragments/10480.breaking new file mode 100644 index 0000000000..42b0272581 --- /dev/null +++ b/doc/whatsnew/fragments/10480.breaking @@ -0,0 +1,5 @@ +The message-id of ``continue-in-finally`` was changed from ``E0116`` to ``W0136``. The warning is +now emitted for every Python version since it will raise a syntax warning in Python 3.14. +See `PEP 765 - Disallow return/break/continue that exit a finally block `_. + +Refs #10480 diff --git a/doc/whatsnew/fragments/10480.new_check b/doc/whatsnew/fragments/10480.new_check new file mode 100644 index 0000000000..e25adeccb1 --- /dev/null +++ b/doc/whatsnew/fragments/10480.new_check @@ -0,0 +1,5 @@ +Add ``break-in-finally`` warning. Using ``break`` inside the ``finally`` clause +will raise a syntax warning in Python 3.14. +See `PEP 765 - Disallow return/break/continue that exit a finally block `_. + +Refs #10480 diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py index c02a2afb88..2ca185212f 100644 --- a/pylint/checkers/base/basic_error_checker.py +++ b/pylint/checkers/base/basic_error_checker.py @@ -182,12 +182,6 @@ class BasicErrorChecker(_BasicChecker): "nonlocal-and-global", "Emitted when a name is both nonlocal and global.", ), - "E0116": ( - "'continue' not supported inside 'finally' clause", - "continue-in-finally", - "Emitted when the `continue` keyword is found " - "inside a finally clause, which is a SyntaxError.", - ), "E0117": ( "nonlocal name %s found without binding", "nonlocal-without-binding", @@ -201,12 +195,22 @@ class BasicErrorChecker(_BasicChecker): "which results in an error since Python 3.6.", {"minversion": (3, 6)}, ), + "W0136": ( + "'continue' discouraged inside 'finally' clause", + "continue-in-finally", + "Emitted when the `continue` keyword is found " + "inside a finally clause. This will raise a SyntaxWarning " + "starting in Python 3.14.", + ), + "W0137": ( + "'break' discouraged inside 'finally' clause", + "break-in-finally", + "Emitted when the `break` keyword is found " + "inside a finally clause. This will raise a SyntaxWarning " + "starting in Python 3.14.", + ), } - def open(self) -> None: - py_version = self.linter.config.py_version - self._py38_plus = py_version >= (3, 8) - @utils.only_required_for_messages("function-redefined") def visit_classdef(self, node: nodes.ClassDef) -> None: self._check_redefinition("class", node) @@ -369,7 +373,7 @@ def visit_yieldfrom(self, node: nodes.YieldFrom) -> None: def visit_continue(self, node: nodes.Continue) -> None: self._check_in_loop(node, "continue") - @utils.only_required_for_messages("not-in-loop") + @utils.only_required_for_messages("not-in-loop", "break-in-finally") def visit_break(self, node: nodes.Break) -> None: self._check_in_loop(node, "break") @@ -497,9 +501,14 @@ def _check_in_loop( isinstance(parent, nodes.Try) and node in parent.finalbody and isinstance(node, nodes.Continue) - and not self._py38_plus ): self.add_message("continue-in-finally", node=node) + if ( + isinstance(parent, nodes.Try) + and node in parent.finalbody + and isinstance(node, nodes.Break) + ): + self.add_message("break-in-finally", node=node) self.add_message("not-in-loop", node=node, args=node_name) diff --git a/pyproject.toml b/pyproject.toml index 3335a9ae97..1c7cec4511 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -188,7 +188,13 @@ tests/functional/m/member/member_checks_async.py,\ testpaths = [ "tests" ] python_files = [ "*test_*.py" ] addopts = "--strict-markers" -filterwarnings = "error" +filterwarnings = [ + "error", + # Added in Python 3.14 + "ignore:'break' in a 'finally' block:SyntaxWarning", + "ignore:'return' in a 'finally' block:SyntaxWarning", + "ignore:'continue' in a 'finally' block:SyntaxWarning", +] markers = [ "primer_stdlib: Checks for crashes and errors when running pylint on stdlib", "benchmark: Baseline of pylint performance, if this regress something serious happened", diff --git a/tests/functional/c/continue_in_finally.py b/tests/functional/c/continue_in_finally.py index dcdac8f8d1..d03febe5d5 100644 --- a/tests/functional/c/continue_in_finally.py +++ b/tests/functional/c/continue_in_finally.py @@ -12,7 +12,7 @@ try: pass finally: - break + break # [break-in-finally] while True: try: diff --git a/tests/functional/c/continue_in_finally.rc b/tests/functional/c/continue_in_finally.rc deleted file mode 100644 index 77eb3be645..0000000000 --- a/tests/functional/c/continue_in_finally.rc +++ /dev/null @@ -1,2 +0,0 @@ -[main] -py-version=3.7 diff --git a/tests/functional/c/continue_in_finally.txt b/tests/functional/c/continue_in_finally.txt index a427b33691..e7db87753f 100644 --- a/tests/functional/c/continue_in_finally.txt +++ b/tests/functional/c/continue_in_finally.txt @@ -1 +1,2 @@ -continue-in-finally:9:8:9:16::'continue' not supported inside 'finally' clause:UNDEFINED +continue-in-finally:9:8:9:16::'continue' discouraged inside 'finally' clause:UNDEFINED +break-in-finally:15:8:15:13::'break' discouraged inside 'finally' clause:UNDEFINED