PY-80493 Inconsistent return inspection adding "return None" after every line in context manager

Merge-request: IJ-MR-160763
Merged-by: Aleksandr Govenko <aleksandr.govenko@jetbrains.com>

GitOrigin-RevId: b576e30d433d39e426c8854d3ad485594ac9e958
This commit is contained in:
Aleksandr.Govenko
2025-04-22 14:01:06 +00:00
committed by intellij-monorepo-bot
parent c708c87c1f
commit 089e124ea1
9 changed files with 60 additions and 12 deletions
@@ -14,6 +14,8 @@ import com.jetbrains.python.psi.PyReturnStatement
* or none of them should. If any return statement returns an expression, any return statements where no value
* is returned should explicitly state this as return None, and an explicit return statement should be present
* at the end of the function (if reachable).
*
* @see PyMakeReturnsExplicitFix for tests
*/
class PyInconsistentReturnsInspection : PyInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -405,7 +405,10 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
if (instruction instanceof PyRaiseInstruction) {
return ControlFlowUtil.Operation.CONTINUE;
}
if (instruction instanceof PyWithContextExitInstruction withExit && !withExit.isSuppressingExceptions(context)) {
if (instruction instanceof PyWithContextExitInstruction withExit) {
if (collectImplicitReturn && withExit.isSuppressingExceptions(context)) {
returnPoints.add(PsiTreeUtil.getParentOfType(withExit.getElement(), PyWithStatement.class));
}
return ControlFlowUtil.Operation.CONTINUE;
}
final PsiElement element = instruction.getElement();
@@ -1,5 +0,0 @@
def f(x) -> <warning descr="Function returning 'int | None' has implicit return">int | None<caret></warning>:
if x == 1:
return 42
elif x == 2:
<warning descr="Function returning 'int | None' has implicit return">return</warning>
@@ -1,6 +0,0 @@
def f(x) -> int | None:
if x == 1:
return 42
elif x == 2:
return None
return None
@@ -0,0 +1,11 @@
class NotSuppressingContext:
def __enter__(self):
...
def __exit__(self, exc_type, exc_val, exc_tb) -> bool | None:
...
def foo():
with NotSuppressingContext() as st:
foo()
<weak_warning descr="Explicit return statement expected">if bool():
return 1<caret></weak_warning>
@@ -0,0 +1,12 @@
class NotSuppressingContext:
def __enter__(self):
...
def __exit__(self, exc_type, exc_val, exc_tb) -> bool | None:
...
def foo():
with NotSuppressingContext() as st:
foo()
if bool():
return 1
return None
@@ -0,0 +1,10 @@
class SuppressingContext:
def __enter__(self):
...
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
...
def foo():
<weak_warning descr="Explicit return statement expected">with SuppressingContext() as st:
foo()
return 1<caret></weak_warning>
@@ -0,0 +1,11 @@
class SuppressingContext:
def __enter__(self):
...
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
...
def foo():
with SuppressingContext() as st:
foo()
return 1
return None
@@ -13,6 +13,16 @@ public class PyMakeReturnsExplicitFixTest extends PyQuickFixTestCase {
doQuickFixTest(PyInconsistentReturnsInspection.class, PyPsiBundle.message("QFIX.NAME.make.return.stmts.explicit"));
}
// PY-80493
public void testContextManagerSuppressingException() {
doQuickFixTest(PyInconsistentReturnsInspection.class, PyPsiBundle.message("QFIX.NAME.make.return.stmts.explicit"));
}
// PY-80493
public void testContextManagerNotSuppressingException() {
doQuickFixTest(PyInconsistentReturnsInspection.class, PyPsiBundle.message("QFIX.NAME.make.return.stmts.explicit"));
}
@Override
protected void doQuickFixTest(final Class inspectionClass, final String hint) {
final String testFileName = getTestName(true);