Inline lambdas from locals that used only once (IDEA-224757)

GitOrigin-RevId: 28030561c5d5a8bd53b944981522ceedb45f1e0e
This commit is contained in:
Tagir Valeev
2019-11-21 10:11:22 +07:00
committed by intellij-monorepo-bot
parent 3ff1dad861
commit e3e0174973
9 changed files with 150 additions and 34 deletions

View File

@@ -26,15 +26,15 @@ import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import one.util.streamex.IntStreamEx;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.function.Consumer;
/**
@@ -648,16 +648,7 @@ public class CFGBuilder {
*/
public CFGBuilder invokeFunction(int argCount, @Nullable PsiExpression functionalExpression, Nullability resultNullability) {
PsiExpression stripped = PsiUtil.deparenthesizeExpression(functionalExpression);
if (stripped instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)stripped;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if (parameters.length == argCount && lambda.getBody() != null) {
StreamEx.ofReversed(parameters).forEach(p -> assignTo(p).pop());
inlineLambda(lambda, resultNullability);
StreamEx.of(parameters).forEach(p -> add(new FlushVariableInstruction(getFactory().getVarFactory().createVariableValue(p))));
return this;
}
}
if (tryInlineLambda(argCount, functionalExpression, resultNullability, () -> {})) return this;
if (stripped instanceof PsiMethodReferenceExpression) {
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)stripped;
JavaResolveResult resolveResult = methodRef.advancedResolve(false);
@@ -688,7 +679,7 @@ public class CFGBuilder {
}
}
PsiElement qualifier = methodRef.getQualifier();
if(qualifier instanceof PsiTypeElement && ((PsiTypeElement)qualifier).getType() instanceof PsiArrayType) {
if (qualifier instanceof PsiTypeElement && ((PsiTypeElement)qualifier).getType() instanceof PsiArrayType) {
// like String[]::new
splice(argCount)
.push(getFactory().createTypeValue(((PsiTypeElement)qualifier).getType(), Nullability.NOT_NULL));
@@ -715,6 +706,38 @@ public class CFGBuilder {
return this;
}
public boolean tryInlineLambda(int argCount,
@Nullable PsiExpression functionalExpression,
Nullability resultNullability,
Runnable pushArgs) {
PsiExpression stripped = PsiUtil.deparenthesizeExpression(functionalExpression);
if (stripped instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)stripped;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if (parameters.length == argCount && lambda.getBody() != null) {
pushArgs.run();
StreamEx.ofReversed(parameters).forEach(p -> assignTo(p).pop());
inlineLambda(lambda, resultNullability);
StreamEx.of(parameters).forEach(p -> add(new FlushVariableInstruction(getFactory().getVarFactory().createVariableValue(p))));
return true;
}
}
PsiLocalVariable localFn = ExpressionUtils.resolveLocalVariable(stripped);
if (localFn != null) {
PsiLambdaExpression localLambda =
ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(localFn.getInitializer()), PsiLambdaExpression.class);
if (myAnalyzer.wasAdded(localLambda)) {
PsiElement scope = PsiUtil.getVariableCodeBlock(localFn, null);
List<PsiReferenceExpression> refs = VariableAccessUtils.getVariableReferences(localFn, scope);
if (ContainerUtil.getOnlyItem(refs) == stripped) {
myAnalyzer.removeLambda(localLambda);
return tryInlineLambda(argCount, localLambda, resultNullability, pushArgs);
}
}
}
return false;
}
private boolean processKnownMethodReference(int argCount, PsiMethodReferenceExpression methodRef, PsiMethod method) {
if (argCount != 1 || !method.getName().equals("isInstance")) return false;
PsiClassObjectAccessExpression qualifier = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(methodRef.getQualifierExpression()),
@@ -756,7 +779,7 @@ public class CFGBuilder {
* @param resultNullability a required return value nullability
* @return this builder
*/
public CFGBuilder inlineLambda(PsiLambdaExpression lambda, Nullability resultNullability) {
private CFGBuilder inlineLambda(PsiLambdaExpression lambda, Nullability resultNullability) {
PsiElement body = lambda.getBody();
PsiExpression expression = LambdaUtil.extractSingleExpressionFromBody(body);
if (expression != null) {

View File

@@ -130,6 +130,12 @@ public class ControlFlow {
return result.toString();
}
void makeNop(int index) {
SpliceInstruction instruction = new SpliceInstruction(0);
instruction.setIndex(index);
myInstructions.set(index, instruction);
}
public abstract static class ControlFlowOffset {
public abstract int getInstructionOffset();

View File

@@ -31,6 +31,7 @@ import com.intellij.util.containers.FactoryMap;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -2045,6 +2046,24 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitClass(PsiClass aClass) {
}
@Contract("null -> false")
final boolean wasAdded(PsiElement element) {
return element != null &&
myCurrentFlow.getStartOffset(element).getInstructionOffset() > -1 &&
myCurrentFlow.getEndOffset(element).getInstructionOffset() > -1;
}
public void removeLambda(@NotNull PsiLambdaExpression lambda) {
int start = myCurrentFlow.getStartOffset(lambda).getInstructionOffset();
int end = myCurrentFlow.getEndOffset(lambda).getInstructionOffset();
for (int i = start; i < end; i++) {
Instruction inst = myCurrentFlow.getInstruction(i);
if (inst instanceof EscapeInstruction || inst instanceof LambdaInstruction) {
myCurrentFlow.makeNop(i);
}
}
}
/**
* Inline code block (lambda or method body) into this CFG. Incoming parameters are assumed to be handled already (if necessary)
*

View File

@@ -15,11 +15,10 @@
*/
package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.CFGBuilder;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import one.util.streamex.EntryStream;
import org.jetbrains.annotations.NotNull;
@@ -30,25 +29,18 @@ import org.jetbrains.annotations.NotNull;
public class LambdaInliner implements CallInliner {
@Override
public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression());
if (qualifier == null) return false;
JavaResolveResult result = call.getMethodExpression().advancedResolve(false);
PsiMethod method = (PsiMethod)result.getElement();
if (method == null || method != LambdaUtil.getFunctionalInterfaceMethod(method.getContainingClass())) return false;
PsiTypeCastExpression typeCastExpression = ObjectUtils
.tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiTypeCastExpression.class);
if (typeCastExpression == null) return false;
PsiLambdaExpression lambda =
ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(typeCastExpression.getOperand()), PsiLambdaExpression.class);
if (lambda == null || lambda.getBody() == null) return false;
if (method.isVarArgs()) return false; // TODO: support varargs
PsiExpression[] args = call.getArgumentList().getExpressions();
PsiParameter[] parameters = lambda.getParameterList().getParameters();
PsiParameter[] parameters = method.getParameterList().getParameters();
if (args.length != parameters.length) return false;
EntryStream.zip(args, parameters).forKeyValue(
(arg, parameter) -> builder.pushForWrite(builder.getFactory().getVarFactory().createVariableValue(parameter))
.pushExpression(arg)
.boxUnbox(arg, parameter.getType())
.assign()
.pop());
builder.inlineLambda(lambda, Nullability.UNKNOWN);
return true;
PsiSubstitutor substitutor = result.getSubstitutor();
return builder.tryInlineLambda(args.length, qualifier, DfaPsiUtil.getTypeNullability(substitutor.substitute(method.getReturnType())),
() -> EntryStream.zip(args, parameters)
.forKeyValue((arg, parameter) -> builder.pushExpression(arg).boxUnbox(arg, parameter.getType())));
}
}

View File

@@ -37,6 +37,16 @@ class EscapeAnalysis {
int[] x = new int[] {0};
Runnable r = () -> x[0] = 1;
r.run();
if(<warning descr="Condition 'x[0] == 1' is always 'true'">x[0] == 1</warning>) {
System.out.println("ok");
}
}
void testLambda2() {
int[] x = new int[] {0};
Runnable r = () -> x[0] = 1;
r.run();
r.run();
if(x[0] == 1) {
System.out.println("ok");
}

View File

@@ -0,0 +1,41 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
class App {
void usedOnce() {
Consumer<String> c = s -> {
if (<warning descr="Condition 's.isEmpty()' is always 'false'">s.isEmpty()</warning>) {
System.out.println("Empty string!");
}
};
c.accept("foo");
}
void usedTwice() {
Consumer<String> c = s -> {
if (s.isEmpty()) {
System.out.println("Empty string!");
}
};
c.accept("foo");
c.accept("bar");
}
void inStream() {
Consumer<String> c = s -> {
if (<warning descr="Condition 's.isEmpty()' is always 'false'">s.isEmpty()</warning>) {
System.out.println("Empty string!");
}
};
Stream.of("foo", "bar", "baz").forEach(c);
}
void inStream2() {
Consumer<String> c = s -> {
if (s.isEmpty()) {
System.out.println("Empty string!");
}
};
Stream.of("foo", "bar", "").forEach(c);
}
}

View File

@@ -0,0 +1,23 @@
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
class OptIssue {
public static List<MyObject> getMyObject(List<EventObject> events){
Predicate<EventObject> isPresent = event -> event.getMethod().isPresent();
// No warning -- IDEA-224757
Function<EventObject, MyObject> getItem = e -> e.getMethod().get();
return events.stream().filter(isPresent).map(getItem).collect(Collectors.toList());
}
private static class MyObject {
}
private static class EventObject {
public Optional<MyObject> getMethod() {
return null;
}
}
}

View File

@@ -261,4 +261,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
setupTypeUseAnnotations("typeUse", myFixture);
doTest();
}
public void testInlineLambdaFromLocal() { doTest(); }
}

View File

@@ -14,6 +14,7 @@ public class OptionalGetWithoutIsPresentInspectionTest extends LightJavaCodeInsi
}
public void testOptionalGet() { doTest(); }
public void testOptionalGetInlineLambda() { doTest(); }
@NotNull
@Override