From 30c444d86ac1685d140c7f2bea3bc109d03033a1 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 17 May 2012 19:00:50 +0400 Subject: [PATCH] Improved control flow analysis before code fragment extraction (PY-5865, PY-6413) --- .../codeFragment/PyCodeFragmentUtil.java | 215 ++++++++++-------- 1 file changed, 122 insertions(+), 93 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java index 0d3be71ba1dc..8494917ee66d 100644 --- a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java @@ -4,20 +4,21 @@ import com.intellij.codeInsight.codeFragment.CannotCreateCodeFragmentException; import com.intellij.codeInsight.codeFragment.CodeFragment; import com.intellij.codeInsight.codeFragment.CodeFragmentUtil; import com.intellij.codeInsight.codeFragment.Position; -import com.intellij.codeInsight.controlflow.ConditionalInstruction; +import com.intellij.codeInsight.controlflow.ControlFlow; import com.intellij.codeInsight.controlflow.Instruction; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.containers.HashSet; import com.jetbrains.python.PyBundle; 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.psi.*; -import com.jetbrains.python.psi.impl.PyBinaryExpressionNavigator; import com.jetbrains.python.psi.impl.PyForStatementNavigator; import org.jetbrains.annotations.NotNull; +import java.util.*; + /** * @author oleg */ @@ -25,107 +26,135 @@ public class PyCodeFragmentUtil { private PyCodeFragmentUtil() { } + @NotNull public static CodeFragment createCodeFragment(@NotNull final ScopeOwner owner, @NotNull final PsiElement startInScope, @NotNull final PsiElement endInScope) throws CannotCreateCodeFragmentException { final int start = startInScope.getTextOffset(); final int end = endInScope.getTextOffset() + endInScope.getTextLength(); - - // Check for statements inside code fragment - owner.acceptChildren(new PyRecursiveElementVisitor(){ - @Override - public void visitPyClass(final PyClass node) { - if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.class.declaration.inside")); - } - } - - @Override - public void visitPyFunction(final PyFunction node) { - if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.function.declaration.inside")); - } - } - - @Override - public void visitPyFromImportStatement(PyFromImportStatement node) { - if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.from.import.inside")); - } - } - }); - - // Control flow inspection - final HashSet outerInstructions = new HashSet(); - boolean returnInstructionInside = false; - final Instruction[] flow = ControlFlowCache.getControlFlow(owner).getInstructions(); - for (Instruction instruction : flow) { - final PsiElement element = instruction.getElement(); - if (element!=null && CodeFragmentUtil.elementFit(element, start, end)){ - if (element instanceof PyReturnStatement){ - returnInstructionInside = true; - } - if (element instanceof PyBreakStatement && !CodeFragmentUtil.elementFit(((PyBreakStatement) element).getLoopStatement(), start, end)){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.no.corresponding.loop.for.break")); - } - if (element instanceof PyContinueStatement && !CodeFragmentUtil.elementFit(((PyContinueStatement) element).getLoopStatement(), start, end)){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.no.corresponding.loop.for.continue")); - } - - for (Instruction next : instruction.allSucc()) { - // Ignore conditional instruction - if (next instanceof ConditionalInstruction){ - continue; - } - if (next instanceof ReadWriteInstruction && ((ReadWriteInstruction)next).getAccess().isAssertTypeAccess()) { - continue; - } - final PsiElement nextElement = next.getElement(); - // Ignore binary operations control flow - if (nextElement != null && PyBinaryExpressionNavigator.getBinaryExpressionByOperand(nextElement) != null){ - continue; - } - // We ignore except blocks - if (nextElement instanceof PyExceptPart){ - continue; - } - // We allow raise statements in code - if (nextElement == null && PsiTreeUtil.getParentOfType(element, PyRaiseStatement.class) != null){ - continue; - } - if (!CodeFragmentUtil.elementFit(nextElement, start, end)){ - outerInstructions.add(next); - } - } - } + final ControlFlow flow = ControlFlowCache.getControlFlow(owner); + if (flow == null) { + throw new CannotCreateCodeFragmentException("Cannot determine execution flow for the code fragment"); } - // If we see more than 1 outer instruction, controlflow is interrupted - if (outerInstructions.size() > 2){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); + final List subGraph = getFragmentSubGraph(flow, start, end); + final AnalysisResult subGraphAnalysis = analyseSubGraph(subGraph, start, end); + if (subGraphAnalysis.regularExits > 0 && subGraphAnalysis.returns > 0) { + throw new CannotCreateCodeFragmentException( + PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); } - if (outerInstructions.size() == 2){ - boolean errorFound = true; - for (Instruction outerInstruction : outerInstructions) { - // Here we check control flow when for statement content is beeing extracted - final PsiElement element = outerInstruction.getElement(); - if (element != null && (PyForStatementNavigator.getPyForStatementByIterable(element) != null || - PyForStatementNavigator.getPyForStatementByBody(element) != null)) { - // In case when return instruction is inside - if (!returnInstructionInside){ - errorFound = false; - break; - } - } - } - if (errorFound){ - throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); - } + if (subGraphAnalysis.targetInstructions > 1) { + throw new CannotCreateCodeFragmentException( + PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); + } + if (subGraphAnalysis.starImports > 0) { + throw new CannotCreateCodeFragmentException( + PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.from.import.inside")); } - // Building code fragment final PyCodeFragmentBuilder builder = new PyCodeFragmentBuilder(owner, start, end); owner.acceptChildren(builder); - return new CodeFragment(builder.inElements, builder.outElements, returnInstructionInside); + return new CodeFragment(builder.inElements, builder.outElements, subGraphAnalysis.returns > 0); + } + + @NotNull + private static List getFragmentSubGraph(@NotNull ControlFlow flow, int start, int end) { + List instructions = new ArrayList(); + for (Instruction instruction : flow.getInstructions()) { + final PsiElement element = instruction.getElement(); + if (element != null) { + if (CodeFragmentUtil.getPosition(element, start, end) == Position.INSIDE) { + instructions.add(instruction); + } + } + } + // Hack for including inner assert type instructions that can point to elements outside of the selected scope + for (Instruction instruction : flow.getInstructions()) { + if (instruction instanceof ReadWriteInstruction) { + final ReadWriteInstruction readWriteInstruction = (ReadWriteInstruction)instruction; + if (readWriteInstruction.getAccess().isAssertTypeAccess()) { + boolean innerAssertType = true; + for (Instruction next : readWriteInstruction.allSucc()) { + if (!instructions.contains(next)) { + innerAssertType = false; + break; + } + } + if (innerAssertType && !instructions.contains(instruction)) { + instructions.add(instruction); + } + } + } + } + return instructions; + } + + private static class AnalysisResult { + private final int starImports; + private final int targetInstructions; + private final int regularExits; + private final int returns; + + public AnalysisResult(int starImports, int targetInstructions, int returns, int regularExits) { + this.starImports = starImports; + this.targetInstructions = targetInstructions; + this.regularExits = regularExits; + this.returns = returns; + } + } + + @NotNull + private static AnalysisResult analyseSubGraph(@NotNull List subGraph, int start, int end) { + int returnSources = 0; + int regularSources = 0; + final Set targetInstructions = new HashSet(); + int starImports = 0; + + for (Pair edge : getOutgoingEdges(subGraph)) { + final Instruction sourceInstruction = edge.getFirst(); + final Instruction targetInstruction = edge.getSecond(); + final PsiElement source = sourceInstruction.getElement(); + final PsiElement target = targetInstruction.getElement(); + + final PyReturnStatement returnStatement = PsiTreeUtil.getParentOfType(source, PyReturnStatement.class, false); + final boolean isExceptTarget = target instanceof PyExceptPart || target instanceof PyFinallyPart; + final boolean isLoopTarget = target instanceof PyWhileStatement || PyForStatementNavigator.getPyForStatementByIterable(target) != null; + + if (target != null && !isExceptTarget && !isLoopTarget) { + targetInstructions.add(targetInstruction); + } + + if (returnStatement != null && CodeFragmentUtil.getPosition(returnStatement, start, end) == Position.INSIDE) { + returnSources++; + } + else if (!isExceptTarget) { + regularSources++; + } + } + + for (Instruction instruction : subGraph) { + final PsiElement element = instruction.getElement(); + if (element instanceof PyFromImportStatement) { + final PyFromImportStatement fromImportStatement = (PyFromImportStatement)element; + if (fromImportStatement.getStarImportElement() != null) { + starImports++; + } + } + } + + return new AnalysisResult(starImports, targetInstructions.size(), returnSources, regularSources); + } + + @NotNull + private static Set> getOutgoingEdges(@NotNull Collection subGraph) { + final Set> outgoing = new HashSet>(); + for (Instruction instruction : subGraph) { + for (Instruction next : instruction.allSucc()) { + if (!subGraph.contains(next)) { + outgoing.add(Pair.create(instruction, next)); + } + } + } + return outgoing; } } \ No newline at end of file