mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Improved control flow analysis before code fragment extraction (PY-5865, PY-6413)
This commit is contained in:
@@ -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<Instruction> outerInstructions = new HashSet<Instruction>();
|
||||
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<Instruction> 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<Instruction> getFragmentSubGraph(@NotNull ControlFlow flow, int start, int end) {
|
||||
List<Instruction> instructions = new ArrayList<Instruction>();
|
||||
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<Instruction> subGraph, int start, int end) {
|
||||
int returnSources = 0;
|
||||
int regularSources = 0;
|
||||
final Set<Instruction> targetInstructions = new HashSet<Instruction>();
|
||||
int starImports = 0;
|
||||
|
||||
for (Pair<Instruction, Instruction> 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<Pair<Instruction, Instruction>> getOutgoingEdges(@NotNull Collection<Instruction> subGraph) {
|
||||
final Set<Pair<Instruction, Instruction>> outgoing = new HashSet<Pair<Instruction, Instruction>>();
|
||||
for (Instruction instruction : subGraph) {
|
||||
for (Instruction next : instruction.allSucc()) {
|
||||
if (!subGraph.contains(next)) {
|
||||
outgoing.add(Pair.create(instruction, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
return outgoing;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user