PY-80524 Explicit return statement expected false positive for an if inside try / except

(cherry picked from commit 6877fc7b34622d7ae884233af96e06ea92c918c1)

IJ-MR-161824

GitOrigin-RevId: 8c3219ea8bb8f547f7c314af8cb0d432c042cd7c
This commit is contained in:
Aleksandr.Govenko
2025-05-01 18:57:35 +02:00
committed by intellij-monorepo-bot
parent a8934362ff
commit d8f57b77c6
23 changed files with 481 additions and 234 deletions

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.psi;
import com.jetbrains.python.ast.PyAstWithItem;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,4 +19,6 @@ public interface PyWithItem extends PyAstWithItem, PyElement {
default @Nullable PyExpression getTarget() {
return (PyExpression)PyAstWithItem.super.getTarget();
}
boolean isSuppressingExceptions(TypeEvalContext context);
}

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.psi;
import com.jetbrains.python.ast.PyAstWithStatement;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
public interface PyWithStatement extends PyAstWithStatement, PyCompoundStatement, PyNamedElementContainer, PyStatementListContainer {
@@ -13,4 +14,6 @@ public interface PyWithStatement extends PyAstWithStatement, PyCompoundStatement
@Override
PyWithItem[] getWithItems();
boolean isSuppressingExceptions(TypeEvalContext context);
}

View File

@@ -468,7 +468,8 @@ QFIX.NAME.make.function.return.type=Make function return inferred type
QFIX.make.function.return.type=Make ''{0}'' return ''{1}''
# PyMakeReturnsExplicitQuickFix
QFIX.NAME.make.return.stmts.explicit=Make 'return None' statements explicit
QFIX.add.explicit.return.none=Add explicit 'return None'
QFIX.replace.with.return.none=Replace with 'return None'
# Add method quick-fix
QFIX.NAME.add.method.to.class=Add method to class
@@ -1022,8 +1023,8 @@ INSP.inconsistent.indentation.previous.line.used.spaces.this.line.uses.tabs=Inco
# PyInconsistentReturnsInspection
INSP.NAME.inconsistent.returns=Inconsistent return statements
INSP.inconsistent.returns.stmt.expected=Explicit return statement expected
INSP.inconsistent.returns.value.expected=Explicit return value expected
INSP.inconsistent.returns.missing.return.stmt.on.some.paths=Missing return statement on some paths
INSP.inconsistent.returns.return.without.value='return' without value is inconsistent with other paths
# PyMissingTypeHintsInspection

View File

