Performance optimization in completion: search context variables of functional types later

This commit is contained in:
Valentin Kipyatkov
2015-10-30 18:26:12 +03:00
parent 0a5a5a2e36
commit b05d490eeb
19 changed files with 320 additions and 172 deletions
@@ -154,22 +154,30 @@ class BasicCompletionSession(
}
override fun doComplete() {
if (smartCompletion != null) {
val (additionalItems, @Suppress("UNUSED_VARIABLE") inheritanceSearcher) = smartCompletion.additionalItems(lookupElementFactory)
fun completeWithSmartCompletion(lookupElementFactory: LookupElementFactory) {
if (smartCompletion != null) {
val (additionalItems, @Suppress("UNUSED_VARIABLE") inheritanceSearcher) = smartCompletion.additionalItems(lookupElementFactory)
// all additional items should have SMART_COMPLETION_ITEM_PRIORITY_KEY to be recognized by SmartCompletionInBasicWeigher
for (item in additionalItems) {
if (item.getUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY) == null) {
item.putUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY, SmartCompletionItemPriority.DEFAULT)
// all additional items should have SMART_COMPLETION_ITEM_PRIORITY_KEY to be recognized by SmartCompletionInBasicWeigher
for (item in additionalItems) {
if (item.getUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY) == null) {
item.putUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY, SmartCompletionItemPriority.DEFAULT)
}
}
}
collector.addElements(additionalItems)
collector.addElements(additionalItems)
}
}
val (imported, notImported) = referenceVariants!!
collector.addDescriptorElements(imported, lookupElementFactory)
collector.addDescriptorElements(notImported, lookupElementFactory, notImported = true)
val contextVariableTypesForSmartCompletion = withCollectRequiredContextVariableTypes(::completeWithSmartCompletion)
fun completeReferenceVariants(lookupElementFactory: LookupElementFactory) {
val (imported, notImported) = referenceVariants!!
collector.addDescriptorElements(imported, lookupElementFactory)
collector.addDescriptorElements(notImported, lookupElementFactory, notImported = true)
}
val contextVariableTypesForReferenceVariants = withCollectRequiredContextVariableTypes(::completeReferenceVariants)
KEYWORDS_ONLY.doComplete()
@@ -188,7 +196,7 @@ class BasicCompletionSession(
}
}
packageNames.forEach { collector.addElement(lookupElementFactory.createLookupElementForPackage(it)) }
packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) }
}
flushToResultSet()
@@ -196,22 +204,35 @@ class BasicCompletionSession(
NamedArgumentCompletion.complete(collector, expectedInfos)
flushToResultSet()
completeNonImported()
flushToResultSet()
val contextVariablesProvider = RealContextVariablesProvider(referenceVariantsHelper, position)
withContextVariablesProvider(contextVariablesProvider) { lookupElementFactory ->
if (contextVariableTypesForSmartCompletion.any { contextVariablesProvider.functionTypeVariables(it).isNotEmpty() }) {
completeWithSmartCompletion(lookupElementFactory)
}
if (position.containingFile is KtCodeFragment) {
val variantsAndFactory = getRuntimeReceiverTypeReferenceVariants()
if (variantsAndFactory != null) {
val variants = variantsAndFactory.first
val lookupElementFactory = variantsAndFactory.second
collector.addDescriptorElements(variants.imported, lookupElementFactory, withReceiverCast = true)
collector.addDescriptorElements(variants.notImportedExtensions, lookupElementFactory, withReceiverCast = true, notImported = true)
flushToResultSet()
if (contextVariableTypesForReferenceVariants.any { contextVariablesProvider.functionTypeVariables(it).isNotEmpty() }) {
val (imported, notImported) = referenceVariantsWithSingleFunctionTypeParameter()!!
collector.addDescriptorElements(imported, lookupElementFactory)
collector.addDescriptorElements(notImported, lookupElementFactory, notImported = true)
}
completeNonImported(lookupElementFactory)
flushToResultSet()
if (position.containingFile is KtCodeFragment) {
val variantsAndFactory = getRuntimeReceiverTypeReferenceVariants(lookupElementFactory)
if (variantsAndFactory != null) {
val variants = variantsAndFactory.first
@Suppress("NAME_SHADOWING") val lookupElementFactory = variantsAndFactory.second
collector.addDescriptorElements(variants.imported, lookupElementFactory, withReceiverCast = true)
collector.addDescriptorElements(variants.notImportedExtensions, lookupElementFactory, withReceiverCast = true, notImported = true)
flushToResultSet()
}
}
}
}
private fun completeNonImported() {
private fun completeNonImported(lookupElementFactory: LookupElementFactory) {
if (shouldCompleteTopLevelCallablesFromIndex()) {
collector.addDescriptorElements(getTopLevelCallables(), lookupElementFactory, notImported = true)
}
@@ -292,7 +313,7 @@ class BasicCompletionSession(
"override" -> {
collector.addElement(lookupElement)
OverridesCompletion(collector, lookupElementFactory).complete(position)
OverridesCompletion(collector, basicLookupElementFactory).complete(position)
}
"class" -> {
@@ -377,7 +398,7 @@ class BasicCompletionSession(
KEYWORDS_ONLY.doComplete()
if (shouldCompleteParameterNameAndType()) {
val parameterNameAndTypeCompletion = ParameterNameAndTypeCompletion(collector, lookupElementFactory, prefixMatcher, resolutionFacade)
val parameterNameAndTypeCompletion = ParameterNameAndTypeCompletion(collector, basicLookupElementFactory, prefixMatcher, resolutionFacade)
// if we are typing parameter name, restart completion each time we type an upper case letter because new suggestions will appear (previous words can be used as user prefix)
val prefixPattern = StandardPatterns.string().with(object : PatternCondition<String>("Prefix ends with uppercase letter") {
@@ -470,7 +491,7 @@ class BasicCompletionSession(
}
superClasses
.map { lookupElementFactory.createLookupElement(it, useReceiverTypes = false, qualifyNestedClasses = true, includeClassTypeArguments = false) }
.map { basicLookupElementFactory.createLookupElement(it, qualifyNestedClasses = true, includeClassTypeArguments = false) }
.forEach { collector.addElement(it) }
}
}
@@ -38,7 +38,7 @@ import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
class BasicLookupElementFactory(
private val project: Project,
private val insertHandlerProvider: InsertHandlerProvider
val insertHandlerProvider: InsertHandlerProvider
) {
public fun createLookupElement(
descriptor: DeclarationDescriptor,
@@ -132,14 +132,16 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
protected val referenceVariantsHelper = ReferenceVariantsHelper(bindingContext, resolutionFacade, isVisibleFilter)
protected val callTypeAndReceiver: CallTypeAndReceiver<*, *>
protected val lookupElementFactory: LookupElementFactory
protected val receiverTypes: Collection<KotlinType>?
init {
val (callTypeAndReceiver, receiverTypes) = detectCallTypeAndReceiverTypes()
this.callTypeAndReceiver = callTypeAndReceiver
this.lookupElementFactory = createLookupElementFactory(callTypeAndReceiver.callType, receiverTypes)
this.receiverTypes = receiverTypes
}
protected val basicLookupElementFactory = BasicLookupElementFactory(project, InsertHandlerProvider(callTypeAndReceiver.callType) { expectedInfos })
// LookupElementsCollector instantiation is deferred because virtual call to createSorter uses data from derived classes
protected val collector: LookupElementsCollector by lazy(LazyThreadSafetyMode.NONE) {
LookupElementsCollector(prefixMatcher, parameters, resultSet, createSorter())
@@ -273,12 +275,13 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
data class ReferenceVariants(val imported: Collection<DeclarationDescriptor>, val notImportedExtensions: Collection<CallableDescriptor>)
protected val referenceVariants: ReferenceVariants? by lazy {
descriptorKindFilter?.let { collectReferenceVariants(it) }
if (nameExpression != null && descriptorKindFilter != null) collectReferenceVariants(descriptorKindFilter!!, nameExpression) else null
}
private fun collectReferenceVariants(descriptorKindFilter: DescriptorKindFilter, runtimeReceiver: ExpressionReceiver? = null): ReferenceVariants {
private fun collectReferenceVariants(descriptorKindFilter: DescriptorKindFilter, nameExpression: KtSimpleNameExpression, runtimeReceiver: ExpressionReceiver? = null): ReferenceVariants {
var variants = referenceVariantsHelper.getReferenceVariants(
nameExpression!!,
nameExpression,
descriptorKindFilter,
descriptorNameFilter,
filterOutJavaGettersAndSetters = false,
@@ -315,8 +318,15 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
return ReferenceVariants(variants, notImportedExtensions)
}
protected fun getRuntimeReceiverTypeReferenceVariants(): Pair<ReferenceVariants, LookupElementFactory>? {
protected fun referenceVariantsWithSingleFunctionTypeParameter(): ReferenceVariants? {
val variants = referenceVariants ?: return null
val filter: (DeclarationDescriptor) -> Boolean = { it is FunctionDescriptor && LookupElementFactory.hasSingleFunctionTypeParameter(it) }
return ReferenceVariants(variants.imported.filter(filter), variants.notImportedExtensions.filter(filter))
}
protected fun getRuntimeReceiverTypeReferenceVariants(lookupElementFactory: LookupElementFactory): Pair<ReferenceVariants, LookupElementFactory>? {
val evaluator = file.getCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR) ?: return null
val referenceVariants = referenceVariants ?: return null
val explicitReceiver = callTypeAndReceiver.receiver as? KtExpression ?: return null
val type = bindingContext.getType(explicitReceiver) ?: return null
@@ -325,12 +335,12 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
val runtimeType = evaluator(explicitReceiver)
if (runtimeType == null || runtimeType == type) return null
val (variants, notImportedExtensions) = collectReferenceVariants(descriptorKindFilter!!, ExpressionReceiver(explicitReceiver, runtimeType))
val filteredVariants = filterVariantsForRuntimeReceiverType(variants, referenceVariants!!.imported)
val filteredNotImportedExtensions = filterVariantsForRuntimeReceiverType(notImportedExtensions, referenceVariants!!.notImportedExtensions)
val (variants, notImportedExtensions) = collectReferenceVariants(descriptorKindFilter!!, nameExpression!!, ExpressionReceiver(explicitReceiver, runtimeType))
val filteredVariants = filterVariantsForRuntimeReceiverType(variants, referenceVariants.imported)
val filteredNotImportedExtensions = filterVariantsForRuntimeReceiverType(notImportedExtensions, referenceVariants.notImportedExtensions)
val referenceVariants = ReferenceVariants(filteredVariants, filteredNotImportedExtensions)
return Pair(referenceVariants, lookupElementFactory.copy(receiverTypes = listOf(runtimeType)))
val runtimeVariants = ReferenceVariants(filteredVariants, filteredNotImportedExtensions)
return Pair(runtimeVariants, lookupElementFactory.copy(receiverTypes = listOf(runtimeType)))
}
private fun <TDescriptor : DeclarationDescriptor> filterVariantsForRuntimeReceiverType(
@@ -373,24 +383,26 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
protected fun addClassesFromIndex(kindFilter: (ClassKind) -> Boolean) {
AllClassesCompletion(parameters, indicesHelper, prefixMatcher, resolutionFacade, kindFilter)
.collect(
{ descriptor -> collector.addDescriptorElements(descriptor, lookupElementFactory, notImported = true) },
{ javaClass -> collector.addElement(lookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) }
{ descriptor -> collector.addElement(basicLookupElementFactory.createLookupElement(descriptor), notImported = true) },
{ javaClass -> collector.addElement(basicLookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) }
)
}
private fun createLookupElementFactory(callType: CallType<*>?, receiverTypes: Collection<KotlinType>?): LookupElementFactory {
val contextVariablesProvider = {
nameExpression?.let {
val descriptorFilter = DescriptorKindFilter.VARIABLES exclude DescriptorKindExclude.Extensions // we exclude extensions by performance reasons
referenceVariantsHelper.getReferenceVariants(it, CallTypeAndReceiver.DEFAULT, descriptorFilter, nameFilter = { true })
.map { it as VariableDescriptor }
} ?: emptyList()
}
protected fun withCollectRequiredContextVariableTypes(action: (LookupElementFactory) -> Unit): Collection<FuzzyType> {
val provider = CollectRequiredTypesContextVariablesProvider()
val lookupElementFactory = createLookupElementFactory(provider)
action(lookupElementFactory)
return provider.requiredTypes
}
val insertHandlerProvider = InsertHandlerProvider(callType) { expectedInfos }
return LookupElementFactory(resolutionFacade, receiverTypes,
callType, expression?.parent is KtSimpleNameStringTemplateEntry,
insertHandlerProvider, contextVariablesProvider)
protected fun withContextVariablesProvider(contextVariablesProvider: ContextVariablesProvider, action: (LookupElementFactory) -> Unit) {
val lookupElementFactory = createLookupElementFactory(contextVariablesProvider)
action(lookupElementFactory)
}
protected fun createLookupElementFactory(contextVariablesProvider: ContextVariablesProvider): LookupElementFactory {
return LookupElementFactory(basicLookupElementFactory, resolutionFacade, receiverTypes,
callTypeAndReceiver.callType, contextVariablesProvider)
}
private fun detectCallTypeAndReceiverTypes(): Pair<CallTypeAndReceiver<*, *>, Collection<KotlinType>?> {
@@ -29,12 +29,8 @@ import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.util.findLabelAndCall
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstanceToExpression
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
@@ -94,22 +90,6 @@ fun LookupElement.withReceiverCast(): LookupElement {
}
}
fun LookupElement.withBracesSurrounding(): LookupElement {
return object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
val startOffset = context.getStartOffset()
context.getDocument().insertString(startOffset, "{")
context.getOffsetMap().addOffset(CompletionInitializationContext.START_OFFSET, startOffset + 1)
val tailOffset = context.getTailOffset()
context.getDocument().insertString(tailOffset, "}")
context.setTailOffset(tailOffset)
super.handleInsert(context)
}
}
}
val KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY = Key<Unit>("KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY")
fun LookupElement.keepOldArgumentListOnTab(): LookupElement {
@@ -310,7 +290,7 @@ fun breakOrContinueExpressionItems(position: KtElement, breakOrContinue: String)
return result
}
fun LookupElementFactory.createLookupElementForType(type: KotlinType): LookupElement? {
fun BasicLookupElementFactory.createLookupElementForType(type: KotlinType): LookupElement? {
if (type.isError()) return null
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(type)) {
@@ -320,7 +300,7 @@ fun LookupElementFactory.createLookupElementForType(type: KotlinType): LookupEle
}
else {
val classifier = type.getConstructor().getDeclarationDescriptor() ?: return null
val baseLookupElement = createLookupElement(classifier, useReceiverTypes = false, qualifyNestedClasses = true, includeClassTypeArguments = false)
val baseLookupElement = createLookupElement(classifier, qualifyNestedClasses = true, includeClassTypeArguments = false)
val itemText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(type)
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2015 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.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.psi.PsiElement
import com.intellij.util.SmartList
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.types.TypeSubstitutor
import java.util.*
interface ContextVariablesProvider {
fun functionTypeVariables(requiredType: FuzzyType): Collection<Pair<VariableDescriptor, TypeSubstitutor>>
}
class RealContextVariablesProvider(
private val referenceVariantsHelper: ReferenceVariantsHelper,
private val contextElement: PsiElement
) : ContextVariablesProvider {
private val functionTypeVariables by lazy {
collectVariables().filter { KotlinBuiltIns.isFunctionOrExtensionFunctionType(it.type) }
}
private fun collectVariables(): Collection<VariableDescriptor> {
val descriptorFilter = DescriptorKindFilter.VARIABLES exclude DescriptorKindExclude.Extensions // we exclude extensions by performance reasons
return referenceVariantsHelper.getReferenceVariants(contextElement, CallTypeAndReceiver.DEFAULT, descriptorFilter, nameFilter = { true })
.map { it as VariableDescriptor }
}
override fun functionTypeVariables(requiredType: FuzzyType): Collection<Pair<VariableDescriptor, TypeSubstitutor>> {
val result = SmartList<Pair<VariableDescriptor, TypeSubstitutor>>()
for (variable in functionTypeVariables) {
val substitutor = variable.fuzzyReturnType()?.checkIsSubtypeOf(requiredType) ?: continue
result.add(variable to substitutor)
}
return result
}
}
class CollectRequiredTypesContextVariablesProvider : ContextVariablesProvider {
private val _requiredTypes = HashSet<FuzzyType>()
public val requiredTypes: Set<FuzzyType>
get() = _requiredTypes
override fun functionTypeVariables(requiredType: FuzzyType): Collection<Pair<VariableDescriptor, TypeSubstitutor>> {
_requiredTypes.add(requiredType)
return emptyList()
}
}
@@ -87,7 +87,7 @@ class KDocNameCompletionSession(parameters: CompletionParameters,
.filter { it.getName().asString() !in documentedParameters }
descriptors.forEach {
collector.addElement(lookupElementFactory.createLookupElement(it, useReceiverTypes = false, parametersAndTypeGrayed = true))
collector.addElement(basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true))
}
}
@@ -108,7 +108,7 @@ class KDocNameCompletionSession(parameters: CompletionParameters,
}
scope.collectDescriptorsFiltered(nameFilter = descriptorNameFilter).filter(::isApplicable).forEach {
val element = lookupElementFactory.createLookupElement(it, useReceiverTypes = false, parametersAndTypeGrayed = true)
val element = basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true)
collector.addElement(object: LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext?) {
// insert only plain name here, no qualifier/parentheses/etc.
@@ -21,7 +21,6 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.codeInsight.lookup.impl.LookupCellRenderer
import com.intellij.psi.PsiClass
import com.intellij.util.SmartList
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
@@ -31,9 +30,8 @@ import org.jetbrains.kotlin.idea.completion.handlers.lambdaPresentation
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
@@ -44,28 +42,27 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
data /* we need copy() */
class LookupElementFactory(
val basicFactory: BasicLookupElementFactory,
private val resolutionFacade: ResolutionFacade,
private val receiverTypes: Collection<KotlinType>?,
private val callType: CallType<*>?,
private val isInStringTemplateAfterDollar: Boolean,
public val insertHandlerProvider: InsertHandlerProvider,
private val contextVariablesProvider: () -> Collection<VariableDescriptor>
private val contextVariablesProvider: ContextVariablesProvider
) {
private val basicFactory = BasicLookupElementFactory(resolutionFacade.project, insertHandlerProvider)
private val functionTypeContextVariables by lazy(LazyThreadSafetyMode.NONE) {
contextVariablesProvider().filter { KotlinBuiltIns.isFunctionOrExtensionFunctionType(it.type) }
companion object {
fun hasSingleFunctionTypeParameter(descriptor: FunctionDescriptor): Boolean {
val parameter = descriptor.original.valueParameters.singleOrNull() ?: return false
return KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameter.type)
}
}
val insertHandlerProvider = basicFactory.insertHandlerProvider
public fun createStandardLookupElementsForDescriptor(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean): Collection<LookupElement> {
val result = SmartList<LookupElement>()
val isNormalCall = callType == CallType.DEFAULT || callType == CallType.DOT || callType == CallType.SAFE
var lookupElement = createLookupElement(descriptor, useReceiverTypes, parametersAndTypeGrayed = !isNormalCall && callType != CallType.INFIX)
if (isInStringTemplateAfterDollar && (descriptor is FunctionDescriptor || descriptor is ClassifierDescriptor)) {
lookupElement = lookupElement.withBracesSurrounding()
}
result.add(lookupElement)
// add special item for function with one argument of function type with more than one parameter
@@ -80,11 +77,12 @@ class LookupElementFactory(
// check that all parameters except for the last one are optional
val lastParameter = descriptor.valueParameters.lastOrNull() ?: return
if (!descriptor.valueParameters.all { it == lastParameter || it.hasDefaultValue() }) return
val parameterType = lastParameter.type
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) {
val isSingleParameter = descriptor.valueParameters.size() == 1
val functionParameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(parameterType).size()
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(lastParameter.original.type)) {
val isSingleParameter = descriptor.valueParameters.size == 1
val parameterType = lastParameter.type
val functionParameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(parameterType).size
// we don't need special item inserting lambda for single functional parameter that does not need multiple arguments because the default item will be special in this case
if (!isSingleParameter || functionParameterCount > 1) {
add(createFunctionCallElementWithLambda(descriptor, parameterType, functionParameterCount > 1, useReceiverTypes))
@@ -94,12 +92,9 @@ class LookupElementFactory(
//TODO: also ::function? at least for local functions
//TODO: order for them
val fuzzyParameterType = FuzzyType(parameterType, descriptor.typeParameters)
for (variable in functionTypeContextVariables) {
val substitutor = variable.fuzzyReturnType()?.checkIsSubtypeOf(fuzzyParameterType)
if (substitutor != null) {
val substitutedDescriptor = descriptor.substitute(substitutor) ?: continue
add(createFunctionCallElementWithArgument(substitutedDescriptor, variable.name.asString(), useReceiverTypes))
}
for ((variable, substitutor) in contextVariablesProvider.functionTypeVariables(fuzzyParameterType)) {
val substitutedDescriptor = descriptor.substitute(substitutor)
add(createFunctionCallElementWithArgument(substitutedDescriptor, variable.name.render(), useReceiverTypes))
}
}
}
@@ -139,10 +134,6 @@ class LookupElementFactory(
}
}
if (isInStringTemplateAfterDollar) {
lookupElement = lookupElement.withBracesSurrounding()
}
return lookupElement
}
@@ -150,13 +141,7 @@ class LookupElementFactory(
var lookupElement = createLookupElement(descriptor, useReceiverTypes)
val needTypeArguments = (insertHandlerProvider.insertHandler(descriptor) as KotlinFunctionInsertHandler.Normal).inputTypeArguments
lookupElement = FunctionCallWithArgumentLookupElement(lookupElement, descriptor, argumentText, needTypeArguments)
if (isInStringTemplateAfterDollar) {
lookupElement = lookupElement.withBracesSurrounding()
}
return lookupElement
return FunctionCallWithArgumentLookupElement(lookupElement, descriptor, argumentText, needTypeArguments)
}
private inner class FunctionCallWithArgumentLookupElement(
@@ -283,12 +268,4 @@ class LookupElementFactory(
val typeParameter = receiverParameter.type.constructor.declarationDescriptor as? TypeParameterDescriptor ?: return false
return typeParameter.containingDeclaration == original
}
public fun createLookupElementForJavaClass(psiClass: PsiClass, qualifyNestedClasses: Boolean = false, includeClassTypeArguments: Boolean = true): LookupElement {
return basicFactory.createLookupElementForJavaClass(psiClass, qualifyNestedClasses, includeClassTypeArguments)
}
public fun createLookupElementForPackage(name: FqName): LookupElement {
return basicFactory.createLookupElementForPackage(name)
}
}
@@ -49,7 +49,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
class OverridesCompletion(
private val collector: LookupElementsCollector,
private val lookupElementFactory: LookupElementFactory
private val lookupElementFactory: BasicLookupElementFactory
) {
private val PRESENTATION_RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions {
modifiers = emptySet()
@@ -66,7 +66,7 @@ class OverridesCompletion(
if (isConstructorParameter && memberObject.descriptor !is PropertyDescriptor) continue
val descriptor = memberObject.descriptor
var lookupElement = lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false)
var lookupElement = lookupElementFactory.createLookupElement(descriptor)
var text = "override " + PRESENTATION_RENDERER.render(descriptor)
if (descriptor is FunctionDescriptor) {
@@ -48,7 +48,7 @@ import java.util.*
class ParameterNameAndTypeCompletion(
private val collector: LookupElementsCollector,
private val lookupElementFactory: LookupElementFactory,
private val lookupElementFactory: BasicLookupElementFactory,
private val prefixMatcher: PrefixMatcher,
private val resolutionFacade: ResolutionFacade
) {
@@ -112,9 +112,9 @@ class ParameterNameAndTypeCompletion(
if (descriptor != null) {
val parameterType = descriptor.getType()
if (parameterType.isVisible(visibilityFilter)) {
val lookupElement = MyLookupElement.create(name, ArbitraryType(parameterType), lookupElementFactory)
val lookupElement = MyLookupElement.create(name, ArbitraryType(parameterType), lookupElementFactory)!!
val count = lookupElementToCount[lookupElement] ?: 0
lookupElementToCount[lookupElement!!] = count + 1
lookupElementToCount[lookupElement] = count + 1
}
}
}
@@ -159,24 +159,24 @@ class ParameterNameAndTypeCompletion(
}
private abstract class Type(private val idString: String) {
abstract fun createTypeLookupElement(lookupElementFactory: LookupElementFactory): LookupElement?
abstract fun createTypeLookupElement(lookupElementFactory: BasicLookupElementFactory): LookupElement?
override fun equals(other: Any?) = other is Type && other.idString == idString
override fun hashCode() = idString.hashCode()
}
private class DescriptorType(private val classifier: ClassifierDescriptor) : Type(IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classifier)) {
override fun createTypeLookupElement(lookupElementFactory: LookupElementFactory)
= lookupElementFactory.createLookupElement(classifier, useReceiverTypes = false, qualifyNestedClasses = true)
override fun createTypeLookupElement(lookupElementFactory: BasicLookupElementFactory)
= lookupElementFactory.createLookupElement(classifier, qualifyNestedClasses = true)
}
private class JavaClassType(private val psiClass: PsiClass) : Type(psiClass.getQualifiedName()!!) {
override fun createTypeLookupElement(lookupElementFactory: LookupElementFactory)
override fun createTypeLookupElement(lookupElementFactory: BasicLookupElementFactory)
= lookupElementFactory.createLookupElementForJavaClass(psiClass, qualifyNestedClasses = true)
}
private class ArbitraryType(private val type: KotlinType) : Type(IdeDescriptorRenderers.SOURCE_CODE.renderType(type)) {
override fun createTypeLookupElement(lookupElementFactory: LookupElementFactory)
override fun createTypeLookupElement(lookupElementFactory: BasicLookupElementFactory)
= lookupElementFactory.createLookupElementForType(type)
}
@@ -187,7 +187,7 @@ class ParameterNameAndTypeCompletion(
) : LookupElementDecorator<LookupElement>(typeLookupElement) {
companion object {
fun create(parameterName: String, type: Type, factory: LookupElementFactory): LookupElement? {
fun create(parameterName: String, type: Type, factory: BasicLookupElementFactory): LookupElement? {
val typeLookupElement = type.createTypeLookupElement(factory) ?: return null
val lookupElement = MyLookupElement(parameterName, type, typeLookupElement)
return lookupElement.suppressAutoInsertion()
@@ -37,6 +37,8 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
object KotlinClassifierInsertHandler : BaseDeclarationInsertHandler() {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
surroundWithBracesIfInStringTemplate(context)
super.handleInsert(context, item)
val file = context.getFile()
@@ -58,16 +58,20 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() {
) = Normal(inputTypeArguments, inputValueArguments, argumentText, lambdaInfo, argumentsOnly)
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
val document = context.document
if (!argumentsOnly) {
surroundWithBracesIfInStringTemplate(context)
super.handleInsert(context, item)
}
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset) ?: return
val startOffset = context.startOffset
val element = context.file.findElementAt(startOffset) ?: return
addArguments(context, element)
}
@@ -16,8 +16,31 @@
package org.jetbrains.kotlin.idea.completion.handlers
import com.intellij.codeInsight.completion.CompletionInitializationContext
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.openapi.editor.Document
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.lexer.KtTokens
fun surroundWithBracesIfInStringTemplate(context: InsertionContext) {
val startOffset = context.startOffset
val document = context.document
if (startOffset > 0 && document.charsSequence[startOffset - 1] == '$') {
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
if (context.file.findElementAt(startOffset - 1)?.node?.elementType == KtTokens.SHORT_TEMPLATE_ENTRY_START) {
document.insertString(startOffset, "{")
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, startOffset + 1)
val tailOffset = context.tailOffset
document.insertString(tailOffset, "}")
context.tailOffset = tailOffset
}
}
}
fun CharSequence.indexOfSkippingSpace(c: Char, startIndex: Int): Int? {
for (i in startIndex..this.length() - 1) {
@@ -22,8 +22,8 @@ import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.completion.BasicLookupElementFactory
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.idea.completion.createLookupElementForType
import org.jetbrains.kotlin.idea.completion.fuzzyType
import org.jetbrains.kotlin.idea.imports.importableFqName
@@ -36,7 +36,7 @@ object ClassLiteralItems {
public fun addToCollection(
collection: MutableCollection<LookupElement>,
expectedInfos: Collection<ExpectedInfo>,
lookupElementFactory: LookupElementFactory,
lookupElementFactory: BasicLookupElementFactory,
isJvmModule: Boolean
) {
val typeAndSuffixToExpectedInfos = LinkedHashMap<Pair<KotlinType, String>, MutableList<ExpectedInfo>>()
@@ -173,7 +173,7 @@ class SmartCompletion(
}
private fun additionalItemsNoPostProcess(lookupElementFactory: LookupElementFactory): Pair<Collection<LookupElement>, InheritanceItemsSearcher?> {
val asTypePositionItems = buildForAsTypePosition(lookupElementFactory)
val asTypePositionItems = buildForAsTypePosition(lookupElementFactory.basicFactory)
if (asTypePositionItems != null) {
assert(expectedInfos.isEmpty())
return Pair(asTypePositionItems, null)
@@ -209,7 +209,7 @@ class SmartCompletion(
.addToCollection(items, expectedInfos, expression, descriptorsToSkip)
}
ClassLiteralItems.addToCollection(items, expectedInfos, lookupElementFactory, isJvmModule)
ClassLiteralItems.addToCollection(items, expectedInfos, lookupElementFactory.basicFactory, isJvmModule)
if (!forBasicCompletion) {
LambdaItems.addToCollection(items, expectedInfos)
@@ -368,7 +368,7 @@ class SmartCompletion(
}
}
private fun buildForAsTypePosition(lookupElementFactory: LookupElementFactory): Collection<LookupElement>? {
private fun buildForAsTypePosition(lookupElementFactory: BasicLookupElementFactory): Collection<LookupElement>? {
val binaryExpression = ((expression.getParent() as? KtUserType)
?.getParent() as? KtTypeReference)
?.getParent() as? KtBinaryExpressionWithTypeRHS
@@ -64,45 +64,68 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
return
}
if (expression != null) {
addFunctionLiteralArgumentCompletions()
if (expression == null) return
val (additionalItems, inheritanceSearcher) = smartCompletion!!.additionalItems(lookupElementFactory)
collector.addElements(additionalItems)
addFunctionLiteralArgumentCompletions()
if (nameExpression != null) {
val filter = smartCompletion!!.descriptorFilter
if (filter != null) {
val (imported, notImported) = referenceVariants!!
imported.forEach { collector.addElements(filter(it, lookupElementFactory)) }
notImported.forEach { collector.addElements(filter(it, lookupElementFactory), notImported = true) }
var inheritanceSearcher: InheritanceItemsSearcher? = null
val contextVariableTypesForAdditionalItems = withCollectRequiredContextVariableTypes { lookupElementFactory ->
val pair = smartCompletion!!.additionalItems(lookupElementFactory)
collector.addElements(pair.first)
inheritanceSearcher = pair.second
}
val filter = smartCompletion!!.descriptorFilter
var contextVariableTypesForReferenceVariants = filter?.let {
withCollectRequiredContextVariableTypes { lookupElementFactory ->
val (imported, notImported) = referenceVariants ?: return@withCollectRequiredContextVariableTypes
imported.forEach { collector.addElements(filter(it, lookupElementFactory)) }
notImported.forEach { collector.addElements(filter(it, lookupElementFactory), notImported = true) }
}
}
flushToResultSet()
val contextVariablesProvider = RealContextVariablesProvider(referenceVariantsHelper, position)
withContextVariablesProvider(contextVariablesProvider) { lookupElementFactory ->
if (contextVariableTypesForAdditionalItems.any { contextVariablesProvider.functionTypeVariables(it).isNotEmpty() }) {
val additionalItems = smartCompletion!!.additionalItems(lookupElementFactory).first
collector.addElements(additionalItems)
}
if (filter != null && contextVariableTypesForReferenceVariants!!.any { contextVariablesProvider.functionTypeVariables(it).isNotEmpty() }) {
val (imported, notImported) = referenceVariantsWithSingleFunctionTypeParameter()!!
imported.forEach { collector.addElements(filter(it, lookupElementFactory)) }
notImported.forEach { collector.addElements(filter(it, lookupElementFactory), notImported = true) }
}
flushToResultSet()
if (filter != null) {
if (shouldCompleteTopLevelCallablesFromIndex()) {
getTopLevelCallables().forEach { collector.addElements(filter(it, lookupElementFactory), notImported = true) }
flushToResultSet()
}
if (shouldCompleteTopLevelCallablesFromIndex()) {
getTopLevelCallables().forEach { collector.addElements(filter(it, lookupElementFactory), notImported = true) }
if (position.getContainingFile() is KtCodeFragment) {
val variantsAndFactory = getRuntimeReceiverTypeReferenceVariants(lookupElementFactory)
if (variantsAndFactory != null) {
val variants = variantsAndFactory.first
@Suppress("NAME_SHADOWING") val lookupElementFactory = variantsAndFactory.second
variants.imported.forEach { collector.addElements(filter(it, lookupElementFactory).map { it.withReceiverCast() }) }
variants.notImportedExtensions.forEach { collector.addElements(filter(it, lookupElementFactory).map { it.withReceiverCast() }, notImported = true) }
flushToResultSet()
}
if (position.getContainingFile() is KtCodeFragment) {
val variantsAndFactory = getRuntimeReceiverTypeReferenceVariants()
if (variantsAndFactory != null) {
val variants = variantsAndFactory.first
val lookupElementFactory = variantsAndFactory.second
variants.imported.forEach { collector.addElements(filter(it, lookupElementFactory).map { it.withReceiverCast() }) }
variants.notImportedExtensions.forEach { collector.addElements(filter(it, lookupElementFactory).map { it.withReceiverCast() }, notImported = true) }
flushToResultSet()
}
}
}
// it makes no sense to search inheritors if there is no reference because it means that we have prefix like "this@"
inheritanceSearcher?.search({ prefixMatcher.prefixMatches(it) }) {
collector.addElement(it)
flushToResultSet()
}
}
}
// it makes no sense to search inheritors if there is no reference because it means that we have prefix like "this@"
inheritanceSearcher?.search({ prefixMatcher.prefixMatches(it) }) {
collector.addElement(it)
flushToResultSet()
}
}
// special completion for outside parenthesis lambda argument
@@ -0,0 +1,12 @@
class C {
companion object {
fun create(p: (Int) -> Unit) = C()
}
}
val handler: (Int) -> Unit = {}
fun v: C = cr<caret>
// EXIST: { allLookupStrings: "C, create", itemText: "C.create", tailText: " {...} (p: (Int) -> Unit) (<root>)", typeText:"C" }
// EXIST: { allLookupStrings: "C, create", itemText: "C.create", tailText: "(handler) (<root>)", typeText:"C" }
+16 -4
View File
@@ -1,6 +1,18 @@
fun foo(p: (String, Char) -> Unit): String {}
class C {
companion object {
fun foo(p: (Int) -> Unit) = C()
}
}
fun v: String = fo<caret>
fun foo(p: (String, Char) -> Unit): C {}
// EXIST: { lookupString:"foo", itemText: "foo", tailText: "(p: (String, Char) -> Unit) (<root>)", typeText:"String" }
// EXIST: { lookupString:"foo", itemText: "foo", tailText: " { String, Char -> ... } (p: (String, Char) -> Unit) (<root>)", typeText:"String" }
val handler1: (String, Char) -> Unit = {}
val handler2: (Int) -> Unit = {}
fun v: C = fo<caret>
// EXIST: { lookupString:"foo", itemText: "foo", tailText: "(p: (String, Char) -> Unit) (<root>)", typeText:"C" }
// EXIST: { lookupString:"foo", itemText: "foo", tailText: " { String, Char -> ... } (p: (String, Char) -> Unit) (<root>)", typeText:"C" }
// EXIST: { lookupString:"foo", itemText: "foo", tailText: "(handler1) (<root>)", typeText:"C" }
// EXIST: { allLookupStrings: "C, foo", itemText: "C.foo", tailText: " {...} (p: (Int) -> Unit) (<root>)", typeText:"C" }
// EXIST: { allLookupStrings: "C, foo", itemText: "C.foo", tailText: "(handler2) (<root>)", typeText:"C" }
@@ -1389,6 +1389,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("ContextVariables3.kt")
public void testContextVariables3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/ContextVariables3.kt");
doTest(fileName);
}
@TestMetadata("ContextVariablesFilter.kt")
public void testContextVariablesFilter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/ContextVariablesFilter.kt");
@@ -1389,6 +1389,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("ContextVariables3.kt")
public void testContextVariables3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/ContextVariables3.kt");
doTest(fileName);
}
@TestMetadata("ContextVariablesFilter.kt")
public void testContextVariablesFilter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/ContextVariablesFilter.kt");