method chain completion: suggest chains for arrays

This commit is contained in:
Dmitry Batkovich
2017-04-20 16:12:08 +03:00
parent 7c3a0031e3
commit 4d79d6635a
11 changed files with 151 additions and 51 deletions
@@ -20,6 +20,7 @@ import com.intellij.compiler.chainsSearch.OccurrencesAware;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.backwardRefs.LightRef;
import org.jetbrains.jps.backwardRefs.SignatureData;
import java.util.SortedSet;
@@ -33,7 +34,7 @@ public abstract class CompilerReferenceServiceEx extends CompilerReferenceServic
@NotNull
public abstract SortedSet<OccurrencesAware<MethodIncompleteSignature>> findMethodReferenceOccurrences(@NotNull String rawReturnType,
boolean allowIterators)
@SignatureData.IteratorKind byte iteratorKind)
throws ReferenceIndexUnavailableException;
public abstract boolean mayHappen(@NotNull LightRef qualifier, @NotNull LightRef base, int probabilityThreshold)
@@ -222,14 +222,14 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
@NotNull
@Override
public SortedSet<OccurrencesAware<MethodIncompleteSignature>> findMethodReferenceOccurrences(@NotNull String rawReturnType,
boolean allowIterators) {
@SignatureData.IteratorKind byte iteratorKind) {
try {
myReadDataLock.lock();
if (myReader == null) throw new ReferenceIndexUnavailableException();
try {
final int type = myReader.getNameEnumerator().tryEnumerate(rawReturnType);
if (type == 0) return Collections.emptySortedSet();
return Stream.of(new SignatureData(type, (byte)0, true), new SignatureData(type, (byte)0, false))
return Stream.of(new SignatureData(type, iteratorKind, true), new SignatureData(type, iteratorKind, false))
.flatMap(sd -> myReader.getMembersFor(sd)
.stream()
.filter(r -> r instanceof LightRef.JavaLightMethodRef)
@@ -16,10 +16,7 @@
package com.intellij.compiler.backwardRefs
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.jps.backwardRefs.LightRef
import org.jetbrains.jps.backwardRefs.SignatureData
@@ -60,7 +57,20 @@ class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef,
.filter { it.hasModifierProperty(PsiModifier.STATIC) == isStatic }
.filter {
val returnType = it.returnType
returnType is PsiClassType && returnType.resolve()?.qualifiedName == rawReturnType
when (signatureData.iteratorKind) {
SignatureData.ARRAY_ONE_DIM -> {
when (returnType) {
is PsiArrayType -> {
val componentType = returnType.componentType
componentType is PsiClassType && componentType.resolve()?.qualifiedName == rawReturnType
}
else -> false
}
}
SignatureData.ITERATOR_ONE_DIM -> false
SignatureData.ZERO_DIM -> returnType is PsiClassType && returnType.resolve()?.qualifiedName == rawReturnType
else -> throw IllegalStateException("kind is unsupported ${signatureData.iteratorKind}")
}
}
.sortedBy({ it.parameterList.parametersCount })
.toTypedArray()
@@ -24,6 +24,7 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.backwardRefs.SignatureData;
import java.util.*;
import java.util.stream.Collectors;
@@ -56,7 +57,7 @@ public class ChainsSearcher {
private static SearchInitializer createInitializer(TargetType target,
CompilerReferenceServiceEx compilerReferenceServiceEx,
ChainCompletionContext context) {
SortedSet<OccurrencesAware<MethodIncompleteSignature>> methods = compilerReferenceServiceEx.findMethodReferenceOccurrences(target.getClassQName(), false);
SortedSet<OccurrencesAware<MethodIncompleteSignature>> methods = compilerReferenceServiceEx.findMethodReferenceOccurrences(target.getClassQName(), target.getArrayKind());
return new SearchInitializer(methods, context);
}
@@ -96,7 +97,7 @@ public class ChainsSearcher {
continue;
}
String currentReturnType = headSignature.getOwner();
SortedSet<OccurrencesAware<MethodIncompleteSignature>> nextMethods = indexReader.findMethodReferenceOccurrences(currentReturnType, false);
SortedSet<OccurrencesAware<MethodIncompleteSignature>> nextMethods = indexReader.findMethodReferenceOccurrences(currentReturnType, SignatureData.ZERO_DIM);
MaxSizeTreeSet<OccurrencesAware<MethodIncompleteSignature>> currentSignatures =
new MaxSizeTreeSet<>(maxResultSize);
for (OccurrencesAware<MethodIncompleteSignature> indexValue : nextMethods) {
@@ -41,7 +41,7 @@ public class MethodsChain {
int weight,
@NotNull ChainCompletionContext context) {
PsiClass qualifier = context.resolveQualifierClass(signature);
if (qualifier == null || (!signature.isStatic() && isInheritorOrSelf(context.getTarget().getPsiType(), qualifier))) {
if (qualifier == null || (!signature.isStatic() && InheritanceUtil.isInheritorOrSelf(context.getTarget().getTargetClass(), qualifier, true))) {
return null;
}
PsiMethod[] methods = context.resolve(signature);
@@ -166,9 +166,4 @@ public class MethodsChain {
Set<PsiMethod> deepestSupers1 = methodSet1.stream().flatMap(m -> Arrays.stream(m.findDeepestSuperMethods())).collect(Collectors.toSet());
return methodSet2.stream().flatMap(m -> Arrays.stream(m.findDeepestSuperMethods())).anyMatch(deepestSupers1::contains);
}
private static boolean isInheritorOrSelf(@NotNull PsiType type, @NotNull PsiClass superCandidate) {
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
return aClass != null && InheritanceUtil.isInheritorOrSelf(aClass, superCandidate, true);
}
}
@@ -165,4 +165,8 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
private static ElementPattern<PsiElement> patternForMethodCallParameter() {
return psiElement().withSuperParent(3, PsiMethodCallExpressionImpl.class);
}
private static boolean suggestIterators(@NotNull CompletionParameters parameters) {
return parameters.getInvocationCount() > 1;
}
}
@@ -16,23 +16,24 @@
package com.intellij.compiler.chainsSearch.context;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.backwardRefs.SignatureData;
/**
* @author Dmitry Batkovich
*/
public class TargetType {
private final String myClassQName;
private final boolean myArray;
private final byte myArrayKind;
private final PsiType myPsiType;
public TargetType(String classQName,
boolean isArray,
byte arrayKind,
PsiType targetType) {
myClassQName = classQName;
myArray = isArray;
myArrayKind = arrayKind;
myPsiType = targetType;
}
@@ -40,22 +41,15 @@ public class TargetType {
return myClassQName;
}
public boolean isArray() {
return myArray;
}
public PsiType getPsiType() {
return myPsiType;
@SignatureData.IteratorKind
public byte getArrayKind() {
return myArrayKind;
}
public PsiClass getTargetClass() {
return PsiUtil.resolveClassInType(myPsiType);
}
public boolean isAssignableFrom(PsiClass psiClass) {
return myPsiType.isAssignableFrom(JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory().createType(psiClass));
}
@Nullable
public static TargetType create(PsiType type) {
if (type instanceof PsiArrayType) {
@@ -69,23 +63,29 @@ public class TargetType {
@Nullable
private static TargetType create(PsiArrayType arrayType) {
PsiType currentComponentType = arrayType.getComponentType();
while (currentComponentType instanceof PsiArrayType) {
currentComponentType = ((PsiArrayType)currentComponentType).getComponentType();
}
if (!(currentComponentType instanceof PsiClassType)) {
return null;
}
String targetQName = arrayType.getCanonicalText();
return new TargetType(targetQName, true, arrayType);
// only 1-dim arrays accepted
PsiType componentType = arrayType.getComponentType();
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(componentType);
if (aClass == null) return null;
String targetQName = aClass.getQualifiedName();
if (targetQName == null) return null;
return new TargetType(targetQName, SignatureData.ARRAY_ONE_DIM, arrayType);
}
@Nullable
private static TargetType create(PsiClassType classType) {
PsiClassType.ClassResolveResult resolvedGenerics = classType.resolveGenerics();
PsiClass resolvedClass = resolvedGenerics.getElement();
if (resolvedClass == null) {
return null;
PsiClass resolvedClass = PsiUtil.resolveClassInClassTypeOnly(classType);
byte iteratorKind = SignatureData.ZERO_DIM;
if (resolvedClass == null) return null;
String iteratorClass = isIterator(resolvedClass);
if (iteratorClass != null) {
PsiClassType streamType = (PsiClassType)PsiUtil.substituteTypeParameter(classType, iteratorClass, 0, false);
if (streamType == null) return null;
PsiType[] parameters = streamType.getParameters();
if (parameters.length != 1 || !(parameters[0] instanceof PsiClassType)) return null;
resolvedClass = PsiUtil.resolveClassInClassTypeOnly(parameters[0]);
if (resolvedClass == null) return null;
iteratorKind = SignatureData.ITERATOR_ONE_DIM;
}
String classQName = resolvedClass.getQualifiedName();
if (classQName == null) {
@@ -94,6 +94,19 @@ public class TargetType {
if (resolvedClass.hasTypeParameters()) {
return null;
}
return new TargetType(classQName, false, classType);
return new TargetType(classQName, iteratorKind, classType);
}
private static String isIterator(PsiClass resolvedClass) {
if (InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_LANG_ITERABLE)) {
return CommonClassNames.JAVA_LANG_ITERABLE;
}
if (InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_UTIL_ITERATOR)) {
return CommonClassNames.JAVA_UTIL_ITERATOR;
}
if (InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_UTIL_STREAM_STREAM)) {
return CommonClassNames.JAVA_UTIL_STREAM_STREAM;
}
return null;
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2000-2014 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.
*/
import java.jang.String;
class PsiMethod {
}
interface PsiClass {
PsiMethod[] getMethods();
}
public class TestCompletion {
PsiClass c;
public void method() {
PsiMethod[] m = <caret>
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2014 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.
*/
public class TestIndex {
public void statMethod(PsiClass c) {
c.getMethods();
c.getMethods();
c.getMethods();
c.getMethods();
c.getMethods();
c.getMethods();
c.getMethods();
c.getMethods();
}
}
class PsiMethod {
}
interface PsiClass {
PsiMethod[] getMethods();
}
@@ -99,7 +99,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
assertAdvisorLookupElementEquals("getInstance().findFile().findElementAt", 0, 5, 3, 0, assertOneElement(doCompletion()));
}
public void _testChainsWithIndependentCallings() {
public void testChainsWithIndependentCallings() {
assertOneElement(doCompletion());
}
@@ -210,6 +210,11 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
doTestRendering();
}
public void testArray() {
WeightableChainLookupElement element = assertOneElement(doCompletion());
assertEquals("c.getMethods", element.getLookupString());
}
public void assertAdvisorLookupElementEquals(String lookupText,
int unreachableParametersCount,
int lastMethodWeight,
@@ -15,19 +15,23 @@
*/
package org.jetbrains.jps.backwardRefs;
import org.intellij.lang.annotations.MagicConstant;
public class SignatureData {
public static final byte ZERO_DIM = 0;
public static final byte ARRAY_ONE_DIM = 1;
// represents java's Iterator, Iterable and BaseStream
public static final byte ITERATOR_ONE_DIM = 2;
@MagicConstant(intValues = {ZERO_DIM, ARRAY_ONE_DIM, ITERATOR_ONE_DIM})
public @interface IteratorKind {}
private final int myRawReturnType;
private final byte myArrayDimension;
private final byte myArrayKind;
private final boolean myStatic;
public SignatureData(int rawReturnType, byte arrayDimension, boolean isStatic) {
public SignatureData(int rawReturnType, byte arrayKind, boolean isStatic) {
myRawReturnType = rawReturnType;
myArrayDimension = arrayDimension;
myArrayKind = arrayKind;
myStatic = isStatic;
}
@@ -36,7 +40,7 @@ public class SignatureData {
}
public byte getIteratorKind() {
return myArrayDimension;
return myArrayKind;
}
public boolean isStatic() {
@@ -51,7 +55,7 @@ public class SignatureData {
SignatureData data = (SignatureData)o;
if (myRawReturnType != data.myRawReturnType) return false;
if (myArrayDimension != data.myArrayDimension) return false;
if (myArrayKind != data.myArrayKind) return false;
if (myStatic != data.myStatic) return false;
return true;
@@ -60,7 +64,7 @@ public class SignatureData {
@Override
public int hashCode() {
int result = myRawReturnType;
result = 31 * result + myArrayDimension;
result = 31 * result + myArrayKind;
result = 31 * result + (myStatic ? 1 : 0);
return result;
}