@@ -2,14 +2,7 @@ package com.jetbrains.python.codeInsight.controlflow
import com.intellij.codeInsight.controlflow.ControlFlowBuilder
import com.intellij.codeInsight.controlflow.impl.InstructionImpl
import com.intellij.psi.util.PsiTreeUtil
import com.jetbrains.python.psi.PyWithItem
import com.jetbrains.python.psi.PyWithStatement
import com.jetbrains.python.psi.impl.PyBuiltinCache
import com.jetbrains.python.psi.impl.PyEvaluator
import com.jetbrains.python.psi.types.PyCollectionType
import com.jetbrains.python.psi.types.PyLiteralType
import com.jetbrains.python.psi.types.PyTypeUtil
import com.jetbrains.python.psi.types.TypeEvalContext
class PyWithContextExitInstruction(builder: ControlFlowBuilder, withItem: PyWithItem): InstructionImpl(builder, withItem) {
@@ -20,13 +13,5 @@ class PyWithContextExitInstruction(builder: ControlFlowBuilder, withItem: PyWith
* While traversing CFG, use this method to know if you should let your traversal consider this node.
* Usually, you would want it only if the context manager DOES suppress exceptions.
*/
fun isSuppressingExceptions(context: TypeEvalContext): Boolean {
val withStmt = PsiTreeUtil.getParentOfType(element, PyWithStatement::class.java, false) ?: return false
val abstractType = if (withStmt.isAsync) "contextlib.AbstractAsyncContextManager" else "contextlib.AbstractContextManager"
return context.getType(element.expression)
.let { PyTypeUtil.convertToType(it, abstractType, element, context) }
.let { (it as? PyCollectionType)?.elementTypes?.getOrNull(1) }
.let { it == PyBuiltinCache.getInstance(element).boolType ||
it is PyLiteralType && PyEvaluator.getBooleanLiteralValue(it.expression) == true }
}
fun isSuppressingExceptions(context: TypeEvalContext): Boolean = element.isSuppressingExceptions(context)
}

View File

@@ -0,0 +1,9 @@
package com.jetbrains.python.extensions
import com.jetbrains.python.psi.PyMatchStatement
import com.jetbrains.python.psi.types.TypeEvalContext
fun PyMatchStatement.isExhaustive(context: TypeEvalContext): Boolean {
// TODO: placeholder until exhaustive match is introduced
return caseClauses.any { it.pattern?.isIrrefutable == true }
}

View File

@@ -1,12 +1,18 @@
package com.jetbrains.python.inspections
package com.jetbrains.python.inspections
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.findParentInFile
import com.intellij.psi.util.parentOfType
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.inspections.quickfix.PyMakeReturnsExplicitFix
import com.jetbrains.python.psi.PyFunction
import com.jetbrains.python.psi.PyReturnStatement
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner
import com.jetbrains.python.extensions.isExhaustive
import com.jetbrains.python.inspections.quickfix.PyMakeReturnExplicitFix
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.types.TypeEvalContext
/**
* PEP8:
@@ -14,26 +20,54 @@ 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 {
return object : PyInspectionVisitor(holder, getContext(session)) {
override fun visitPyFunction(node: PyFunction) {
val returnPoints = node.getReturnPoints(myTypeEvalContext)
val hasExplicitReturns = returnPoints.any { (it as? PyReturnStatement)?.expression != null }
if (hasExplicitReturns) {
returnPoints
.filter { (it !is PyReturnStatement || it.expression == null) }
.forEach {
val message =
if (it is PyReturnStatement) PyPsiBundle.message("INSP.inconsistent.returns.value.expected")
else PyPsiBundle.message("INSP.inconsistent.returns.stmt.expected")
this.holder!!.problem(it, message).fix(PyMakeReturnsExplicitFix(node)).register()
}
val hasExplicitReturns = returnPoints.any { it is PyReturnStatement && it.expression != null }
if (!hasExplicitReturns) return
for (statement in returnPoints) {
val message = when {
statement is PyReturnStatement && statement.expression == null ->
PyPsiBundle.message("INSP.inconsistent.returns.return.without.value")
statement !is PyReturnStatement && !isTooComplexCase(statement, myTypeEvalContext) ->
PyPsiBundle.message("INSP.inconsistent.returns.missing.return.stmt.on.some.paths")
else -> continue
}
this.holder!!
.problem(statement, message)
.range(statement.firstLineRange() ?: statement.textRange)
.fix(PyMakeReturnExplicitFix(statement)).register()
}
}
}
}
}
private fun isTooComplexCase(statement: PyStatement, context: TypeEvalContext): Boolean {
val parent = statement.findParentInFile(withSelf = false) {
it is ScopeOwner
|| it is PyTryExceptStatement
|| it is PyForPart
|| it is PyWhilePart
|| it is PyIfPart && it.parentOfType<PyIfStatement>()?.elsePart == null
|| (it is PyWithStatement && it.isSuppressingExceptions(context))
|| (it is PyMatchStatement && !it.isExhaustive(context))
}
return parent !is ScopeOwner
}
private fun PsiElement.firstLineRange(): TextRange? {
val document = containingFile.fileDocument
val startOffset = textRange.startOffset
val lineEndOffset = document.getLineEndOffset(document.getLineNumber(startOffset))
val endOffset = minOf(lineEndOffset, textRange.endOffset)
return TextRange(0, endOffset - startOffset)
}

View File

@@ -0,0 +1,37 @@
package com.jetbrains.python.inspections.quickfix
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.Presentation
import com.intellij.modcommand.PsiUpdateModCommandAction
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.psi.*
/**
* Appends missing `return None`, and transforms `return` into `return None`.
*/
class PyMakeReturnExplicitFix(statement: PyStatement) : PsiUpdateModCommandAction<PyStatement>(statement) {
override fun getFamilyName(): @IntentionFamilyName String = PyPsiBundle.message("QFIX.add.explicit.return.none")
override fun getPresentation(context: ActionContext, element: PyStatement): Presentation? {
if (element is PyReturnStatement || element is PyPassStatement) {
return Presentation.of(PyPsiBundle.message("QFIX.replace.with.return.none"))
}
return super.getPresentation(context, element)
}
override fun invoke(context: ActionContext, element: PyStatement, updater: ModPsiUpdater) {
val elementGenerator = PyElementGenerator.getInstance(element.getProject())
val languageLevel = LanguageLevel.forElement(element)
val returnStmt = elementGenerator.createFromText(languageLevel, PyReturnStatement::class.java, "return None")
if (element is PyReturnStatement || element is PyPassStatement) {
element.replace(returnStmt)
}
else {
element.parent.addAfter(returnStmt, element)
}
}
}

View File

@@ -1,46 +0,0 @@
package com.jetbrains.python.inspections.quickfix;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandAction;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
/**
* Appends missing {@code return None}, and transforms {@code return} into {@code return None}.
*/
public class PyMakeReturnsExplicitFix extends PsiUpdateModCommandAction<PyFunction> {
public PyMakeReturnsExplicitFix(@NotNull PyFunction function) {
super(function);
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PyFunction element, @NotNull ModPsiUpdater updater) {
var returnPoints = element.getReturnPoints(TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()));
for (var point : returnPoints) {
makeExplicit(point);
}
}
@Override
public @NotNull String getFamilyName() {
return PyPsiBundle.message("QFIX.NAME.make.return.stmts.explicit");
}
private static void makeExplicit(@NotNull PyStatement stmt) {
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(stmt.getProject());
LanguageLevel languageLevel = LanguageLevel.forElement(stmt);
var returnStmt = elementGenerator.createFromText(languageLevel, PyReturnStatement.class, "return None");
if ((stmt instanceof PyReturnStatement ret && ret.getExpression() == null) || (stmt instanceof PyPassStatement)) {
stmt.replace(returnStmt);
}
else if (!(stmt instanceof PyReturnStatement)) {
stmt.getParent().addAfter(returnStmt, stmt);
}
}
}

