From abae4c8d05ce910ea03678535635d77c63362c4c Mon Sep 17 00:00:00 2001 From: "Aleksandr.Govenko" Date: Fri, 4 Jul 2025 15:43:34 +0200 Subject: [PATCH] =?UTF-8?q?PY-80237=20Support=20PEP=20765=20=E2=80=93=20Di?= =?UTF-8?q?sallow=20return/break/continue=20that=20exit=20a=20finally=20bl?= =?UTF-8?q?ock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitOrigin-RevId: 7c5e956759071ccf96ebb8d31628569633977801 --- .../resources/messages/PyPsiBundle.properties | 2 + .../validation/PyCompatibilityVisitor.java | 29 ++++++++++-- .../Py3CompatibilityInspectionTest.java | 45 +++++++++++++++++++ .../PyCompatibilityInspectionTest.java | 2 +- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index c038302e8d1d..3a925b0f9b73 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -785,6 +785,8 @@ INSP.compatibility.feature.support.positional.only.parameters=support positional INSP.compatibility.support.equality.signs.in.fstrings=support equality signs in f-strings INSP.compatibility.feature.support.assignment.expressions=support assignment expressions INSP.compatibility.feature.support.continue.inside.finally.clause=support 'continue' inside 'finally' clause +INSP.compatibility.feature.support.break.inside.finally.clause=support 'break' inside 'finally' clause +INSP.compatibility.feature.support.return.inside.finally.clause=support 'return' inside 'finally' clause INSP.compatibility.feature.support.arbitrary.expressions.as.decorator=support arbitrary expressions as a decorator INSP.compatibility.feature.have.type.long=have type long. Use int instead. INSP.compatibility.feature.have.method=have method {0} diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyCompatibilityVisitor.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyCompatibilityVisitor.java index bfc06590becc..c4652aaff91e 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyCompatibilityVisitor.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyCompatibilityVisitor.java @@ -448,6 +448,14 @@ public abstract class PyCompatibilityVisitor extends PyElementVisitor { } } } + + if (PsiTreeUtil.getParentOfType(node, PyFinallyPart.class, false, PyFunction.class) != null) { + registerForAllMatchingVersions( + level -> level.isAtLeast(LanguageLevel.PYTHON314) && registerForLanguageLevel(level), + PyPsiBundle.message("INSP.compatibility.feature.support.return.inside.finally.clause"), + node + ); + } } @Override @@ -750,9 +758,24 @@ public abstract class PyCompatibilityVisitor extends PyElementVisitor { super.visitPyContinueStatement(node); if (PsiTreeUtil.getParentOfType(node, PyFinallyPart.class, false, PyLoopStatement.class) != null) { - registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON38) && registerForLanguageLevel(level), - PyPsiBundle.message("INSP.compatibility.feature.support.continue.inside.finally.clause"), - node); + registerForAllMatchingVersions( + level -> (level.isOlderThan(LanguageLevel.PYTHON38) || level.isAtLeast(LanguageLevel.PYTHON314)) && registerForLanguageLevel(level), + PyPsiBundle.message("INSP.compatibility.feature.support.continue.inside.finally.clause"), + node + ); + } + } + + @Override + public void visitPyBreakStatement(@NotNull PyBreakStatement node) { + super.visitPyBreakStatement(node); + + if (PsiTreeUtil.getParentOfType(node, PyFinallyPart.class, false, PyLoopStatement.class) != null) { + registerForAllMatchingVersions( + level -> level.isAtLeast(LanguageLevel.PYTHON314) && registerForLanguageLevel(level), + PyPsiBundle.message("INSP.compatibility.feature.support.break.inside.finally.clause"), + node + ); } } diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3CompatibilityInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3CompatibilityInspectionTest.java index 377855402eb1..4939683e721f 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3CompatibilityInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3CompatibilityInspectionTest.java @@ -20,6 +20,51 @@ import org.jetbrains.annotations.NotNull; public class Py3CompatibilityInspectionTest extends PyInspectionTestCase { + // PY-80237 + public void testBreakInFinallyBlock() { + doTestByText(""" + while True: + try: + print("a") + finally: + break""" + ); + } + + // PY-80237 + public void testReturnInFinallyBlock() { + doTestByText(""" + def foo(): + try: + pass + finally: + return""" + ); + } + + + // PY-80237 + public void testReturnInFunctionDefinitionInFinallyBlock() { + doTestByText(""" + try: + pass + finally: + def f(): + return 42""" + ); + } + + // PY-80237 + public void testBreakInLoopInFinallyBlock() { + doTestByText(""" + try: + pass + finally: + for x in [1, 2, 3]: + break""" + ); + } + // PY-18965 public void testExec() { doTest(); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java index 580346d566eb..7ac423661dba 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java @@ -246,7 +246,7 @@ public class PyCompatibilityInspectionTest extends PyInspectionTestCase { try: print("a") finally: - continue""") + continue""") ); }