KT-12103 Smart completion for nested SAM-adapter produces short unresolved name

#KT-12103 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-04-28 21:52:12 +03:00
parent cd6b709ef5
commit 0769c5453f
11 changed files with 88 additions and 63 deletions
@@ -16,7 +16,10 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.CompletionProgressIndicator
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.*
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.Key
@@ -359,14 +362,14 @@ fun LookupElement.decorateAsStaticMember(
memberDescriptor: DeclarationDescriptor,
classNameAsLookupString: Boolean
): LookupElement? {
var container = memberDescriptor.containingDeclaration as? ClassDescriptor ?: return null
var classDescriptor = if (container.isCompanionObject)
val container = memberDescriptor.containingDeclaration as? ClassDescriptor ?: return null
val classDescriptor = if (container.isCompanionObject)
container.containingDeclaration as? ClassDescriptor ?: return null
else
container
val containerFqName = container.importableFqName ?: return null
val qualifierPresentation = classDescriptor.name.asString()
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
return object: LookupElementDecorator<LookupElement>(this) {
override fun getAllLookupStrings(): Set<String> {
@@ -395,31 +398,14 @@ fun LookupElement.decorateAsStaticMember(
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
val file = context.file as KtFile
val classImportableFqName = container.importableFqName
val useImport = classImportableFqName != null && file.importDirectives.any {
!it.isAllUnder && it.importPath?.fqnPart()?.parent() == classImportableFqName
}
val addMemberImport = file.importDirectives.any { !it.isAllUnder && it.importPath?.fqnPart()?.parent() == containerFqName }
var insertQualifier = true
if (useImport) {
if (addMemberImport) {
psiDocumentManager.commitAllDocuments()
if (ImportInsertHelper.getInstance(context.project).importDescriptor(file, memberDescriptor) != ImportDescriptorResult.FAIL) {
insertQualifier = false
}
ImportInsertHelper.getInstance(context.project).importDescriptor(file, memberDescriptor)
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
}
if (insertQualifier) {
val prefix = qualifierText + "."
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
shortenReferences(context, offset, offset + prefix.length)
}
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
super.handleInsert(context)
}
}
@@ -31,16 +31,12 @@ import org.jetbrains.kotlin.types.KotlinType
import java.util.*
class InsertHandlerProvider(
private val callType: CallType<*>?,
private val callType: CallType<*>,
expectedInfosCalculator: () -> Collection<ExpectedInfo>
) {
private val expectedInfos by lazy(LazyThreadSafetyMode.NONE) { expectedInfosCalculator() }
fun insertHandler(descriptor: DeclarationDescriptor): InsertHandler<LookupElement> {
if (callType == null) {
error("Cannot create InsertHandler when no CallType known")
}
return when (descriptor) {
is FunctionDescriptor -> {
when (callType) {
@@ -48,7 +44,7 @@ class InsertHandlerProvider(
val needTypeArguments = needTypeArguments(descriptor)
val parameters = descriptor.valueParameters
when (parameters.size) {
0 -> KotlinFunctionInsertHandler.Normal(needTypeArguments, inputValueArguments = false)
0 -> KotlinFunctionInsertHandler.Normal(callType, needTypeArguments, inputValueArguments = false)
1 -> {
if (callType != CallType.SUPER_MEMBERS) { // for super call we don't suggest to generate "super.foo { ... }" (seems to be non-typical use)
@@ -57,28 +53,28 @@ class InsertHandlerProvider(
if (getValueParametersCountFromFunctionType(parameterType) <= 1) {
// otherwise additional item with lambda template is to be added
return KotlinFunctionInsertHandler.Normal(
needTypeArguments, inputValueArguments = false,
callType, needTypeArguments, inputValueArguments = false,
lambdaInfo = GenerateLambdaInfo(parameterType, false)
)
}
}
}
KotlinFunctionInsertHandler.Normal(needTypeArguments, inputValueArguments = true)
KotlinFunctionInsertHandler.Normal(callType, needTypeArguments, inputValueArguments = true)
}
else -> KotlinFunctionInsertHandler.Normal(needTypeArguments, inputValueArguments = true)
else -> KotlinFunctionInsertHandler.Normal(callType, needTypeArguments, inputValueArguments = true)
}
}
CallType.INFIX -> KotlinFunctionInsertHandler.Infix
else -> KotlinFunctionInsertHandler.OnlyName
else -> KotlinFunctionInsertHandler.OnlyName(callType)
}
}
is PropertyDescriptor -> KotlinPropertyInsertHandler
is PropertyDescriptor -> KotlinPropertyInsertHandler(callType)
is ClassifierDescriptor -> KotlinClassifierInsertHandler
@@ -63,7 +63,7 @@ data /* we need copy() */
class LookupElementFactory(
val basicFactory: BasicLookupElementFactory,
private val receiverTypes: Collection<KotlinType>?,
private val callType: CallType<*>?,
private val callType: CallType<*>,
private val inDescriptor: DeclarationDescriptor,
private val contextVariablesProvider: ContextVariablesProvider,
private val standardLookupElementsPostProcessor: (LookupElement) -> LookupElement = { it }
@@ -188,7 +188,7 @@ class LookupElementFactory(
}
override fun handleInsert(context: InsertionContext) {
KotlinFunctionInsertHandler.Normal(inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this)
KotlinFunctionInsertHandler.Normal(callType, inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this)
}
}
@@ -235,7 +235,8 @@ class LookupElementFactory(
}
override fun handleInsert(context: InsertionContext) {
KotlinFunctionInsertHandler.Normal(inputTypeArguments = needTypeArguments, inputValueArguments = false, argumentText = argumentText).handleInsert(context, this)
KotlinFunctionInsertHandler.Normal(callType, inputTypeArguments = needTypeArguments, inputValueArguments = false, argumentText = argumentText)
.handleInsert(context, this)
}
}
@@ -20,14 +20,17 @@ import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.completion.isAfterDot
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.DescriptorUtils
abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler() {
abstract class KotlinCallableInsertHandler(val callType: CallType<*>) : BaseDeclarationInsertHandler() {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
@@ -40,19 +43,21 @@ abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler() {
val file = context.file
val o = item.`object`
if (file is KtFile && o is DeclarationLookupObject) {
val descriptor = o.descriptor as? CallableDescriptor
if (descriptor != null) {
// for completion after dot, import insertion may be required only for extensions
if (context.isAfterDot() && descriptor.extensionReceiverParameter == null) {
return
}
val descriptor = o.descriptor as? CallableDescriptor ?: return
if (descriptor.extensionReceiverParameter != null || callType == CallType.CALLABLE_REFERENCE) {
if (DescriptorUtils.isTopLevelDeclaration(descriptor)) {
runWriteAction {
ImportInsertHelper.getInstance(context.getProject()).importDescriptor(file, descriptor)
ImportInsertHelper.getInstance(context.project).importDescriptor(file, descriptor)
}
}
}
else if (callType == CallType.DEFAULT) {
val fqName = descriptor.importableFqName ?: return
context.document.replaceString(context.startOffset, context.tailOffset, fqName.render())
PsiDocumentManager.getInstance(context.project).commitAllDocuments()
ShortenReferences.DEFAULT.process(file, context.startOffset, context.tailOffset)
}
}
}
}
@@ -27,6 +27,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.codeStyle.CodeStyleSettingsManager
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.psiUtil.endOffset
@@ -34,15 +35,16 @@ import org.jetbrains.kotlin.types.KotlinType
class GenerateLambdaInfo(val lambdaType: KotlinType, val explicitParameters: Boolean)
sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() {
sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallableInsertHandler(callType) {
class Normal(
callType: CallType<*>,
val inputTypeArguments: Boolean,
val inputValueArguments: Boolean,
val argumentText: String = "",
val lambdaInfo: GenerateLambdaInfo? = null,
val argumentsOnly: Boolean = false
) : KotlinFunctionInsertHandler() {
) : KotlinFunctionInsertHandler(callType) {
init {
if (lambdaInfo != null) {
assert(argumentText == "")
@@ -51,12 +53,13 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() {
//TODO: add 'data' or special annotation when supported
fun copy(
callType: CallType<*> = this.callType,
inputTypeArguments: Boolean = this.inputTypeArguments,
inputValueArguments: Boolean = this.inputValueArguments,
argumentText: String = this.argumentText,
lambdaInfo: GenerateLambdaInfo? = this.lambdaInfo,
argumentsOnly: Boolean = this.argumentsOnly
) = Normal(inputTypeArguments, inputValueArguments, argumentText, lambdaInfo, argumentsOnly)
) = Normal(callType, inputTypeArguments, inputValueArguments, argumentText, lambdaInfo, argumentsOnly)
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
@@ -193,7 +196,7 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() {
= CodeStyleSettingsManager.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
}
object Infix : KotlinFunctionInsertHandler() {
object Infix : KotlinFunctionInsertHandler(CallType.INFIX) {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
@@ -207,7 +210,7 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() {
}
}
object OnlyName : KotlinFunctionInsertHandler()
class OnlyName(callType: CallType<*>) : KotlinFunctionInsertHandler(callType)
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
@@ -20,8 +20,9 @@ import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.util.CallType
object KotlinPropertyInsertHandler : KotlinCallableInsertHandler() {
class KotlinPropertyInsertHandler(callType: CallType<*>) : KotlinCallableInsertHandler(callType) {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
@@ -153,7 +153,7 @@ class TypeInstantiationItems(
var allLookupStrings = setOf(lookupString)
var itemText = lookupString
var signatureText: String? = null
var typeText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classifier)
val typeText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classifier)
val insertHandler: InsertHandler<LookupElement>
if (isAbstract) {
@@ -218,12 +218,12 @@ class TypeInstantiationItems(
}
val baseInsertHandler = when (visibleConstructors.size) {
0 -> KotlinFunctionInsertHandler.Normal(inputTypeArguments = false, inputValueArguments = false, argumentsOnly = true)
0 -> KotlinFunctionInsertHandler.Normal(CallType.DEFAULT, inputTypeArguments = false, inputValueArguments = false, argumentsOnly = true)
1 -> (lookupElementFactory.insertHandlerProvider.insertHandler(visibleConstructors.single()) as KotlinFunctionInsertHandler.Normal)
.copy(argumentsOnly = true)
else -> KotlinFunctionInsertHandler.Normal(inputTypeArguments = false, inputValueArguments = true, argumentsOnly = true)
else -> KotlinFunctionInsertHandler.Normal(CallType.DEFAULT, inputTypeArguments = false, inputValueArguments = true, argumentsOnly = true)
}
insertHandler = object : InsertHandler<LookupElement> {
@@ -0,0 +1,6 @@
fun foo(x: Outer.Nested) {
}
fun bar() {
foo(<caret>)
}
@@ -0,0 +1,5 @@
public class Outer {
public interface Nested {
void foo();
}
}
@@ -0,0 +1,6 @@
fun foo(x: Outer.Nested) {
}
fun bar() {
foo(Outer.Nested { <caret> })
}
@@ -17,11 +17,11 @@
package org.jetbrains.kotlin.idea.completion.test.handlers
import com.intellij.codeInsight.completion.CompletionType
import junit.framework.TestCase
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.idea.completion.test.COMPLETION_TEST_DATA_BASE_PATH
import org.jetbrains.kotlin.idea.completion.test.KotlinCompletionTestCase
import java.io.File
import kotlin.test.assertTrue
class SmartCompletionMultifileHandlerTest : KotlinCompletionTestCase() {
fun testImportExtensionFunction() { doTest() }
@@ -30,12 +30,14 @@ class SmartCompletionMultifileHandlerTest : KotlinCompletionTestCase() {
fun testAnonymousObjectGenericJava() { doTest() }
fun testNestedSamAdapter() { doTest(lookupString = "Nested") }
override fun setUp() {
setType(CompletionType.SMART)
super.setUp()
}
fun doTest() {
private fun doTest(lookupString: String? = null, itemText: String? = null) {
val fileName = getTestName(false)
val fileNames = listOf(fileName + "-1.kt", fileName + "-2.kt", fileName + ".java")
@@ -44,8 +46,22 @@ class SmartCompletionMultifileHandlerTest : KotlinCompletionTestCase() {
complete(1)
if (myItems != null) {
assertTrue(myItems.size == 1, "Multiple items in completion")
selectItem(myItems[0])
fun isMatching(lookupElement: LookupElement): Boolean {
if (lookupString != null && lookupElement.lookupString != lookupString) return false
val presentation = LookupElementPresentation()
lookupElement.renderElement(presentation)
if (itemText != null && presentation.itemText != itemText) return false
return true
}
val items = myItems.filter(::isMatching)
when (items.size) {
0 -> fail("No matching items found")
1 -> selectItem(myItems[0])
else -> fail("Multiple matching items found")
}
}
checkResultByFile(fileName + ".kt.after")