PY-80237 Support PEP 765 – Disallow return/break/continue that exit a finally block

GitOrigin-RevId: 7c5e956759071ccf96ebb8d31628569633977801
This commit is contained in:
Aleksandr.Govenko
2025-07-17 16:28:47 +00:00
committed by intellij-monorepo-bot
parent 64b8d93400
commit abae4c8d05
4 changed files with 74 additions and 4 deletions
@@ -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}
@@ -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
);
}
}
@@ -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:
<error descr="Python version 3.14 does not support 'break' inside 'finally' clause">break</error>"""
);
}
// PY-80237
public void testReturnInFinallyBlock() {
doTestByText("""
def foo():
try:
pass
finally:
<error descr="Python version 3.14 does not support 'return' inside 'finally' clause">return</error>"""
);
}
// 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();
@@ -246,7 +246,7 @@ public class PyCompatibilityInspectionTest extends PyInspectionTestCase {
try:
print("a")
finally:
<warning descr="Python versions 2.7, 3.7 do not support 'continue' inside 'finally' clause">continue</warning>""")
<warning descr="Python versions 2.7, 3.7, 3.14 do not support 'continue' inside 'finally' clause">continue</warning>""")
);
}