mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 23:31:05 +07:00
PY-81676 Don't report lines with assert_never as unreachable
(cherry picked from commit afdbf35915823de02a6b8551f57770113d0feb2f) IJ-CR-172556 GitOrigin-RevId: 0f8526e2877736ac606db9ed4657a530a66d6f23
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5a60b17b4d
commit
adcc559e5c
@@ -23,6 +23,7 @@ import com.intellij.codeInsight.controlflow.TransparentInstruction;
|
||||
import com.intellij.codeInsight.controlflow.impl.ConditionalInstructionImpl;
|
||||
import com.intellij.codeInsight.controlflow.impl.TransparentInstructionImpl;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
@@ -32,6 +33,7 @@ import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.*;
|
||||
import com.jetbrains.python.psi.types.PyNeverType;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -44,6 +46,9 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
private final ControlFlowBuilder myBuilder = new ControlFlowBuilder();
|
||||
|
||||
private @Nullable TrueFalseNodes myTrueFalseNodes;
|
||||
|
||||
// see com.jetbrains.python.PyPatternTypeTest.testMatchClassPatternShadowingCapture
|
||||
private final @NotNull List<String> myPatternBindingNames = new ArrayList<>();
|
||||
|
||||
private record TrueFalseNodes(@NotNull Instruction trueNode, @NotNull Instruction falseNode) {}
|
||||
|
||||
@@ -334,12 +339,46 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
public void visitPyMatchStatement(@NotNull PyMatchStatement matchStatement) {
|
||||
myBuilder.startNode(matchStatement);
|
||||
PyExpression subject = matchStatement.getSubject();
|
||||
String subjectName = PyTypeAssertionEvaluator.getAssertionTargetName(subject);
|
||||
if (subject != null) {
|
||||
subject.accept(this);
|
||||
}
|
||||
for (PyCaseClause caseClause : matchStatement.getCaseClauses()) {
|
||||
visitPyCaseClause(caseClause);
|
||||
Instruction nextClause = myBuilder.prevInstruction;
|
||||
boolean unreachable = false;
|
||||
for (PyCaseClause clause : matchStatement.getCaseClauses()) {
|
||||
myBuilder.prevInstruction = nextClause;
|
||||
nextClause = addTransparentInstruction();
|
||||
|
||||
myPatternBindingNames.clear();
|
||||
|
||||
PyPattern pattern = clause.getPattern();
|
||||
if (pattern != null) {
|
||||
pattern.accept(this);
|
||||
if (!myPatternBindingNames.contains(subjectName)) {
|
||||
addTypeAssertionNodes(clause, true);
|
||||
}
|
||||
}
|
||||
|
||||
PyExpression guard = clause.getGuardCondition();
|
||||
if (guard != null) {
|
||||
TransparentInstruction trueNode = addTransparentInstruction();
|
||||
visitCondition(guard, trueNode, nextClause);
|
||||
myBuilder.prevInstruction = trueNode;
|
||||
addTypeAssertionNodes(guard, true);
|
||||
}
|
||||
|
||||
if (unreachable) {
|
||||
addAssertTypeNever();
|
||||
}
|
||||
if (pattern != null && pattern.isIrrefutable()) {
|
||||
unreachable = true;
|
||||
}
|
||||
myBuilder.startNode(clause.getStatementList());
|
||||
clause.getStatementList().accept(this);
|
||||
myBuilder.addPendingEdge(matchStatement, myBuilder.prevInstruction);
|
||||
myBuilder.updatePendingElementScope(clause.getStatementList(), matchStatement);
|
||||
}
|
||||
myBuilder.prevInstruction = nextClause;
|
||||
myBuilder.addNodeAndCheckPending(new TransparentInstructionImpl(myBuilder, matchStatement, ""));
|
||||
if (!myBuilder.prevInstruction.allPred().isEmpty()) {
|
||||
addTypeAssertionNodes(matchStatement, false);
|
||||
@@ -348,53 +387,20 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
myBuilder.prevInstruction = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyCaseClause(@NotNull PyCaseClause clause) {
|
||||
PyPattern pattern = clause.getPattern();
|
||||
if (pattern != null) {
|
||||
pattern.accept(this);
|
||||
}
|
||||
|
||||
TransparentInstruction trueNode = addTransparentInstruction();
|
||||
TransparentInstruction falseNode = addTransparentInstruction();
|
||||
PyExpression guard = clause.getGuardCondition();
|
||||
if (guard != null) {
|
||||
visitCondition(guard, trueNode, falseNode);
|
||||
addTypeAssertionNodes(guard, true);
|
||||
myBuilder.addPendingEdge(clause, falseNode);
|
||||
}
|
||||
else {
|
||||
myBuilder.addEdge(myBuilder.prevInstruction, trueNode);
|
||||
}
|
||||
myBuilder.prevInstruction = trueNode;
|
||||
|
||||
clause.getStatementList().accept(this);
|
||||
|
||||
if (clause.getParent() instanceof PyMatchStatement matchStatement) {
|
||||
myBuilder.addPendingEdge(matchStatement, myBuilder.prevInstruction);
|
||||
myBuilder.updatePendingElementScope(clause.getStatementList(), matchStatement);
|
||||
}
|
||||
myBuilder.prevInstruction = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitWildcardPattern(@NotNull PyWildcardPattern node) {
|
||||
myBuilder.startNode(node);
|
||||
addTypeAssertionNodes(node, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyPattern(@NotNull PyPattern node) {
|
||||
boolean isRefutable = !node.isIrrefutable();
|
||||
if (isRefutable) {
|
||||
myBuilder.addNodeAndCheckPending(new RefutablePatternInstruction(myBuilder, node, false));
|
||||
myBuilder.addPendingEdge(node.getParent(), myBuilder.prevInstruction);
|
||||
}
|
||||
else {
|
||||
myBuilder.startNode(node);
|
||||
}
|
||||
myBuilder.addPendingEdge(node.getParent(), myBuilder.prevInstruction);
|
||||
|
||||
node.acceptChildren(this);
|
||||
myBuilder.updatePendingElementScope(node, node.getParent());
|
||||
|
||||
addTypeAssertionNodes(node, true);
|
||||
if (isRefutable) {
|
||||
myBuilder.addNode(new RefutablePatternInstruction(myBuilder, node, true));
|
||||
}
|
||||
@@ -429,7 +435,6 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
node.getClassNameReference().accept(this);
|
||||
myBuilder.addPendingEdge(node.getParent(), myBuilder.prevInstruction);
|
||||
|
||||
addTypeAssertionNodes(node, true);
|
||||
node.getArgumentList().acceptChildren(this);
|
||||
myBuilder.updatePendingElementScope(node, node.getParent());
|
||||
|
||||
@@ -443,7 +448,6 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
node.getValue().accept(this);
|
||||
myBuilder.addPendingEdge(node.getParent(), myBuilder.prevInstruction);
|
||||
|
||||
addTypeAssertionNodes(node, true);
|
||||
myBuilder.addNode(new RefutablePatternInstruction(myBuilder, node, true));
|
||||
}
|
||||
|
||||
@@ -453,9 +457,20 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
// So no need to create an additional fail edge
|
||||
myBuilder.startNode(node);
|
||||
node.acceptChildren(this);
|
||||
myPatternBindingNames.add(node.getTarget().getName());
|
||||
myBuilder.updatePendingElementScope(node, node.getParent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyCapturePattern(@NotNull PyCapturePattern node) {
|
||||
node.acceptChildren(this);
|
||||
// Although capture pattern is irrefutable, I add fail edge
|
||||
// here to add some connection to the next case clause.
|
||||
// Perhaps this can be reworked and simplified later.
|
||||
myBuilder.addPendingEdge(node.getParent(), myBuilder.prevInstruction);
|
||||
myPatternBindingNames.add(node.getTarget().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyGroupPattern(@NotNull PyGroupPattern node) {
|
||||
// GroupPattern can't fail by itself – it fails only if its child fails.
|
||||
@@ -478,13 +493,16 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
if (condition != null) {
|
||||
visitCondition(condition, thenNode, elseNode);
|
||||
}
|
||||
myBuilder.prevInstruction = thenNode;
|
||||
|
||||
Boolean conditionResult = PyEvaluator.evaluateAsBooleanNoResolve(condition);
|
||||
myBuilder.prevInstruction = unreachable || Boolean.FALSE.equals(conditionResult) ? null : thenNode;
|
||||
if (unreachable || Boolean.FALSE.equals(conditionResult)) {
|
||||
// Condition is always False, or some previous condition is always True.
|
||||
addAssertTypeNever();
|
||||
}
|
||||
if (Boolean.TRUE.equals(conditionResult)) {
|
||||
unreachable = true;
|
||||
}
|
||||
|
||||
visitPyStatementPart(ifPart);
|
||||
|
||||
exitInstructions.add(myBuilder.prevInstruction);
|
||||
@@ -494,7 +512,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
final PyElsePart elsePart = node.getElsePart();
|
||||
if (elsePart != null) {
|
||||
if (unreachable) {
|
||||
myBuilder.prevInstruction = null;
|
||||
addAssertTypeNever();
|
||||
}
|
||||
visitPyStatementPart(elsePart);
|
||||
}
|
||||
@@ -1124,6 +1142,13 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
return instruction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to mark a branch as unreachable.
|
||||
*/
|
||||
private void addAssertTypeNever() {
|
||||
myBuilder.addNode(ReadWriteInstruction.assertType(myBuilder, null, null, context -> Ref.create(PyNeverType.NEVER)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to collect all pending edges
|
||||
* that we used to build CFG for `node`,
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.intellij.codeInsight.controlflow.Instruction
|
||||
import com.intellij.openapi.util.Version
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.findParentOfType
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.PyEvaluator
|
||||
@@ -88,7 +89,16 @@ class PyDataFlow(scopeOwner: ScopeOwner, controlFlow: ControlFlow, private val c
|
||||
* - calls to functions annotated with `NoReturn`
|
||||
*/
|
||||
fun PsiElement.isUnreachableForInspection(context: TypeEvalContext): Boolean {
|
||||
return isUnreachableByControlFlow(context) && !isFirstTerminatingStatement(context)
|
||||
return PyUtil.getParameterizedCachedValue(this, context) { isUnreachableForInspectionNoCache(it) }
|
||||
}
|
||||
|
||||
private fun PsiElement.isUnreachableForInspectionNoCache(context: TypeEvalContext): Boolean {
|
||||
if (parent is PyElement && parent.isUnreachableForInspection(context)) return true
|
||||
return isUnreachableByControlFlow(context) && when (this) {
|
||||
is PyStatementList -> !(statements.firstOrNull()?.isIgnoredUnreachableStatement(context) ?: true)
|
||||
is PyStatement -> !this.isIgnoredUnreachableStatement(context)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,12 +138,11 @@ fun PsiElement.findInstructionNumber(flow: Array<Instruction>): Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun PsiElement.isFirstTerminatingStatement(context: TypeEvalContext): Boolean {
|
||||
if (this.isTerminatingStatement(context)) {
|
||||
val prevSibling = prevSiblingOfType<PyElement>() ?: return true
|
||||
return !prevSibling.isTerminatingStatement(context) && !prevSibling.isUnreachableByControlFlow(context)
|
||||
}
|
||||
return false
|
||||
private fun PyStatement.isIgnoredUnreachableStatement(context: TypeEvalContext): Boolean {
|
||||
val parentBlock = this.parent as? PyStatementList ?: return false
|
||||
if (parentBlock.statements[0] != this) return false
|
||||
val parentCompoundStatement = parentBlock.findParentOfType<PyStatement>() ?: return false
|
||||
return !parentCompoundStatement.isUnreachableByControlFlow(context) && isTerminatingStatement(context)
|
||||
}
|
||||
|
||||
private fun PsiElement.isTerminatingStatement(context: TypeEvalContext): Boolean {
|
||||
|
||||
@@ -168,10 +168,11 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyPattern(@NotNull PyPattern node) {
|
||||
final PsiElement parent = PsiTreeUtil.skipParentsOfType(node, PyCaseClause.class, PyGroupPattern.class, PyAsPattern.class, PyOrPattern.class);
|
||||
if (parent instanceof PyMatchStatement matchStatement) {
|
||||
pushAssertion(matchStatement.getSubject(), myPositive, context -> context.getType(node));
|
||||
public void visitPyCaseClause(@NotNull PyCaseClause node) {
|
||||
var pattern = node.getPattern();
|
||||
if (pattern == null) return;
|
||||
if (node.getParent() instanceof PyMatchStatement matchStatement) {
|
||||
pushAssertion(matchStatement.getSubject(), myPositive, context -> context.getType(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +190,10 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
for (PyCaseClause cs : matchStatement.getCaseClauses()) {
|
||||
if (cs.getPattern() == null) continue;
|
||||
if (cs.getGuardCondition() != null) continue;
|
||||
if (cs.getPattern().isIrrefutable()) {
|
||||
subjectType = PyNeverType.NEVER;
|
||||
break;
|
||||
}
|
||||
subjectType = Ref.deref(createAssertionType(subjectType, context.getType(cs.getPattern()), false, context));
|
||||
}
|
||||
|
||||
@@ -358,6 +363,19 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable String getAssertionTargetName(@Nullable PyExpression expression) {
|
||||
PyExpression target = PyPsiUtils.flattenParens(expression);
|
||||
if (target instanceof PyAssignmentExpression walrus) {
|
||||
return getAssertionTargetName(walrus.getTarget());
|
||||
}
|
||||
if (target instanceof PyReferenceExpression || target instanceof PyTargetExpression) {
|
||||
if (!((PyQualifiedExpression)target).isQualified()) {
|
||||
return target.getName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isIfReferenceStatement(@NotNull PyExpression node) {
|
||||
return PsiTreeUtil.skipParentsOfType(node, PyParenthesizedExpression.class) instanceof PyIfPart;
|
||||
}
|
||||
@@ -392,4 +410,4 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
return myFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import com.intellij.codeInsight.dataflow.map.DfaMapInstance;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyDataFlowKt;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable;
|
||||
@@ -37,11 +37,8 @@ public class PyReachingDefsDfaInstance implements DfaMapInstance<ScopeVariable>
|
||||
if (element == null || ((PyFile) element.getContainingFile()).getLanguageLevel().isPython2()){
|
||||
return processReducedMap(map, instruction, element);
|
||||
}
|
||||
var scope = ScopeUtil.getScopeOwner(element);
|
||||
if (scope != null) {
|
||||
if (ControlFlowCache.getDataFlow(scope, myContext).isUnreachable(instruction)) {
|
||||
return UNREACHABLE_MARKER;
|
||||
}
|
||||
if (PyDataFlowKt.isUnreachableForInspection(element, myContext)) {
|
||||
return UNREACHABLE_MARKER;
|
||||
}
|
||||
if (instruction instanceof CallInstruction callInstruction) {
|
||||
if (callInstruction.isNoReturnCall(myContext)) {
|
||||
|
||||
@@ -5,13 +5,13 @@ import com.intellij.codeInspection.LocalInspectionToolSession
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.util.findParentInFile
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.codeInsight.controlflow.isUnreachableForInspection
|
||||
import com.jetbrains.python.psi.PyElement
|
||||
import com.jetbrains.python.psi.PyStatement
|
||||
import com.jetbrains.python.psi.PyStatementList
|
||||
|
||||
/**
|
||||
* Detects unreachable code using control flow graph
|
||||
* Detects unreachable code using the control flow graph
|
||||
*/
|
||||
class PyUnreachableCodeInspection : PyInspection() {
|
||||
override fun buildVisitor(
|
||||
@@ -20,12 +20,16 @@ class PyUnreachableCodeInspection : PyInspection() {
|
||||
session: LocalInspectionToolSession
|
||||
): PsiElementVisitor {
|
||||
return object : PyInspectionVisitor(holder, getContext(session)) {
|
||||
override fun visitPyElement(node: PyElement) {
|
||||
override fun visitPyStatementList(node: PyStatementList) {
|
||||
if (node.parent.isUnreachableForInspection(myTypeEvalContext)) return
|
||||
if (node.isUnreachableForInspection(myTypeEvalContext)) {
|
||||
registerProblem(node, PyPsiBundle.message("INSP.unreachable.code"), ProblemHighlightType.LIKE_UNUSED_SYMBOL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPyStatement(node: PyStatement) {
|
||||
if (node.parent.isUnreachableForInspection(myTypeEvalContext)) return
|
||||
if (node.isUnreachableForInspection(myTypeEvalContext)) {
|
||||
if (node.findParentInFile { it.isUnreachableForInspection(myTypeEvalContext) } != null) {
|
||||
// We only want to highlight top level unreachable code
|
||||
return
|
||||
}
|
||||
registerProblem(node, PyPsiBundle.message("INSP.unreachable.code"), ProblemHighlightType.LIKE_UNUSED_SYMBOL)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user