From 134496d1820d5c318cb608be146d7e11bfad6748 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 21 Apr 2014 22:14:20 +0400 Subject: [PATCH] Smart completion: special items with "!!" and "?:" for nullable members --- .../codeInsight/lookup/annotations.xml | 3 + .../plugin/codeInsight/ShortenReferences.kt | 2 +- ...rtHandler.kt => WithTailInsertHandlers.kt} | 34 +++++-- .../plugin/completion/smart/LambdaItems.kt | 4 +- .../completion/smart/SmartCompletion.kt | 17 ++-- .../plugin/completion/smart/StaticMembers.kt | 27 ++++-- .../jet/plugin/completion/smart/ThisItems.kt | 23 +++-- .../smart/TypeInstantiationItems.kt | 2 +- .../jet/plugin/completion/smart/Utils.kt | 88 ++++++++++++++++--- .../handlers/smart/NullableValue1.kt | 7 ++ .../handlers/smart/NullableValue1.kt.after | 7 ++ .../handlers/smart/NullableValue2.kt | 9 ++ .../handlers/smart/NullableValue2.kt.after | 9 ++ .../handlers/smart/NullableValue3.kt | 11 +++ .../handlers/smart/NullableValue3.kt.after | 11 +++ .../smart/AutoNotNullTypeWithQualifier.kt | 4 +- .../completion/smart/ClassObjectMembers.kt | 4 +- idea/testData/completion/smart/EmptyPrefix.kt | 4 +- .../testData/completion/smart/NullableThis.kt | 6 ++ .../JvmSmartCompletionTestGenerated.java | 5 ++ .../AbstractSmartCompletionHandlerTest.kt | 4 +- .../handlers/CompletionHandlerTestBase.kt | 58 ++++++------ .../SmartCompletionHandlerTestGenerated.java | 15 ++++ 23 files changed, 279 insertions(+), 75 deletions(-) rename idea/src/org/jetbrains/jet/plugin/completion/handlers/{WithTailInsertHandler.kt => WithTailInsertHandlers.kt} (63%) create mode 100644 idea/testData/completion/handlers/smart/NullableValue1.kt create mode 100644 idea/testData/completion/handlers/smart/NullableValue1.kt.after create mode 100644 idea/testData/completion/handlers/smart/NullableValue2.kt create mode 100644 idea/testData/completion/handlers/smart/NullableValue2.kt.after create mode 100644 idea/testData/completion/handlers/smart/NullableValue3.kt create mode 100644 idea/testData/completion/handlers/smart/NullableValue3.kt.after create mode 100644 idea/testData/completion/smart/NullableThis.kt diff --git a/annotations/com/intellij/codeInsight/lookup/annotations.xml b/annotations/com/intellij/codeInsight/lookup/annotations.xml index b60cd8c9298..9c0946b6ea6 100644 --- a/annotations/com/intellij/codeInsight/lookup/annotations.xml +++ b/annotations/com/intellij/codeInsight/lookup/annotations.xml @@ -83,6 +83,9 @@ name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String, boolean)'> + + + diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt b/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt index 7dfd124f59e..a05f44778dc 100644 --- a/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt @@ -61,7 +61,7 @@ public object ShortenReferences { } private fun process(elements: Iterable, elementFilter: (PsiElement) -> FilterResult) { - for ((file, fileElements) in elements.groupBy { element -> element.getContainingFile() as JetFile }) { + for ((file, fileElements) in elements.groupBy { element -> element.getContainingJetFile() }) { // first resolve all qualified references - optimization val referenceToContext = JetFileReferencesResolver.resolve(file, fileElements, visitShortNames = false) diff --git a/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandler.kt b/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandlers.kt similarity index 63% rename from idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandler.kt rename to idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandlers.kt index bfa508bf7ca..0c5fe2f0ebb 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandlers.kt @@ -2,11 +2,14 @@ package org.jetbrains.jet.plugin.completion.handlers import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.openapi.editor.* import com.intellij.openapi.editor.event.* import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager -class WithTailInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : InsertHandler { +abstract class WithTailInsertHandlerBase : InsertHandler { + protected abstract fun insertTail(document: Document, offset: Int): Int + override fun handleInsert(context: InsertionContext, item: LookupElement) { val document = context.getDocument() val caretModel = context.getEditor().getCaretModel() @@ -37,21 +40,34 @@ class WithTailInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : Inser } val moveCaret = caretModel.getOffset() == maxChangeOffset + val endTailOffset = insertTail(document, maxChangeOffset) + if (moveCaret) { + caretModel.moveToOffset(endTailOffset) + } + } +} +class WithTailCharInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : WithTailInsertHandlerBase() { + override fun insertTail(document: Document, offset: Int): Int { fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getText(TextRange(offset, offset + 1))[0] == c - if (isCharAt(maxChangeOffset, tailChar)) { - document.deleteString(maxChangeOffset, maxChangeOffset + 1) + if (isCharAt(offset, tailChar)) { + document.deleteString(offset, offset + 1) - if (spaceAfter && isCharAt(maxChangeOffset, ' ')) { - document.deleteString(maxChangeOffset, maxChangeOffset + 1) + if (spaceAfter && isCharAt(offset, ' ')) { + document.deleteString(offset, offset + 1) } } val textToInsert = if (spaceAfter) tailChar + " " else tailChar.toString() - document.insertString(maxChangeOffset, textToInsert) - if (moveCaret) { - caretModel.moveToOffset(maxChangeOffset + textToInsert.length) - } + document.insertString(offset, textToInsert) + return offset + textToInsert.length + } +} + +class WithTailStringInsertHandler(val tail: String) : WithTailInsertHandlerBase() { + override fun insertTail(document: Document, offset: Int): Int { + document.insertString(offset, tail) + return offset + tail.length } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt index fbbbb546e06..d8fe602681a 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt @@ -29,7 +29,7 @@ class LambdaItems(val project: Project) { val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1 if (offerNoParametersLambda) { val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false) - collection.add(addTailToLookupElement(lookupElement, functionExpectedInfos)) + collection.add(lookupElement.addTail(functionExpectedInfos)) } if (singleSignatureLength != 0) { @@ -44,7 +44,7 @@ class LambdaItems(val project: Project) { val lookupElement = LookupElementBuilder.create(lookupString) .withInsertHandler(LambdaInsertHandler(functionType, useExplicitTypes)) .suppressAutoInsertion() - collection.add(addTailToLookupElement(lookupElement, functionExpectedInfos.filter { it.`type` == functionType })) + collection.add(lookupElement.addTail(functionExpectedInfos.filter { it.`type` == functionType })) } } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt index ba0ea1a9bde..dc64ee202e8 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt @@ -55,13 +55,16 @@ class SmartCompletion(val expression: JetSimpleNameExpression, for (descriptor in referenceVariants) { if (itemsToSkip.contains(descriptor)) continue - val matchedExpectedInfos = expectedInfos.filter { expectedInfo -> - typesWithAutoCasts(descriptor).any { it.isSubtypeOf(expectedInfo.`type`) } - } - if (matchedExpectedInfos.isNotEmpty()) { - val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) - result.add(addTailToLookupElement(lookupElement, matchedExpectedInfos)) + val types = typesWithAutoCasts(descriptor) + val nonNullTypes = types.map { TypeUtils.makeNotNullable(it) } + val classifier = { (expectedInfo: ExpectedInfo) -> + when { + types.any { it.isSubtypeOf(expectedInfo.`type`) } -> ExpectedInfoClassification.MATCHES + nonNullTypes.any { it.isSubtypeOf(expectedInfo.`type`) } -> ExpectedInfoClassification.MAKE_NOT_NULLABLE + else -> ExpectedInfoClassification.NOT_MATCHES + } } + result.addLookupElements(expectedInfos, classifier, { DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) }) if (receiver == null) { toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) } @@ -129,7 +132,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, } } - return addTailToLookupElement(lookupElement, matchedExpectedInfos) + return lookupElement.addTail(matchedExpectedInfos) } if (descriptor is SimpleFunctionDescriptor) { diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt index 8931f07dd1f..89109794e3d 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt @@ -21,6 +21,7 @@ import com.intellij.codeInsight.completion.InsertionContext import org.jetbrains.jet.plugin.project.ResolveSessionForBodies import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.psi.JetExpression +import java.util.ArrayList // adds java static members, enum members and members from class object class StaticMembers(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) { @@ -45,15 +46,27 @@ class StaticMembers(val bindingContext: BindingContext, val resolveSession: Reso fun processMember(descriptor: DeclarationDescriptor) { if (descriptor is DeclarationDescriptorWithVisibility && !Visibilities.isVisible(descriptor, scope.getContainingDeclaration())) return - val matchedExpectedInfos = expectedInfos.filter { - expectedInfo -> - descriptor is CallableDescriptor && descriptor.getReturnType()?.let { it.isSubtypeOf(expectedInfo.`type`) } ?: false - || DescriptorUtils.isEnumEntry(descriptor) /* we do not need to check type of enum entry because it's taken from proper enum */ + val classifier: (ExpectedInfo) -> ExpectedInfoClassification + if (descriptor is CallableDescriptor) { + val returnType = descriptor.getReturnType() + if (returnType == null) return + classifier = { + expectedInfo -> + when { + returnType.isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MATCHES + returnType.isNullable() && TypeUtils.makeNotNullable(returnType).isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MAKE_NOT_NULLABLE + else -> ExpectedInfoClassification.NOT_MATCHES + } + } + } + else if (DescriptorUtils.isEnumEntry(descriptor)) { + classifier = { ExpectedInfoClassification.MATCHES } /* we do not need to check type of enum entry because it's taken from proper enum */ + } + else{ + return } - if (matchedExpectedInfos.isEmpty()) return - val lookupElement = createLookupElement(descriptor, classDescriptor) - collection.add(addTailToLookupElement(lookupElement, matchedExpectedInfos)) + collection.addLookupElements(expectedInfos, classifier, { createLookupElement(descriptor, classDescriptor) }) } if (classDescriptor is JavaClassDescriptor) { diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt index 5659ef587a1..c63b4c200e5 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt @@ -17,6 +17,7 @@ import org.jetbrains.jet.lang.psi.JetValueArgumentList import org.jetbrains.jet.lang.psi.JetCallExpression import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.lang.types.TypeUtils class ThisItems(val bindingContext: BindingContext) { public fun addToCollection(collection: MutableCollection, context: JetExpression, expectedInfos: Collection) { @@ -27,15 +28,21 @@ class ThisItems(val bindingContext: BindingContext) { for (i in 0..receivers.size - 1) { val receiver = receivers[i] val thisType = receiver.getType() - val matchedExpectedInfos = expectedInfos.filter { thisType.isSubtypeOf(it.`type`) } - if (matchedExpectedInfos.notEmpty) { - //TODO: use this code when KT-4258 fixed - //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue) - val qualifier = if (i == 0) null else thisQualifierName(receiver) ?: continue - val expressionText = if (qualifier == null) "this" else "this@" + qualifier - val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType)) - collection.add(addTailToLookupElement(lookupElement, matchedExpectedInfos)) + val classifier = { (expectedInfo: ExpectedInfo) -> + when { + thisType.isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MATCHES + thisType.isNullable() && TypeUtils.makeNotNullable(thisType).isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MAKE_NOT_NULLABLE + else -> ExpectedInfoClassification.NOT_MATCHES + } } + fun lookupElementFactory(): LookupElement? { + //TODO: use this code when KT-4258 fixed + //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: return null) + val qualifier = if (i == 0) null else (thisQualifierName(receiver) ?: return null) + val expressionText = if (qualifier == null) "this" else "this@" + qualifier + return LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType)) + } + collection.addLookupElements(expectedInfos, classifier, ::lookupElementFactory) } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt index 797a136dad4..8d49f6b4c83 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt @@ -101,6 +101,6 @@ class TypeInstantiationItems(val bindingContext: BindingContext, val resolveSess } } - collection.add(addTailToLookupElement(lookupElement, tail)) + collection.add(lookupElement.addTail(tail)) } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt index b9d9bbfa3bb..40d3dabf5c2 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt @@ -5,17 +5,18 @@ import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.completion.InsertionContext import com.intellij.psi.PsiDocumentManager import org.jetbrains.jet.lang.psi.JetFile -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.jet.lang.psi.JetElement import org.jetbrains.jet.plugin.codeInsight.ShortenReferences import java.util.HashSet import com.intellij.codeInsight.lookup.LookupElementDecorator -import org.jetbrains.jet.plugin.completion.handlers.WithTailInsertHandler +import org.jetbrains.jet.plugin.completion.handlers.WithTailCharInsertHandler import com.intellij.codeInsight.lookup.AutoCompletionPolicy import org.jetbrains.jet.lang.descriptors.FunctionDescriptor import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.lang.types.checker.JetTypeChecker +import com.intellij.codeInsight.lookup.LookupElementPresentation +import org.jetbrains.jet.plugin.completion.handlers.WithTailStringInsertHandler +import java.util.ArrayList class ArtificialElementInsertHandler( val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler{ @@ -42,29 +43,94 @@ fun mergeTails(tails: Collection): Tail? { return if (HashSet(tails).size == 1) tails.first() else null } -fun addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { +fun LookupElement.addTail(tail: Tail?): LookupElement { return when (tail) { - null -> lookupElement + null -> this - Tail.COMMA -> object: LookupElementDecorator(lookupElement) { + Tail.COMMA -> object: LookupElementDecorator(this) { override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement) + WithTailCharInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, getDelegate()) } } - Tail.PARENTHESIS -> object: LookupElementDecorator(lookupElement) { + Tail.PARENTHESIS -> object: LookupElementDecorator(this) { override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler(')', false).handleInsert(context, lookupElement) + WithTailCharInsertHandler(')', false).handleInsert(context, getDelegate()) } } } } -fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedInfos: Collection): LookupElement - = addTailToLookupElement(lookupElement, mergeTails(matchedExpectedInfos.map { it.tail })) +fun LookupElement.addTail(matchedExpectedInfos: Collection): LookupElement + = addTail(mergeTails(matchedExpectedInfos.map { it.tail })) fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this) +enum class ExpectedInfoClassification { + MATCHES + MAKE_NOT_NULLABLE + NOT_MATCHES +} + +fun MutableCollection.addLookupElements(expectedInfos: Collection, + infoClassifier: (ExpectedInfo) -> ExpectedInfoClassification, + lookupElementFactory: () -> LookupElement?) { + val matchedInfos = ArrayList() + val matchedInfosNotNullable = ArrayList() + for (info in expectedInfos) { + when (infoClassifier(info)) { + ExpectedInfoClassification.MATCHES -> matchedInfos.add(info) + ExpectedInfoClassification.MAKE_NOT_NULLABLE -> matchedInfosNotNullable.add(info) + } + } + + if (matchedInfos.isNotEmpty()) { + val lookupElement = lookupElementFactory() + if (lookupElement != null) { + add(lookupElement.addTail(matchedInfos)) + } + } + else if (matchedInfosNotNullable.isNotEmpty()) { + addLookupElementsForNullable(lookupElementFactory, matchedInfosNotNullable) + } +} + +fun MutableCollection.addLookupElementsForNullable(factory: () -> LookupElement?, matchedInfos: Collection) { + run { + var lookupElement = factory() + if (lookupElement != null) { + lookupElement = object: LookupElementDecorator(lookupElement!!) { + override fun renderElement(presentation: LookupElementPresentation) { + super.renderElement(presentation) + presentation.setItemText("!! " + presentation.getItemText()) + } + override fun handleInsert(context: InsertionContext) { + WithTailStringInsertHandler("!!").handleInsert(context, getDelegate()) + } + } + lookupElement = lookupElement!!.suppressAutoInsertion() + add(lookupElement!!.addTail(matchedInfos)) + } + } + + run { + var lookupElement = factory() + if (lookupElement != null) { + lookupElement = object: LookupElementDecorator(lookupElement!!) { + override fun renderElement(presentation: LookupElementPresentation) { + super.renderElement(presentation) + presentation.setItemText("?: " + presentation.getItemText()) + } + override fun handleInsert(context: InsertionContext) { + WithTailStringInsertHandler(" ?: ").handleInsert(context, getDelegate()) //TODO: code style + } + } + lookupElement = lookupElement!!.suppressAutoInsertion() + add(lookupElement!!.addTail(matchedInfos)) + } + } +} + fun functionType(function: FunctionDescriptor): JetType? { return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(), null, diff --git a/idea/testData/completion/handlers/smart/NullableValue1.kt b/idea/testData/completion/handlers/smart/NullableValue1.kt new file mode 100644 index 00000000000..5aa470b0e73 --- /dev/null +++ b/idea/testData/completion/handlers/smart/NullableValue1.kt @@ -0,0 +1,7 @@ +fun foo(s: String, c: Char){ } + +fun bar(sss: String?) { + foo() +} + +// ELEMENT_TEXT: "!! sss" diff --git a/idea/testData/completion/handlers/smart/NullableValue1.kt.after b/idea/testData/completion/handlers/smart/NullableValue1.kt.after new file mode 100644 index 00000000000..7c41567d43e --- /dev/null +++ b/idea/testData/completion/handlers/smart/NullableValue1.kt.after @@ -0,0 +1,7 @@ +fun foo(s: String, c: Char){ } + +fun bar(sss: String?) { + foo(sss!!, ) +} + +// ELEMENT_TEXT: "!! sss" diff --git a/idea/testData/completion/handlers/smart/NullableValue2.kt b/idea/testData/completion/handlers/smart/NullableValue2.kt new file mode 100644 index 00000000000..2256819947a --- /dev/null +++ b/idea/testData/completion/handlers/smart/NullableValue2.kt @@ -0,0 +1,9 @@ +fun foo(s: String){ } + +fun getString(i: Int): String?{} + +fun bar() { + foo() +} + +// ELEMENT_TEXT: "?: getString(i: Int)" diff --git a/idea/testData/completion/handlers/smart/NullableValue2.kt.after b/idea/testData/completion/handlers/smart/NullableValue2.kt.after new file mode 100644 index 00000000000..0d28b7437ff --- /dev/null +++ b/idea/testData/completion/handlers/smart/NullableValue2.kt.after @@ -0,0 +1,9 @@ +fun foo(s: String){ } + +fun getString(i: Int): String?{} + +fun bar() { + foo(getString() ?: ) +} + +// ELEMENT_TEXT: "?: getString(i: Int)" diff --git a/idea/testData/completion/handlers/smart/NullableValue3.kt b/idea/testData/completion/handlers/smart/NullableValue3.kt new file mode 100644 index 00000000000..c1604bc16d1 --- /dev/null +++ b/idea/testData/completion/handlers/smart/NullableValue3.kt @@ -0,0 +1,11 @@ +class K { + class object { + fun bar(): K? = K() + } +} + +fun foo(){ + val k : K = +} + +// ELEMENT_TEXT: "!! K.bar()" diff --git a/idea/testData/completion/handlers/smart/NullableValue3.kt.after b/idea/testData/completion/handlers/smart/NullableValue3.kt.after new file mode 100644 index 00000000000..274bd34fb0c --- /dev/null +++ b/idea/testData/completion/handlers/smart/NullableValue3.kt.after @@ -0,0 +1,11 @@ +class K { + class object { + fun bar(): K? = K() + } +} + +fun foo(){ + val k : K = K.bar()!! +} + +// ELEMENT_TEXT: "!! K.bar()" diff --git a/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt b/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt index 18781eed32b..674679b98f7 100644 --- a/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt +++ b/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt @@ -7,4 +7,6 @@ class Foo(val prop1 : String?, val prop2 : String?){ } // EXIST: prop1 -// ABSENT: prop2 +// ABSENT: { lookupString:"prop2", itemText:"prop2" } +// EXIST: { lookupString:"prop2", itemText:"!! prop2" } +// EXIST: { lookupString:"prop2", itemText:"?: prop2" } diff --git a/idea/testData/completion/smart/ClassObjectMembers.kt b/idea/testData/completion/smart/ClassObjectMembers.kt index 6c4b0b217f3..a511fe34741 100644 --- a/idea/testData/completion/smart/ClassObjectMembers.kt +++ b/idea/testData/completion/smart/ClassObjectMembers.kt @@ -17,5 +17,7 @@ fun foo(){ // EXIST: { lookupString:"K.foo", itemText:"K.foo", tailText:" (sample)", typeText:"K" } // EXIST: { lookupString:"K.bar", itemText:"K.bar()", tailText:" (sample)", typeText:"K" } // ABSENT: K.x -// ABSENT: K.kk +// ABSENT: { itemText:"K.kk" } +// EXIST: { lookupString:"K.kk", itemText:"!! K.kk", tailText:" (sample)", typeText:"K?" } +// EXIST: { lookupString:"K.kk", itemText:"?: K.kk", tailText:" (sample)", typeText:"K?" } // ABSENT: K.privateVal diff --git a/idea/testData/completion/smart/EmptyPrefix.kt b/idea/testData/completion/smart/EmptyPrefix.kt index 6c87b561d70..74e0927cbb7 100644 --- a/idea/testData/completion/smart/EmptyPrefix.kt +++ b/idea/testData/completion/smart/EmptyPrefix.kt @@ -20,7 +20,9 @@ fun f3() : String{} // EXIST: p1 // EXIST: p2 // ABSENT: p3 -// ABSENT: p4 +// ABSENT: { lookupString:"p4", itemText:"p4" } +// EXIST: { lookupString:"p4", itemText:"!! p4" } +// EXIST: { lookupString:"p4", itemText:"?: p4" } // EXIST: f1 // EXIST: f2 // ABSENT: f3 diff --git a/idea/testData/completion/smart/NullableThis.kt b/idea/testData/completion/smart/NullableThis.kt new file mode 100644 index 00000000000..50696961bf5 --- /dev/null +++ b/idea/testData/completion/smart/NullableThis.kt @@ -0,0 +1,6 @@ +fun String?.foo(){ + val s : String = +} + +// EXIST: { lookupString:"this", itemText: "!! this", typeText:"String?" } +// EXIST: { lookupString:"this", itemText: "?: this", typeText:"String?" } diff --git a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java index c159ca610fe..4558bacebd7 100644 --- a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java @@ -256,6 +256,11 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest("idea/testData/completion/smart/NotSillyAssignment.kt"); } + @TestMetadata("NullableThis.kt") + public void testNullableThis() throws Exception { + doTest("idea/testData/completion/smart/NullableThis.kt"); + } + @TestMetadata("OverloadedConstructorArgument.kt") public void testOverloadedConstructorArgument() throws Exception { doTest("idea/testData/completion/smart/OverloadedConstructorArgument.kt"); diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/AbstractSmartCompletionHandlerTest.kt b/idea/tests/org/jetbrains/jet/completion/handlers/AbstractSmartCompletionHandlerTest.kt index 85e7e3ca8bf..ad25a4952d0 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/AbstractSmartCompletionHandlerTest.kt +++ b/idea/tests/org/jetbrains/jet/completion/handlers/AbstractSmartCompletionHandlerTest.kt @@ -37,6 +37,7 @@ import org.jetbrains.jet.InTextDirectivesUtils abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase() { private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:" private val LOOKUP_STRING_PREFIX = "ELEMENT:" + private val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:" private val TAIL_TEXT_PREFIX = "TAIL_TEXT:" private val COMPLETION_CHAR_PREFIX = "CHAR:" @@ -49,6 +50,7 @@ abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase( val fileText = fixture.getFile()!!.getText() val invocationCount = InTextDirectivesUtils.getPrefixedInt(fileText, INVOCATION_COUNT_PREFIX) ?: 1 val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX) + val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX) val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX) val completionCharString = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX) val completionChar = when(completionCharString) { @@ -56,6 +58,6 @@ abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase( "\\t" -> '\t' else -> error("Uknown completion char") } - doTestWithTextLoaded(invocationCount, lookupString, tailText, completionChar) + doTestWithTextLoaded(invocationCount, lookupString, itemText, tailText, completionChar) } } diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/CompletionHandlerTestBase.kt b/idea/tests/org/jetbrains/jet/completion/handlers/CompletionHandlerTestBase.kt index 719e8fccda6..d56e74dc2f9 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/CompletionHandlerTestBase.kt +++ b/idea/tests/org/jetbrains/jet/completion/handlers/CompletionHandlerTestBase.kt @@ -42,20 +42,24 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe protected override fun setUp() { super.setUp() - fixture = myFixture!! + fixture = myFixture } - protected fun doTest() : Unit = doTest(2, null, null, '\n') + protected fun doTest() : Unit = doTest(2, null, null, null, '\n') protected fun doTest(time : Int, lookupString : String?, tailText : String?, completionChar : Char) { - fixture.configureByFile(fileName()) - doTestWithTextLoaded(time, lookupString, tailText, completionChar) + doTest(time, lookupString, null, tailText, completionChar) } - protected fun doTestWithTextLoaded(time : Int, lookupString : String?, tailText : String?, completionChar : Char) { - if (lookupString != null || tailText != null) { + protected fun doTest(time : Int, lookupString : String?, itemText: String?, tailText : String?, completionChar : Char) { + fixture.configureByFile(fileName()) + doTestWithTextLoaded(time, lookupString, itemText, tailText, completionChar) + } + + protected fun doTestWithTextLoaded(time : Int, lookupString : String?, itemText: String?, tailText : String?, completionChar : Char) { + if (lookupString != null || itemText != null || tailText != null) { fixture.complete(completionType, time) - val item = getExistentLookupElement(lookupString, tailText) + val item = getExistentLookupElement(lookupString, itemText, tailText) if (item != null) { selectItem(item, completionChar) } @@ -70,38 +74,42 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe fixture.checkResultByFile(afterFileName()) } - private fun getExistentLookupElement(lookupString : String?, tailText : String?) : LookupElement? { + private fun getExistentLookupElement(lookupString : String?, itemText: String?, tailText : String?) : LookupElement? { val lookup = LookupManager.getInstance(getProject())?.getActiveLookup() as LookupImpl? if (lookup == null) return null var foundElement : LookupElement? = null val presentation = LookupElementPresentation() for (lookupElement in lookup.getItems()) { - val lookupOk : Boolean - if (lookupString != null) { - lookupOk = lookupElement.getLookupString().contains(lookupString) - } - else { - lookupOk = true - } + val lookupOk = if (lookupString != null) lookupElement.getLookupString().contains(lookupString) else true if (lookupOk) { - val tailOk : Boolean - if (tailText != null) { - lookupElement.renderElement(presentation) - val itemTailText : String? = presentation.getTailText() - tailOk = itemTailText != null && itemTailText.contains(tailText) + lookupElement.renderElement(presentation) + + val textOk = if (itemText != null) { + val itemItemText = presentation.getItemText() + itemItemText != null && itemItemText.contains(itemText) } else { - tailOk = true + true } - if (tailOk) { - if (foundElement != null) { - Assert.fail("Several elements satisfy to completion restrictions") + if (textOk) { + val tailOk = if (tailText != null) { + val itemTailText = presentation.getTailText() + itemTailText != null && itemTailText.contains(tailText) + } + else { + true } - foundElement = lookupElement + if (tailOk) { + if (foundElement != null) { + Assert.fail("Several elements satisfy to completion restrictions") + } + + foundElement = lookupElement + } } } } diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java index 7a2b55e0e46..70e855c00f5 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java @@ -246,6 +246,21 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest("idea/testData/completion/handlers/smart/MergeTail4.kt"); } + @TestMetadata("NullableValue1.kt") + public void testNullableValue1() throws Exception { + doTest("idea/testData/completion/handlers/smart/NullableValue1.kt"); + } + + @TestMetadata("NullableValue2.kt") + public void testNullableValue2() throws Exception { + doTest("idea/testData/completion/handlers/smart/NullableValue2.kt"); + } + + @TestMetadata("NullableValue3.kt") + public void testNullableValue3() throws Exception { + doTest("idea/testData/completion/handlers/smart/NullableValue3.kt"); + } + @TestMetadata("TabReplaceComma1.kt") public void testTabReplaceComma1() throws Exception { doTest("idea/testData/completion/handlers/smart/TabReplaceComma1.kt");