From 1d8fda01728df9d2321d86303ff6a5e7396283db Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Tue, 28 Nov 2017 15:22:30 +0300 Subject: [PATCH] Allow multiple outcoming edges from `if` conditions (PY-25974) --- .../controlflow/InstructionBuilder.java | 19 ++++++--- .../controlflow/PyControlFlowBuilder.java | 42 +++++++++++++------ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java index 56548ee86bd1..74ca03b77780 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java @@ -18,8 +18,9 @@ package com.jetbrains.python.codeInsight.controlflow; import com.google.common.collect.Lists; import com.intellij.codeInsight.controlflow.ControlFlowBuilder; import com.intellij.codeInsight.controlflow.Instruction; -import com.jetbrains.python.psi.PyReferenceExpression; import com.intellij.psi.util.QualifiedName; +import com.jetbrains.python.psi.PyReferenceExpression; +import org.jetbrains.annotations.NotNull; import java.util.List; @@ -30,9 +31,11 @@ public class InstructionBuilder { private InstructionBuilder() { } - public static List buildInstructions(ControlFlowBuilder builder, List assertions) { - List result = Lists.newArrayList(); - for (PyTypeAssertionEvaluator.Assertion def: assertions) { + @NotNull + public static List buildInstructions(@NotNull ControlFlowBuilder builder, + @NotNull List assertions) { + final List result = Lists.newArrayList(); + for (PyTypeAssertionEvaluator.Assertion def : assertions) { final PyReferenceExpression e = def.getElement(); final QualifiedName qname = e.asQualifiedName(); final String name = qname != null ? qname.toString() : e.getName(); @@ -41,9 +44,13 @@ public class InstructionBuilder { return result; } - public static void addAssertInstructions(ControlFlowBuilder builder, PyTypeAssertionEvaluator assertionEvaluator) { - for (Instruction instr : buildInstructions(builder, assertionEvaluator.getDefinitions())) { + @NotNull + public static List addAssertInstructions(@NotNull ControlFlowBuilder builder, + @NotNull PyTypeAssertionEvaluator assertionEvaluator) { + final List instructions = buildInstructions(builder, assertionEvaluator.getDefinitions()); + for (Instruction instr : instructions) { builder.addNode(instr); } + return instructions; } } diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index 2e5ce2fd828c..00c98d032383 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.QualifiedName; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; @@ -278,17 +279,18 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { } } - private Instruction getPrevInstruction(final PyElement condition) { - final Ref head = new Ref<>(myBuilder.prevInstruction); + @NotNull + private List> getPrevInstructions(final PyElement condition) { + final List> result = ContainerUtil.newArrayList(Pair.create(condition, myBuilder.prevInstruction)); myBuilder.processPending((pendingScope, instruction) -> { if (pendingScope != null && PsiTreeUtil.isAncestor(condition, pendingScope, false)) { - head.set(instruction); + result.add(Pair.create(pendingScope, instruction)); } else { myBuilder.addPendingEdge(pendingScope, instruction); } }); - return head.get(); + return result; } @Override @@ -327,8 +329,11 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { } // Set the head as the last instruction of condition PyElement lastCondition = condition; - Instruction lastBranchingPoint = getPrevInstruction(condition); - myBuilder.prevInstruction = lastBranchingPoint; + + List> lastBranchingPoints = getPrevInstructions(condition); + lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond())); + myBuilder.prevInstruction = null; + final PyStatementList thenStatements = ifPart.getStatementList(); myBuilder.startConditionalNode(thenStatements, condition, true); InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator); @@ -344,18 +349,22 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { myBuilder.addPendingEdge(node, myBuilder.prevInstruction); for (final PyIfPart part : node.getElifParts()) { // Set the head as the false branch - myBuilder.prevInstruction = lastBranchingPoint; + lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond())); + myBuilder.prevInstruction = null; + myBuilder.startConditionalNode(part, lastCondition, false); condition = part.getCondition(); assertionEvaluator = new PyTypeAssertionEvaluator(); if (condition != null) { lastCondition = condition; - lastBranchingPoint = getPrevInstruction(lastCondition); + lastBranchingPoints = getPrevInstructions(lastCondition); condition.accept(this); condition.accept(assertionEvaluator); } // Set the head as the last instruction of condition - myBuilder.prevInstruction = getPrevInstruction(lastCondition); + getPrevInstructions(lastCondition).forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond())); + myBuilder.prevInstruction = null; + myBuilder.startConditionalNode(part, lastCondition, true); final PyStatementList statementList = part.getStatementList(); InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator); @@ -379,15 +388,24 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { final PyElsePart elseBranch = node.getElsePart(); if (elseBranch != null) { // Set the head as the false branch - myBuilder.prevInstruction = lastBranchingPoint; + lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond())); + myBuilder.prevInstruction = null; + myBuilder.startConditionalNode(elseBranch, lastCondition, false); InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); elseBranch.accept(this); myBuilder.addPendingEdge(node, myBuilder.prevInstruction); } else { - myBuilder.prevInstruction = lastBranchingPoint; - InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); + myBuilder.prevInstruction = null; + final Instruction instruction = ContainerUtil.getFirstItem(InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator)); + if (instruction != null) { + lastBranchingPoints.forEach(p -> myBuilder.addEdge(p.getSecond(), instruction)); + } + else { + lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond())); + } + myBuilder.addPendingEdge(node, myBuilder.prevInstruction); } }