java smart completion: prefer all items with exactly expected type, not only classes (IDEA-169490)

This commit is contained in:
peter
2017-05-15 09:16:00 +02:00
parent 75f8c42d7f
commit 29672cb324
6 changed files with 59 additions and 26 deletions
@@ -298,9 +298,9 @@ public class JavaCompletionSorting {
private final CompletionParameters myParameters;
private final CompletionLocation myLocation;
public PreferDefaultTypeWeigher(ExpectedTypeInfo[] expectedTypes, CompletionParameters parameters) {
public PreferDefaultTypeWeigher(@NotNull ExpectedTypeInfo[] expectedTypes, CompletionParameters parameters) {
super("defaultType");
myExpectedTypes = expectedTypes == null ? null : ContainerUtil.map2Array(expectedTypes, ExpectedTypeInfo.class, info -> {
myExpectedTypes = ContainerUtil.map2Array(expectedTypes, ExpectedTypeInfo.class, info -> {
PsiType type = removeClassWildcard(info.getType());
PsiType defaultType = removeClassWildcard(info.getDefaultType());
if (type == info.getType() && defaultType == info.getDefaultType()) {
@@ -328,25 +328,16 @@ public class JavaCompletionSorting {
}
}
if (myExpectedTypes == null) return MyResult.normal;
PsiType itemType = JavaCompletionUtil.getLookupElementType(item);
if (itemType == null || !itemType.isValid()) return MyResult.normal;
if (object instanceof PsiClass) {
for (final ExpectedTypeInfo info : myExpectedTypes) {
if (TypeConversionUtil.erasure(info.getType().getDeepComponentType()).equals(TypeConversionUtil.erasure(itemType))) {
return AbstractExpectedTypeSkipper.skips(item, myLocation) ? MyResult.expectedNoSelect : MyResult.exactlyExpected;
}
}
if (isExactlyExpected(item, itemType)) {
return AbstractExpectedTypeSkipper.skips(item, myLocation) ? MyResult.expectedNoSelect : MyResult.exactlyExpected;
}
if (itemType == null) return MyResult.normal;
for (final ExpectedTypeInfo expectedInfo : myExpectedTypes) {
final PsiType defaultType = expectedInfo.getDefaultType();
final PsiType expectedType = expectedInfo.getType();
if (!expectedType.isValid()) {
return MyResult.normal;
}
if (defaultType != expectedType) {
if (defaultType.equals(itemType)) {
@@ -365,6 +356,27 @@ public class JavaCompletionSorting {
return MyResult.normal;
}
private boolean isExactlyExpected(@NotNull LookupElement item, @Nullable PsiType itemType) {
if (JavaCompletionUtil.SUPER_METHOD_PARAMETERS.get(item) != null) {
return true;
}
if (itemType == null || itemType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || hasUnboundTypeArguments(item)) {
return false;
}
return ContainerUtil.exists(myExpectedTypes, info -> box(info.getType().getDeepComponentType()).equals(box(itemType)));
}
private static boolean hasUnboundTypeArguments(@NotNull LookupElement item) {
JavaMethodCallElement call = item.as(JavaMethodCallElement.CLASS_CONDITION_KEY);
return call != null && !call.getInferenceSubstitutor().equals(PsiSubstitutor.EMPTY);
}
private PsiType box(PsiType expectedType) {
PsiClassType boxed = expectedType instanceof PsiPrimitiveType ? ((PsiPrimitiveType)expectedType).getBoxedType(myParameters.getPosition()) : null;
return boxed != null ? boxed : expectedType;
}
private static PsiType removeClassWildcard(PsiType type) {
if (type instanceof PsiClassType) {
final PsiClass psiClass = ((PsiClassType)type).resolve();
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiType;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -52,15 +53,13 @@ public class JavaMethodMergingContributor extends CompletionContributor {
return AutoCompletionDecision.SHOW_LOOKUP;
}
final PsiMethod method = (PsiMethod)o;
final JavaChainLookupElement chain = item.as(JavaChainLookupElement.CLASS_CONDITION_KEY);
final String name = method.getName() + "#" + (chain == null ? "" : chain.getQualifier().getLookupString());
String name = joinLookupStrings(item);
if (commonName != null && !commonName.equals(name)) {
return AutoCompletionDecision.SHOW_LOOKUP;
}
commonName = name;
allMethods.add(method);
allMethods.add((PsiMethod)o);
}
for (LookupElement item : items) {
@@ -73,6 +72,10 @@ public class JavaMethodMergingContributor extends CompletionContributor {
return super.handleAutoCompletionPossibility(context);
}
public static String joinLookupStrings(LookupElement item) {
return StreamEx.of(item.getAllLookupStrings()).sorted().joining("#");
}
public static LookupElement findBestOverload(LookupElement[] items) {
LookupElement best = items[0];
for (int i = 1; i < items.length; i++) {
@@ -0,0 +1,12 @@
class Map {
static class Builder {}
static Builder builder() {}
}
class BiMap extends Map {
static class Builder extends Map.Builder {}
static Builder builder() {}
}
class Usage {
Map.Builder b = bui<caret>
}
@@ -33,7 +33,7 @@ class SmartTypeCompletionOrderingTest extends CompletionSortingTestCase {
}
void testJComponentAdd() throws Throwable {
checkPreferredItems(0, "name", "b", "fooBean239", "foo", "this")
checkPreferredItems(0, "name", "b", "fooBean239", "foo")
}
void testJComponentAddNew() throws Throwable {
@@ -323,7 +323,7 @@ class SmartTypeCompletionOrderingTest extends CompletionSortingTestCase {
}
void testPreferLocalOverThis() {
checkPreferredItems 0, 'value', 'this', 'hashCode'
checkPreferredItems 0, 'value', 'hashCode', 'this'
}
void testGetLogger() {
@@ -339,7 +339,7 @@ class SmartTypeCompletionOrderingTest extends CompletionSortingTestCase {
}
void testPreferLocalWildcardClassOverObject() {
checkPreferredItems 0, 'type', 'Object.class'
checkPreferredItems 0, 'type', 'forName', 'forName', 'Object.class'
}
void testPreferStringsInStringConcatenation() {
@@ -361,6 +361,13 @@ class SmartTypeCompletionOrderingTest extends CompletionSortingTestCase {
assert lookup.items.size() == 2
}
void testPreferGlobalMembersReturningExpectedType() {
configureNoCompletion(getTestName(false) + ".java")
def items = myFixture.complete(CompletionType.SMART, 2)
assert LookupElementPresentation.renderElement(items[0]).itemText == 'Map.builder'
assert LookupElementPresentation.renderElement(items[1]).itemText == 'BiMap.builder'
}
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + BASE_PATH
@@ -830,7 +830,7 @@ public class SmartTypeCompletionTest extends LightFixtureCompletionTestCase {
}
public void testNoClassLiteral() throws Exception {
doActionTest();
assertStringItems("Object.class", "getClass", "forName", "forName");
assertStringItems("forName", "forName", "Object.class", "getClass");
}
public void testClassLiteralInAnno2() throws Throwable {
@@ -51,9 +51,8 @@ public class GrMethodMergingContributor extends CompletionContributor {
return AutoCompletionDecision.SHOW_LOOKUP;
}
final PsiMethod method = (PsiMethod)o;
final JavaChainLookupElement chain = item.as(JavaChainLookupElement.CLASS_CONDITION_KEY);
final String name = method.getName() + "#" + (chain == null ? "" : chain.getQualifier().getLookupString());
PsiMethod method = (PsiMethod)o;
String name = JavaMethodMergingContributor.joinLookupStrings(item);
if (commonName != null && !commonName.equals(name)) {
return AutoCompletionDecision.SHOW_LOOKUP;