IDEA-167583 Uncompilable code after conversion Stream -> Loop with lambda

This commit is contained in:
Tagir Valeev
2017-02-03 00:18:43 +03:00
parent a74f2db438
commit 7a4f84ead5
8 changed files with 125 additions and 47 deletions

View File

@@ -106,14 +106,14 @@ abstract class FunctionHelper {
return null;
}
void suggestVariableName(StreamVariable var, int index) {
void preprocessVariable(StreamToLoopReplacementContext context, StreamVariable var, int index) {
String name = getParameterName(index);
if (name != null) {
var.addBestNameCandidate(name);
}
}
void suggestOutputNames(StreamVariable var) {}
void suggestOutputNames(StreamToLoopReplacementContext context, StreamVariable var) {}
List<String> suggestFinalOutputNames(StreamToLoopReplacementContext context, String desiredName, String worstCaseName) {
List<String> candidates = Arrays.asList(JavaCodeStyleManager.getInstance(context.getProject())
@@ -433,11 +433,10 @@ abstract class FunctionHelper {
}
@Override
void suggestOutputNames(StreamVariable var) {
void suggestOutputNames(StreamToLoopReplacementContext context, StreamVariable var) {
// myMethodRef is physical at this point
Project project = myMethodRef.getProject();
PsiTypeCastExpression castExpr = (PsiTypeCastExpression)JavaPsiFacade.getElementFactory(project)
.createExpressionFromText("(" + myType + ")" + myMethodRef.getText(), myMethodRef);
PsiTypeCastExpression castExpr = (PsiTypeCastExpression)context.createExpression("(" + myType + ")" + myMethodRef.getText());
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)castExpr.getOperand();
PsiLambdaExpression lambda = LambdaRefactoringUtil.convertMethodReferenceToLambda(methodRef, true, true);
if(lambda != null) {
@@ -632,9 +631,27 @@ abstract class FunctionHelper {
}
@Override
void suggestOutputNames(StreamVariable var) {
void preprocessVariable(StreamToLoopReplacementContext context, StreamVariable var, int index) {
super.preprocessVariable(context, var, index);
boolean hasClassOrLambda =
StreamEx.ofTree(myBody, e -> StreamEx.of(e.getChildren())).anyMatch(e -> e instanceof PsiLambdaExpression || e instanceof PsiClass);
if (hasClassOrLambda) {
PsiLambdaExpression lambda = (PsiLambdaExpression)context.createExpression(getParameterName(index) + "->" + myBody.getText());
PsiParameter parameter = lambda.getParameterList().getParameters()[0];
PsiElement body = lambda.getBody();
LOG.assertTrue(body != null);
boolean mayBeNotFinal = ReferencesSearch.search(parameter, new LocalSearchScope(body))
.forEach(e -> PsiTreeUtil.getParentOfType(e.getElement(), PsiLambdaExpression.class, PsiClass.class) == lambda);
if (!mayBeNotFinal) {
var.markFinal();
}
}
}
@Override
void suggestOutputNames(StreamToLoopReplacementContext context, StreamVariable var) {
Project project = myBody.getProject();
PsiExpression expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText("(" + var.getType() + ")" + getText(), myBody);
PsiExpression expr = context.createExpression("(" + var.getType() + ")" + getText());
suggestFromExpression(var, project, expr);
}
}

View File

@@ -53,7 +53,7 @@ abstract class Operation {
public void registerReusedElements(Consumer<PsiElement> consumer) {}
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {}
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {}
@Nullable
static Operation createIntermediate(@NotNull String name, @NotNull PsiExpression[] args,
@@ -133,8 +133,8 @@ abstract class Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myFn.suggestVariableName(inVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myFn.preprocessVariable(context, inVar, 0);
}
}
@@ -193,9 +193,9 @@ abstract class Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
super.suggestNames(inVar, outVar);
myFn.suggestOutputNames(outVar);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
super.preprocessVariables(context, inVar, outVar);
myFn.suggestOutputNames(context, outVar);
}
@Override
@@ -253,8 +253,11 @@ abstract class Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myFn.suggestVariableName(inVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
String name = myFn.getParameterName(0);
if (name != null) {
inVar.addBestNameCandidate(name);
}
}
@Override

View File

@@ -136,8 +136,8 @@ abstract class SourceOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
if(myQualifier instanceof PsiReferenceExpression) {
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
if (myQualifier instanceof PsiReferenceExpression) {
String name = ((PsiReferenceExpression)myQualifier).getReferenceName();
if(name != null) {
String singularName = StringUtil.unpluralize(name);
@@ -264,8 +264,8 @@ abstract class SourceOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myFn.suggestVariableName(outVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myFn.preprocessVariable(context, outVar, 0);
}
@Override
@@ -306,10 +306,17 @@ abstract class SourceOperation extends Operation {
if(!ExpressionUtils.isSimpleExpression(context.createExpression(bound))) {
bound = context.declare("bound", outVar.getType(), bound);
}
String loopVar = outVar.getName();
String reassign = "";
if (outVar.isFinal()) {
loopVar = context.registerVarName(Arrays.asList("i", "j", "idx"));
reassign = outVar.getDeclaration(loopVar);
}
return context.getLoopLabel() +
"for(" + outVar.getDeclaration() + " = " + myOrigin.getText() + ";" +
outVar + (myInclusive ? "<=" : "<") + bound + ";" +
outVar + "++) {\n" +
"for(" + outVar.getType() + " " + loopVar + " = " + myOrigin.getText() + ";" +
loopVar + (myInclusive ? "<=" : "<") + bound + ";" +
loopVar + "++) {\n" +
reassign +
code + "}\n";
}
}
@@ -334,7 +341,7 @@ abstract class SourceOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
String name = myCall.getMethodExpression().getReferenceName();
if (name != null) {
String unpluralized = StringUtil.unpluralize(name);

View File

@@ -302,7 +302,6 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
TerminalOperation terminal = getTerminal(operations);
if (terminal == null) return;
allOperations(operations).forEach(or -> or.myOperation.suggestNames(or.myInVar, or.myOutVar));
PsiStatement statement = PsiTreeUtil.getParentOfType(terminalCall, PsiStatement.class);
LOG.assertTrue(statement != null);
CommentTracker ct = new CommentTracker();
@@ -353,6 +352,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
private static void registerVariables(List<OperationRecord> operations, StreamToLoopReplacementContext context) {
allOperations(operations).forEach(or -> or.myOperation.preprocessVariables(context, or.myInVar, or.myOutVar));
allOperations(operations).map(or -> or.myOperation).forEach(op -> op.registerReusedElements(context::registerReusedElement));
allOperations(operations).map(or -> or.myInVar).distinct().forEach(var -> var.register(context));
}

View File

@@ -29,9 +29,9 @@ import java.util.List;
/**
* This class represents a variable which holds stream element. Its lifecycle is the following:
* 1. Construction: fast, in case you don't need to perform a fix actually
* 2. Gather name candidates (addBestNameCandidate/addOtherNameCandidate can be called).
* 2. Preprocessing (addBestNameCandidate/addOtherNameCandidate/markFinal can be called).
* 3. Register variable in {@code StreamToLoopReplacementContext}: actual variable name is assigned here
* 4. Usage in code generation: getName()/getType() could be called.
* 4. Usage in code generation: getName()/getType()/isFinal() could be called.
*
* @author Tagir Valeev
*/
@@ -55,6 +55,7 @@ class StreamVariable {
String myName;
@NotNull String myType;
boolean myFinal;
private Collection<String> myBestCandidates = new LinkedHashSet<>();
private Collection<String> myOtherCandidates = new LinkedHashSet<>();
@@ -68,6 +69,13 @@ class StreamVariable {
myName = name;
}
/**
* Call if the resulting variable must be declared final (e.g. used in lambdas)
*/
public void markFinal() {
myFinal = true;
}
/**
* Register best name candidate for this variable (like lambda argument which was explicitly present in the original code).
*
@@ -120,6 +128,10 @@ class StreamVariable {
return getType() + " " + getName() + "=" + initializer + ";\n";
}
public boolean isFinal() {
return myFinal;
}
@Override
public String toString() {
if (myName == null) {

View File

@@ -420,8 +420,8 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myAccumulator.suggestVariableName(inVar, 1);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myAccumulator.preprocessVariable(context, inVar, 1);
}
@Override
@@ -545,8 +545,8 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myFn.suggestVariableName(inVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myFn.preprocessVariable(context, inVar, 0);
}
@Override
@@ -569,7 +569,7 @@ abstract class TerminalOperation extends Operation {
interface CollectorOperation {
// Non-trivial finishers are not supported
default void transform(StreamToLoopReplacementContext context, String item) {}
default void suggestNames(StreamVariable inVar, StreamVariable outVar) {}
default void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {}
default void registerReusedElements(Consumer<PsiElement> consumer) {}
String getSupplier();
String getAccumulatorUpdater(StreamVariable inVar, String acc);
@@ -823,9 +823,9 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myKeyExtractor.suggestVariableName(inVar, 0);
myValueExtractor.suggestVariableName(inVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myKeyExtractor.preprocessVariable(context, inVar, 0);
myValueExtractor.preprocessVariable(context, inVar, 0);
}
@Override
@@ -890,9 +890,9 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myKeyExtractor.suggestVariableName(inVar, 0);
myCollector.suggestNames(inVar, outVar);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myKeyExtractor.preprocessVariable(context, inVar, 0);
myCollector.preprocessVariables(context, inVar, outVar);
}
@Override
@@ -931,9 +931,9 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myPredicate.suggestVariableName(inVar, 0);
myCollector.suggestNames(inVar, outVar);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myPredicate.preprocessVariable(context, inVar, 0);
myCollector.preprocessVariables(context, inVar, outVar);
}
@Override
@@ -971,8 +971,8 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myMapper.suggestVariableName(inVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myMapper.preprocessVariable(context, inVar, 0);
}
@Override
@@ -1007,7 +1007,7 @@ abstract class TerminalOperation extends Operation {
private void createVariable(StreamToLoopReplacementContext context, String item) {
myMapper.transform(context, item);
myVariable = new StreamVariable(myMapper.getResultType());
myDownstream.suggestNames(myVariable, StreamVariable.STUB);
myDownstream.preprocessVariables(context, myVariable, StreamVariable.STUB);
myMapper.suggestFinalOutputNames(context, null, null).forEach(myVariable::addOtherNameCandidate);
myVariable.register(context);
}
@@ -1067,8 +1067,8 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myFn.suggestVariableName(inVar, 0);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myFn.preprocessVariable(context, inVar, 0);
}
@Override
@@ -1101,8 +1101,8 @@ abstract class TerminalOperation extends Operation {
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
myOrigin.suggestNames(inVar, outVar);
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myOrigin.preprocessVariables(context, inVar, outVar);
}
@Override

View File

@@ -1,6 +1,7 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.ArrayList;
import java.util.function.Supplier;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
@@ -71,11 +72,31 @@ public class Main {
return stat;
}
static void print(Supplier<String> messageSupplier) {
System.out.println(messageSupplier.get());
}
public static void main(String[] args) {
System.out.println(test());
System.out.println(testUseName());
System.out.println(testNested());
System.out.println(testNestedRename());
System.out.println(String.join("|", testNestedUseName()).length());
// convert to loop
for (int j = 1; j < 2; j++) {
int x1 = j;
if (x1 > 0) {
print(() -> "attempt #" + x1);
}
}
// convert to loop
for (int x = 1; x < 2; x++) {
if (x > 0) {
int i = x + 1;
print(() -> "attempt #" + i);
}
}
}
}

View File

@@ -1,5 +1,6 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.function.Supplier;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
@@ -29,11 +30,28 @@ public class Main {
return IntStream.range(0, 20).filter(x -> x > 2).flatMap(limit -> Stream.iterate(String.valueOf(limit), x -> x + limit).limit(limit).mapToInt(x -> x.length())).summaryStatistics();
}
static void print(Supplier<String> messageSupplier) {
System.out.println(messageSupplier.get());
}
public static void main(String[] args) {
System.out.println(test());
System.out.println(testUseName());
System.out.println(testNested());
System.out.println(testNestedRename());
System.out.println(String.join("|", testNestedUseName()).length());
IntStream.range(1, 2) // convert to loop
.filter(x -> x > 0)
.forEach(i -> {
print(() -> "attempt #" + i);
});
IntStream.range(1, 2) // convert to loop
.filter(x -> x > 0)
.map(x -> x + 1)
.forEach(i -> {
print(() -> "attempt #" + i);
});
}
}