UnrollLoopAction: support counting loop; limit max iterations to unroll

This commit is contained in:
Tagir Valeev
2017-10-09 11:24:05 +07:00
parent 8e19d90cd7
commit cdd80f8de4
6 changed files with 167 additions and 40 deletions
@@ -28,10 +28,11 @@ import com.intellij.refactoring.util.InlineUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Objects;
import java.util.*;
import static com.siyeh.ig.callMatcher.CallMatcher.anyOf;
import static com.siyeh.ig.callMatcher.CallMatcher.staticCall;
@@ -43,21 +44,19 @@ public class UnrollLoopAction extends PsiElementBaseIntentionAction {
anyOf(staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singleton", "singletonList").parameterCount(1),
staticCall(CommonClassNames.JAVA_UTIL_LIST, "of").parameterTypes("E"));
private static final int MAX_ITERATIONS = 64;
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull final PsiElement element) {
PsiForeachStatement loop = PsiTreeUtil.getParentOfType(element, PsiForeachStatement.class);
if (loop == null) return false;
if (!(loop.getParent() instanceof PsiCodeBlock)) return false;
PsiExpression iteratedValue = ExpressionUtils.resolveExpression(loop.getIteratedValue());
PsiParameter iterationParameter = loop.getIterationParameter();
if (extractExpressions(iteratedValue).length == 0) return false;
PsiLoopStatement loop = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class);
PsiVariable iterationParameter = getVariable(loop);
if (iterationParameter == null || !(loop.getParent() instanceof PsiCodeBlock)) return false;
List<PsiExpression> expressions = extractExpressions(loop);
if (expressions.isEmpty() || expressions.size() > MAX_ITERATIONS) return false;
PsiStatement[] statements = ControlFlowUtils.unwrapBlock(loop.getBody());
if (statements.length == 0) return false;
if (Arrays.stream(statements).anyMatch(PsiDeclarationStatement.class::isInstance)) return false;
if (VariableAccessUtils.variableIsAssigned(iterationParameter, loop)) return false;
//if (isBreakChain(loop)) {
// statements = Arrays.copyOfRange(statements, 0, statements.length - 1);
//}
if (VariableAccessUtils.variableIsAssigned(iterationParameter, loop.getBody())) return false;
for (PsiStatement statement : statements) {
if (isLoopBreak(statement)) continue;
boolean acceptable = PsiTreeUtil.processElements(statement, e -> {
@@ -70,29 +69,77 @@ public class UnrollLoopAction extends PsiElementBaseIntentionAction {
return true;
}
@NotNull
private static PsiExpression[] extractExpressions(PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiArrayInitializerExpression) {
return ((PsiArrayInitializerExpression)expression).getInitializers();
@Contract("null -> null")
@Nullable
private static PsiVariable getVariable(PsiLoopStatement loop) {
if (loop instanceof PsiForeachStatement) {
return ((PsiForeachStatement)loop).getIterationParameter();
}
if (expression instanceof PsiNewExpression) {
PsiArrayInitializerExpression initializer = ((PsiNewExpression)expression).getArrayInitializer();
return initializer == null ? PsiExpression.EMPTY_ARRAY : initializer.getInitializers();
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
if (SINGLETON_CONSTRUCTOR.test(call)) {
return call.getArgumentList().getExpressions();
if (loop instanceof PsiForStatement) {
CountingLoop countingLoop = CountingLoop.from((PsiForStatement)loop);
if (countingLoop != null) {
return countingLoop.getCounter();
}
if (LIST_CONSTRUCTOR.test(call)) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length > 1 || MethodCallUtils.isVarArgCall(call)) {
return args;
}
return null;
}
@NotNull
private static List<PsiExpression> extractExpressions(PsiLoopStatement loop) {
if (loop instanceof PsiForeachStatement) {
PsiExpression expression = ExpressionUtils.resolveExpression(((PsiForeachStatement)loop).getIteratedValue());
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiArrayInitializerExpression) {
return Arrays.asList(((PsiArrayInitializerExpression)expression).getInitializers());
}
if (expression instanceof PsiNewExpression) {
PsiArrayInitializerExpression initializer = ((PsiNewExpression)expression).getArrayInitializer();
return initializer == null ? Collections.emptyList() : Arrays.asList(initializer.getInitializers());
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
if (SINGLETON_CONSTRUCTOR.test(call)) {
return Arrays.asList(call.getArgumentList().getExpressions());
}
if (LIST_CONSTRUCTOR.test(call)) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length > 1 || MethodCallUtils.isVarArgCall(call)) {
return Arrays.asList(args);
}
}
}
}
return PsiExpression.EMPTY_ARRAY;
if (loop instanceof PsiForStatement) {
CountingLoop countingLoop = CountingLoop.from((PsiForStatement)loop);
if (countingLoop != null) {
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;
String suffix = PsiType.LONG.equals(countingLoop.getCounter().getType()) ? "L" : "";
if (countingLoop.isIncluding()) {
diff++; // overflow is ok: diff will become negative and we will exit
}
if (diff < 0 || diff > MAX_ITERATIONS) return Collections.emptyList();
int size = (int)(diff); // Less or equal to MAX_ITERATIONS => fits to int
PsiElementFactory factory = JavaPsiFacade.getElementFactory(loop.getProject());
return new AbstractList<PsiExpression>() {
@Override
public PsiExpression get(int index) {
return factory.createExpressionFromText(String.valueOf(fromValue + index) + suffix, loop);
}
@Override
public int size() {
return size;
}
};
}
}
return Collections.emptyList();
}
@NotNull
@@ -109,23 +156,22 @@ public class UnrollLoopAction extends PsiElementBaseIntentionAction {
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
PsiForeachStatement loop = PsiTreeUtil.getParentOfType(element, PsiForeachStatement.class);
PsiLoopStatement loop = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class);
if (loop == null) return;
if (!(loop.getParent() instanceof PsiCodeBlock)) return;
PsiExpression iteratedValue = loop.getIteratedValue();
PsiExpression[] expressions = extractExpressions(ExpressionUtils.resolveExpression(iteratedValue));
if (expressions.length == 0) return;
List<PsiExpression> expressions = extractExpressions(loop);
if (expressions.isEmpty()) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
CommentTracker ct = new CommentTracker();
PsiElement anchor = loop;
for (PsiExpression expression : expressions) {
PsiForeachStatement copy = (PsiForeachStatement)factory.createStatementFromText(ct.text(loop), element);
PsiParameter parameter = copy.getIterationParameter();
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(copy))) {
PsiLoopStatement copy = (PsiLoopStatement)factory.createStatementFromText(ct.text(loop), element);
PsiVariable variable = Objects.requireNonNull(getVariable(copy));
for (PsiReference reference : ReferencesSearch.search(variable, new LocalSearchScope(copy))) {
final PsiElement referenceElement = reference.getElement();
if (referenceElement instanceof PsiJavaCodeReferenceElement) {
ct.markUnchanged(expression);
InlineUtil.inlineVariable(parameter, expression, (PsiJavaCodeReferenceElement)referenceElement);
InlineUtil.inlineVariable(variable, expression, (PsiJavaCodeReferenceElement)referenceElement);
}
}
PsiStatement body = copy.getBody();
@@ -151,8 +197,11 @@ public class UnrollLoopAction extends PsiElementBaseIntentionAction {
}
}
}
PsiLocalVariable variable = ExpressionUtils.resolveLocalVariable(iteratedValue);
if (variable != null) ct.delete(variable);
if (loop instanceof PsiForeachStatement) {
PsiExpression iteratedValue = ((PsiForeachStatement)loop).getIteratedValue();
PsiLocalVariable variable = ExpressionUtils.resolveLocalVariable(iteratedValue);
if (variable != null) ct.delete(variable);
}
ct.deleteAndRestoreComments(loop);
}
@@ -0,0 +1,15 @@
// "Unroll loop" "true"
class Test {
void test() {
System.out.println("Hi!" + 0);
System.out.println("Hi!" + 1);
System.out.println("Hi!" + 2);
System.out.println("Hi!" + 3);
System.out.println("Hi!" + 4);
System.out.println("Hi!" + 5);
System.out.println("Hi!" + 6);
System.out.println("Hi!" + 7);
System.out.println("Hi!" + 8);
System.out.println("Hi!" + 9);
}
}
@@ -0,0 +1,38 @@
// "Unroll loop" "true"
class Test {
void test() {
if (0L % 7 != 6) {
System.out.println("Hi!" + 0L);
if (1L % 7 != 6) {
System.out.println("Hi!" + 1L);
if (2L % 7 != 6) {
System.out.println("Hi!" + 2L);
if (3L % 7 != 6) {
System.out.println("Hi!" + 3L);
if (4L % 7 != 6) {
System.out.println("Hi!" + 4L);
if (5L % 7 != 6) {
System.out.println("Hi!" + 5L);
if (6L % 7 != 6) {
System.out.println("Hi!" + 6L);
if (7L % 7 != 6) {
System.out.println("Hi!" + 7L);
if (8L % 7 != 6) {
System.out.println("Hi!" + 8L);
if (9L % 7 != 6) {
System.out.println("Hi!" + 9L);
if (10L % 7 != 6) {
System.out.println("Hi!" + 10L);
}
}
}
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,8 @@
// "Unroll loop" "true"
class Test {
void test() {
fo<caret>r (int i = 0; i < 10; i++) {
System.out.println("Hi!" + i);
}
}
}
@@ -0,0 +1,8 @@
// "Unroll loop" "false"
class Test {
void test() {
fo<caret>r (int i = 0; i < 1000; i++) {
System.out.println("Hi!" + i);
}
}
}
@@ -0,0 +1,9 @@
// "Unroll loop" "true"
class Test {
void test() {
fo<caret>r (long i = 0; i <= 10; i++) {
if (i % 7 == 6) break;
System.out.println("Hi!" + i);
}
}
}