diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index 394fccea0b53..b7645424e8da 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -699,11 +699,12 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { @Override public void visitPyRaiseStatement(final @NotNull PyRaiseStatement node) { - myBuilder.addNodeAndCheckPending(new PyRaiseInstruction(myBuilder, node)); + myBuilder.startNode(node); final PyExpression[] expressions = node.getExpressions(); for (PyExpression expression : expressions) { expression.accept(this); } + myBuilder.addNode(new PyRaiseInstruction(myBuilder, node)); myBuilder.addPendingEdge(null, myBuilder.prevInstruction); myBuilder.flowAbrupted(); } diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyDataFlow.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyDataFlow.kt index a3f9203feeb7..cd323a153b05 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyDataFlow.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyDataFlow.kt @@ -4,7 +4,10 @@ import com.intellij.codeInsight.controlflow.ControlFlow import com.intellij.codeInsight.controlflow.ControlFlowUtil import com.intellij.codeInsight.controlflow.Instruction import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil +import com.jetbrains.python.psi.* +import com.jetbrains.python.psi.impl.PyEvaluator import com.jetbrains.python.psi.types.PyNeverType import com.jetbrains.python.psi.types.TypeEvalContext import org.jetbrains.annotations.ApiStatus @@ -50,13 +53,77 @@ class PyDataFlow(controlFlow: ControlFlow, private val context: TypeEvalContext) } } -fun PsiElement.isUnreachable(context: TypeEvalContext): Boolean { +/** + * Checks if inspections should flag a Python element as unreachable. + * + * This method considers special cases where code might be technically unreachable + * but should not be reported as an issue. + * In particular, the first terminating statement in a sequence is considered valid + * and should not be reported as unreachable. + * + * Terminating statements include: + * - `raise` statements + * - `assert False` + * - calls to functions annotated with `NoReturn` + */ +fun PsiElement.isUnreachableForInspection(context: TypeEvalContext): Boolean { + return isUnreachableByControlFlow(context) && !isFirstTerminatingStatement(context) +} + +/** + * Determines if the element is unreachable by control flow analysis. + * If the element does not have corresponding instruction in CFG, searches for the nearest parent that has. + */ +fun PsiElement.isUnreachableByControlFlow(context: TypeEvalContext): Boolean { + return PyUtil.getParameterizedCachedValue(this, context) { this.isUnreachableByControlFlowNoCache(it) } +} + +private fun PsiElement.isUnreachableByControlFlowNoCache(context: TypeEvalContext): Boolean { val scope = ScopeUtil.getScopeOwner(this) if (scope != null) { - val flow = ControlFlowCache.getControlFlow(scope).getInstructions() - val idx = ControlFlowUtil.findInstructionNumberByElement(flow, this) - if (idx < 0) return false - return ControlFlowCache.getDataFlow(scope, context).isUnreachable(flow[idx]) + val flow = ControlFlowCache.getDataFlow(scope, context) + val instructions = flow.instructions + val idx = ControlFlowUtil.findInstructionNumberByElement(instructions, this) + if (idx < 0 || instructions[idx].isAuxiliary()) { + val parent = this.parent + return parent != null && parent.isUnreachableByControlFlow(context) + } + return flow.isUnreachable(instructions[idx]) } return false -} \ No newline at end of file +} + +private fun PsiElement.isFirstTerminatingStatement(context: TypeEvalContext): Boolean { + if (this.isTerminatingStatement(context)) { + val prevSibling = prevSiblingOfType() ?: return true + return !prevSibling.isTerminatingStatement(context) && !prevSibling.isUnreachableByControlFlow(context) + } + return false +} + +private fun PsiElement.isTerminatingStatement(context: TypeEvalContext): Boolean { + return when (this) { + is PyRaiseStatement -> true + is PyAssertStatement -> getArguments().firstOrNull()?.asBooleanNoResolve() == false + is PyExpressionStatement -> expression is PyCallExpression && context.getType(expression) is PyNeverType + else -> false + } +} + +private fun Instruction.isAuxiliary(): Boolean { + return when (this) { + is PyRaiseInstruction -> true + is PyWithContextExitInstruction -> true + is PyFinallyFailExitInstruction -> true + is ReadWriteInstruction -> access.isAssertTypeAccess + else -> false + } +} + +private fun PyExpression.asBooleanNoResolve(): Boolean? { + return PyEvaluator.evaluateAsBooleanNoResolve(this) +} + +private inline fun PsiElement.prevSiblingOfType(): T? { + return PsiTreeUtil.getPrevSiblingOfType(this, T::class.java) +} diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyInspectionsUtil.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyInspectionsUtil.java deleted file mode 100644 index e99f5b7558b1..000000000000 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyInspectionsUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2000-2019 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.psi.PsiElement; -import com.intellij.util.containers.ContainerUtil; -import com.jetbrains.python.codeInsight.controlflow.*; -import com.jetbrains.python.psi.PyStatementListContainer; -import com.jetbrains.python.psi.types.PyNeverType; -import com.jetbrains.python.psi.types.TypeEvalContext; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public final class PyInspectionsUtil { - - /** - * Collects a list of unreachable elements, iterating through CFG backwards - */ - @ApiStatus.Internal - public static @NotNull List collectUnreachable(@NotNull ScopeOwner owner, @Nullable PsiElement anchor, @NotNull TypeEvalContext context) { - final ControlFlow flow = ControlFlowCache.getControlFlow(owner); - final Instruction[] instructions = flow.getInstructions(); - final List 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 getReachablePredecessors(@NotNull Instruction instruction, @NotNull TypeEvalContext context) { - // TODO: merge this with PyDataFlow - 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; - if (it instanceof ReadWriteInstruction rw && rw.getAccess().isAssertTypeAccess()) { - var type = rw.getType(context, null); - return !(type != null && type.get() instanceof PyNeverType); - } - return true; - }); - } - - private static @Nullable PsiElement getRelevantElement(@NotNull Instruction instruction) { - if (instruction instanceof PyWithContextExitInstruction) { - return null; - } - if (instruction instanceof ReadWriteInstruction rw && rw.getAccess().isAssertTypeAccess()) { - return null; - } - PsiElement element = instruction.getElement(); - if (element instanceof PyStatementListContainer) { - return ((PyStatementListContainer)element).getStatementList(); - } - return element; - } - - private PyInspectionsUtil() { - } -} diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java index 14abdcb12003..fffbd73c062c 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java @@ -67,7 +67,7 @@ public final class PyUnboundLocalVariableInspection extends PyInspection { if (PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class) != null) { return; } - if (PyDataFlowKt.isUnreachable(node, myTypeEvalContext)) { + if (PyDataFlowKt.isUnreachableForInspection(node, myTypeEvalContext)) { return; } final String name = node.getReferencedName(); diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnreachableCodeInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnreachableCodeInspection.kt index df734dc41d95..1c1fb8ff0733 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnreachableCodeInspection.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnreachableCodeInspection.kt @@ -2,11 +2,13 @@ package com.jetbrains.python.inspections import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder -import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.util.findParentInFile import com.jetbrains.python.PyPsiBundle -import com.jetbrains.python.codeInsight.controlflow.ScopeOwner +import com.jetbrains.python.codeInsight.controlflow.isUnreachableForInspection +import com.jetbrains.python.psi.PyElement /** * Detects unreachable code using control flow graph @@ -18,13 +20,15 @@ class PyUnreachableCodeInspection : PyInspection() { session: LocalInspectionToolSession ): PsiElementVisitor { return object : PyInspectionVisitor(holder, getContext(session)) { - override fun visitElement(element: PsiElement) { - if (element is ScopeOwner) { - for (e in PyInspectionsUtil.collectUnreachable(element, null, myTypeEvalContext)) { - registerProblem(e, PyPsiBundle.message("INSP.unreachable.code")) + override fun visitPyElement(node: PyElement) { + 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) } } } } -} +} \ No newline at end of file diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java index aa57de9cadac..7c97579ffaed 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java @@ -193,7 +193,7 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor return; } if (!expr.isQualified()) { - if (PyDataFlowKt.isUnreachable(expr, myTypeEvalContext)) { + if (PyDataFlowKt.isUnreachableForInspection(expr, myTypeEvalContext)) { return; } ContainerUtil.addIfNotNull(fixes, getTrueFalseQuickFix(refText)); diff --git a/python/testData/codeInsight/controlflow/ContinueInPositiveIteration.txt b/python/testData/codeInsight/controlflow/ContinueInPositiveIteration.txt index 48de0160573a..ddc0c16f7c8d 100644 --- a/python/testData/codeInsight/controlflow/ContinueInPositiveIteration.txt +++ b/python/testData/codeInsight/controlflow/ContinueInPositiveIteration.txt @@ -15,7 +15,8 @@ 14(15) element: PyExpressionStatement 15(16) READ ACCESS: sys 16(4,17) element: PyCallExpression: sys.exit -17(18) raise: PyRaiseStatement +17(18) element: PyRaiseStatement 18(19) READ ACCESS: Exception 19(20) element: PyCallExpression: Exception -20() element: null \ No newline at end of file +20(21) raise: PyRaiseStatement +21() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/DoubleTry.txt b/python/testData/codeInsight/controlflow/DoubleTry.txt index 7647910cf923..7b81d80ed7db 100644 --- a/python/testData/codeInsight/controlflow/DoubleTry.txt +++ b/python/testData/codeInsight/controlflow/DoubleTry.txt @@ -2,26 +2,27 @@ 1(2) element: PyTryExceptStatement 2(3,5) element: PyTryPart 3(4,5) element: PyFromImportStatement -4(5,9) WRITE ACCESS: lsprof +4(5,10) WRITE ACCESS: lsprof 5(6) element: PyExceptPart 6(7) READ ACCESS: ImportError -7(8) raise: PyRaiseStatement -8(26) READ ACCESS: Error -9(10) element: PyAssignmentStatement -10(11) WRITE ACCESS: p -11(12) element: PyTryExceptStatement -12(13,17) element: PyTryPart -13(14,17) element: PyReturnStatement -14(17) READ ACCESS: foo -15(16,17) element: PyAssignmentStatement -16(17,21) WRITE ACCESS: x -17(18) element: PyFinallyPart -18(19) element: PyPrintStatement -19(20) READ ACCESS: p -20(26) finally fail exit -21(22) element: PyFinallyPart -22(23) element: PyPrintStatement -23(24) READ ACCESS: p -24(25) element: PyAssignmentStatement -25(26) WRITE ACCESS: y -26() element: null \ No newline at end of file +7(8) element: PyRaiseStatement +8(9) READ ACCESS: Error +9(27) raise: PyRaiseStatement +10(11) element: PyAssignmentStatement +11(12) WRITE ACCESS: p +12(13) element: PyTryExceptStatement +13(14,18) element: PyTryPart +14(15,18) element: PyReturnStatement +15(18) READ ACCESS: foo +16(17,18) element: PyAssignmentStatement +17(18,22) WRITE ACCESS: x +18(19) element: PyFinallyPart +19(20) element: PyPrintStatement +20(21) READ ACCESS: p +21(27) finally fail exit +22(23) element: PyFinallyPart +23(24) element: PyPrintStatement +24(25) READ ACCESS: p +25(26) element: PyAssignmentStatement +26(27) WRITE ACCESS: y +27() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/IfElseReturn.txt b/python/testData/codeInsight/controlflow/IfElseReturn.txt index 6a3ed2c902d2..dba3ec444cff 100644 --- a/python/testData/codeInsight/controlflow/IfElseReturn.txt +++ b/python/testData/codeInsight/controlflow/IfElseReturn.txt @@ -1,19 +1,20 @@ 0(1) element: null 1(2) element: PyIfStatement 2(3,4) READ ACCESS: markup -3(15) element: null. Condition: markup:false +3(16) element: null. Condition: markup:false 4(5) element: null. Condition: markup:true 5(6) ASSERTTYPE ACCESS: markup 6(7) element: PyStatementList 7(8) element: PyTryExceptStatement 8(9,11) element: PyTryPart 9(10,11) element: PyReturnStatement -10(11,18) READ ACCESS: renderer +10(11,19) READ ACCESS: renderer 11(12) element: PyExceptPart 12(13) READ ACCESS: KeyError -13(14) raise: PyRaiseStatement -14(18) READ ACCESS: Error -15(16) element: PyStatementList -16(17) element: PyReturnStatement -17(18) READ ACCESS: body -18() element: null \ No newline at end of file +13(14) element: PyRaiseStatement +14(15) READ ACCESS: Error +15(19) raise: PyRaiseStatement +16(17) element: PyStatementList +17(18) element: PyReturnStatement +18(19) READ ACCESS: body +19() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/IfFor.txt b/python/testData/codeInsight/controlflow/IfFor.txt index 0837094ec773..2e40810309b1 100644 --- a/python/testData/codeInsight/controlflow/IfFor.txt +++ b/python/testData/codeInsight/controlflow/IfFor.txt @@ -7,14 +7,15 @@ 6(7) element: PyStatementList 7(8) element: PyForStatement 8(9) READ ACCESS: range -9(10,17) element: PyCallExpression: range +9(10,18) element: PyCallExpression: range 10(11) element: PyTargetExpression: _ 11(12) WRITE ACCESS: _ -12(10,17) element: PyPrintStatement +12(10,18) element: PyPrintStatement 13(14) element: PyStatementList -14(15) raise: PyRaiseStatement +14(15) element: PyRaiseStatement 15(16) READ ACCESS: Exception -16(19) element: PyCallExpression: Exception -17(18) element: PyReturnStatement -18(19) READ ACCESS: True -19() element: null \ No newline at end of file +16(17) element: PyCallExpression: Exception +17(20) raise: PyRaiseStatement +18(19) element: PyReturnStatement +19(20) READ ACCESS: True +20() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/Raise.txt b/python/testData/codeInsight/controlflow/Raise.txt index d14c3908eb44..20d920cf0baf 100644 --- a/python/testData/codeInsight/controlflow/Raise.txt +++ b/python/testData/codeInsight/controlflow/Raise.txt @@ -1,14 +1,16 @@ 0(1) element: null 1(2) element: PyTryExceptStatement -2(3,6) element: PyTryPart -3(4,6) raise: PyRaiseStatement -4(5,6) READ ACCESS: Exception -5(6,13) element: PyCallExpression: Exception -6(7) element: PyExceptPart -7(8) element: PyAssignmentStatement -8(9) READ ACCESS: Exception -9(10) element: PyCallExpression: Exception -10(11) WRITE ACCESS: foo -11(12) raise: PyRaiseStatement -12(13) READ ACCESS: foo -13() element: null \ No newline at end of file +2(3,7) element: PyTryPart +3(4,7) element: PyRaiseStatement +4(5,7) READ ACCESS: Exception +5(6,7) element: PyCallExpression: Exception +6(7,15) raise: PyRaiseStatement +7(8) element: PyExceptPart +8(9) element: PyAssignmentStatement +9(10) READ ACCESS: Exception +10(11) element: PyCallExpression: Exception +11(12) WRITE ACCESS: foo +12(13) element: PyRaiseStatement +13(14) READ ACCESS: foo +14(15) raise: PyRaiseStatement +15() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/Try.txt b/python/testData/codeInsight/controlflow/Try.txt index e38e18280b9d..3b93646a9880 100644 --- a/python/testData/codeInsight/controlflow/Try.txt +++ b/python/testData/codeInsight/controlflow/Try.txt @@ -16,7 +16,7 @@ 15(16,19,27,30) READ ACCESS: s 16(17,19,27,30) element: PyCallExpression: s.strip 17(18,19,27,30) element: PyCallExpression: int -18(19,27,30,36) WRITE ACCESS: i +18(19,27,30,37) WRITE ACCESS: i 19(20) element: PyExceptPart 20(21) READ ACCESS: IOError 21(22) WRITE ACCESS: errno @@ -24,14 +24,15 @@ 23(24) element: PyPrintStatement 24(25) READ ACCESS: errno 25(26) READ ACCESS: strerror -26(36) element: PyCallExpression: "I/O error({0}): {1}".format +26(37) element: PyCallExpression: "I/O error({0}): {1}".format 27(28) element: PyExceptPart 28(29) READ ACCESS: ValueError -29(36) element: PyPrintStatement +29(37) element: PyPrintStatement 30(31) element: PyExceptPart 31(32) element: PyPrintStatement 32(33) element: PySubscriptionExpression 33(34) READ ACCESS: sys 34(35) element: PyCallExpression: sys.exc_info -35(36) raise: PyRaiseStatement -36() element: null \ No newline at end of file +35(36) element: PyRaiseStatement +36(37) raise: PyRaiseStatement +37() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/TryBreak.txt b/python/testData/codeInsight/controlflow/TryBreak.txt index 3017b6425fdd..fad4dcf7996f 100644 --- a/python/testData/codeInsight/controlflow/TryBreak.txt +++ b/python/testData/codeInsight/controlflow/TryBreak.txt @@ -2,13 +2,14 @@ 1(2) element: PyTryExceptStatement 2(3,8) element: PyTryPart 3(4,8) element: PyForStatement -4(5,8,12) READ ACCESS: bar +4(5,8,13) READ ACCESS: bar 5(6,8) element: PyTargetExpression: i 6(7,8) WRITE ACCESS: i -7(12) element: PyBreakStatement +7(13) element: PyBreakStatement 8(9) element: PyExceptPart -9(10) raise: PyRaiseStatement +9(10) element: PyRaiseStatement 10(11) READ ACCESS: Exception -11(13) element: PyCallExpression: Exception -12(13) element: PyReturnStatement -13() element: null \ No newline at end of file +11(12) element: PyCallExpression: Exception +12(14) raise: PyRaiseStatement +13(14) element: PyReturnStatement +14() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/TryExceptNoFinally.txt b/python/testData/codeInsight/controlflow/TryExceptNoFinally.txt index 3da384848799..a6959c561fc5 100644 --- a/python/testData/codeInsight/controlflow/TryExceptNoFinally.txt +++ b/python/testData/codeInsight/controlflow/TryExceptNoFinally.txt @@ -5,16 +5,17 @@ 4(5,8,12) WRITE ACCESS: value 5(6) element: PyElsePart 6(7) element: PyAssignmentStatement -7(17) WRITE ACCESS: value +7(18) WRITE ACCESS: value 8(9) element: PyExceptPart 9(10) READ ACCESS: ValueError 10(11) element: PyAssignmentStatement -11(17) WRITE ACCESS: value +11(18) WRITE ACCESS: value 12(13) element: PyExceptPart 13(14) READ ACCESS: SomethingElse 14(15) element: PyAssignmentStatement 15(16) WRITE ACCESS: value -16(19) raise: PyRaiseStatement -17(18) element: PyPrintStatement -18(19) READ ACCESS: value -19() element: null \ No newline at end of file +16(17) element: PyRaiseStatement +17(20) raise: PyRaiseStatement +18(19) element: PyPrintStatement +19(20) READ ACCESS: value +20() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/TryRaiseFinally.txt b/python/testData/codeInsight/controlflow/TryRaiseFinally.txt index 4f22fb9c4ace..74935d419d70 100644 --- a/python/testData/codeInsight/controlflow/TryRaiseFinally.txt +++ b/python/testData/codeInsight/controlflow/TryRaiseFinally.txt @@ -1,9 +1,10 @@ 0(1) element: null 1(2) element: PyTryExceptStatement -2(3,5) element: PyTryPart -3(4,5) raise: PyRaiseStatement -4(5) READ ACCESS: KeyboardInterrupt -5(6) element: PyFinallyPart -6(7) element: PyPrintStatement -7(8) finally fail exit -8() element: null \ No newline at end of file +2(3,6) element: PyTryPart +3(4,6) element: PyRaiseStatement +4(5,6) READ ACCESS: KeyboardInterrupt +5(6) raise: PyRaiseStatement +6(7) element: PyFinallyPart +7(8) element: PyPrintStatement +8(9) finally fail exit +9() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/TryTry.txt b/python/testData/codeInsight/controlflow/TryTry.txt index 15068a6d33b8..d195749efa7d 100644 --- a/python/testData/codeInsight/controlflow/TryTry.txt +++ b/python/testData/codeInsight/controlflow/TryTry.txt @@ -2,80 +2,81 @@ 1(2) element: PyAssignmentStatement 2(3) WRITE ACCESS: a 3(4) element: PyTryExceptStatement -4(5,71) element: PyTryPart -5(6,71) element: PyAssignmentStatement -6(7,71) WRITE ACCESS: b -7(8,71) element: PyForStatement -8(9,71) element: PyTargetExpression: x -9(10,71) WRITE ACCESS: x -10(11,71) element: PyTryExceptStatement -11(12,60) element: PyTryPart -12(13,60) element: PyAssignmentStatement -13(14,60) WRITE ACCESS: c -14(15,60) element: PyTryExceptStatement -15(16,51) element: PyTryPart -16(17,51) element: PyAssignmentStatement -17(18,51) WRITE ACCESS: d -18(19,51) element: PyIfStatement -19(20,22,51) READ ACCESS: x +4(5,72) element: PyTryPart +5(6,72) element: PyAssignmentStatement +6(7,72) WRITE ACCESS: b +7(8,72) element: PyForStatement +8(9,72) element: PyTargetExpression: x +9(10,72) WRITE ACCESS: x +10(11,72) element: PyTryExceptStatement +11(12,61) element: PyTryPart +12(13,61) element: PyAssignmentStatement +13(14,61) WRITE ACCESS: c +14(15,61) element: PyTryExceptStatement +15(16,52) element: PyTryPart +16(17,52) element: PyAssignmentStatement +17(18,52) WRITE ACCESS: d +18(19,52) element: PyIfStatement +19(20,22,52) READ ACCESS: x 20(21) element: null. Condition: x == 0:false -21(51,26) ASSERTTYPE ACCESS: x +21(52,26) ASSERTTYPE ACCESS: x 22(23) element: null. Condition: x == 0:true -23(51,24) ASSERTTYPE ACCESS: x +23(52,24) ASSERTTYPE ACCESS: x 24(25) element: PyStatementList -25(55) element: PyBreakStatement -26(27,29,51) READ ACCESS: x +25(56) element: PyBreakStatement +26(27,29,52) READ ACCESS: x 27(28) element: null. Condition: x == 1:false -28(51,33) ASSERTTYPE ACCESS: x +28(52,33) ASSERTTYPE ACCESS: x 29(30) element: null. Condition: x == 1:true -30(51,31) ASSERTTYPE ACCESS: x +30(52,31) ASSERTTYPE ACCESS: x 31(32) element: PyStatementList -32(7,55) element: PyContinueStatement -33(34,36,51) READ ACCESS: x +32(7,56) element: PyContinueStatement +33(34,36,52) READ ACCESS: x 34(35) element: null. Condition: x == 2:false -35(51,42) ASSERTTYPE ACCESS: x +35(52,43) ASSERTTYPE ACCESS: x 36(37) element: null. Condition: x == 2:true -37(51,38) ASSERTTYPE ACCESS: x +37(52,38) ASSERTTYPE ACCESS: x 38(39) element: PyStatementList -39(40,51) raise: PyRaiseStatement -40(41,51) READ ACCESS: Exception -41(51) element: PyCallExpression: Exception -42(43,45,51) READ ACCESS: x -43(44) element: null. Condition: x == 3:false -44(51,49) ASSERTTYPE ACCESS: x -45(46) element: null. Condition: x == 3:true -46(51,47) ASSERTTYPE ACCESS: x -47(48) element: PyStatementList -48(51) element: PyReturnStatement -49(50,51) element: PyAssignmentStatement -50(51,55) WRITE ACCESS: e -51(52,60) element: PyFinallyPart -52(53,60) element: PyAssignmentStatement -53(54,60) WRITE ACCESS: f -54(60) finally fail exit -55(56,60) element: PyFinallyPart -56(57,60) element: PyAssignmentStatement -57(60,64,58) WRITE ACCESS: f -58(59,60) element: PyAssignmentStatement -59(60,64) WRITE ACCESS: g -60(61,71) element: PyFinallyPart -61(62,71) element: PyAssignmentStatement -62(63,71) WRITE ACCESS: h -63(71) finally fail exit -64(65,71) element: PyFinallyPart -65(66,71) element: PyAssignmentStatement -66(71,67,69) WRITE ACCESS: h -67(68,71) element: PyAssignmentStatement -68(8,71,69) WRITE ACCESS: i -69(70,71) element: PyAssignmentStatement -70(71,75) WRITE ACCESS: j -71(72) element: PyFinallyPart -72(73) element: PyAssignmentStatement -73(74) WRITE ACCESS: k -74(80) finally fail exit -75(76) element: PyFinallyPart -76(77) element: PyAssignmentStatement -77(78) WRITE ACCESS: k -78(79) element: PyAssignmentStatement -79(80) WRITE ACCESS: l -80() element: null \ No newline at end of file +39(40,52) element: PyRaiseStatement +40(41,52) READ ACCESS: Exception +41(42,52) element: PyCallExpression: Exception +42(52) raise: PyRaiseStatement +43(44,46,52) READ ACCESS: x +44(45) element: null. Condition: x == 3:false +45(52,50) ASSERTTYPE ACCESS: x +46(47) element: null. Condition: x == 3:true +47(52,48) ASSERTTYPE ACCESS: x +48(49) element: PyStatementList +49(52) element: PyReturnStatement +50(51,52) element: PyAssignmentStatement +51(52,56) WRITE ACCESS: e +52(53,61) element: PyFinallyPart +53(54,61) element: PyAssignmentStatement +54(55,61) WRITE ACCESS: f +55(61) finally fail exit +56(57,61) element: PyFinallyPart +57(58,61) element: PyAssignmentStatement +58(61,65,59) WRITE ACCESS: f +59(60,61) element: PyAssignmentStatement +60(61,65) WRITE ACCESS: g +61(62,72) element: PyFinallyPart +62(63,72) element: PyAssignmentStatement +63(64,72) WRITE ACCESS: h +64(72) finally fail exit +65(66,72) element: PyFinallyPart +66(67,72) element: PyAssignmentStatement +67(72,68,70) WRITE ACCESS: h +68(69,72) element: PyAssignmentStatement +69(8,72,70) WRITE ACCESS: i +70(71,72) element: PyAssignmentStatement +71(72,76) WRITE ACCESS: j +72(73) element: PyFinallyPart +73(74) element: PyAssignmentStatement +74(75) WRITE ACCESS: k +75(81) finally fail exit +76(77) element: PyFinallyPart +77(78) element: PyAssignmentStatement +78(79) WRITE ACCESS: k +79(80) element: PyAssignmentStatement +80(81) WRITE ACCESS: l +81() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/WithRaiseException.txt b/python/testData/codeInsight/controlflow/WithRaiseException.txt index 54cd067bb119..9082637671d5 100644 --- a/python/testData/codeInsight/controlflow/WithRaiseException.txt +++ b/python/testData/codeInsight/controlflow/WithRaiseException.txt @@ -1,18 +1,19 @@ 0(1) element: null 1(2) element: PyWithStatement 2(4) READ ACCESS: context_manager -3(15) exit context manager: context_manager +3(16) exit context manager: context_manager 4(5,3) element: PyIfStatement 5(6,7,3) READ ACCESS: c -6(12) element: null. Condition: c:false +6(13) 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 \ No newline at end of file +10(11,3) element: PyRaiseStatement +11(12,3) READ ACCESS: ValueError +12(18,3) raise: PyRaiseStatement +13(14,3) element: PyAssignmentStatement +14(15,3) READ ACCESS: c +15(16,3) WRITE ACCESS: val +16(17) element: PyPrintStatement +17(18) READ ACCESS: val +18() element: null \ No newline at end of file diff --git a/python/testData/inspections/PyUnreachableCodeInspection/UnreachableCodeReportedAfterBuiltinExit.py b/python/testData/inspections/PyUnreachableCodeInspection/UnreachableCodeReportedAfterBuiltinExit.py index 1caf122724d5..2f140db45fc8 100644 --- a/python/testData/inspections/PyUnreachableCodeInspection/UnreachableCodeReportedAfterBuiltinExit.py +++ b/python/testData/inspections/PyUnreachableCodeInspection/UnreachableCodeReportedAfterBuiltinExit.py @@ -1,4 +1,4 @@ def test_exit(): exit() print("should be reported as unreachable") - return True \ No newline at end of file + return True \ No newline at end of file diff --git a/python/testData/inspections/PyUnreachableCodeInspection/UnreachablePatternAfterIrrefutableCaseClause.py b/python/testData/inspections/PyUnreachableCodeInspection/UnreachablePatternAfterIrrefutableCaseClause.py index 3e801a35c5d0..8bf2c1817320 100644 --- a/python/testData/inspections/PyUnreachableCodeInspection/UnreachablePatternAfterIrrefutableCaseClause.py +++ b/python/testData/inspections/PyUnreachableCodeInspection/UnreachablePatternAfterIrrefutableCaseClause.py @@ -2,4 +2,4 @@ match 42: case x: pass case 42: - pass \ No newline at end of file + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java index 169352093751..3a738885d58b 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java @@ -25,6 +25,64 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase { runWithLanguageLevel(LanguageLevel.PYTHON26, () -> doTest()); } + // PY-81674 + public void testFinallyEarlyExit() { + doTestByText(""" +def f(): + try: + print("Hello, world!") + finally: + assert False + print("Goodbye, world!") + + print("This is unreachable") + """); + } + + // PY-81674 + public void testConsecutiveTerminating() { + doTestByText(""" +def f1(): + exit() + raise Exception() + print("unreachable") + assert False + +def f2(): + raise Exception() + assert False + print("unreachable") + exit() + +def f3(): + assert False + exit() + print("unreachable") + raise Exception() + """); + } + + // PY-81674 + public void testNoNestedWarnings() { + doTestByText(""" +from enum import Enum + +class Foo(Enum): + A = 0 + B = 1 + +print(exit()) + +def unreachable(foo: Foo) -> None: + if foo is Foo.A: + ... + elif foo is Foo.B: + ... + else: + print("also unreachable") + """); + } + // PY-81593 public void testReachabilityLogicalOperatorChaining() { doTestByText(""" @@ -175,24 +233,26 @@ def sup2(b): assert False print("reachable") -def nosup(b): +def nosupRaise(b): with NoSuppress(): a = 42 raise ValueError("Something went wrong") print("unreachable") +def nosupAssert(b): with NoSuppress(): assert b a = 42 assert False print("unreachable") -def nosup2(b): +def nosup2Raise(b): with NoSuppress2(): a = 42 raise ValueError("Something went wrong") print("unreachable") +def nosup2Assert(b): with NoSuppress2(): assert b a = 42 @@ -256,12 +316,13 @@ async def sup(b): assert False print("reachable") -async def nosup(b): +async def nosupRaise(b): async with AsyncNoSuppress(): a = 42 raise ValueError("Something went wrong") print("unreachable") +async def nosupAssertFalse(b): async with AsyncNoSuppress(): assert b a = 42