diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index ab91e2b3dca9..30700ce2af44 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -474,7 +474,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { boolean isStaticallyTrue = false; if (condition != null) { condition.accept(this); - isStaticallyTrue = PyEvaluator.evaluateAsBooleanNoResolve(condition, false); + isStaticallyTrue = loopHasAtLeastOneIteration(node); } final Instruction head = myBuilder.prevInstruction; final PyElsePart elsePart = node.getElsePart(); @@ -509,7 +509,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { } final Instruction head = myBuilder.prevInstruction; final PyElsePart elsePart = node.getElsePart(); - if (elsePart == null && !PyEvaluator.evaluateAsBooleanNoResolve(source, false)) { + if (elsePart == null && !loopHasAtLeastOneIteration(node)) { myBuilder.addPendingEdge(node, myBuilder.prevInstruction); } final PyStatementList list = forPart.getStatementList(); @@ -544,6 +544,16 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { myBuilder.flowAbrupted(); } + private static boolean loopHasAtLeastOneIteration(@NotNull PyLoopStatement loopStatement) { + final PyExpression expression = loopStatement instanceof PyForStatement + ? ((PyForStatement)loopStatement).getForPart().getSource() + : loopStatement instanceof PyWhileStatement + ? ((PyWhileStatement)loopStatement).getWhilePart().getCondition() + : null; + + return PyEvaluator.evaluateAsBooleanNoResolve(expression, false); + } + @Override public void visitPyBreakStatement(final PyBreakStatement node) { myBuilder.startNode(node); @@ -569,6 +579,15 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { else { myBuilder.addPendingEdge(null, null); } + + // There is no edge between loop statement and next after loop instruction + // when loop has at least one iteration + // so `continue` is marked as one more last instruction in the loop + // see visitPyWhileStatement + // see visitPyForStatement + if (loopHasAtLeastOneIteration(loop)) { + myBuilder.addPendingEdge(loop, myBuilder.prevInstruction); + } } myBuilder.flowAbrupted(); } diff --git a/python/testData/codeInsight/controlflow/continueinpositiveiteration.py b/python/testData/codeInsight/controlflow/continueinpositiveiteration.py new file mode 100644 index 000000000000..bf46208f6871 --- /dev/null +++ b/python/testData/codeInsight/controlflow/continueinpositiveiteration.py @@ -0,0 +1,7 @@ +import sys + +for s in "abc": + if len(s) == 1: + continue + sys.exit(0) +raise Exception("the end") \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/continueinpositiveiteration.txt b/python/testData/codeInsight/controlflow/continueinpositiveiteration.txt new file mode 100644 index 000000000000..530697b5e4b3 --- /dev/null +++ b/python/testData/codeInsight/controlflow/continueinpositiveiteration.txt @@ -0,0 +1,16 @@ +0(1) element: null +1(2) element: PyImportStatement +2(3) WRITE ACCESS: sys +3(4) element: PyForStatement +4(5) element: PyTargetExpression: s +5(6) WRITE ACCESS: s +6(7) element: PyIfStatement +7(8) READ ACCESS: len +8(9,11) READ ACCESS: s +9(10) element: PyStatementList. Condition: len(s) == 1:true +10(3,13) element: PyContinueStatement +11(12) element: PyExpressionStatement +12(15) READ ACCESS: sys +13(14) element: PyRaiseStatement +14(15) READ ACCESS: Exception +15() 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 fa2030c84547..ecaf22b901a7 100644 --- a/python/testData/codeInsight/controlflow/trytry.txt +++ b/python/testData/codeInsight/controlflow/trytry.txt @@ -23,7 +23,7 @@ 22(23,37) element: PyIfPartElif. Condition: x == 0:false 23(24,26,37) READ ACCESS: x 24(25) element: PyStatementList. Condition: x == 1:true -25(7,37) element: PyContinueStatement +25(7,37,40) element: PyContinueStatement 26(27,37) element: PyIfPartElif. Condition: x == 1:false 27(28,31,37) READ ACCESS: x 28(29) element: PyStatementList. Condition: x == 2:true diff --git a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java index b6a56702ea11..98c949d23625 100644 --- a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java +++ b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java @@ -284,6 +284,11 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase { doTest(); } + // PY-29767 + public void testContinueInPositiveIteration() { + doTest(); + } + private void doTestFirstStatement() { final String testName = getTestName(false).toLowerCase(); configureByFile(testName + ".py"); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java index d6f409cf4b9e..edb6d0292fcc 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java @@ -127,6 +127,19 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase { ); } + // PY-29767 + public void testContinueInPositiveIterationWithExitPoint() { + doTestByText( + "import sys\n" + + "\n" + + "for s in \"abc\":\n" + + " if len(s) == 1:\n" + + " continue\n" + + " sys.exit(0)\n" + + "raise Exception(\"the end\")" + ); + } + @NotNull @Override protected Class getInspectionClass() {