Smart completion: no instantiation of anonymous object with uninferred type arguments in code

This commit is contained in:
Valentin Kipyatkov
2015-09-03 21:33:02 +03:00
parent 40de063ebd
commit fe3e9341a1
12 changed files with 238 additions and 41 deletions
@@ -34,7 +34,9 @@ import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
import org.jetbrains.kotlin.idea.core.psiClassToDescriptor
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptor
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
@@ -43,11 +45,8 @@ import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.PossiblyBareType
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.utils.addIfNotNull
class TypeInstantiationItems(
@@ -64,7 +63,7 @@ class TypeInstantiationItems(
inheritanceSearchers: MutableCollection<InheritanceItemsSearcher>,
expectedInfos: Collection<ExpectedInfo>
) {
val expectedInfosGrouped: Map<JetType?, List<ExpectedInfo>> = expectedInfos.groupBy { it.fuzzyType?.type?.makeNotNullable() }
val expectedInfosGrouped: Map<FuzzyType?, List<ExpectedInfo>> = expectedInfos.groupBy { it.fuzzyType?.makeNotNullable() }
for ((type, infos) in expectedInfosGrouped) {
if (type == null) continue
val tail = mergeTails(infos.map { it.tail })
@@ -75,34 +74,34 @@ class TypeInstantiationItems(
private fun addTo(
items: MutableCollection<LookupElement>,
inheritanceSearchers: MutableCollection<InheritanceItemsSearcher>,
type: JetType,
fuzzyType: FuzzyType,
tail: Tail?
) {
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(type)) return // do not show "object: ..." for function types
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(fuzzyType.type)) return // do not show "object: ..." for function types
val classifier = type.getConstructor().getDeclarationDescriptor()
val classifier = fuzzyType.type.constructor.declarationDescriptor
if (classifier !is ClassDescriptor) return
addSamConstructorItem(items, classifier, tail)
val typeArgs = type.getArguments()
items.addIfNotNull(createTypeInstantiationItem(classifier, typeArgs, tail))
items.addIfNotNull(createTypeInstantiationItem(fuzzyType, tail))
if (!forOrdinaryCompletion && !KotlinBuiltIns.isAny(classifier)) { // do not search inheritors of Any
inheritanceSearchers.addInheritorSearcher(classifier, classifier, typeArgs, tail)
val typeArgs = fuzzyType.type.arguments
inheritanceSearchers.addInheritorSearcher(classifier, classifier, typeArgs, fuzzyType.freeParameters, tail)
val javaClassId = JavaToKotlinClassMap.INSTANCE.mapKotlinToJava(DescriptorUtils.getFqName(classifier))
if (javaClassId != null) {
val javaAnalog = resolutionFacade.moduleDescriptor.resolveTopLevelClass(javaClassId.asSingleFqName(), NoLookupLocation.FROM_IDE)
if (javaAnalog != null) {
inheritanceSearchers.addInheritorSearcher(javaAnalog, classifier, typeArgs, tail)
inheritanceSearchers.addInheritorSearcher(javaAnalog, classifier, typeArgs, fuzzyType.freeParameters, tail)
}
}
}
}
private fun MutableCollection<InheritanceItemsSearcher>.addInheritorSearcher(
descriptor: ClassDescriptor, kotlinClassDescriptor: ClassDescriptor, typeArgs: List<TypeProjection>, tail: Tail?
descriptor: ClassDescriptor, kotlinClassDescriptor: ClassDescriptor, typeArgs: List<TypeProjection>, freeParameters: Collection<TypeParameterDescriptor>, tail: Tail?
) {
val _declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(resolutionFacade.project, descriptor) ?: return
val declaration = if (_declaration is JetDeclaration)
@@ -115,14 +114,12 @@ class TypeInstantiationItems(
is JetClassOrObject -> LightClassUtil.getPsiClass(declaration) ?: return
else -> return
}
add(InheritanceSearcher(psiClass, kotlinClassDescriptor, typeArgs, tail))
add(InheritanceSearcher(psiClass, kotlinClassDescriptor, typeArgs, freeParameters, tail))
}
private fun createTypeInstantiationItem(
classifier: ClassDescriptor,
typeArgs: List<TypeProjection>,
tail: Tail?
): LookupElement? {
private fun createTypeInstantiationItem(fuzzyType: FuzzyType, tail: Tail?): LookupElement? {
val classifier = fuzzyType.type.constructor.declarationDescriptor as? ClassDescriptor ?: return null
var lookupElement = lookupElementFactory.createLookupElement(classifier, useReceiverTypes = false)
if (DescriptorUtils.isNonCompanionObject(classifier)) {
@@ -152,10 +149,17 @@ class TypeInstantiationItems(
val insertHandler: InsertHandler<LookupElement>
if (isAbstract) {
val typeArgs = fuzzyType.type.arguments
// drop "in" and "out" from type arguments - they cannot be used in constructor call
val typeArgsToUse = typeArgs.map { TypeProjectionImpl(Variance.INVARIANT, it.getType()) }
itemText += IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderTypeArguments(typeArgsToUse)
typeText += IdeDescriptorRenderers.SOURCE_CODE.renderTypeArguments(typeArgsToUse)
val typeArgsToUse = typeArgs.map { TypeProjectionImpl(Variance.INVARIANT, it.type) }
val allTypeArgsKnown = fuzzyType.freeParameters.isEmpty() || typeArgs.none { it.type.areTypeParametersUsedInside(fuzzyType.freeParameters) }
if (allTypeArgsKnown) {
itemText += IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderTypeArguments(typeArgsToUse)
}
else {
itemText += "<...>"
}
val constructorParenthesis = if (classifier.getKind() != ClassKind.INTERFACE) "()" else ""
itemText += constructorParenthesis
@@ -163,15 +167,29 @@ class TypeInstantiationItems(
lookupString = "object"
allLookupStrings = setOf(lookupString, lookupElement.getLookupString())
insertHandler = InsertHandler<LookupElement> { context, item ->
val editor = context.getEditor()
val startOffset = context.getStartOffset()
val text = "object: $typeText$constructorParenthesis {}"
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
editor.getCaretModel().moveToOffset(startOffset + text.length() - 1)
val startOffset = context.startOffset
shortenReferences(context, startOffset, startOffset + text.length())
val text1 = "object: $typeText"
val text2 = "$constructorParenthesis {}"
val text = if (allTypeArgsKnown)
text1 + IdeDescriptorRenderers.SOURCE_CODE.renderTypeArguments(typeArgsToUse) + text2
else
text1 + "<>" + text2
ImplementMembersHandler().invoke(context.getProject(), editor, context.getFile(), true)
context.document.replaceString(startOffset, context.tailOffset, text)
if (allTypeArgsKnown) {
context.editor.caretModel.moveToOffset(startOffset + text.length() - 1)
shortenReferences(context, startOffset, startOffset + text.length())
ImplementMembersHandler().invoke(context.project, context.editor, context.file, true)
}
else {
context.editor.caretModel.moveToOffset(startOffset + text1.length() + 1) // put caret into "<>"
shortenReferences(context, startOffset, startOffset + text.length())
}
}
lookupElement = lookupElement.suppressAutoInsertion()
lookupElement = lookupElement.assignSmartCompletionPriority(SmartCompletionItemPriority.ANONYMOUS_OBJECT)
@@ -245,6 +263,10 @@ class TypeInstantiationItems(
return InstantiationLookupElement(lookupElement).addTail(tail)
}
private fun JetType.areTypeParametersUsedInside(freeParameters: Collection<TypeParameterDescriptor>): Boolean {
return FuzzyType(this, freeParameters).freeParameters.isNotEmpty()
}
private fun addSamConstructorItem(collection: MutableCollection<LookupElement>, `class`: ClassDescriptor, tail: Tail?) {
if (`class`.getKind() == ClassKind.INTERFACE) {
val container = `class`.getContainingDeclaration()
@@ -264,13 +286,15 @@ class TypeInstantiationItems(
}
private inner class InheritanceSearcher(
val psiClass: PsiClass,
val classDescriptor: ClassDescriptor,
val typeArgs: List<TypeProjection>,
val tail: Tail?) : InheritanceItemsSearcher {
private val psiClass: PsiClass,
private val classDescriptor: ClassDescriptor,
private val typeArgs: List<TypeProjection>,
private val freeParameters: Collection<TypeParameterDescriptor>,
private val tail: Tail?) : InheritanceItemsSearcher {
private val baseHasTypeArgs = classDescriptor.typeConstructor.parameters.isNotEmpty()
private val expectedType = JetTypeImpl.create(Annotations.EMPTY, classDescriptor, false, typeArgs)
private val expectedFuzzyType = FuzzyType(expectedType, freeParameters)
override fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) {
val parameters = ClassInheritorsSearch.SearchParameters(psiClass, inheritorSearchScope, true, true, false, nameFilter)
@@ -280,18 +304,19 @@ class TypeInstantiationItems(
{ toFromOriginalFileMapper.toSyntheticFile(it) as JetClassOrObject? }) as? ClassDescriptor ?: continue
if (!visibilityFilter(descriptor)) continue
var inheritorFuzzyType = FuzzyType(descriptor.defaultType, descriptor.typeConstructor.parameters)
val hasTypeArgs = descriptor.getTypeConstructor().getParameters().isNotEmpty()
val resultingType = if (hasTypeArgs) {
val reconstructionResult = PossiblyBareType.bare(descriptor.getTypeConstructor(), false).reconstruct(expectedType)
reconstructionResult.getResultingType() ?: continue
if (hasTypeArgs || baseHasTypeArgs) {
val substitutor = inheritorFuzzyType.checkIsSubtypeOf(expectedFuzzyType) ?: continue
if (!substitutor.isEmpty) {
val inheritorTypeSubstituted = substitutor.substitute(inheritorFuzzyType.type, Variance.INVARIANT)!!
if (!ErrorUtils.containsUninferredParameter(inheritorTypeSubstituted)) {
inheritorFuzzyType = FuzzyType(inheritorTypeSubstituted, freeParameters + inheritorFuzzyType.freeParameters)
}
}
}
else {
descriptor.getDefaultType()
}
// check if derived type matches type arguments for base
if (baseHasTypeArgs && !resultingType.isSubtypeOf(expectedType)) continue
val lookupElement = createTypeInstantiationItem(descriptor, resultingType.getArguments(), tail) ?: continue
val lookupElement = createTypeInstantiationItem(inheritorFuzzyType, tail) ?: continue
consumer(lookupElement.assignSmartCompletionPriority(SmartCompletionItemPriority.INHERITOR_INSTANTIATION))
}
}
@@ -0,0 +1,9 @@
interface A<T>
fun <T> foo(a: A<T>){}
fun g() {
foo(<caret>)
}
// ELEMENT: object
@@ -0,0 +1,9 @@
interface A<T>
fun <T> foo(a: A<T>){}
fun g() {
foo(object: A<<caret>> {})
}
// ELEMENT: object
@@ -0,0 +1,9 @@
interface A<T>
fun <T> foo(a: A<T>){}
fun g() {
foo(<caret>)
}
// EXIST: { lookupString:"object", itemText:"object: A<...>{...}" }
@@ -0,0 +1,16 @@
interface I<T>
abstract class C1<T> : I<T>
abstract class C2 : I<String>
abstract class C3 : I<Int>
fun foo(i: I<Int>){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "object: I<Int>{...}" }
// EXIST: { itemText: "object: C1<Int>(){...}" }
// EXIST: { itemText: "object: C3(){...}" }
// NOTHING_ELSE
@@ -0,0 +1,17 @@
interface I<T>
abstract class C1<T> : I<T>
abstract class C2 : I<String>
abstract class C3 : I<Int>
fun <T> foo(i: I<T>){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "object: I<...>{...}" }
// EXIST: { itemText: "object: C1<...>(){...}" }
// EXIST: { itemText: "object: C2(){...}" }
// EXIST: { itemText: "object: C3(){...}" }
// NOTHING_ELSE
@@ -0,0 +1,18 @@
interface I<T1, T2>
abstract class C1<T> : I<T, String>
abstract class C2 : I<Int, String>
abstract class C3 : I<String, Int>
abstract class C4<T> : I<String, T>
fun <T> foo(i: I<T, String>){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "object: I<...>{...}" }
// EXIST: { itemText: "object: C1<...>(){...}" }
// EXIST: { itemText: "object: C2(){...}" }
// EXIST: { itemText: "object: C4<String>(){...}" }
// NOTHING_ELSE
@@ -0,0 +1,13 @@
interface I<T1>
abstract class C<T1, T2> : I<T1>
fun <T> foo(i: I<T>){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "object: I<...>{...}" }
// EXIST: { itemText: "object: C<...>(){...}" }
// NOTHING_ELSE
@@ -0,0 +1,13 @@
interface I<T1>
abstract class C<T1, T2> : I<T1>
fun foo(i: I<String>){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "object: I<String>{...}" }
// EXIST: { itemText: "object: C<...>(){...}" }
// NOTHING_ELSE
@@ -0,0 +1,20 @@
interface X
interface Y : X
interface Z
interface I<T>
abstract class C1<T> : I<T>
abstract class C2 : I<Y>
abstract class C3 : I<Z>
fun <T : X> foo(i: I<T>){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "object: I<...>{...}" }
// EXIST: { itemText: "object: C1<X>(){...}" }
// EXIST: { itemText: "object: C2(){...}" }
// NOTHING_ELSE
@@ -562,6 +562,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName);
}
@TestMetadata("UninferredTypeArgs.kt")
public void testUninferredTypeArgs() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/anonymousObject/UninferredTypeArgs.kt");
doTest(fileName);
}
@TestMetadata("WithPrefix.kt")
public void testWithPrefix() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/anonymousObject/WithPrefix.kt");
@@ -1167,6 +1173,42 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
public void testAllFilesPresentInInheritors() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/smart/inheritors"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("GenericClass1.kt")
public void testGenericClass1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/inheritors/GenericClass1.kt");
doTest(fileName);
}
@TestMetadata("GenericClass2.kt")
public void testGenericClass2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/inheritors/GenericClass2.kt");
doTest(fileName);
}
@TestMetadata("GenericClass3.kt")
public void testGenericClass3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/inheritors/GenericClass3.kt");
doTest(fileName);
}
@TestMetadata("GenericClass4.kt")
public void testGenericClass4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/inheritors/GenericClass4.kt");
doTest(fileName);
}
@TestMetadata("GenericClass5.kt")
public void testGenericClass5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/inheritors/GenericClass5.kt");
doTest(fileName);
}
@TestMetadata("GenericClass6.kt")
public void testGenericClass6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/inheritors/GenericClass6.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/smart/multipleArgsItem")
@@ -89,6 +89,12 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest(fileName);
}
@TestMetadata("AnonymousObjectUninferredTypeArgs.kt")
public void testAnonymousObjectUninferredTypeArgs() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/AnonymousObjectUninferredTypeArgs.kt");
doTest(fileName);
}
@TestMetadata("AutoCompleteAfterAs1.kt")
public void testAutoCompleteAfterAs1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/AutoCompleteAfterAs1.kt");