mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-51564 "code is unreachable" warning does not consider that context managers can abort
Added PyWithContextExitInstruction that works as 'except' node in try-except statements, but for 'with context-manager' statements. This allows to see when it can recover from exception by checking the type of `contextlib.AbstractContextManager` Merge-request: IJ-MR-152213 Merged-by: Aleksandr Govenko <aleksandr.govenko@jetbrains.com> GitOrigin-RevId: fd5a139de31d47136cc86b4a7e090a20b73b8744
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cb97edfcf7
commit
6659947437
-6
@@ -24,10 +24,4 @@ class CallInstruction(builder: ControlFlowBuilder, call: PyCallExpression) : Ins
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun allPredWithoutNoReturn(instruction: Instruction, typeEvalContext: TypeEvalContext): List<Instruction> {
|
||||
return instruction.allPred().filter { it !is CallInstruction || !it.isNoReturnCall(typeEvalContext) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
-67
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.jetbrains.python.codeInsight.controlflow;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlow;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowBuilder;
|
||||
@@ -39,12 +38,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
|
||||
private static final @NotNull Set<String> EXCEPTION_SUPPRESSORS = ImmutableSet.of("suppress", "assertRaises", "assertRaisesRegex");
|
||||
|
||||
private final ControlFlowBuilder myBuilder = new ControlFlowBuilder();
|
||||
|
||||
private @Nullable TrueFalseNodes myTrueFalseNodes;
|
||||
@@ -312,6 +308,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
myBuilder.startNode(node);
|
||||
TransparentInstruction trueNode = addTransparentInstruction();
|
||||
TransparentInstruction falseNode = addTransparentInstruction();
|
||||
TransparentInstruction exitNode = addTransparentInstruction();
|
||||
final PyExpression condition = node.getCondition();
|
||||
if (condition != null) {
|
||||
visitCondition(condition, trueNode, falseNode);
|
||||
@@ -321,13 +318,14 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
if (truePart != null) {
|
||||
myBuilder.prevInstruction = trueNode;
|
||||
truePart.accept(this);
|
||||
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
|
||||
myBuilder.addEdge(myBuilder.prevInstruction, exitNode);
|
||||
}
|
||||
if (falsePart != null) {
|
||||
myBuilder.prevInstruction = falseNode;
|
||||
falsePart.accept(this);
|
||||
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
|
||||
myBuilder.addEdge(myBuilder.prevInstruction, exitNode);
|
||||
}
|
||||
myBuilder.prevInstruction = exitNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -569,16 +567,6 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
for (PyExpression expression : expressions) {
|
||||
expression.accept(this);
|
||||
}
|
||||
|
||||
myBuilder.processPending((pendingScope, instruction) -> {
|
||||
final PsiElement pendingElement = instruction.getElement();
|
||||
if (pendingElement != null && PsiTreeUtil.isAncestor(node, pendingElement, false)) {
|
||||
myBuilder.addEdge(null, instruction);
|
||||
}
|
||||
else {
|
||||
myBuilder.addPendingEdge(pendingScope, instruction);
|
||||
}
|
||||
});
|
||||
myBuilder.addPendingEdge(null, myBuilder.prevInstruction);
|
||||
myBuilder.flowAbrupted();
|
||||
}
|
||||
@@ -590,7 +578,8 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
if (expression != null) {
|
||||
expression.accept(this);
|
||||
}
|
||||
abruptFlow(node);
|
||||
myBuilder.addPendingEdge(null, myBuilder.prevInstruction);
|
||||
myBuilder.flowAbrupted();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -822,25 +811,16 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
// assert False
|
||||
if (args.length >= 1) {
|
||||
if (!PyEvaluator.evaluateAsBooleanNoResolve(args[0], true)) {
|
||||
myBuilder.processPending((pendingScope, instruction) -> {
|
||||
final PsiElement pendingElement = instruction.getElement();
|
||||
if (pendingElement != null && PsiTreeUtil.isAncestor(node, pendingElement, false)) {
|
||||
myBuilder.addEdge(null, instruction);
|
||||
}
|
||||
else {
|
||||
myBuilder.addPendingEdge(pendingScope, instruction);
|
||||
}
|
||||
});
|
||||
myBuilder.addPendingEdge(null, myBuilder.prevInstruction);
|
||||
myBuilder.flowAbrupted();
|
||||
return;
|
||||
}
|
||||
myBuilder.flowAbrupted();
|
||||
return;
|
||||
}
|
||||
|
||||
TransparentInstruction trueNode = addTransparentInstruction();
|
||||
TransparentInstruction falseNode = addTransparentInstruction();
|
||||
visitCondition(args[0], trueNode, falseNode);
|
||||
|
||||
PyRaiseInstruction raiseInstruction = new PyRaiseInstruction(myBuilder, null);
|
||||
PyRaiseInstruction raiseInstruction = new PyRaiseInstruction(myBuilder, node);
|
||||
myBuilder.instructions.add(raiseInstruction);
|
||||
myBuilder.addEdge(falseNode, raiseInstruction);
|
||||
|
||||
@@ -857,28 +837,54 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
|
||||
@Override
|
||||
public void visitPyWithStatement(final @NotNull PyWithStatement node) {
|
||||
super.visitPyWithStatement(node);
|
||||
myBuilder.startNode(node);
|
||||
|
||||
final boolean suppressor = StreamEx
|
||||
.of(node.getWithItems())
|
||||
.map(PyWithItem::getExpression)
|
||||
.select(PyCallExpression.class)
|
||||
.map(PyCallExpression::getCallee)
|
||||
.select(PyReferenceExpression.class)
|
||||
.anyMatch(it -> EXCEPTION_SUPPRESSORS.contains(it.getReferencedName()));
|
||||
|
||||
myBuilder.processPending((pendingScope, instruction) -> {
|
||||
final PsiElement element = instruction.getElement();
|
||||
if (element != null &&
|
||||
PsiTreeUtil.isAncestor(node, element, true) &&
|
||||
(suppressor && canRaiseExceptions(instruction)
|
||||
|| PsiTreeUtil.getParentOfType(element, PyRaiseStatement.class) != null
|
||||
|| PsiTreeUtil.getParentOfType(element, PyAssertStatement.class) != null
|
||||
)) {
|
||||
myBuilder.addPendingEdge(node, instruction);
|
||||
List<Instruction> exits = new ArrayList<>();
|
||||
for (var item : node.getWithItems()) {
|
||||
int itemStart = myBuilder.instructions.size();
|
||||
item.accept(this);
|
||||
int itemEnd = myBuilder.instructions.size();
|
||||
for (int i = itemStart; i < itemEnd; i++) {
|
||||
final Instruction instruction = myBuilder.instructions.get(i);
|
||||
final PsiElement e = instruction.getElement();
|
||||
if (e == null || !canRaiseExceptions(instruction) || !PsiTreeUtil.isAncestor(node, e, false)) {
|
||||
continue;
|
||||
}
|
||||
for (var exit : exits) {
|
||||
myBuilder.addEdge(myBuilder.instructions.get(i), exit);
|
||||
}
|
||||
}
|
||||
myBuilder.addPendingEdge(pendingScope, instruction);
|
||||
});
|
||||
|
||||
var nextExit = new PyWithContextExitInstruction(myBuilder, item);
|
||||
exits.add(nextExit);
|
||||
myBuilder.instructions.add(nextExit);
|
||||
// ControlFlowUtil.iterate assumes nodes are added to CFG in order they are created
|
||||
}
|
||||
|
||||
final var toAllExits = addTransparentInstruction();
|
||||
final var fromAllExits = addTransparentInstruction();
|
||||
for (var exit : exits) {
|
||||
myBuilder.addEdge(toAllExits, exit);
|
||||
myBuilder.addEdge(exit, fromAllExits);
|
||||
}
|
||||
|
||||
int stmtStart = myBuilder.instructions.size();
|
||||
node.getStatementList().accept(this);
|
||||
int stmtEnd = myBuilder.instructions.size();
|
||||
|
||||
for (int j = stmtStart; j < stmtEnd; j++) {
|
||||
final Instruction instruction = myBuilder.instructions.get(j);
|
||||
final PsiElement e = instruction.getElement();
|
||||
if (e == null || !canRaiseExceptions(instruction) || !PsiTreeUtil.isAncestor(node, e, false)) {
|
||||
continue;
|
||||
}
|
||||
myBuilder.addEdge(instruction, toAllExits);
|
||||
}
|
||||
|
||||
// Checks if exit nodes will have at least one predecessor
|
||||
if (exits.size() > 1 || !toAllExits.allPred().isEmpty()) {
|
||||
myBuilder.addPendingEdge(node, fromAllExits);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -935,26 +941,16 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
statementList.accept(this);
|
||||
}
|
||||
|
||||
private void abruptFlow(final PsiElement node) {
|
||||
// Here we process pending instructions!!!
|
||||
myBuilder.processPending((pendingScope, instruction) -> {
|
||||
if (pendingScope != null && PsiTreeUtil.isAncestor(node, pendingScope, false)) {
|
||||
myBuilder.addPendingEdge(null, instruction);
|
||||
}
|
||||
else {
|
||||
myBuilder.addPendingEdge(pendingScope, instruction);
|
||||
}
|
||||
});
|
||||
myBuilder.addPendingEdge(null, myBuilder.prevInstruction);
|
||||
myBuilder.flowAbrupted();
|
||||
}
|
||||
|
||||
private static boolean canRaiseExceptions(final Instruction instruction) {
|
||||
private static boolean canRaiseExceptions(@NotNull Instruction instruction) {
|
||||
if (instruction instanceof ReadWriteInstruction) {
|
||||
return true;
|
||||
}
|
||||
return !PsiTreeUtil.instanceOf(instruction.getElement(),
|
||||
PyStatementList.class);
|
||||
PsiElement element = instruction.getElement();
|
||||
return !(element instanceof PyReturnStatement returnStatement && returnStatement.getExpression() == null
|
||||
|| element instanceof PyContinueStatement
|
||||
|| element instanceof PyBreakStatement
|
||||
|| element instanceof PyPassStatement
|
||||
|| element instanceof PyStatementList);
|
||||
}
|
||||
|
||||
private void addTypeAssertionNodes(@NotNull PyExpression condition, boolean positive) {
|
||||
|
||||
+8
-1
@@ -2,6 +2,13 @@ package com.jetbrains.python.codeInsight.controlflow
|
||||
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowBuilder
|
||||
import com.intellij.codeInsight.controlflow.impl.InstructionImpl
|
||||
import com.jetbrains.python.psi.PyAssertStatement
|
||||
import com.jetbrains.python.psi.PyRaiseStatement
|
||||
import com.jetbrains.python.psi.PyStatement
|
||||
|
||||
class PyRaiseInstruction(builder: ControlFlowBuilder, raiseStatement: PyRaiseStatement?): InstructionImpl(builder, raiseStatement)
|
||||
class PyRaiseInstruction private constructor(builder: ControlFlowBuilder, raiseOrAssert: PyStatement): InstructionImpl(builder, raiseOrAssert) {
|
||||
constructor(builder: ControlFlowBuilder, raise: PyRaiseStatement): this(builder, raise as PyStatement)
|
||||
constructor(builder: ControlFlowBuilder, assert: PyAssertStatement): this(builder, assert as PyStatement)
|
||||
|
||||
override fun getElementPresentation(): String = "raise: ${element}"
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
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.types.PyCollectionType
|
||||
import com.jetbrains.python.psi.types.PyTypeUtil
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
|
||||
class PyWithContextExitInstruction(builder: ControlFlowBuilder, withItem: PyWithItem): InstructionImpl(builder, withItem) {
|
||||
override fun getElementPresentation(): String = "exit context manager: ${element.text}"
|
||||
override fun getElement(): PyWithItem = super.getElement() as PyWithItem
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -128,7 +128,10 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
||||
PARAM_SPEC, PARAM_SPEC_EXT,
|
||||
TYPE_VAR_TUPLE, TYPE_VAR_TUPLE_EXT
|
||||
);
|
||||
|
||||
|
||||
public static final String CONTEXT_MANAGER = "contextlib.AbstractContextManager";
|
||||
public static final String ASYNC_CONTEXT_MANAGER = "contextlib.AbstractAsyncContextManager";
|
||||
|
||||
public static final Set<String> TYPE_DICT_QUALIFIERS = Set.of(REQUIRED, REQUIRED_EXT, NOT_REQUIRED, NOT_REQUIRED_EXT, READONLY, READONLY_EXT);
|
||||
|
||||
public static final String UNPACK = "typing.Unpack";
|
||||
|
||||
+47
-17
@@ -4,42 +4,72 @@ package com.jetbrains.python.inspections;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlow;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowUtil;
|
||||
import com.intellij.codeInsight.controlflow.Instruction;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyWithContextExitInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.psi.PyStatementListContainer;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class PyInspectionsUtil {
|
||||
@ApiStatus.Internal
|
||||
public static boolean hasAnyInterruptedControlFlowPaths(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(element);
|
||||
if (owner != null) {
|
||||
final ControlFlow flow = ControlFlowCache.getControlFlow(owner);
|
||||
final Instruction[] instructions = flow.getInstructions();
|
||||
final int start = ControlFlowUtil.findInstructionNumberByElement(instructions, element);
|
||||
if (start >= 0) {
|
||||
final Ref<Boolean> resultRef = Ref.create(false);
|
||||
ControlFlowUtil.iteratePrev(start, instructions, instruction -> {
|
||||
if (CallInstruction.Companion.allPredWithoutNoReturn(instruction, context).isEmpty() && !isFirstInstruction(instruction)) {
|
||||
resultRef.set(true);
|
||||
return ControlFlowUtil.Operation.BREAK;
|
||||
}
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
});
|
||||
return resultRef.get();
|
||||
}
|
||||
return !collectUnreachable(owner, element, context).isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects a list of unreachable elements, iterating through CFG backwards
|
||||
*
|
||||
* @param anchor the anchor element to start the iteration from, can be null to start from the last instruction
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
static boolean isFirstInstruction(Instruction instruction) {
|
||||
return instruction.num() == 0;
|
||||
public static @NotNull List<PsiElement> collectUnreachable(@NotNull ScopeOwner owner, @Nullable PsiElement anchor, @NotNull TypeEvalContext context) {
|
||||
final ControlFlow flow = ControlFlowCache.getControlFlow(owner);
|
||||
final Instruction[] instructions = flow.getInstructions();
|
||||
final List<PsiElement> unreachable = new ArrayList<>();
|
||||
final int start = anchor != null ? ControlFlowUtil.findInstructionNumberByElement(instructions, anchor) : instructions.length - 1;
|
||||
if (start >= 0) {
|
||||
ControlFlowUtil.iteratePrev(start, instructions, instruction -> {
|
||||
if (getReachablePredecessors(instruction, context).isEmpty() && instruction.num() != 0) {
|
||||
ContainerUtil.addIfNotNull(unreachable, getRelevantElement(instruction));
|
||||
}
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
});
|
||||
}
|
||||
return unreachable;
|
||||
}
|
||||
|
||||
private static @NotNull List<Instruction> getReachablePredecessors(@NotNull Instruction instruction, @NotNull TypeEvalContext context) {
|
||||
return ContainerUtil.filter(instruction.allPred(), it -> {
|
||||
if (it instanceof CallInstruction ci && ci.isNoReturnCall(context)) return false;
|
||||
if (it instanceof PyWithContextExitInstruction wi && !wi.isSuppressingExceptions(context)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement getRelevantElement(@NotNull Instruction instruction) {
|
||||
if (instruction instanceof PyWithContextExitInstruction) {
|
||||
return null;
|
||||
}
|
||||
PsiElement element = instruction.getElement();
|
||||
if (element instanceof PyStatementListContainer) {
|
||||
return ((PyStatementListContainer)element).getStatementList();
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private PyInspectionsUtil() {
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ import com.intellij.psi.PsiPolyVariantReference;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyWithContextExitInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
|
||||
|
||||
+3
-28
@@ -1,24 +1,14 @@
|
||||
// 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.inspections;
|
||||
|
||||
import com.intellij.codeInsight.controlflow.ControlFlow;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowUtil;
|
||||
import com.intellij.codeInsight.controlflow.Instruction;
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession;
|
||||
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;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Detects unreachable code using control flow graph
|
||||
@@ -38,27 +28,12 @@ public final class PyUnreachableCodeInspection extends PyInspection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitElement(final @NotNull PsiElement element) {
|
||||
if (element instanceof ScopeOwner) {
|
||||
final ControlFlow flow = ControlFlowCache.getControlFlow((ScopeOwner)element);
|
||||
final Instruction[] instructions = flow.getInstructions();
|
||||
final List<PsiElement> unreachable = new ArrayList<>();
|
||||
if (instructions.length > 0) {
|
||||
ControlFlowUtil.iteratePrev(instructions.length - 1, instructions, instruction -> {
|
||||
if (CallInstruction.Companion.allPredWithoutNoReturn(instruction, myTypeEvalContext).isEmpty() && !PyInspectionsUtil.isFirstInstruction(instruction)) {
|
||||
unreachable.add(unwrapStatementListContainer(instruction.getElement()));
|
||||
}
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
});
|
||||
}
|
||||
for (PsiElement e : unreachable) {
|
||||
public void visitElement(@NotNull final PsiElement element) {
|
||||
if (element instanceof ScopeOwner owner) {
|
||||
for (PsiElement e : PyInspectionsUtil.collectUnreachable(owner, null, myTypeEvalContext)) {
|
||||
registerProblem(e, PyPsiBundle.message("INSP.unreachable.code"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement unwrapStatementListContainer(@Nullable PsiElement element) {
|
||||
return element instanceof PyStatementListContainer ? ((PyStatementListContainer)element).getStatementList() : element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyWithContextExitInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
|
||||
|
||||
@@ -22,10 +22,7 @@ import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyStubElementTypes;
|
||||
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyRaiseInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.controlflow.*;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
|
||||
@@ -391,6 +388,9 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
|
||||
if (instruction instanceof PyRaiseInstruction) {
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
if (instruction instanceof PyWithContextExitInstruction withExit && !withExit.isSuppressingExceptions(context)) {
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
final PsiElement element = instruction.getElement();
|
||||
if (!(element instanceof PyStatement statement)) {
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
|
||||
@@ -1676,6 +1676,16 @@ public final class PyTypeChecker {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static @Nullable PyType convertToType(@Nullable PyType type, @NotNull PyClassType superType, @NotNull TypeEvalContext context) {
|
||||
MatchContext matchContext = new MatchContext(context, new GenericSubstitutions(), false);
|
||||
Optional<Boolean> matched = match(superType, type, matchContext);
|
||||
if (matched.orElse(false)) {
|
||||
return substitute(superType, matchContext.mySubstitutions, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static class Generics {
|
||||
private final @NotNull Set<PyTypeVarType> typeVars = new LinkedHashSet<>();
|
||||
|
||||
@@ -19,6 +19,9 @@ import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.UserDataHolder;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyPsiFacade;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -167,4 +170,16 @@ public final class PyTypeUtil {
|
||||
public static @Nullable PyType getEffectiveBound(@NotNull PyTypeVarType typeVarType) {
|
||||
return typeVarType.getConstraints().isEmpty() ? typeVarType.getBound() : PyUnionType.union(typeVarType.getConstraints());
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static @Nullable PyType convertToType(@Nullable PyType type,
|
||||
@NotNull String superTypeName,
|
||||
@NotNull PsiElement anchor,
|
||||
@NotNull TypeEvalContext context) {
|
||||
PyClass superClass = PyPsiFacade.getInstance(anchor.getProject()).createClassByQName(superTypeName, anchor);
|
||||
if (superClass == null) return null;
|
||||
PyClassType superClassType = ObjectUtils.notNull(PyTypeChecker.findGenericDefinitionType(superClass, context),
|
||||
new PyClassTypeImpl(superClass, false));
|
||||
return PyTypeChecker.convertToType(type, superClassType, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,7 @@ import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.Version;
|
||||
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;
|
||||
import com.jetbrains.python.codeInsight.controlflow.*;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator;
|
||||
@@ -77,6 +74,12 @@ public final class PyDefUseUtil {
|
||||
final HashMap<PyCallSiteExpression, ConditionalInstruction> pendingTypeGuard = new HashMap<>();
|
||||
ControlFlowUtil.iteratePrev(startNum, instructions,
|
||||
instruction -> {
|
||||
if (instruction instanceof PyWithContextExitInstruction withExit) {
|
||||
// probably should remove acceptTypeAssertions and make context nullable
|
||||
if (!withExit.isSuppressingExceptions(context)) {
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
}
|
||||
if (acceptTypeAssertions && instruction instanceof CallInstruction callInstruction) {
|
||||
var typeGuardInstruction = pendingTypeGuard.get(instruction.getElement());
|
||||
if (typeGuardInstruction != null) {
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
15(18) ASSERTTYPE ACCESS: s
|
||||
16(17) element: null. Condition: isinstance(s, basestring):true
|
||||
17(19) ASSERTTYPE ACCESS: s
|
||||
18(19) element: null
|
||||
18(19) raise: PyAssertStatement
|
||||
19() element: null
|
||||
@@ -15,7 +15,7 @@
|
||||
14(15) element: PyExpressionStatement
|
||||
15(16) READ ACCESS: sys
|
||||
16(4,17) element: PyCallExpression: sys.exit
|
||||
17(18) element: PyRaiseStatement
|
||||
17(18) raise: PyRaiseStatement
|
||||
18(19) READ ACCESS: Exception
|
||||
19(20) element: PyCallExpression: Exception
|
||||
20() element: null
|
||||
@@ -5,7 +5,7 @@
|
||||
4(5,9) WRITE ACCESS: lsprof
|
||||
5(6) element: PyExceptPart
|
||||
6(7) READ ACCESS: ImportError
|
||||
7(8) element: PyRaiseStatement
|
||||
7(8) raise: PyRaiseStatement
|
||||
8(25) READ ACCESS: Error
|
||||
9(10) element: PyAssignmentStatement
|
||||
10(11) WRITE ACCESS: p
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
10(11,18) READ ACCESS: renderer
|
||||
11(12) element: PyExceptPart
|
||||
12(13) READ ACCESS: KeyError
|
||||
13(14) element: PyRaiseStatement
|
||||
13(14) raise: PyRaiseStatement
|
||||
14(18) READ ACCESS: Error
|
||||
15(16) element: PyStatementList
|
||||
16(17) element: PyReturnStatement
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
0(1) element: null
|
||||
1(2) element: PyTryExceptStatement
|
||||
2(3,6) element: PyTryPart
|
||||
3(4,6) element: PyRaiseStatement
|
||||
3(4,6) raise: PyRaiseStatement
|
||||
4(5,6) READ ACCESS: Exception
|
||||
5(6,13) element: PyCallExpression: Exception
|
||||
6(7) element: PyExceptPart
|
||||
@@ -9,6 +9,6 @@
|
||||
8(9) READ ACCESS: Exception
|
||||
9(10) element: PyCallExpression: Exception
|
||||
10(11) WRITE ACCESS: foo
|
||||
11(12) element: PyRaiseStatement
|
||||
11(12) raise: PyRaiseStatement
|
||||
12(13) READ ACCESS: foo
|
||||
13() element: null
|
||||
@@ -33,5 +33,5 @@
|
||||
32(33) element: PySubscriptionExpression
|
||||
33(34) READ ACCESS: sys
|
||||
34(35) element: PyCallExpression: sys.exc_info
|
||||
35(36) element: PyRaiseStatement
|
||||
35(36) raise: PyRaiseStatement
|
||||
36() element: null
|
||||
@@ -5,9 +5,9 @@
|
||||
4(5,8,12) READ ACCESS: bar
|
||||
5(6,8) element: PyTargetExpression: i
|
||||
6(7,8) WRITE ACCESS: i
|
||||
7(8,12) element: PyBreakStatement
|
||||
7(12) element: PyBreakStatement
|
||||
8(9) element: PyExceptPart
|
||||
9(10) element: PyRaiseStatement
|
||||
9(10) raise: PyRaiseStatement
|
||||
10(11) READ ACCESS: Exception
|
||||
11(13) element: PyCallExpression: Exception
|
||||
12(13) element: PyReturnStatement
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
0(1) element: null
|
||||
1(2) element: PyTryExceptStatement
|
||||
2(3,5) element: PyTryPart
|
||||
3(4,5) element: PyRaiseStatement
|
||||
3(4,5) raise: PyRaiseStatement
|
||||
4(5) READ ACCESS: KeyboardInterrupt
|
||||
5(6) element: PyFinallyPart
|
||||
6(7) element: PyPrintStatement
|
||||
|
||||
@@ -23,21 +23,21 @@
|
||||
22(23) element: null. Condition: x == 0:true
|
||||
23(51,24) ASSERTTYPE ACCESS: x
|
||||
24(25) element: PyStatementList
|
||||
25(51,54) element: PyBreakStatement
|
||||
25(54) element: PyBreakStatement
|
||||
26(27,29,51) READ ACCESS: x
|
||||
27(28) element: null. Condition: x == 1:false
|
||||
28(51,33) ASSERTTYPE ACCESS: x
|
||||
29(30) element: null. Condition: x == 1:true
|
||||
30(51,31) ASSERTTYPE ACCESS: x
|
||||
31(32) element: PyStatementList
|
||||
32(7,51,54) element: PyContinueStatement
|
||||
32(7,54) element: PyContinueStatement
|
||||
33(34,36,51) READ ACCESS: x
|
||||
34(35) element: null. Condition: x == 2:false
|
||||
35(51,42) ASSERTTYPE ACCESS: x
|
||||
36(37) element: null. Condition: x == 2:true
|
||||
37(51,38) ASSERTTYPE ACCESS: x
|
||||
38(39) element: PyStatementList
|
||||
39(40,51) element: PyRaiseStatement
|
||||
39(40,51) raise: PyRaiseStatement
|
||||
40(41,51) READ ACCESS: Exception
|
||||
41(51) element: PyCallExpression: Exception
|
||||
42(43,45,51) READ ACCESS: x
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
0(1) element: null
|
||||
1(2) element: PyWithStatement
|
||||
2(3) READ ACCESS: context_manager
|
||||
3(4) element: PyAssertStatement
|
||||
4(5) READ ACCESS: True
|
||||
5(6) READ ACCESS: f
|
||||
6(7) element: PyCallExpression: f
|
||||
7(8,9) READ ACCESS: True
|
||||
8(10) element: null. Condition: True:false
|
||||
9(11) element: null. Condition: True:true
|
||||
10(13) element: null
|
||||
11(12) element: PyPrintStatement
|
||||
12(13) element: PyPrintStatement
|
||||
13() element: null
|
||||
2(4) READ ACCESS: context_manager
|
||||
3(13) exit context manager: context_manager
|
||||
4(5,3) element: PyAssertStatement
|
||||
5(6,3) READ ACCESS: True
|
||||
6(7,3) READ ACCESS: f
|
||||
7(8,3) element: PyCallExpression: f
|
||||
8(9,10,3) READ ACCESS: True
|
||||
9(11) element: null. Condition: True:false
|
||||
10(12) element: null. Condition: True:true
|
||||
11(14,3) raise: PyAssertStatement
|
||||
12(13,3) element: PyPrintStatement
|
||||
13(14) element: PyPrintStatement
|
||||
14() element: null
|
||||
@@ -1,10 +1,11 @@
|
||||
0(1) element: null
|
||||
1(2) element: PyWithStatement
|
||||
2(3) READ ACCESS: context_manager
|
||||
3(4) element: PyAssertStatement
|
||||
4(5) READ ACCESS: False
|
||||
5(6) READ ACCESS: f
|
||||
6(8,9) element: PyCallExpression: f
|
||||
7(8) element: PyPrintStatement
|
||||
8(9) element: PyPrintStatement
|
||||
9() element: null
|
||||
2(4) READ ACCESS: context_manager
|
||||
3(9) exit context manager: context_manager
|
||||
4(5,3) element: PyAssertStatement
|
||||
5(6,3) READ ACCESS: False
|
||||
6(7,3) READ ACCESS: f
|
||||
7(10,3) element: PyCallExpression: f
|
||||
8(9,3) element: PyPrintStatement
|
||||
9(10) element: PyPrintStatement
|
||||
10() element: null
|
||||
@@ -1,17 +1,18 @@
|
||||
0(1) element: null
|
||||
1(2) element: PyWithStatement
|
||||
2(3) READ ACCESS: context_manager
|
||||
3(4) element: PyIfStatement
|
||||
4(5,6) READ ACCESS: c
|
||||
5(11) element: null. Condition: c:false
|
||||
6(7) element: null. Condition: c:true
|
||||
7(8) ASSERTTYPE ACCESS: c
|
||||
8(9) element: PyStatementList
|
||||
9(10) element: PyRaiseStatement
|
||||
10(14,16) READ ACCESS: ValueError
|
||||
11(12) element: PyAssignmentStatement
|
||||
12(13) READ ACCESS: c
|
||||
13(14) WRITE ACCESS: val
|
||||
14(15) element: PyPrintStatement
|
||||
15(16) READ ACCESS: val
|
||||
16() element: null
|
||||
2(4) READ ACCESS: context_manager
|
||||
3(15) exit context manager: context_manager
|
||||
4(5,3) element: PyIfStatement
|
||||
5(6,7,3) READ ACCESS: c
|
||||
6(12) element: null. Condition: c:false
|
||||
7(8) element: null. Condition: c:true
|
||||
8(3,9) ASSERTTYPE ACCESS: c
|
||||
9(10) element: PyStatementList
|
||||
10(11,3) raise: PyRaiseStatement
|
||||
11(17,3) READ ACCESS: ValueError
|
||||
12(13,3) element: PyAssignmentStatement
|
||||
13(14,3) READ ACCESS: c
|
||||
14(15,3) WRITE ACCESS: val
|
||||
15(16) element: PyPrintStatement
|
||||
16(17) READ ACCESS: val
|
||||
17() element: null
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
with cm1, cm2, cm3:
|
||||
assert False, f()
|
||||
print("Unreachable")
|
||||
|
||||
print("Reachable")
|
||||
@@ -0,0 +1,15 @@
|
||||
0(1) element: null
|
||||
1(2) element: PyWithStatement
|
||||
2(4) READ ACCESS: cm1
|
||||
3(13) exit context manager: cm1
|
||||
4(3,6) READ ACCESS: cm2
|
||||
5(13) exit context manager: cm2
|
||||
6(3,5,8) READ ACCESS: cm3
|
||||
7(13) exit context manager: cm3
|
||||
8(9,3,5,7) element: PyAssertStatement
|
||||
9(10,3,5,7) READ ACCESS: False
|
||||
10(11,3,5,7) READ ACCESS: f
|
||||
11(14,3,5,7) element: PyCallExpression: f
|
||||
12(13,3,5,7) element: PyPrintStatement
|
||||
13(14) element: PyPrintStatement
|
||||
14() element: null
|
||||
@@ -17,7 +17,7 @@ def test(c):
|
||||
"""
|
||||
x1 = f1(c)
|
||||
f2(x1) # Weaker union types
|
||||
f3(<warning descr="Expected type 'int', got 'Union[str, list, None]' instead">x1</warning>)
|
||||
f3(<warning descr="Expected type 'int', got 'Union[list, str, None]' instead">x1</warning>)
|
||||
|
||||
f2(<warning descr="Expected type 'str', got 'int' instead">x1.count('')</warning>)
|
||||
f3(x1.count(''))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import unittest
|
||||
|
||||
def f():
|
||||
return
|
||||
<warning descr="This code is unreachable">a = 1</warning>
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
class C(object):
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc, value, traceback):
|
||||
return True
|
||||
|
||||
|
||||
def f11():
|
||||
with C():
|
||||
raise Exception()
|
||||
print(1) #pass
|
||||
|
||||
|
||||
def g2():
|
||||
raise Exception()
|
||||
|
||||
|
||||
def f12():
|
||||
with C():
|
||||
return g2()
|
||||
<warning descr="This code is unreachable">print(1) #pass</warning>
|
||||
|
||||
def f13():
|
||||
with C():
|
||||
assert False
|
||||
print(1) #pass
|
||||
|
||||
|
||||
class A1(TestCase):
|
||||
def f3(self):
|
||||
with C():
|
||||
g2()
|
||||
print(1) #pass
|
||||
|
||||
|
||||
import contextlib
|
||||
from contextlib import suppress
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
def f21():
|
||||
with suppress(Exception):
|
||||
raise Exception()
|
||||
print(1) #pass
|
||||
|
||||
|
||||
def f22():
|
||||
with contextlib.suppress(Exception):
|
||||
return g2()
|
||||
print(1) #pass
|
||||
|
||||
|
||||
class A2(TestCase):
|
||||
def f3(self):
|
||||
with self.assertRaises(Exception):
|
||||
g2()
|
||||
print(1) #pass
|
||||
@@ -302,6 +302,11 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-51564
|
||||
public void testWithSeveralContextsAssert() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-29767
|
||||
public void testContinueInPositiveIteration() {
|
||||
doTest();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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;
|
||||
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.PyTypeUtil;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class PyTypeConversionTest extends PyTestCase {
|
||||
public void testCustomIterableClassToTypingIterable() {
|
||||
doTest("typing.Iterable", "Iterable[str]", """
|
||||
from typing import Iterator
|
||||
|
||||
class Class:
|
||||
def __next__(self) -> str:
|
||||
return "foo"
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return self
|
||||
|
||||
expr = Class()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDictToTypingMapping() {
|
||||
doTest("typing.Mapping", "Mapping[str, int]", """
|
||||
expr = {"foo": 42}
|
||||
""");
|
||||
}
|
||||
|
||||
public void testTupleToTypingIterable() {
|
||||
doTest("typing.Iterable", "Iterable[int | str]", """
|
||||
expr = (1, "foo")
|
||||
""");
|
||||
}
|
||||
|
||||
public void testCustomContextManagerClassToContextlibAbstractContextManager() {
|
||||
doTest("contextlib.AbstractContextManager", "AbstractContextManager[int, bool | None]", """
|
||||
class CustomManager:
|
||||
def __enter__(self) -> int:
|
||||
return 42
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb) -> bool | None:
|
||||
pass
|
||||
|
||||
expr = CustomManager()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testContextManagerGeneratorToContextlibAbstractContextManager() {
|
||||
doTest("contextlib.AbstractContextManager", "AbstractContextManager[str, bool | None]", """
|
||||
import contextlib
|
||||
|
||||
@contextlib.contextmanager
|
||||
def f():
|
||||
yield "foo"
|
||||
|
||||
expr = f()
|
||||
""");
|
||||
}
|
||||
|
||||
public void doTest(@NotNull String superTypeFqn, @NotNull String expectedResultType, @NotNull String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
|
||||
TypeEvalContext context = TypeEvalContext.codeAnalysis(expr.getProject(), expr.getContainingFile());
|
||||
PyType classType = assertInstanceOf(context.getType(expr), PyClassType.class);
|
||||
PyType converted = PyTypeUtil.convertToType(classType, superTypeFqn, expr, context);
|
||||
String actualType = PythonDocumentationProvider.getTypeName(converted, context);
|
||||
assertEquals(expectedResultType, actualType);
|
||||
}
|
||||
}
|
||||
+130
-26
@@ -25,42 +25,146 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON26, () -> doTest());
|
||||
}
|
||||
|
||||
// PY-7420, PY-16419, PY-26417
|
||||
public void testWithSuppressedExceptions() {
|
||||
doTest();
|
||||
// PY-51564
|
||||
public void testWithNotContext() {
|
||||
doTestByText("""
|
||||
class NotContext:
|
||||
pass
|
||||
|
||||
def no_context():
|
||||
with NotContext():
|
||||
raise ValueError("Something went wrong")
|
||||
<warning descr="This code is unreachable">print("unreachable")</warning>
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-7420, PY-16419, PY-26417
|
||||
public void testWithNotSuppressedExceptions() {
|
||||
// PY-51564
|
||||
public void testWithContextlibUnittest() {
|
||||
doTestByText("""
|
||||
import contextlib
|
||||
from contextlib import suppress
|
||||
from unittest import TestCase
|
||||
|
||||
def cl():
|
||||
with suppress(Exception):
|
||||
raise ValueError("Something went wrong")
|
||||
print("reachable")
|
||||
|
||||
class A(TestCase):
|
||||
def f(self):
|
||||
with self.assertRaises(Exception):
|
||||
raise ValueError("Something went wrong")
|
||||
print("reachable")
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-51564
|
||||
public void testWith() {
|
||||
doTestByText(
|
||||
"""
|
||||
class C(object):
|
||||
def __enter__(self):
|
||||
return self
|
||||
class Suppress:
|
||||
def __enter__(self): ...
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> bool: ...
|
||||
|
||||
def __exit__(self, exc, value, traceback):
|
||||
return False
|
||||
class NoSuppress:
|
||||
def __enter__(self): ...
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> bool | None: ...
|
||||
|
||||
def f1():
|
||||
with C():
|
||||
raise Exception()
|
||||
print(1) #pass
|
||||
def sup(b):
|
||||
with Suppress():
|
||||
a = 42
|
||||
raise ValueError("Something went wrong")
|
||||
print("reachable")
|
||||
|
||||
with Suppress():
|
||||
assert b
|
||||
a = 42
|
||||
assert False
|
||||
print("reachable")
|
||||
|
||||
def g2():
|
||||
raise Exception()
|
||||
|
||||
def f2():
|
||||
with C():
|
||||
return g2()
|
||||
<warning descr="This code is unreachable">print(1) #pass</warning>
|
||||
|
||||
def f3():
|
||||
with C():
|
||||
g2()
|
||||
print(1) #pass"""
|
||||
def nosup(b):
|
||||
with NoSuppress():
|
||||
a = 42
|
||||
raise ValueError("Something went wrong")
|
||||
<warning descr="This code is unreachable">print("unreachable")</warning>
|
||||
|
||||
with NoSuppress():
|
||||
assert b
|
||||
a = 42
|
||||
assert False
|
||||
<warning descr="This code is unreachable">print("unreachable")</warning>
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
// PY-51564
|
||||
public void testMiltipleWith() {
|
||||
doTestByText("""
|
||||
import contextlib
|
||||
|
||||
@contextlib.contextmanager
|
||||
def raising_exception_in_enter(p):
|
||||
if p:
|
||||
raise Exception
|
||||
yield
|
||||
|
||||
|
||||
def f():
|
||||
with contextlib.suppress(Exception):
|
||||
return
|
||||
<warning descr="This code is unreachable">print("Unreachable")</warning>
|
||||
|
||||
def f2(p):
|
||||
with contextlib.suppress(Exception):
|
||||
with raising_exception_in_enter(p):
|
||||
return
|
||||
print("Reachable")
|
||||
|
||||
|
||||
def f3(p):
|
||||
with contextlib.suppress(Exception), raising_exception_in_enter(p):
|
||||
return
|
||||
print("Reachable")
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-51564
|
||||
public void testAsyncWith() {
|
||||
doTestByText("""
|
||||
class AsyncSuppress:
|
||||
async def __aenter__(self): ...
|
||||
async def __aexit__(self, exc_type, exc_value, traceback) -> bool: ...
|
||||
|
||||
class AsyncNoSuppress:
|
||||
async def __aenter__(self): ...
|
||||
async def __aexit__(self, exc_type, exc_value, traceback) -> bool | None: ...
|
||||
|
||||
async def sup(b):
|
||||
async with AsyncSuppress():
|
||||
a = 42
|
||||
raise ValueError("Something went wrong")
|
||||
print("reachable")
|
||||
|
||||
async with AsyncSuppress():
|
||||
assert b
|
||||
a = 42
|
||||
assert False
|
||||
print("reachable")
|
||||
|
||||
async def nosup(b):
|
||||
async with AsyncNoSuppress():
|
||||
a = 42
|
||||
raise ValueError("Something went wrong")
|
||||
<warning descr="This code is unreachable">print("unreachable")</warning>
|
||||
|
||||
async with AsyncNoSuppress():
|
||||
assert b
|
||||
a = 42
|
||||
assert False
|
||||
<warning descr="This code is unreachable">print("unreachable")</warning>
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-25974
|
||||
public void testExprOrSysExitAssignedToVar() {
|
||||
doTest();
|
||||
|
||||
@@ -149,7 +149,7 @@ public class PyUnusedLocalInspectionTest extends PyInspectionTestCase {
|
||||
|
||||
def f12():
|
||||
with C():
|
||||
<weak_warning descr="Local variable 'x' value is not used">x</weak_warning> = 2
|
||||
x = 2
|
||||
return g2()
|
||||
print(x) #pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user