relevant method chain completion: chains ended with casts are valid too

This commit is contained in:
Dmitry Batkovich
2017-09-25 17:34:21 +03:00
parent 4ddca30168
commit 87e841ef88
12 changed files with 220 additions and 91 deletions

View File

@@ -30,7 +30,6 @@ import com.intellij.util.indexing.StorageException;
import com.intellij.util.indexing.ValueContainer;
import gnu.trove.THashSet;
import gnu.trove.TIntHashSet;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.backwardRefs.CompilerBackwardReferenceIndex;
@@ -179,22 +178,20 @@ class CompilerReferenceReader {
return myIndex;
}
TObjectIntHashMap<LightRef> getTypeCasts(@NotNull LightRef.LightClassHierarchyElementDef castType, @NotNull TIntHashSet fileIds) throws StorageException {
TObjectIntHashMap<LightRef> typeCastStats = new TObjectIntHashMap<>();
@NotNull
OccurrenceCounter<LightRef> getTypeCastOperands(@NotNull LightRef.LightClassHierarchyElementDef castType, @Nullable TIntHashSet fileIds) throws StorageException {
OccurrenceCounter<LightRef> result = new OccurrenceCounter<>();
myIndex.get(CompilerIndices.BACK_CAST).getData(castType).forEach(new ValueContainer.ContainerAction<Collection<LightRef>>() {
@Override
public boolean perform(int id, Collection<LightRef> values) {
if (!fileIds.contains(id)) return true;
if (fileIds != null && !fileIds.contains(id)) return true;
for (LightRef ref : values) {
if (!typeCastStats.adjustValue(ref, 1)) {
typeCastStats.put(ref, 1);
}
result.add(ref);
}
return true;
}
});
return typeCastStats;
return result;
}
static boolean exists(Project project) {

View File

@@ -16,7 +16,9 @@
package com.intellij.compiler.backwardRefs;
import com.intellij.compiler.CompilerReferenceService;
import com.intellij.compiler.chainsSearch.MethodRefAndOccurrences;
import com.intellij.compiler.chainsSearch.ChainOpAndOccurrences;
import com.intellij.compiler.chainsSearch.MethodCall;
import com.intellij.compiler.chainsSearch.TypeCast;
import com.intellij.compiler.chainsSearch.context.ChainCompletionContext;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
@@ -35,9 +37,13 @@ public abstract class CompilerReferenceServiceEx extends CompilerReferenceServic
}
@NotNull
public abstract SortedSet<MethodRefAndOccurrences> findMethodReferenceOccurrences(@NotNull String rawReturnType,
@SignatureData.IteratorKind byte iteratorKind,
@NotNull ChainCompletionContext context)
public abstract SortedSet<ChainOpAndOccurrences<MethodCall>> findMethodReferenceOccurrences(@NotNull String rawReturnType,
@SignatureData.IteratorKind byte iteratorKind,
@NotNull ChainCompletionContext context)
throws ReferenceIndexUnavailableException;
@Nullable
public abstract ChainOpAndOccurrences<TypeCast> getMostUsedTypeCast(@NotNull String operandQName)
throws ReferenceIndexUnavailableException;
@Nullable

View File

@@ -21,7 +21,8 @@ import com.intellij.compiler.backwardRefs.view.CompilerReferenceHierarchyTestInf
import com.intellij.compiler.backwardRefs.view.DirtyScopeTestInfo;
import com.intellij.compiler.chainsSearch.ChainSearchMagicConstants;
import com.intellij.compiler.chainsSearch.MethodCall;
import com.intellij.compiler.chainsSearch.MethodRefAndOccurrences;
import com.intellij.compiler.chainsSearch.ChainOpAndOccurrences;
import com.intellij.compiler.chainsSearch.TypeCast;
import com.intellij.compiler.chainsSearch.context.ChainCompletionContext;
import com.intellij.compiler.server.BuildManager;
import com.intellij.compiler.server.BuildManagerListener;
@@ -60,8 +61,6 @@ import com.intellij.util.io.PersistentEnumeratorBase;
import com.intellij.util.messages.MessageBusConnection;
import gnu.trove.THashSet;
import gnu.trove.TIntHashSet;
import gnu.trove.TObjectIntHashMap;
import gnu.trove.TObjectIntProcedure;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -223,9 +222,9 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
@NotNull
@Override
public SortedSet<MethodRefAndOccurrences> findMethodReferenceOccurrences(@NotNull String rawReturnType,
@SignatureData.IteratorKind byte iteratorKind,
@NotNull ChainCompletionContext context) {
public SortedSet<ChainOpAndOccurrences<MethodCall>> findMethodReferenceOccurrences(@NotNull String rawReturnType,
@SignatureData.IteratorKind byte iteratorKind,
@NotNull ChainCompletionContext context) {
try {
myReadDataLock.lock();
try {
@@ -244,15 +243,15 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
.distinct()
.map(r -> {
int count = myReader.getOccurrenceCount(r);
return count <= 1 ? null : new MethodRefAndOccurrences(
return count <= 1 ? null : new ChainOpAndOccurrences<>(
new MethodCall((LightRef.JavaLightMethodRef)r, sd, context),
count);
}))
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(x -> x.getSignature(), Collectors.summarizingInt(x -> x.getOccurrenceCount())))
.collect(Collectors.groupingBy(x -> x.getOperation(), Collectors.summarizingInt(x -> x.getOccurrenceCount())))
.entrySet()
.stream()
.map(e -> new MethodRefAndOccurrences(e.getKey(), (int)e.getValue().getSum()))
.map(e -> new ChainOpAndOccurrences<>(e.getKey(), (int)e.getValue().getSum()))
.collect(Collectors.toCollection(TreeSet::new));
}
finally {
@@ -265,6 +264,31 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
}
}
@Nullable
@Override
public ChainOpAndOccurrences<TypeCast> getMostUsedTypeCast(@NotNull String operandQName)
throws ReferenceIndexUnavailableException {
try {
myReadDataLock.lock();
try {
if (myReader == null) throw new ReferenceIndexUnavailableException();
int nameId = getNameId(operandQName);
if (nameId == 0) return null;
LightRef.JavaLightClassRef target = new LightRef.JavaLightClassRef(nameId);
OccurrenceCounter<LightRef> typeCasts = myReader.getTypeCastOperands(target, null);
LightRef bestCast = typeCasts.getBest();
if (bestCast == null) return null;
return new ChainOpAndOccurrences<>(new TypeCast((LightRef.LightClassHierarchyElementDef)bestCast, target, this), typeCasts.getBestOccurrences());
}
finally {
myReadDataLock.unlock();
}
} catch (Exception e) {
onException(e, "best type cast search");
return null;
}
}
/**
* finds one best candidate to do a cast type before given method call (eg.: <code>((B) a).someMethod()</code>). Follows given formula:
*
@@ -282,25 +306,11 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
LightRef.LightClassHierarchyElementDef owner = method.getOwner();
TObjectIntHashMap<LightRef> typeCasts = myReader.getTypeCasts(owner, ids);
LightRef[] best = {null};
int[] bestFileCount = {0};
typeCasts.forEachEntry(new TObjectIntProcedure<LightRef>() {
@Override
public boolean execute(LightRef operandType, int matchedFileCount) {
if (ids.size() > probabilityThreshold * (ids.size() - matchedFileCount)) {
if (best[0] == null || bestFileCount[0] < matchedFileCount) {
best[0] = operandType;
bestFileCount[0] = matchedFileCount;
}
}
return true;
}
});
return (LightRef.LightClassHierarchyElementDef)best[0];
OccurrenceCounter<LightRef> bestTypeCast = myReader.getTypeCastOperands(owner, ids);
LightRef best = bestTypeCast.getBest();
return best != null && ids.size() > probabilityThreshold * (ids.size() - bestTypeCast.getBestOccurrences())
? (LightRef.LightClassHierarchyElementDef)best
: null;
}
finally {
myReadDataLock.unlock();

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.compiler.backwardRefs;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
class OccurrenceCounter<T> {
private final TObjectIntHashMap<T> myOccurrenceMap;
private T myBest;
private int myBestOccurrences;
OccurrenceCounter() {
myOccurrenceMap = new TObjectIntHashMap<>();
}
void add(@NotNull T element) {
int prevOccurrences = myOccurrenceMap.get(element);
if (prevOccurrences == 0) {
myOccurrenceMap.put(element, 1);
} else {
myOccurrenceMap.adjustValue(element, 1);
}
if (myBest == null) {
myBestOccurrences = 1;
myBest = element;
} else if (myBest.equals(element)) {
myBestOccurrences++;
} else {
myBestOccurrences = prevOccurrences + 1;
myBest = element;
}
}
@Nullable
T getBest() {
return myBest;
}
int getBestOccurrences() {
return myBestOccurrences;
}
}

View File

@@ -17,17 +17,17 @@ package com.intellij.compiler.chainsSearch;
import org.jetbrains.annotations.NotNull;
public class MethodRefAndOccurrences implements Comparable<MethodRefAndOccurrences> {
private final MethodCall mySignature;
public class ChainOpAndOccurrences<T extends RefChainOperation> implements Comparable<ChainOpAndOccurrences> {
private final T myOp;
private final int myOccurrences;
public MethodRefAndOccurrences(final MethodCall signature, final int occurrences) {
mySignature = signature;
public ChainOpAndOccurrences(final T op, final int occurrences) {
myOp = op;
myOccurrences = occurrences;
}
public MethodCall getSignature() {
return mySignature;
public T getOperation() {
return myOp;
}
public int getOccurrenceCount() {
@@ -35,16 +35,16 @@ public class MethodRefAndOccurrences implements Comparable<MethodRefAndOccurrenc
}
@Override
public int compareTo(@NotNull final MethodRefAndOccurrences that) {
public int compareTo(@NotNull final ChainOpAndOccurrences that) {
final int sub = -getOccurrenceCount() + that.getOccurrenceCount();
if (sub != 0) {
return sub;
}
return mySignature.hashCode() - that.mySignature.hashCode();
return myOp.hashCode() - that.myOp.hashCode();
}
@Override
public String toString() {
return getOccurrenceCount() + " for " + mySignature;
return getOccurrenceCount() + " for " + myOp;
}
}

View File

@@ -30,6 +30,7 @@ public interface ChainOperation {
myCastClass = castClass;
}
@NotNull
public PsiClass getCastClass() {
return myCastClass;
}

View File

@@ -41,12 +41,20 @@ public class ChainSearcher {
private static SearchInitializer createInitializer(ChainSearchTarget target,
CompilerReferenceServiceEx referenceServiceEx,
ChainCompletionContext context) {
SortedSet<MethodRefAndOccurrences> methods = Collections.emptySortedSet();
SortedSet<ChainOpAndOccurrences<? extends RefChainOperation>> operations = new TreeSet<>();
for (byte kind : target.getArrayKind()) {
SortedSet<MethodRefAndOccurrences> currentMethods = referenceServiceEx.findMethodReferenceOccurrences(target.getClassQName(), kind, context);
methods = methods == null ? currentMethods : unionSortedSet(currentMethods, methods);
SortedSet<ChainOpAndOccurrences<MethodCall>> methods = referenceServiceEx.findMethodReferenceOccurrences(target.getClassQName(), kind, context);
operations.addAll(methods);
}
return new SearchInitializer(methods, context);
if (operations.isEmpty()) {
ChainOpAndOccurrences<TypeCast> typeCast = referenceServiceEx.getMostUsedTypeCast(target.getClassQName());
if (typeCast != null) {
operations.add(typeCast);
}
}
return new SearchInitializer(operations, context);
}
@NotNull
@@ -68,22 +76,22 @@ public class ChainSearcher {
// otherwise try to find chain continuation
boolean updated = false;
SortedSet<MethodRefAndOccurrences> candidates = referenceServiceEx.findMethodReferenceOccurrences(head.getQualifierRawName(), SignatureData.ZERO_DIM, context);
SortedSet<ChainOpAndOccurrences<MethodCall>> candidates = referenceServiceEx.findMethodReferenceOccurrences(head.getQualifierRawName(), SignatureData.ZERO_DIM, context);
LightRef ref = head.getLightRef();
for (MethodRefAndOccurrences candidate : candidates) {
for (ChainOpAndOccurrences<MethodCall> candidate : candidates) {
if (candidate.getOccurrenceCount() * ChainSearchMagicConstants.FILTER_RATIO < currentChain.getChainWeight()) {
break;
}
MethodCall sign = candidate.getSignature();
MethodCall sign = candidate.getOperation();
if ((sign.isStatic() || !sign.getQualifierRawName().equals(context.getTarget().getClassQName())) &&
(!(ref instanceof LightRef.JavaLightMethodRef) ||
referenceServiceEx.mayHappen(candidate.getSignature().getLightRef(), ref, ChainSearchMagicConstants.METHOD_PROBABILITY_THRESHOLD))) {
referenceServiceEx.mayHappen(candidate.getOperation().getLightRef(), ref, ChainSearchMagicConstants.METHOD_PROBABILITY_THRESHOLD))) {
OperationChain
continuation = currentChain.continuationWithMethod(candidate.getSignature(), candidate.getOccurrenceCount(), context);
continuation = currentChain.continuationWithMethod(candidate.getOperation(), candidate.getOccurrenceCount(), context);
if (continuation != null) {
boolean stopChain =
candidate.getSignature().isStatic() || context.hasQualifier(context.resolvePsiClass(candidate.getSignature().getQualifierDef()));
candidate.getOperation().isStatic() || context.hasQualifier(context.resolvePsiClass(candidate.getOperation().getQualifierDef()));
if (stopChain) {
addChainIfNotPresent(continuation, result);
}
@@ -126,7 +134,7 @@ public class ChainSearcher {
List<OperationChain> result,
ChainCompletionContext context,
CompilerReferenceServiceEx referenceServiceEx) {
RefChainOperation signature = currentChain.getHeadMethodCall();
RefChainOperation signature = currentChain.getHead();
// type cast + introduced qualifier: it's too complex chain
if (currentChain.hasCast()) return;
if (!context.getTarget().getClassQName().equals(signature.getQualifierRawName())) {
@@ -148,6 +156,7 @@ public class ChainSearcher {
private static boolean addChainIfTerminal(OperationChain currentChain, List<OperationChain> result, int pathMaximalLength,
ChainCompletionContext context) {
RefChainOperation signature = currentChain.getHeadMethodCall();
if (signature == null) return false;
RefChainOperation head = currentChain.getHead();
if (((MethodCall)signature).isStatic() ||
context.hasQualifier(context.resolvePsiClass(head.getQualifierDef())) ||

View File

@@ -40,25 +40,35 @@ public class OperationChain {
private final PsiClass myQualifierClass;
@Nullable
public static OperationChain create(@NotNull MethodCall signature,
public static OperationChain create(@NotNull RefChainOperation operation,
int weight,
@NotNull ChainCompletionContext context) {
PsiClass qualifier = context.resolvePsiClass(signature.getQualifierDef());
if (qualifier == null || (!signature.isStatic() && InheritanceUtil.isInheritorOrSelf(context.getTarget().getTargetClass(), qualifier, true))) {
return null;
if (operation instanceof MethodCall) {
MethodCall signature = (MethodCall) operation;
PsiClass qualifier = context.resolvePsiClass(signature.getQualifierDef());
if (qualifier == null || (!signature.isStatic() && InheritanceUtil.isInheritorOrSelf(context.getTarget().getTargetClass(), qualifier, true))) {
return null;
}
PsiMethod[] methods = context.resolve(signature);
if (methods.length == 0) return null;
Set<PsiClass> classes = Arrays.stream(methods)
.flatMap(m -> Arrays.stream(m.getParameterList().getParameters()))
.map(p -> PsiUtil.resolveClassInType(p.getType()))
.collect(Collectors.toSet());
PsiClass contextClass = context.getTarget().getTargetClass();
if (classes.contains(contextClass)) {
return null;
}
classes.add(contextClass);
return new OperationChain(qualifier, new ChainOperation[] {new ChainOperation.MethodCall(methods)}, signature, signature, weight);
}
PsiMethod[] methods = context.resolve(signature);
if (methods.length == 0) return null;
Set<PsiClass> classes = Arrays.stream(methods)
.flatMap(m -> Arrays.stream(m.getParameterList().getParameters()))
.map(p -> PsiUtil.resolveClassInType(p.getType()))
.collect(Collectors.toSet());
PsiClass contextClass = context.getTarget().getTargetClass();
if (classes.contains(contextClass)) {
return null;
else {
TypeCast cast = (TypeCast)operation;
PsiClass operand = context.resolvePsiClass(cast.getLightRef());
PsiClass castType = context.resolvePsiClass(cast.getCastTypeRef());
if (operand == null || castType == null) return null;
return new OperationChain(operand, new ChainOperation[] {new ChainOperation.TypeCast(operand, castType)}, cast, null, weight);
}
classes.add(contextClass);
return new OperationChain(qualifier, new ChainOperation[] {new ChainOperation.MethodCall(methods)}, signature, signature, weight);
}
private OperationChain(@NotNull PsiClass qualifierClass,
@@ -77,7 +87,7 @@ public class OperationChain {
return Arrays.stream(myReverseOperations).anyMatch(op -> op instanceof ChainOperation.TypeCast);
}
@NotNull
@Nullable
public MethodCall getHeadMethodCall() {
return myHeadMethodCall;
}
@@ -118,20 +128,18 @@ public class OperationChain {
ChainOperation[] newReverseOperations = new ChainOperation[length() + 1];
System.arraycopy(myReverseOperations, 0, newReverseOperations, 0, myReverseOperations.length);
newReverseOperations[length()] = head.getPath()[0];
return new OperationChain(head.getQualifierClass(), newReverseOperations, head.getHead(), signature, Math.min(weight, getChainWeight()));
return new OperationChain(head.getQualifierClass(), newReverseOperations, head.getHead() , signature, Math.min(weight, getChainWeight()));
}
@Nullable
OperationChain continuationWithCast(@NotNull TypeCast cast,
@NotNull ChainCompletionContext context) {
PsiClass operand = context.resolvePsiClass(cast.getLightRef());
PsiClass castType = context.resolvePsiClass(cast.getCastTypeRef());
if (operand == null || castType == null) return null;
OperationChain head = create(cast, 0, context);
if (head == null) return null;
ChainOperation[] newReverseOperations = new ChainOperation[length() + 1];
System.arraycopy(myReverseOperations, 0, newReverseOperations, 0, myReverseOperations.length);
newReverseOperations[length()] = new ChainOperation.TypeCast(operand, castType);
return new OperationChain(operand, newReverseOperations, cast, myHeadMethodCall, getChainWeight());
newReverseOperations[length()] = head.getPath()[0];
return new OperationChain(head.getQualifierClass(), newReverseOperations, head.getHead(), myHeadMethodCall, getChainWeight());
}
@Override

View File

@@ -22,9 +22,9 @@ import java.util.*;
public class SearchInitializer {
private final ChainCompletionContext myContext;
private final LinkedList<OperationChain> myQueue;
private final LinkedHashMap<MethodCall, OperationChain> myChains;
private final LinkedHashMap<RefChainOperation, OperationChain> myChains;
public SearchInitializer(SortedSet<MethodRefAndOccurrences> indexValues,
public SearchInitializer(SortedSet<ChainOpAndOccurrences<? extends RefChainOperation>> indexValues,
ChainCompletionContext context) {
myContext = context;
int size = indexValues.size();
@@ -33,7 +33,7 @@ public class SearchInitializer {
myQueue = new LinkedList<>();
myChains = new LinkedHashMap<>(chains.size());
for (OperationChain chain : chains) {
MethodCall signature = (MethodCall)chain.getHead();
RefChainOperation signature = chain.getHead();
myQueue.add(chain);
myChains.put(signature, chain);
}
@@ -43,15 +43,15 @@ public class SearchInitializer {
return myQueue;
}
public LinkedHashMap<MethodCall, OperationChain> getChains() {
public LinkedHashMap<RefChainOperation, OperationChain> getChains() {
return myChains;
}
private void populateFrequentlyUsedMethod(SortedSet<MethodRefAndOccurrences> signatures,
private void populateFrequentlyUsedMethod(SortedSet<? extends ChainOpAndOccurrences> operations,
List<OperationChain> chains) {
int bestOccurrences = -1;
for (MethodRefAndOccurrences indexValue : signatures) {
OperationChain operationChain = OperationChain.create(indexValue.getSignature(), indexValue.getOccurrenceCount(), myContext);
for (ChainOpAndOccurrences indexValue : operations) {
OperationChain operationChain = OperationChain.create(indexValue.getOperation(), indexValue.getOccurrenceCount(), myContext);
if (operationChain != null) {
chains.add(operationChain);
int occurrences = indexValue.getOccurrenceCount();

View File

@@ -0,0 +1,13 @@
interface InspectionManager {
static InspectionManager getInstance() {
return null;
}
}
interface InspectionManagerEx extends InspectionManager {
}
class Test {
void m() {
InspectionManagerEx m1 = <caret>
}
}

View File

@@ -0,0 +1,22 @@
interface InspectionManager {
static InspectionManager getInstance() {
return null;
}
}
interface InspectionManagerEx extends InspectionManager {
}
class Test {
void m() {
InspectionManagerEx m1 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m2 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m3 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m4 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m5 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m6 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m7 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m8 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m9 = (InspectionManagerEx)InspectionManager.getInstance();
InspectionManagerEx m0 = (InspectionManagerEx)InspectionManager.getInstance();
}
}

View File

@@ -257,6 +257,11 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
assertEquals("(InspectionManagerEx)getInstance().createContext", element.toString());
}
public void testChainEndedWithCast() {
JavaRelevantChainLookupElement element = assertOneElement(doCompletion());
assertEquals("(InspectionManagerEx)getInstance", element.toString());
}
public void assertAdvisorLookupElementEquals(String lookupText,
int unreachableParametersCount,
int chainSize,