View File

@@ -13,20 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.impl;
package com.jetbrains.python.psi.impl
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyWithItem;
import com.intellij.lang.ASTNode
import com.intellij.psi.util.PsiTreeUtil
import com.jetbrains.python.psi.PyElementVisitor
import com.jetbrains.python.psi.PyWithItem
import com.jetbrains.python.psi.PyWithStatement
import com.jetbrains.python.psi.types.PyCollectionType
import com.jetbrains.python.psi.types.PyLiteralType
import com.jetbrains.python.psi.types.PyTypeUtil
import com.jetbrains.python.psi.types.TypeEvalContext
public class PyWithItemImpl extends PyElementImpl implements PyWithItem {
public PyWithItemImpl(ASTNode astNode) {
super(astNode);
class PyWithItemImpl(astNode: ASTNode?) : PyElementImpl(astNode), PyWithItem {
override fun acceptPyVisitor(pyVisitor: PyElementVisitor) {
pyVisitor.visitPyWithItem(this)
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyWithItem(this);
override fun isSuppressingExceptions(context: TypeEvalContext): Boolean {
val withStmt = PsiTreeUtil.getParentOfType(this, PyWithStatement::class.java, false) ?: return false
val abstractType = if (withStmt.isAsync) "contextlib.AbstractAsyncContextManager" else "contextlib.AbstractContextManager"
return context.getType(expression)
.let { PyTypeUtil.convertToType(it, abstractType, this, context) }
.let { (it as? PyCollectionType)?.elementTypes?.getOrNull(1) }
.let {
it == PyBuiltinCache.getInstance(this).boolType ||
it is PyLiteralType && PyEvaluator.getBooleanLiteralValue(it.expression) == true
}
}
}

View File

@@ -1,42 +1,34 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.impl;
package com.jetbrains.python.psi.impl
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiListLikeElement;
import com.intellij.psi.PsiNamedElement;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.PyWithItem;
import com.jetbrains.python.psi.PyWithStatement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiListLikeElement
import com.intellij.psi.PsiNamedElement
import com.jetbrains.python.ast.PyAstWithStatement
import com.jetbrains.python.psi.PyElementVisitor
import com.jetbrains.python.psi.PyUtil
import com.jetbrains.python.psi.PyWithItem
import com.jetbrains.python.psi.PyWithStatement
import com.jetbrains.python.psi.types.TypeEvalContext
import java.util.Arrays;
import java.util.List;
public class PyWithStatementImpl extends PyElementImpl implements PyWithStatement, PsiListLikeElement {
public PyWithStatementImpl(ASTNode astNode) {
super(astNode);
class PyWithStatementImpl(astNode: ASTNode?) : PyElementImpl(astNode), PyWithStatement, PsiListLikeElement {
override fun acceptPyVisitor(pyVisitor: PyElementVisitor) {
pyVisitor.visitPyWithStatement(this)
}
@Override
protected void acceptPyVisitor(final PyElementVisitor pyVisitor) {
pyVisitor.visitPyWithStatement(this);
fun getNamedElement(the_name: String): PsiNamedElement? {
return PyUtil.IterHelper.findName(getNamedElements(), the_name)
}
public @Nullable PsiNamedElement getNamedElement(final @NotNull String the_name) {
return PyUtil.IterHelper.findName(getNamedElements(), the_name);
override fun getWithItems(): Array<PyWithItem> {
return childrenToPsi(PyAstWithStatement.WITH_ITEM, PyWithItem.EMPTY_ARRAY)
}
@Override
public PyWithItem[] getWithItems() {
return childrenToPsi(WITH_ITEM, PyWithItem.EMPTY_ARRAY);
override fun getComponents(): List<PyWithItem> {
return withItems.toList()
}
@Override
public @NotNull List<? extends PsiElement> getComponents() {
return Arrays.asList(getWithItems());
override fun isSuppressingExceptions(context: TypeEvalContext): Boolean {
return withItems.any { it.isSuppressingExceptions(context) }
}
}

View File

@@ -0,0 +1,5 @@
def func(x, y, z):
<weak_warning descr="Missing return statement on some paths"><caret>if x:</weak_warning>
return 42
elif y:
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>

View File

@@ -0,0 +1,6 @@
def func(x, y, z):
if x:
return 42
elif y:
return
return None

View File

@@ -0,0 +1,5 @@
def func(x, y, z):
<weak_warning descr="Missing return statement on some paths">if x: </weak_warning>
return 42
elif y:
<weak_warning descr="'return' without value is inconsistent with other paths"><caret>return</weak_warning>

View File

@@ -0,0 +1,5 @@
def func(x, y, z):
if x:
return 42
elif y:
return None

View File

@@ -1,15 +0,0 @@
def f(x) -> int | None:
y = 42
print(y)
<weak_warning descr="Explicit return statement expected">if x == 1:
return 42
elif x == 2:
<weak_warning descr="Explicit return value expected">return<caret></weak_warning>
elif x == 3:
raise Exception()
elif x == 4:
assert False
elif x == 5:
<weak_warning descr="Explicit return statement expected">assert x</weak_warning>
elif x == 4:
<weak_warning descr="Explicit return statement expected">pass</weak_warning></weak_warning>

View File

@@ -1,17 +0,0 @@
def f(x) -> int | None:
y = 42
print(y)
if x == 1:
return 42
elif x == 2:
return None
elif x == 3:
raise Exception()
elif x == 4:
assert False
elif x == 5:
assert x
return None
elif x == 4:
return None
return None

View File

@@ -1,11 +0,0 @@
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>

View File

@@ -1,12 +0,0 @@
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

View File

@@ -1,10 +0,0 @@
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>

View File

@@ -1,11 +0,0 @@
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

View File

@@ -0,0 +1,278 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.inspections
import com.jetbrains.python.fixtures.PyInspectionTestCase
class PyInconsistentReturnsInspectionTest : PyInspectionTestCase() {
override fun getInspectionClass(): Class<out PyInspection> = PyInconsistentReturnsInspection::class.java
override fun doTestByText(text: String) = super.doTestByText(text.trimIndent())
// --------------------------
// Basic Return Tests
// --------------------------
fun testNoReturns() = doTestByText("""
def f():
x = 1
""")
fun testOnlyExplicitReturn() = doTestByText("""
def f():
return 42
""")
fun testOnlyImplicitNoneNoExplicit() = doTestByText("""
def f():
return
""")
fun testExplicitReturnAndImplicitNone() = doTestByText("""
def f(cond):
if cond:
return 10
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>
""")
fun testExplicitReturnAndImplicitStmt() = doTestByText("""
def f(cond):
<weak_warning descr="Missing return statement on some paths">if cond:</weak_warning>
return 1
""")
// --------------------------
// If Tests
// --------------------------
fun testIfElse() = doTestByText("""
def f(cond, cond2, cond3):
if cond:
return 'yes'
elif cond2:
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>
elif cond3:
<weak_warning descr="Missing return statement on some paths">print()</weak_warning>
else:
return 'no'
""")
fun testIfNoElse() = doTestByText("""
def f(cond, cond2, cond3):
<weak_warning descr="Missing return statement on some paths">if cond:</weak_warning>
return 'yes'
elif cond2:
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>
elif cond3:
print()
""")
// --------------------------
// While Tests
// --------------------------
fun testWhileTrueNoWarning() = doTestByText("""
def f():
while True:
do_something()
""")
fun testWhile() = doTestByText("""
def f():
if x:
return 1
<weak_warning descr="Missing return statement on some paths">while not x:</weak_warning>
break
""")
fun testWhileImplicitNone() = doTestByText("""
def f():
if x:
return 1
<weak_warning descr="Missing return statement on some paths">while not x:</weak_warning>
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>
""")
fun testWhileWithElse() = doTestByText("""
def f():
if x:
return 1
while not x:
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>
else:
<weak_warning descr="Missing return statement on some paths">x = 5</weak_warning>
""")
// --------------------------
// For Tests
// --------------------------
fun testForNoElse() = doTestByText("""
def f(x):
if x:
return 1
<weak_warning descr="Missing return statement on some paths">for i in range(10):</weak_warning>
if i > 10:
<weak_warning descr="'return' without value is inconsistent with other paths">return</weak_warning>
""")
fun testForLoopIncomplete() = doTestByText("""
def x(y):
for i in range(10):
if i > 10:
return i
<weak_warning descr="Missing return statement on some paths">pass</weak_warning>
""")
fun testForLoopWithElsePrint() = doTestByText("""
def x(y):
if y:
return 1
for i in range(10):
if i > 10:
break
else:
<weak_warning descr="Missing return statement on some paths">print()</weak_warning>
""")
// --------------------------
// Assert Tests
// --------------------------
fun testAssertTrue() = doTestByText("""
def f(x):
if x:
return 1
<weak_warning descr="Missing return statement on some paths">assert True</weak_warning>
""")
fun testAssertUndecidable() = doTestByText("""
def f(x):
if x:
return 1
<weak_warning descr="Missing return statement on some paths">assert x</weak_warning>
""")
fun testAssertFalseNoWarning() = doTestByText("""
def f(x):
if x:
return 1
assert False
""")
// --------------------------
// Function Call Tests
// --------------------------
fun testNormalCallExpression() = doTestByText("""
def f(x):
if x:
return 1
<weak_warning descr="Missing return statement on some paths">foo()</weak_warning>
""")
fun testNoReturnCall() = doTestByText("""
import sys
def f(x):
if x:
return 1
sys.exit()
""")
// --------------------------
// Other Statement Tests
// --------------------------
fun testTryExceptNoWarning() = doTestByText("""
def f(x):
if x:
return 1
try:
risky()
except Exception:
handle()
""")
fun testMatchStatement() = doTestByText("""
def f(x):
if x:
return 1
match x:
case 1:
<weak_warning descr="Missing return statement on some paths">foo()</weak_warning>
case _:
<weak_warning descr="Missing return statement on some paths">bar()</weak_warning>
""")
fun testWithStatement() = doTestByText("""
def f():
if x:
return 1
with ctx():
<weak_warning descr="Missing return statement on some paths">body()</weak_warning>
""")
fun testWithStatementSuppressingExceptions() = doTestByText("""
class ctx:
def __enter__(self): ...
def __exit__(self, exc_type, exc_val, exc_tb) -> bool: ...
def f():
if x:
return 1
<weak_warning descr="Missing return statement on some paths">with ctx():</weak_warning>
body()
""")
fun testNestedFunctions() = doTestByText("""
def f(x):
if x:
return 1
<weak_warning descr="Missing return statement on some paths">def g():</weak_warning>
return 2
""")
fun testNestedClass() = doTestByText("""
def f(x):
if x:
return 1
<weak_warning descr="Missing return statement on some paths">class C:</weak_warning>
x = 5
""")
fun testNonexistentFunctionCall() = doTestByText("""
def func_unknown(x):
if x > 0:
return False
<weak_warning descr="Missing return statement on some paths">no_such_function()</weak_warning>
""")
fun testNoReturnFunction() = doTestByText("""
def func_no_noreturn(x):
if x > 0:
return False
<weak_warning descr="Missing return statement on some paths">print("", end="")</weak_warning>
""")
fun testMatchStatementInconsistentReturns() = doTestByText("""
def x(y):
<weak_warning descr="Missing return statement on some paths">match y:</weak_warning>
case 0:
return 1
case 1:
print()
""")
// PY-80524
fun testIfInsideTryExcept() = doTestByText("""
def fn():
try:
if some_condition():
return True
# no warning should be highlighted here
some_non_returning_function()
except Exception:
return False
""")
}

View File

@@ -0,0 +1,31 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.quickFixes
import com.intellij.testFramework.TestDataPath
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.PyQuickFixTestCase
import com.jetbrains.python.inspections.PyInconsistentReturnsInspection
@TestDataPath("\$CONTENT_ROOT/../testData/quickFixes/PyMakeReturnExplicitFixTest")
class PyMakeReturnExplicitFixTest : PyQuickFixTestCase() {
fun testAddExplicitReturn() {
doQuickFixTest(PyPsiBundle.message("QFIX.add.explicit.return.none"))
}
fun testReplaceWithReturnNone() {
doQuickFixTest(PyPsiBundle.message("QFIX.replace.with.return.none"))
}
// Copied from PyQuickFixTestCase to enable weak warnings
override fun doQuickFixTest(hint: String) {
val testFileName = getTestName(true)
myFixture.enableInspections(PyInconsistentReturnsInspection::class.java)
myFixture.configureByFile("$testFileName.py")
myFixture.checkHighlighting(false, false, true)
val intentionAction = myFixture.findSingleIntention(hint)
assertNotNull(intentionAction)
myFixture.launchAction(intentionAction)
myFixture.checkResultByFile(testFileName + "_after.py", true)
}
}

View File

@@ -1,37 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.quickFixes;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.PyQuickFixTestCase;
import com.jetbrains.python.inspections.PyInconsistentReturnsInspection;
@TestDataPath("$CONTENT_ROOT/../testData/quickFixes/PyMakeReturnsExplicitFixTest/")
public class PyMakeReturnsExplicitFixTest extends PyQuickFixTestCase {
public void testAddReturnsFromReturnStmt() {
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);
myFixture.enableInspections(inspectionClass);
myFixture.configureByFile(testFileName + ".py");
myFixture.checkHighlighting(true, false, true);
final var intentionAction = myFixture.findSingleIntention(hint);
assertNotNull(intentionAction);
myFixture.launchAction(intentionAction);
myFixture.checkResultByFile(testFileName + "_after.py", true);
}
}