mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
java resolve: cache resolved qualifiers when global cache is prohibited (IDEA-219505) ^peter
when type inference is called, it's not known if the client calls inference on resolved method or enumerates all possible candidates and thus the results cant be cached; current implementation is pessimistic and prohibit all caching during inference. Thus, for long method call chains which depend on some non-trivial calculations, it may be extremely resource consuming. Let's cache all qualifiers locally: this doesn't prevent recalculation globally but works around performance problems per single call GitOrigin-RevId: b9b42cbc50918259f5de3a81d5f3a38967c153f1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
aac4cbb7af
commit
fd969c0595
@@ -34,7 +34,10 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.CharTable;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.NullableFunction;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -44,6 +47,7 @@ import java.util.*;
|
||||
|
||||
public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements PsiReferenceExpression, SourceJavaCodeReference {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl");
|
||||
private static final ThreadLocal<Map<PsiReferenceExpression, ResolveResult[]>> ourQualifiersCache = ThreadLocal.withInitial(() -> new HashMap<>());
|
||||
|
||||
private volatile String myCachedQName;
|
||||
private volatile String myCachedNormalizedText;
|
||||
@@ -181,28 +185,44 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
|
||||
PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)ref;
|
||||
CompositeElement treeParent = expression.getTreeParent();
|
||||
IElementType parentType = treeParent == null ? null : treeParent.getElementType();
|
||||
|
||||
List<ResolveResult[]> qualifiers = resolveAllQualifiers(expression, containingFile);
|
||||
JavaResolveResult[] result = expression.resolve(parentType, containingFile);
|
||||
|
||||
if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) {
|
||||
result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
|
||||
if (!incompleteCode) {
|
||||
//optimization:
|
||||
//for the expression foo().bar().baz(), first foo() is resolved during resolveAllQualifiers traversal
|
||||
//then next qualifier is picked: foo().bar(); to resolve bar(), foo() should be resolved again
|
||||
//if the global cache worked, then the result is already in ResolveCache
|
||||
//if top level resolve was started in the context where caching is prohibited,
|
||||
//foo() is already in the local cache ourQualifiersCache
|
||||
ResolveResult[] result = ourQualifiersCache.get().get(ref);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
JavaResolveUtil.substituteResults(expression, result);
|
||||
boolean empty = ourQualifiersCache.get().isEmpty();
|
||||
try {
|
||||
resolveAllQualifiers(expression, containingFile);
|
||||
JavaResolveResult[] result = expression.resolve(parentType, containingFile);
|
||||
|
||||
qualifiers.clear(); // hold qualifier target list until this moment to avoid psi elements inside to GC
|
||||
if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) {
|
||||
result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
|
||||
}
|
||||
|
||||
return result;
|
||||
JavaResolveUtil.substituteResults(expression, result);
|
||||
return result;
|
||||
}
|
||||
finally {
|
||||
//clear cache for the top level expression
|
||||
if (empty) {
|
||||
ourQualifiersCache.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<ResolveResult[]> resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull PsiFile containingFile) {
|
||||
private static void resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull PsiFile containingFile) {
|
||||
// to avoid SOE, resolve all qualifiers starting from the innermost
|
||||
PsiElement qualifier = expression.getQualifier();
|
||||
if (qualifier == null) return Collections.emptyList();
|
||||
if (qualifier == null) return;
|
||||
|
||||
final List<ResolveResult[]> qualifiers = new SmartList<>();
|
||||
final ResolveCache resolveCache = ResolveCache.getInstance(containingFile.getProject());
|
||||
boolean physical = containingFile.isPhysical();
|
||||
qualifier.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@@ -221,8 +241,9 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
|
||||
@Override
|
||||
protected void elementFinished(@NotNull PsiElement element) {
|
||||
if (!(element instanceof PsiReferenceExpressionImpl)) return;
|
||||
PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)element;
|
||||
qualifiers.add(resolveCache.resolveWithCaching(expression, INSTANCE, false, false, containingFile));
|
||||
PsiReferenceExpressionImpl chainedQualifier = (PsiReferenceExpressionImpl)element;
|
||||
ourQualifiersCache.get()
|
||||
.put(chainedQualifier, resolveCache.resolveWithCaching(chainedQualifier, INSTANCE, false, false, containingFile));
|
||||
}
|
||||
|
||||
// walk only qualifiers, not their argument and other associated stuff
|
||||
@@ -236,7 +257,6 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
|
||||
@Override
|
||||
public void visitClass(PsiClass aClass) { }
|
||||
});
|
||||
return qualifiers;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import java.util.*;
|
||||
|
||||
class MyTest {
|
||||
|
||||
static Optional<BulkResult> map(BulkResultType bulkResultType) {
|
||||
|
||||
final LocalDateTime documentDateTime = Optional.ofNullable(bulkResultType)
|
||||
.map(BulkResultType::getObservationBulkmailHeader)
|
||||
.map(HeaderType::getDocumentDateTime)
|
||||
.orElseThrow(() -> new InvalidMessageException(""));
|
||||
|
||||
|
||||
final Optional<LocalDateTime> reportedDateTime = Optional.ofNullable(bulkResultType)
|
||||
.map(BulkResultType::getObservationBulkmailHeader)
|
||||
.map(bulkResultHeaderType::getResultReleaseTime)
|
||||
.map(ResultReleaseTimeType::getReportedDateTime);
|
||||
|
||||
|
||||
final Optional<LocalDateTime> lastModificationDateTime = Optional.ofNullable(bulkResultType)
|
||||
.map(BulkResultType::getObservationBulkmailHeader)
|
||||
.map(HeaderType::getLastModificationDateTime);
|
||||
final Optional<String> statusCode =Optional.ofNullable(bulkResultType)
|
||||
.map(BulkResultType::getObservationBulkmailHeader)
|
||||
.map(bulkResultHeaderType::getStatus)
|
||||
.flatMap(statusTypes -> statusTypes.stream().findFirst())
|
||||
.map(StatusBasisType::getCode)
|
||||
.map(CodeType::getValue);
|
||||
|
||||
|
||||
return Optional.ofNullable(bulkResultType)
|
||||
.flatMap(obm -> obm.getObservationBulkmailResult().stream().findFirst())
|
||||
.map(obmResult -> BulkResult.builder()
|
||||
.consignmentId(getConsignmentId(obmResult.getConsignmentID()))
|
||||
.documentDateTime(documentDateTime)
|
||||
.reportedDateTime(reportedDateTime)
|
||||
.lastModificationDateTime(lastModificationDateTime)
|
||||
.customerOrderId(Optional.ofNullable(obmResult.getCustomerOrderID()).map(IdentifierType::getValue))
|
||||
.orderLineNumber(Optional.ofNullable(obmResult.getOrderLineNumber()).map(IdentifierType::getValue))
|
||||
.startDateTime(Optional.ofNullable(obmResult.getResultTime()).map(TimePeriodABIEType::getStartDateTime))
|
||||
.endDateTime(Optional.ofNullable(obmResult.getResultTime()).map(TimePeriodABIEType::getEndDateTime))
|
||||
.statusCode(statusCode)
|
||||
.build());
|
||||
|
||||
}
|
||||
|
||||
private static Object getConsignmentId(Object consignmentID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class Value { }
|
||||
|
||||
private static class CodeType {
|
||||
String getValue() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class IdentifierType {
|
||||
public String getValue() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InvalidMessageException extends RuntimeException {
|
||||
private InvalidMessageException(String s) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class BulkResult {
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
public Builder consignmentId(Object consignmentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder documentDateTime(LocalDateTime documentDateTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder reportedDateTime(Optional<LocalDateTime> reportedDateTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder lastModificationDateTime(Optional<LocalDateTime> lastModificationDateTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder customerOrderId(Optional<String> s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder orderLineNumber(Optional<String> s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder startDateTime(Optional<LocalDateTime> localDateTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder endDateTime(Optional<LocalDateTime> localDateTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Builder statusCode(Optional<String> statusCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public BulkResult build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class bulkResultHeaderType {
|
||||
public static ResultReleaseTimeType getResultReleaseTime(Object o) {
|
||||
return new ResultReleaseTimeType();
|
||||
}
|
||||
|
||||
public static List<StatusBasisType> getStatus(bulkResultHeaderType observationBulkmailHeaderType) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BulkResultResult {
|
||||
public IdentifierType getCustomerOrderID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public IdentifierType getOrderLineNumber() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public TimePeriodABIEType getResultTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getConsignmentID() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BulkResultType {
|
||||
public static bulkResultHeaderType getObservationBulkmailHeader(BulkResultType bulkResultType) {
|
||||
return new bulkResultHeaderType();
|
||||
}
|
||||
|
||||
public Collection<BulkResultResult> getObservationBulkmailResult() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResultReleaseTimeType {
|
||||
public static LocalDateTime getReportedDateTime(Object o) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StatusBasisType {
|
||||
public static CodeType getCode(Object o) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TimePeriodABIEType {
|
||||
public LocalDateTime getStartDateTime() {return null;}
|
||||
public LocalDateTime getEndDateTime() {return null;}
|
||||
}
|
||||
|
||||
public static class HeaderType {
|
||||
public static LocalDateTime getLastModificationDateTime(bulkResultHeaderType observationBulkmailHeaderType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static LocalDateTime getDocumentDateTime(bulkResultHeaderType observationBulkmailHeaderType) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocalDateTime {}
|
||||
}
|
||||
@@ -16,19 +16,14 @@
|
||||
package com.intellij.java.codeInsight.daemon.lambda;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class InferencePerformanceTest extends LightDaemonAnalyzerTestCase {
|
||||
@NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/performance";
|
||||
|
||||
@@ -72,6 +67,10 @@ public class InferencePerformanceTest extends LightDaemonAnalyzerTestCase {
|
||||
assertEmpty(highlightErrors());
|
||||
}
|
||||
|
||||
public void testLongQualifiersChainInsideLambda() {
|
||||
PlatformTestUtil.startPerformanceTest("long qualifiers chain", 12000, this::doTest).usesAllCPUCores().assertTiming();
|
||||
}
|
||||
|
||||
private void doTest() {
|
||||
IdeaTestUtil.setTestVersion(JavaSdkVersion.JDK_1_8, getModule(), getTestRootDisposable());
|
||||
doTest(BASE_PATH + "/" + getTestName(false) + ".java", false, false);
|
||||
|
||||
Reference in New Issue
Block a user