CountingLoop: support decreasing loops

Fixes IDEA-190412 Unroll loop action: support decreasing loops
This commit is contained in:
Tagir Valeev
2018-04-17 17:48:00 +07:00
parent 4417321362
commit 91b269f9fb
12 changed files with 92 additions and 29 deletions
@@ -643,7 +643,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
*/
private boolean addCountingLoopBound(PsiForStatement statement) {
CountingLoop loop = CountingLoop.from(statement);
if (loop == null) return false;
if (loop == null || loop.isDescending()) return false;
PsiLocalVariable counter = loop.getCounter();
Long start = asLong(loop.getInitializer());
Long end = asLong(loop.getBound());
@@ -113,6 +113,13 @@ public class DfaRelationValue extends DfaValue {
}
}
/**
* @return true if this relation is >, >=, <, != or <=
*/
public boolean isInequality() {
return this == LE || this == GE || this == LT || this == GT || this == NE;
}
@Override
public String toString() {
return myName;
@@ -112,13 +112,15 @@ public class UnrollLoopAction extends PsiElementBaseIntentionAction {
if (loop instanceof PsiForStatement) {
CountingLoop countingLoop = CountingLoop.from((PsiForStatement)loop);
if (countingLoop != null) {
boolean descending = countingLoop.isDescending();
long multiplier = descending ? -1 : 1;
Object from = ExpressionUtils.computeConstantExpression(countingLoop.getInitializer());
if (!(from instanceof Integer) && !(from instanceof Long)) return Collections.emptyList();
long fromValue = ((Number)from).longValue();
Object to = ExpressionUtils.computeConstantExpression(countingLoop.getBound());
if (!(to instanceof Integer) && !(to instanceof Long)) return Collections.emptyList();
long toValue = ((Number)to).longValue();
long diff = toValue - fromValue;
long diff = multiplier * (toValue - fromValue);
String suffix = PsiType.LONG.equals(countingLoop.getCounter().getType()) ? "L" : "";
if (countingLoop.isIncluding()) {
diff++; // overflow is ok: diff will become negative and we will exit
@@ -129,7 +131,8 @@ public class UnrollLoopAction extends PsiElementBaseIntentionAction {
return new AbstractList<PsiExpression>() {
@Override
public PsiExpression get(int index) {
return factory.createExpressionFromText(String.valueOf(fromValue + index) + suffix, loop);
long value = fromValue + multiplier * index;
return factory.createExpressionFromText(value + suffix, loop);
}
@Override
@@ -38,7 +38,7 @@ public class SuspiciousListRemoveInLoopInspection extends AbstractBaseJavaLocalI
if (!(parent instanceof PsiForStatement)) return;
CountingLoop loop = CountingLoop.from((PsiForStatement)parent);
if (loop == null) return;
if (loop == null || loop.isDescending()) return;
if (!arg.isReferenceTo(loop.getCounter())) return;
if (ControlFlowUtils.isExecutedOnceInLoop(parentStatement, (PsiLoopStatement)parent)) return;
holder.registerProblem(Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()),
@@ -124,7 +124,12 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
@Nullable
private static PsiExpression findIterableForIndexedLoop(PsiForStatement loop, PsiExpression getElementExpression) {
CountingLoop countingLoop = CountingLoop.from(loop);
if (countingLoop == null || countingLoop.isIncluding() || !ExpressionUtils.isZero(countingLoop.getInitializer())) return null;
if (countingLoop == null ||
countingLoop.isIncluding() ||
countingLoop.isDescending() ||
!ExpressionUtils.isZero(countingLoop.getInitializer())) {
return null;
}
IndexedContainer container = IndexedContainer.fromLengthExpression(countingLoop.getBound());
if (container == null) return null;
PsiExpression index = container.extractIndexFromGetExpression(getElementExpression);
@@ -35,7 +35,7 @@ public class Java8ArraySetAllInspection extends AbstractBaseJavaLocalInspectionT
public void visitForStatement(PsiForStatement statement) {
super.visitForStatement(statement);
CountingLoop loop = CountingLoop.from(statement);
if (loop == null || loop.isIncluding()) return;
if (loop == null || loop.isIncluding() || loop.isDescending()) return;
IndexedContainer container = IndexedContainer.fromLengthExpression(loop.getBound());
if (container == null || !(container.getQualifier().getType() instanceof PsiArrayType)) return;
if (!StreamApiUtil.isSupportedStreamElement(container.getElementType())) return;
@@ -1209,7 +1209,7 @@ public class StreamApiMigrationInspection extends AbstractBaseJavaLocalInspectio
@Nullable
public static CountingLoopSource from(PsiForStatement forStatement) {
CountingLoop loop = CountingLoop.from(forStatement);
if (loop == null) return null;
if (loop == null || loop.isDescending()) return null;
return new CountingLoopSource(forStatement, loop.getCounter(), loop.getInitializer(), loop.getBound(), loop.isIncluding());
}
}
@@ -0,0 +1,16 @@
// "Unroll loop" "true"
class Test {
void test() {
System.out.println("Hi!" + 10);
System.out.println("Hi!" + 9);
System.out.println("Hi!" + 8);
System.out.println("Hi!" + 7);
System.out.println("Hi!" + 6);
System.out.println("Hi!" + 5);
System.out.println("Hi!" + 4);
System.out.println("Hi!" + 3);
System.out.println("Hi!" + 2);
System.out.println("Hi!" + 1);
System.out.println("Hi!" + 0);
}
}
@@ -1,7 +1,7 @@
// "Unroll loop" "true"
class Test {
void test() {
fo<caret>r (int i = 0; i < 10; i++ // line comment
fo<caret>r (int i = 0; i != 10; i++ // line comment
) {
System.out.println("Hi!" + i);
}
@@ -0,0 +1,8 @@
// "Unroll loop" "true"
class Test {
void test() {
fo<caret>r (int i = 10; 0 <= i; --i) {
System.out.println("Hi!" + i);
}
}
}
@@ -0,0 +1,8 @@
// "Unroll loop" "false"
class Test {
void test() {
fo<caret>r (int i = 10; i <= 0; --i) {
System.out.println("Hi!" + i);
}
}
}
@@ -15,6 +15,7 @@
*/
package com.siyeh.ig.psiutils;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
@@ -25,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* Represents a loop of form {@code for(int/long counter = initializer; counter </<= bound; counter++)}
* Represents a loop of form {@code for(int/long counter = initializer; counter </<= bound; counter++/--)}
*
* @author Tagir Valeev
*/
@@ -35,17 +36,20 @@ public class CountingLoop {
final @NotNull PsiExpression myInitializer;
final @NotNull PsiExpression myBound;
final boolean myIncluding;
final boolean myDescending;
private CountingLoop(@NotNull PsiLoopStatement loop,
@NotNull PsiLocalVariable counter,
@NotNull PsiExpression initializer,
@NotNull PsiExpression bound,
boolean including) {
boolean including,
boolean descending) {
myInitializer = initializer;
myCounter = counter;
myLoop = loop;
myBound = bound;
myIncluding = including;
myDescending = descending;
}
/**
@@ -87,6 +91,13 @@ public class CountingLoop {
return myIncluding;
}
/**
* @return true if the loop is descending
*/
public boolean isDescending() {
return myDescending;
}
@Nullable
public static CountingLoop from(PsiForStatement forStatement) {
// check that initialization is for(int/long i = <initial_value>;...;...)
@@ -100,33 +111,38 @@ public class CountingLoop {
if(initializer == null) return null;
// check that increment is like for(...;...;i++)
if(!VariableAccessUtils.variableIsIncremented(counter, forStatement.getUpdate())) return null;
boolean descending;
if(VariableAccessUtils.variableIsIncremented(counter, forStatement.getUpdate())) {
descending = false;
} else if (VariableAccessUtils.variableIsDecremented(counter, forStatement.getUpdate())) {
descending = true;
} else {
return null;
}
// check that condition is like for(...;i<bound;...) or for(...;i<=bound;...)
PsiBinaryExpression condition = tryCast(forStatement.getCondition(), PsiBinaryExpression.class);
if(condition == null) return null;
IElementType type = condition.getOperationTokenType();
boolean closed = false;
PsiExpression bound;
PsiExpression ref;
if(type.equals(JavaTokenType.LE)) {
bound = condition.getROperand();
ref = condition.getLOperand();
RelationType relationType = RelationType.fromElementType(type);
if (relationType == null || !relationType.isInequality()) return null;
if (relationType.isSubRelation(RelationType.EQ)) {
closed = true;
} else if(type.equals(JavaTokenType.LT)) {
bound = condition.getROperand();
ref = condition.getLOperand();
} else if(type.equals(JavaTokenType.GE)) {
bound = condition.getLOperand();
ref = condition.getROperand();
closed = true;
} else if(type.equals(JavaTokenType.GT)) {
bound = condition.getLOperand();
ref = condition.getROperand();
} else return null;
if(bound == null || !ExpressionUtils.isReferenceTo(ref, counter)) return null;
}
if (descending) {
relationType = relationType.getFlipped();
assert relationType != null;
}
PsiExpression bound = ExpressionUtils.getOtherOperand(condition, counter);
if (bound == null) return null;
if (bound == condition.getLOperand()) {
relationType = relationType.getFlipped();
assert relationType != null;
}
if (!relationType.isSubRelation(RelationType.LT)) return null;
if(!TypeConversionUtil.areTypesAssignmentCompatible(counter.getType(), bound)) return null;
if(VariableAccessUtils.variableIsAssigned(counter, forStatement.getBody())) return null;
return new CountingLoop(forStatement, counter, initializer, bound, closed);
return new CountingLoop(forStatement, counter, initializer, bound, closed, descending);
}
}