From c0bfca4f89f77708de618cce679865fcb7865da5 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 1 Oct 2015 14:04:48 +0300 Subject: [PATCH] A::class and A::class.java supported in smart completion too --- .../codeInsight/ReferenceVariantsHelper.kt | 1 + .../jetbrains/kotlin/idea/util/CallType.kt | 3 + .../idea/completion/BasicCompletionSession.kt | 48 +++++--- .../idea/completion/CompletionSession.kt | 12 +- .../kotlin/idea/completion/ExpectedInfos.kt | 6 +- .../idea/completion/InsertHandlerProvider.kt | 6 +- .../kotlin/idea/completion/KeywordValues.kt | 114 ++++++++++++++++++ .../idea/completion/LookupElementFactory.kt | 6 +- .../kotlin/idea/completion/Weighers.kt | 44 ++----- .../idea/completion/smart/KeywordValues.kt | 79 ------------ .../idea/completion/smart/SmartCompletion.kt | 65 +++++++--- .../smart/SmartCompletionSession.kt | 6 +- .../kotlin/idea/completion/smart/Utils.kt | 1 + .../testData/smart/EqOperator.kt | 5 +- .../smart/callableReference/ClassLiteral.kt | 10 ++ .../callableReference/ClassLiteralDotJava.kt | 8 ++ .../test/JvmSmartCompletionTestGenerated.java | 12 ++ 17 files changed, 265 insertions(+), 161 deletions(-) create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordValues.kt delete mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt create mode 100644 idea/idea-completion/testData/smart/callableReference/ClassLiteral.kt create mode 100644 idea/idea-completion/testData/smart/callableReference/ClassLiteralDotJava.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt index 81ccad67067..d9c68070891 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt @@ -112,6 +112,7 @@ public class ReferenceVariantsHelper( is CallTypeAndReceiver.SAFE -> receiverExpression = callTypeAndReceiver.receiver is CallTypeAndReceiver.INFIX -> receiverExpression = callTypeAndReceiver.receiver is CallTypeAndReceiver.UNARY -> receiverExpression = null // can it happen at all? + is CallTypeAndReceiver.UNKNOWN -> return emptyList() else -> throw RuntimeException() //TODO: see KT-9394 } diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt index 24a78194c07..b29236c2d99 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt @@ -28,6 +28,8 @@ import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter public sealed class CallType(val descriptorKindFilter: DescriptorKindFilter) { + object UNKNOWN : CallType(DescriptorKindFilter.ALL) + object DEFAULT : CallType(DescriptorKindFilter.ALL) object DOT : CallType(DescriptorKindFilter.ALL) @@ -78,6 +80,7 @@ public sealed class CallTypeAndReceiver(CallType.UNKNOWN, null) object DEFAULT : CallTypeAndReceiver(CallType.DEFAULT, null) class DOT(receiver: JetExpression) : CallTypeAndReceiver(CallType.DOT, receiver) class SAFE(receiver: JetExpression) : CallTypeAndReceiver(CallType.SAFE, receiver) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 337e9597904..4d0ff9d0b4e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -20,7 +20,7 @@ import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.CompletionSorter import com.intellij.codeInsight.completion.CompletionType -import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.template.TemplateManager import com.intellij.patterns.PatternCondition import com.intellij.patterns.StandardPatterns @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassifierDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.completion.smart.ExpectedInfoClassification import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionItemPriority @@ -47,6 +48,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.util.* class BasicCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, @@ -103,8 +105,9 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, private val smartCompletion = expression?.let { SmartCompletion( - it, resolutionFacade, bindingContext, isVisibleFilter, prefixMatcher, - GlobalSearchScope.EMPTY_SCOPE, toFromOriginalFileMapper, lookupElementFactory, callTypeAndReceiver, forBasicCompletion = true + it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, prefixMatcher, + GlobalSearchScope.EMPTY_SCOPE, toFromOriginalFileMapper, lookupElementFactory, callTypeAndReceiver, + isJvmModule, forBasicCompletion = true ) } @@ -272,9 +275,31 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } private fun completeKeywords() { + val keywordsToSkip = HashSet() + + val keywordValueConsumer = object : KeywordValues.Consumer { + override fun consume(lookupString: String, expectedInfoClassifier: (ExpectedInfo) -> ExpectedInfoClassification, priority: SmartCompletionItemPriority, factory: () -> LookupElement) { + keywordsToSkip.add(lookupString) + val lookupElement = factory() + val matched = expectedInfos.any { + val classification = expectedInfoClassifier(it) + assert(!classification.makeNotNullable) { "Nullable keyword values not supported" } + classification.isMatch() + } + if (matched) { + lookupElement.putUserData(SmartCompletionInBasicWeigher.KEYWORD_VALUE_MATCHED_KEY, matched) + lookupElement.putUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY, priority) + } + collector.addElement(lookupElement) + } + } + KeywordValues.process(keywordValueConsumer, callTypeAndReceiver, bindingContext, resolutionFacade, moduleDescriptor, isJvmModule) + val keywordsPrefix = prefix.substringBefore('@') // if there is '@' in the prefix - use shorter prefix to not loose 'this' etc KeywordCompletion.complete(expression ?: parameters.getPosition(), keywordsPrefix) { lookupElement -> - val keyword = lookupElement.getLookupString() + val keyword = lookupElement.lookupString + if (keyword in keywordsToSkip) return@complete + when (keyword) { // if "this" is parsed correctly in the current context - insert it and all this@xxx items "this" -> { @@ -307,20 +332,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } "class" -> { - if (callTypeAndReceiver is CallTypeAndReceiver.CALLABLE_REFERENCE) { - if (callTypeAndReceiver.receiver != null) { - collector.addElement(lookupElement) - - if (!ProjectStructureUtil.isJsKotlinModule(parameters.originalFile as JetFile)) { - val element = LookupElementBuilder.create(KeywordLookupObject(), "class.java") - .withPresentableText("class") - .withTailText(".java") - .bold() - collector.addElement(element) - } - } - } - else { + if (callTypeAndReceiver !is CallTypeAndReceiver.CALLABLE_REFERENCE) { // otherwise it should be handled by KeywordValues collector.addElement(lookupElement) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index 2dfe2cf5218..37955dc6375 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper import org.jetbrains.kotlin.idea.core.* import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.project.ProjectStructureUtil import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.resolve.frontendService import org.jetbrains.kotlin.idea.util.CallType @@ -74,6 +75,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC protected val resolutionFacade = file.getResolutionFacade() protected val moduleDescriptor = resolutionFacade.moduleDescriptor protected val project = position.getProject() + protected val isJvmModule = !ProjectStructureUtil.isJsKotlinModule(parameters.originalFile as JetFile) protected val nameExpression: JetSimpleNameExpression? protected val expression: JetExpression? @@ -292,8 +294,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC } private fun Collection.filterShadowedNonImported(): Collection { - val callTypeAndReceiver = CallTypeAndReceiver.detect(nameExpression!!) - return ShadowedDeclarationsFilter(bindingContext, resolutionFacade, nameExpression, callTypeAndReceiver) + return ShadowedDeclarationsFilter(bindingContext, resolutionFacade, nameExpression!!, callTypeAndReceiver) .filterNonImported(this, referenceVariants) } @@ -305,7 +306,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC ) } - private fun createLookupElementFactory(callType: CallType<*>, receiverTypes: Collection?): LookupElementFactory { + private fun createLookupElementFactory(callType: CallType<*>?, receiverTypes: Collection?): LookupElementFactory { val contextVariablesProvider = { nameExpression?.let { referenceVariantsHelper.getReferenceVariants(it, DescriptorKindFilter.VARIABLES, { true }, CallTypeAndReceiver.DEFAULT) @@ -321,7 +322,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC private fun detectCallTypeAndReceiverTypes(): Pair, Collection?> { if (nameExpression == null) { - return CallTypeAndReceiver.DEFAULT to null + return CallTypeAndReceiver.UNKNOWN to null } val callTypeAndReceiver = CallTypeAndReceiver.detect(nameExpression) @@ -346,7 +347,8 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC is CallTypeAndReceiver.IMPORT_DIRECTIVE, is CallTypeAndReceiver.PACKAGE_DIRECTIVE, - is CallTypeAndReceiver.TYPE -> + is CallTypeAndReceiver.TYPE, + is CallTypeAndReceiver.UNKNOWN -> // we don't need to highlight immediate members in these cases return callTypeAndReceiver to null diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt index ec214d98c92..249928a1bcb 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt @@ -145,6 +145,8 @@ sealed class ArgumentPositionData(val function: FunctionDescriptor) : ExpectedIn class ReturnValueAdditionalData(val callable: CallableDescriptor) : ExpectedInfo.AdditionalData +object WhenEntryAdditionalData : ExpectedInfo.AdditionalData + class ExpectedInfos( val bindingContext: BindingContext, val resolutionFacade: ResolutionFacade, @@ -489,10 +491,10 @@ class ExpectedInfos( val subject = whenExpression.getSubjectExpression() if (subject != null) { val subjectType = bindingContext.getType(subject) ?: return null - return listOf(ExpectedInfo(subjectType, null, null)) + return listOf(ExpectedInfo(subjectType, null, null, additionalData = WhenEntryAdditionalData)) } else { - return listOf(ExpectedInfo(resolutionFacade.moduleDescriptor.builtIns.booleanType, null, null)) + return listOf(ExpectedInfo(resolutionFacade.moduleDescriptor.builtIns.booleanType, null, null, additionalData = WhenEntryAdditionalData)) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt index bae975f40e6..53323c48d16 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt @@ -27,12 +27,16 @@ import org.jetbrains.kotlin.types.JetType import java.util.* class InsertHandlerProvider( - private val callType: CallType<*>, + private val callType: CallType<*>?, expectedInfosCalculator: () -> Collection ) { private val expectedInfos by lazy(LazyThreadSafetyMode.NONE) { expectedInfosCalculator() } public fun insertHandler(descriptor: DeclarationDescriptor): InsertHandler { + if (callType == null) { + error("Cannot create InsertHandler when no CallType known") + } + return when (descriptor) { is FunctionDescriptor -> { val needTypeArguments = needTypeArguments(descriptor) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordValues.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordValues.kt new file mode 100644 index 00000000000..99d9a57f891 --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordValues.kt @@ -0,0 +1,114 @@ +/* + * 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.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference +import org.jetbrains.kotlin.idea.completion.smart.ExpectedInfoClassification +import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionItemPriority +import org.jetbrains.kotlin.idea.completion.smart.classifyExpectedInfo +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver +import org.jetbrains.kotlin.idea.util.FuzzyType +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.types.JetTypeImpl +import org.jetbrains.kotlin.types.TypeProjectionImpl +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean + +object KeywordValues { + interface Consumer { + fun consume( + lookupString: String, + expectedInfoClassifier: (ExpectedInfo) -> ExpectedInfoClassification, + priority: SmartCompletionItemPriority, + factory: () -> LookupElement) + } + + fun process( + consumer: Consumer, + callTypeAndReceiver: CallTypeAndReceiver<*, *>, + bindingContext: BindingContext, + resolutionFacade: ResolutionFacade, + moduleDescriptor: ModuleDescriptor, + isJvmModule: Boolean + ) { + if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) { + val booleanInfoClassifier = classifier@ { info: ExpectedInfo -> + // no sense in true or false as when entry + if (info.additionalData is WhenEntryAdditionalData) return@classifier ExpectedInfoClassification.noMatch + + if (info.fuzzyType?.type?.isBooleanOrNullableBoolean() ?: false) + ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) + else + ExpectedInfoClassification.noMatch + } + consumer.consume("true", booleanInfoClassifier, SmartCompletionItemPriority.TRUE) { + LookupElementBuilder.create(KeywordLookupObject(), "true").bold() + } + consumer.consume("false", booleanInfoClassifier, SmartCompletionItemPriority.FALSE) { + LookupElementBuilder.create(KeywordLookupObject(), "false").bold() + } + + val nullClassifier = { info: ExpectedInfo -> + if (info.fuzzyType != null && info.fuzzyType!!.type.isMarkedNullable()) + ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) + else + ExpectedInfoClassification.noMatch + } + consumer.consume("null", nullClassifier, SmartCompletionItemPriority.NULL) { + LookupElementBuilder.create(KeywordLookupObject(), "null").bold() + } + } + + if (callTypeAndReceiver is CallTypeAndReceiver.CALLABLE_REFERENCE && callTypeAndReceiver.receiver != null) { + val qualifierType = bindingContext[BindingContext.TYPE, callTypeAndReceiver.receiver] + if (qualifierType != null) { + val kClassDescriptor = resolutionFacade.getFrontendService(ReflectionTypes::class.java).kClass + val classLiteralType = JetTypeImpl.create(Annotations.EMPTY, kClassDescriptor, false, listOf(TypeProjectionImpl(qualifierType))) + val kClassTypes = listOf(FuzzyType(classLiteralType, emptyList())) + val kClassClassifier = { info: ExpectedInfo -> kClassTypes.classifyExpectedInfo(info) } + consumer.consume("class", kClassClassifier, SmartCompletionItemPriority.CLASS_LITERAL) { + LookupElementBuilder.create(KeywordLookupObject(), "class").bold() + } + + if (isJvmModule) { + val javaLangClassDescriptor = resolutionFacade.resolveImportReference(moduleDescriptor, FqName("java.lang.Class")) + .singleOrNull() as? ClassDescriptor + + if (javaLangClassDescriptor != null) { + val javaLangClassType = JetTypeImpl.create(Annotations.EMPTY, javaLangClassDescriptor, false, listOf(TypeProjectionImpl(qualifierType))) + val javaClassTypes = listOf(FuzzyType(javaLangClassType, emptyList())) + val javaClassClassifier = { info: ExpectedInfo -> javaClassTypes.classifyExpectedInfo(info) } + consumer.consume("class", javaClassClassifier, SmartCompletionItemPriority.CLASS_LITERAL) { + LookupElementBuilder.create(KeywordLookupObject(), "class.java") + .withPresentableText("class") + .withTailText(".java") + .bold() + } + } + } + } + } + } +} diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt index 824dc9ec452..908468e1683 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt @@ -45,7 +45,7 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf class LookupElementFactory( private val resolutionFacade: ResolutionFacade, private val receiverTypes: Collection?, - private val callType: CallType<*>, + private val callType: CallType<*>?, private val isInStringTemplateAfterDollar: Boolean, public val insertHandlerProvider: InsertHandlerProvider, contextVariablesProvider: () -> Collection @@ -132,7 +132,7 @@ class LookupElementFactory( } override fun handleInsert(context: InsertionContext) { - KotlinFunctionInsertHandler(callType, inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this) + KotlinFunctionInsertHandler(callType!!, inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this) } } @@ -175,7 +175,7 @@ class LookupElementFactory( } override fun handleInsert(context: InsertionContext) { - KotlinFunctionInsertHandler(callType, inputTypeArguments = needTypeArguments, inputValueArguments = false, argumentText = argumentText).handleInsert(context, this) + KotlinFunctionInsertHandler(callType!!, inputTypeArguments = needTypeArguments, inputValueArguments = false, argumentText = argumentText).handleInsert(context, this) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt index c9fe54e7097..53e0b294b44 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt @@ -21,6 +21,7 @@ import com.intellij.codeInsight.completion.CompletionWeigher import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementWeigher import com.intellij.codeInsight.lookup.WeighingContext +import com.intellij.openapi.util.Key import com.intellij.psi.util.proximity.PsiProximityComparator import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind @@ -33,9 +34,6 @@ import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject 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.types.typeUtil.TypeNullability -import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean -import org.jetbrains.kotlin.types.typeUtil.nullability object PriorityWeigher : LookupElementWeigher("kotlin.priority") { override fun weigh(element: LookupElement, context: WeighingContext) @@ -184,26 +182,32 @@ class SmartCompletionInBasicWeigher( private val callType: CallType<*>, private val resolutionFacade: ResolutionFacade ) : LookupElementWeigher("kotlin.smartInBasic", true, false) { + + companion object { + val KEYWORD_VALUE_MATCHED_KEY = Key("SmartCompletionInBasicWeigher.KEYWORD_VALUE_MATCHED_KEY") + } + private val descriptorsToSkip = smartCompletion.descriptorsToSkip private val expectedInfos = smartCompletion.expectedInfos - private fun fullMatchWeight(nameSimilarity: Int) = (3L shl 32) + nameSimilarity * 3 // true and false should be in between zero-nameSimilarity and 1-nameSimilarity - - private val MATCHED_TRUE_WEIGHT = (3L shl 32) + 2 - private val MATCHED_FALSE_WEIGHT = (3L shl 32) + 1 + private fun fullMatchWeight(nameSimilarity: Int) = (3L shl 32) + nameSimilarity private fun ifNotNullMatchWeight(nameSimilarity: Int) = (2L shl 32) + nameSimilarity private fun smartCompletionItemWeight(nameSimilarity: Int) = (1L shl 32) + nameSimilarity - private val MATCHED_NULL_WEIGHT = 1L - private val NO_MATCH_WEIGHT = 0L private val DESCRIPTOR_TO_SKIP_WEIGHT = -1L // if descriptor is skipped from smart completion then it's probably irrelevant override fun weigh(element: LookupElement): Long { + val keywordValueMatched = element.getUserData(KEYWORD_VALUE_MATCHED_KEY) val smartCompletionPriority = element.getUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY) + + if (keywordValueMatched != null) { + return if (keywordValueMatched) fullMatchWeight(0) else NO_MATCH_WEIGHT + } + if (smartCompletionPriority != null) { // it's an "additional item" came from smart completion, don't match it against expected type return smartCompletionItemWeight(element.getUserData(NAME_SIMILARITY_KEY) ?: 0) } @@ -214,28 +218,6 @@ class SmartCompletionInBasicWeigher( if (expectedInfos.isEmpty()) return NO_MATCH_WEIGHT - if (o is KeywordLookupObject) { - when (element.lookupString) { - "true", "false" -> { - if (expectedInfos.any { it.fuzzyType?.type?.isBooleanOrNullableBoolean() ?: false }) { - return if (element.lookupString == "true") MATCHED_TRUE_WEIGHT else MATCHED_FALSE_WEIGHT - } - else { - return NO_MATCH_WEIGHT - } - } - - "null" -> { - if (expectedInfos.any { it.fuzzyType?.type?.nullability()?.let { it != TypeNullability.NOT_NULL } ?: false }) { - return MATCHED_NULL_WEIGHT - } - else { - return NO_MATCH_WEIGHT - } - } - } - } - val smartCastCalculator = smartCompletion.smartCastCalculator val (fuzzyTypes, name) = when (o) { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt deleted file mode 100644 index bad777a2874..00000000000 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.smart - -import com.intellij.codeInsight.completion.InsertionContext -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.codeInsight.lookup.LookupElementDecorator -import org.jetbrains.kotlin.idea.completion.COMPARISON_TOKENS -import org.jetbrains.kotlin.idea.completion.ExpectedInfo -import org.jetbrains.kotlin.idea.completion.fuzzyType -import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.types.TypeSubstitutor -import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean -import org.jetbrains.kotlin.utils.addToStdlib.singletonList - -object KeywordValues { - public fun addToCollection(collection: MutableCollection, expectedInfos: Collection, expressionWithType: JetExpression) { - var skipTrueFalse = false - - val whenCondition = expressionWithType.getParent() as? JetWhenConditionWithExpression - if (whenCondition != null) { - val entry = whenCondition.getParent() as JetWhenEntry - val whenExpression = entry.getParent() as JetWhenExpression - val entries = whenExpression.getEntries() - if (whenExpression.getElseExpression() == null && entry == entries.last() && entries.size() != 1) { - val lookupElement = LookupElementBuilder.create("else").bold().withTailText(" ->") - collection.add(object: LookupElementDecorator(lookupElement) { - override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler("->", spaceBefore = true, spaceAfter = true).handleInsert(context, getDelegate()) - } - }) - } - if (whenExpression.getSubjectExpression() == null) { // no sense in true or false entries for when with no subject - skipTrueFalse = true - } - } - - if (!skipTrueFalse) { - val booleanInfoClassifier = { info: ExpectedInfo -> - if (info.fuzzyType?.type?.isBooleanOrNullableBoolean() ?: false) ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.noMatch - } - collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE).singletonList() } - collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE).singletonList() } - } - - if (!shouldSkipNull(expressionWithType)) { - val classifier = { info: ExpectedInfo -> - if (info.fuzzyType != null && info.fuzzyType!!.type.isMarkedNullable()) - ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) - else - ExpectedInfoClassification.noMatch - } - collection.addLookupElements(null, expectedInfos, classifier) { - LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL).singletonList() - } - } - } - - private fun shouldSkipNull(expressionWithType: JetExpression): Boolean { - val binaryExpression = expressionWithType.parent as? JetBinaryExpression ?: return false - return binaryExpression.operationToken in COMPARISON_TOKENS - } -} diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt index b9bb4181f69..127be417c8c 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt @@ -19,15 +19,13 @@ package org.jetbrains.kotlin.idea.completion.smart import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.completion.OffsetKey import com.intellij.codeInsight.completion.PrefixMatcher -import com.intellij.codeInsight.lookup.Lookup -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupElementDecorator -import com.intellij.codeInsight.lookup.LookupElementPresentation +import com.intellij.codeInsight.lookup.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.SmartList import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.completion.* +import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver import org.jetbrains.kotlin.idea.util.FuzzyType @@ -52,12 +50,14 @@ class SmartCompletion( private val expression: JetExpression, private val resolutionFacade: ResolutionFacade, private val bindingContext: BindingContext, + private val moduleDescriptor: ModuleDescriptor, private val visibilityFilter: (DeclarationDescriptor) -> Boolean, private val prefixMatcher: PrefixMatcher, private val inheritorSearchScope: GlobalSearchScope, private val toFromOriginalFileMapper: ToFromOriginalFileMapper, private val lookupElementFactory: LookupElementFactory, private val callTypeAndReceiver: CallTypeAndReceiver<*, *>, + private val isJvmModule: Boolean, private val forBasicCompletion: Boolean = false ) { private val expressionWithType = when (callTypeAndReceiver) { @@ -150,10 +150,11 @@ class SmartCompletion( } private fun filterDescriptor(descriptor: DeclarationDescriptor): Collection { + val callType = callTypeAndReceiver.callType if (descriptor in descriptorsToSkip) return emptyList() val result = SmartList() - val types = descriptor.fuzzyTypesForSmartCompletion(smartCastCalculator, callTypeAndReceiver.callType, resolutionFacade) + val types = descriptor.fuzzyTypesForSmartCompletion(smartCastCalculator, callType, resolutionFacade) val infoClassifier = { expectedInfo: ExpectedInfo -> types.classifyExpectedInfo(expectedInfo) } result.addLookupElements(descriptor, expectedInfos, infoClassifier, noNameSimilarityForReturnItself = callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) { descriptor -> @@ -177,23 +178,53 @@ class SmartCompletion( val items = ArrayList() val inheritanceSearchers = ArrayList() - if (expectedInfos.isNotEmpty() && callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) { - TypeInstantiationItems(resolutionFacade, bindingContext, visibilityFilter, toFromOriginalFileMapper, inheritorSearchScope, lookupElementFactory, forBasicCompletion) - .addTo(items, inheritanceSearchers, expectedInfos) - - if (expression is JetSimpleNameExpression) { - StaticMembers(bindingContext, lookupElementFactory).addToCollection(items, expectedInfos, expression, descriptorsToSkip) + if (!forBasicCompletion) { // basic completion adds keyword values on its own + val keywordValueConsumer = object : KeywordValues.Consumer { + override fun consume(lookupString: String, expectedInfoClassifier: (ExpectedInfo) -> ExpectedInfoClassification, priority: SmartCompletionItemPriority, factory: () -> LookupElement) { + items.addLookupElements(null, expectedInfos, expectedInfoClassifier) { + val lookupElement = factory() + lookupElement.putUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY, priority) + listOf(lookupElement) + } + } } + KeywordValues.process(keywordValueConsumer, callTypeAndReceiver, bindingContext, resolutionFacade, moduleDescriptor, isJvmModule) + } - if (!forBasicCompletion) { + if (expectedInfos.isNotEmpty()) { + if (!forBasicCompletion && (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT || callTypeAndReceiver is CallTypeAndReceiver.UNKNOWN /* after this@ */)) { items.addThisItems(expression, expectedInfos, smartCastCalculator) - - LambdaItems.addToCollection(items, expectedInfos) - - KeywordValues.addToCollection(items, expectedInfos, expression) } - MultipleArgumentsItemProvider(bindingContext, smartCastCalculator).addToCollection(items, expectedInfos, expression) + if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) { + TypeInstantiationItems(resolutionFacade, bindingContext, visibilityFilter, toFromOriginalFileMapper, inheritorSearchScope, lookupElementFactory, forBasicCompletion) + .addTo(items, inheritanceSearchers, expectedInfos) + + if (expression is JetSimpleNameExpression) { + StaticMembers(bindingContext, lookupElementFactory).addToCollection(items, expectedInfos, expression, descriptorsToSkip) + } + + if (!forBasicCompletion) { + LambdaItems.addToCollection(items, expectedInfos) + + val whenCondition = expressionWithType.parent as? JetWhenConditionWithExpression + if (whenCondition != null) { + val entry = whenCondition.parent as JetWhenEntry + val whenExpression = entry.parent as JetWhenExpression + val entries = whenExpression.entries + if (whenExpression.elseExpression == null && entry == entries.last() && entries.size() != 1) { + val lookupElement = LookupElementBuilder.create("else").bold().withTailText(" ->") + items.add(object: LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + WithTailInsertHandler("->", spaceBefore = true, spaceAfter = true).handleInsert(context, delegate) + } + }) + } + } + } + + MultipleArgumentsItemProvider(bindingContext, smartCastCalculator).addToCollection(items, expectedInfos, expression) + } } val inheritanceSearcher = if (inheritanceSearchers.isNotEmpty()) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt index 818bfd64475..4fe2a7658e9 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt @@ -39,9 +39,9 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para private val smartCompletion by lazy(LazyThreadSafetyMode.NONE) { expression?.let { - SmartCompletion(it, resolutionFacade, - bindingContext, isVisibleFilter, prefixMatcher, originalSearchScope, - toFromOriginalFileMapper, lookupElementFactory, callTypeAndReceiver) + SmartCompletion(it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, + prefixMatcher, originalSearchScope, toFromOriginalFileMapper, lookupElementFactory, + callTypeAndReceiver, isJvmModule) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt index 786bcb53405..24b780727b7 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt @@ -289,6 +289,7 @@ enum class SmartCompletionItemPriority { IT, TRUE, FALSE, + CLASS_LITERAL, THIS, DEFAULT, NULLABLE, diff --git a/idea/idea-completion/testData/smart/EqOperator.kt b/idea/idea-completion/testData/smart/EqOperator.kt index 647332d4681..9e9540425bc 100644 --- a/idea/idea-completion/testData/smart/EqOperator.kt +++ b/idea/idea-completion/testData/smart/EqOperator.kt @@ -7,10 +7,11 @@ fun f(e1: E, e2: E?, x: Any) { if (e1 == } +// WITH_ORDER +// EXIST: { itemText:"e2" } // EXIST: { lookupString:"A", itemText:"E.A", typeText:"E" } // EXIST: { lookupString:"B", itemText:"E.B", typeText:"E" } -// EXIST: { itemText:"e2" } +// EXIST: null // ABSENT: { itemText:"!! e2" } // ABSENT: e1 // ABSENT: x -// ABSENT: null diff --git a/idea/idea-completion/testData/smart/callableReference/ClassLiteral.kt b/idea/idea-completion/testData/smart/callableReference/ClassLiteral.kt new file mode 100644 index 00000000000..5cc0c7e44ea --- /dev/null +++ b/idea/idea-completion/testData/smart/callableReference/ClassLiteral.kt @@ -0,0 +1,10 @@ +import kotlin.reflect.KClass + +fun foo(p: KClass){} + +fun bar() { + foo(String::) +} + +// EXIST: { lookupString: "class", itemText: "class", attributes: "bold" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/smart/callableReference/ClassLiteralDotJava.kt b/idea/idea-completion/testData/smart/callableReference/ClassLiteralDotJava.kt new file mode 100644 index 00000000000..4a84b37fd35 --- /dev/null +++ b/idea/idea-completion/testData/smart/callableReference/ClassLiteralDotJava.kt @@ -0,0 +1,8 @@ +fun foo(p: Class){} + +fun bar() { + foo(String::) +} + +// EXIST_JAVA_ONLY: { lookupString: "class.java", itemText: "class", tailText: ".java", attributes: "bold" } +// NOTHING_ELSE diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java index 7d3f0c4cc5f..4fdf1baa16f 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java @@ -595,6 +595,18 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/smart/callableReference"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("ClassLiteral.kt") + public void testClassLiteral() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/callableReference/ClassLiteral.kt"); + doTest(fileName); + } + + @TestMetadata("ClassLiteralDotJava.kt") + public void testClassLiteralDotJava() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/callableReference/ClassLiteralDotJava.kt"); + doTest(fileName); + } + @TestMetadata("EmptyQualifier1.kt") public void testEmptyQualifier1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/callableReference/EmptyQualifier1.kt");