[pycharm] remove NoReturn logic from control flow builder

GitOrigin-RevId: 85ba28fd8b86fe0b6337703798a0b869bb793e88
This commit is contained in:
Vladimir Koshelev
2024-07-09 17:25:12 +02:00
committed by intellij-monorepo-bot
parent 8a4dfe563c
commit 2625d2472b
8 changed files with 88 additions and 19 deletions

View File

@@ -0,0 +1,33 @@
package com.jetbrains.python.codeInsight.controlflow
import com.intellij.codeInsight.controlflow.ControlFlowBuilder
import com.intellij.codeInsight.controlflow.Instruction
import com.intellij.codeInsight.controlflow.impl.InstructionImpl
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.psi.PyCallExpression
import com.jetbrains.python.psi.PyFunction
import com.jetbrains.python.psi.resolve.PyResolveContext
import com.jetbrains.python.psi.types.TypeEvalContext
class CallInstruction(builder: ControlFlowBuilder, call: PyCallExpression) : InstructionImpl(builder, call) {
override fun getElement(): PyCallExpression {
return super.getElement() as PyCallExpression
}
fun isNoReturnCall(context: TypeEvalContext): Boolean {
val callees = element.multiResolveCalleeFunction(PyResolveContext.defaultContext(context))
if (callees.size == 1) {
val pyFunction = callees.single()
if (pyFunction is PyFunction) {
return PyTypingTypeProvider.isNoReturn(pyFunction, context)
}
}
return false
}
companion object {
fun allPredWithoutNoReturn(instruction: Instruction, typeEvalContext: TypeEvalContext): List<Instruction> {
return instruction.allPred().filter { it !is CallInstruction || !it.isNoReturnCall(typeEvalContext) }
}
}
}

View File

@@ -32,7 +32,10 @@ import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator;
import com.jetbrains.python.psi.impl.PyEvaluator;
import com.jetbrains.python.psi.impl.PyImportStatementNavigator;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.types.TypeEvalContext;
import kotlin.Triple;
@@ -167,15 +170,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
final PyExpression callee = node.getCallee();
final var callNodeType = getCalleeNodeType(callee);
// Flow abrupted
if (callNodeType instanceof NoReturnCallKind) {
callee.accept(this);
for (PyExpression expression : node.getArguments()) {
expression.accept(this);
}
abruptFlow(node);
}
else if (callNodeType instanceof TypeGuardCallKind typeGuardCallKind && node.getArguments().length > 0) {
if (callNodeType instanceof TypeGuardCallKind typeGuardCallKind && node.getArguments().length > 0) {
expressionToGuards.put(node, typeGuardCallKind.pyFunction);
super.visitPyCallExpression(node);
}
@@ -183,6 +178,9 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
super.visitPyCallExpression(node);
}
var callInstruction = new CallInstruction(myBuilder, node);
myBuilder.addNodeAndCheckPending(callInstruction);
if (node.isCalleeText(PyNames.ASSERT_IS_INSTANCE)) {
final PyTypeAssertionEvaluator assertionEvaluator = new PyTypeAssertionEvaluator();
node.accept(assertionEvaluator);
@@ -1072,9 +1070,9 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
.of(PyResolveUtil.resolveQualifiedNameInScope(qName, scopeOwner, context))
.select(PyFunction.class)
.map(function -> {
if (PyTypingTypeProvider.isNoReturn(function, context)) {
return NoReturnCallKind.INSTANCE;
}
//if (PyTypingTypeProvider.isNoReturn(function, context)) {
// return NoReturnCallKind.INSTANCE;
//}
if (PyTypingTypeProvider.isTypeGuard(function, context)) {
return new TypeGuardCallKind(function);
}
@@ -1111,11 +1109,6 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
private interface CallTypeKind { }
private static class NoReturnCallKind implements CallTypeKind {
private NoReturnCallKind() {}
public static final NoReturnCallKind INSTANCE = new NoReturnCallKind();
}
private record TypeGuardCallKind(@NotNull PyFunction pyFunction) implements CallTypeKind {}
}

View File

@@ -9,6 +9,7 @@ import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.PyStatementListContainer;
@@ -45,7 +46,7 @@ public final class PyUnreachableCodeInspection extends PyInspection {
final List<PsiElement> unreachable = new ArrayList<>();
if (instructions.length > 0) {
ControlFlowUtil.iteratePrev(instructions.length - 1, instructions, instruction -> {
if (instruction.allPred().isEmpty() && !PyInspectionsUtil.isFirstInstruction(instruction)) {
if (CallInstruction.Companion.allPredWithoutNoReturn(instruction, myTypeEvalContext).isEmpty() && !PyInspectionsUtil.isFirstInstruction(instruction)) {
unreachable.add(unwrapStatementListContainer(instruction.getElement()));
}
return ControlFlowUtil.Operation.NEXT;

View File

@@ -22,6 +22,7 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
@@ -76,6 +77,9 @@ public final class PyDefUseUtil {
final Collection<Instruction> result = new LinkedHashSet<>();
ControlFlowUtil.iteratePrev(instr, instructions,
instruction -> {
if (instruction.num() < instructions[instr].num() && instruction instanceof CallInstruction callInstruction) {
if (callInstruction.isNoReturnCall(context)) return ControlFlowUtil.Operation.CONTINUE;
}
final PsiElement element = instruction.getElement();
final PyImplicitImportNameDefiner implicit = PyUtil.as(element, PyImplicitImportNameDefiner.class);
if (instruction instanceof ReadWriteInstruction rwInstruction) {

View File

@@ -0,0 +1,8 @@
from util import panic
from util import Alarmist
def foo():
x = Alarmist()
x.panic("Error!")
<warning descr="This code is unreachable">print("Should be reported as unreachable")</warning>

View File

@@ -0,0 +1,6 @@
from typing import NoReturn
class Alarmist:
def panic(m) -> NoReturn:
print(f'Help: {m}')
raise SystemExit

View File

@@ -2004,6 +2004,26 @@ public class Py3TypeTest extends PyTestCase {
""");
}
public void testNoReturn() {
doTest("Bar",
"""
from typing import NoReturn
class Foo:
def stop(self) -> NoReturn:
raise RuntimeError('no way')
class Bar:
...
def foo(x):
f = Foo()
if not isinstance(x, Bar):
f.stop()
expr = x # expr is Bar, not Union[Bar, Any]
""");
}
// PY-61137
public void testLiteralStringIsNotInferredWithoutExplicitAnnotation() {
doTest("list[str]",

View File

@@ -237,6 +237,10 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase {
doMultiFileTest();
}
public void testUnreachableCodeReportedNoReturnInClassMember() {
doMultiFileTest();
}
// PY-53703
public void testUnreachableCodeReportedAfterNever() {
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {