mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 22:09:38 +07:00
Inline lambdas from locals that used only once (IDEA-224757)
GitOrigin-RevId: 28030561c5d5a8bd53b944981522ceedb45f1e0e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3ff1dad861
commit
e3e0174973
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
*
|
||||
|
||||
@@ -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())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,4 +261,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
setupTypeUseAnnotations("typeUse", myFixture);
|
||||
doTest();
|
||||
}
|
||||
public void testInlineLambdaFromLocal() { doTest(); }
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public class OptionalGetWithoutIsPresentInspectionTest extends LightJavaCodeInsi
|
||||
}
|
||||
|
||||
public void testOptionalGet() { doTest(); }
|
||||
public void testOptionalGetInlineLambda() { doTest(); }
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user