From 3b0fe8831b0364b9610a00ab51c1795b3435b5b3 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 26 Dec 2014 02:09:08 +0300 Subject: [PATCH] Code completion of "this@..." #KT-2035 Fixed --- .../plugin/completion/CompletionSession.kt | 116 +++++++++++++----- .../jet/plugin/completion/CompletionUtils.kt | 66 +++++++++- .../plugin/completion/KeywordCompletion.kt | 18 ++- .../completion/KotlinCompletionCharFilter.kt | 2 +- .../completion/smart/SmartCompletion.kt | 29 +++-- .../jet/plugin/completion/smart/ThisItems.kt | 73 ----------- .../handlers/charFilter/QualifiedThis.kt | 11 ++ .../charFilter/QualifiedThis.kt.after | 11 ++ .../completion/keywords/InArgumentList.kt | 3 +- .../completion/keywords/InCodeBlock.kt | 3 +- .../keywords/InFunctionExpressionBody.kt | 3 +- .../keywords/InGetterExpressionBody.kt | 3 +- .../keywords/InParameterDefaultValue.kt | 3 +- .../keywords/InPropertyInitializer.kt | 3 +- .../completion/keywords/QualifiedThis.kt | 13 ++ idea/testData/completion/keywords/This.kt | 28 +++++ .../completion/keywords/ThisPrefixMatching.kt | 12 ++ .../completion/smart/QualifiedThis.kt | 14 ++- idea/testData/completion/smart/This.kt | 31 ++++- .../KeywordCompletionTestGenerated.java | 18 +++ .../CompletionCharFilterTestGenerated.java | 7 +- 21 files changed, 321 insertions(+), 146 deletions(-) delete mode 100644 idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt create mode 100644 idea/testData/completion/handlers/charFilter/QualifiedThis.kt create mode 100644 idea/testData/completion/handlers/charFilter/QualifiedThis.kt.after create mode 100644 idea/testData/completion/keywords/QualifiedThis.kt create mode 100644 idea/testData/completion/keywords/This.kt create mode 100644 idea/testData/completion/keywords/ThisPrefixMatching.kt diff --git a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.kt b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.kt index b304e1b7089..d2ede063b53 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.kt @@ -41,9 +41,11 @@ import kotlin.properties.Delegates import org.jetbrains.jet.lang.psi.psiUtil.getStrictParentOfType import org.jetbrains.jet.plugin.util.makeNotNullable import org.jetbrains.jet.plugin.util.CallType -import org.jetbrains.jet.plugin.completion.isVisible import org.jetbrains.jet.lang.resolve.scopes.DescriptorKindExclude import org.jetbrains.jet.lang.resolve.lazy.BodyResolveMode +import com.intellij.patterns.StandardPatterns +import com.intellij.util.ProcessingContext +import com.intellij.patterns.PatternCondition class CompletionSessionConfiguration( val completeNonImportedDeclarations: Boolean, @@ -57,17 +59,49 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess protected val parameters: CompletionParameters, resultSet: CompletionResultSet) { protected val position: PsiElement = parameters.getPosition() - protected val jetReference: JetSimpleNameReference? = position.getParent()?.getReferences()?.firstIsInstanceOrNull() private val file = position.getContainingFile() as JetFile protected val resolutionFacade: ResolutionFacade = file.getResolutionFacade() protected val moduleDescriptor: ModuleDescriptor = resolutionFacade.findModuleDescriptor(file) - protected val bindingContext: BindingContext? = jetReference?.let { resolutionFacade.analyze(it.expression, BodyResolveMode.PARTIAL_FOR_COMPLETION) } - protected val inDescriptor: DeclarationDescriptor? = jetReference?.let { bindingContext!!.get(BindingContext.RESOLUTION_SCOPE, it.expression)?.getContainingDeclaration() } - // set prefix matcher here to override default one which relies on CompletionUtil.findReferencePrefix() - // which sometimes works incorrectly for Kotlin + protected val reference: JetSimpleNameReference? + protected val expression: JetExpression? + + ;{ + val reference = position.getParent()?.getReferences()?.firstIsInstanceOrNull() + if (reference != null) { + if (reference.expression is JetLabelReferenceExpression) { + this.expression = reference.expression.getParent().getParent() as? JetThisExpression + this.reference = null + } + else { + this.expression = reference.expression + this.reference = reference + } + } + else { + this.reference = null + this.expression = null + } + } + + protected val bindingContext: BindingContext? = expression?.let { resolutionFacade.analyze(it, BodyResolveMode.PARTIAL_FOR_COMPLETION) } + protected val inDescriptor: DeclarationDescriptor? = expression?.let { bindingContext!!.get(BindingContext.RESOLUTION_SCOPE, it)?.getContainingDeclaration() } + + protected val prefix: String = CompletionUtil.findIdentifierPrefix( + parameters.getPosition().getContainingFile(), + parameters.getOffset(), + StandardPatterns.or(StandardPatterns.character().javaIdentifierPart(), + StandardPatterns.character().with( + object : PatternCondition("@") { + override fun accepts(c: Char?, context: ProcessingContext?): Boolean { + return c == '@' + } + }) + ), + StandardPatterns.character().javaIdentifierStart()) + protected val resultSet: CompletionResultSet = resultSet - .withPrefixMatcher(CompletionUtil.findJavaIdentifierPrefix(parameters)) + .withPrefixMatcher(prefix) .addKotlinSorting(parameters) protected val prefixMatcher: PrefixMatcher = this.resultSet.getPrefixMatcher() @@ -76,8 +110,8 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess = if (bindingContext != null) ReferenceVariantsHelper(bindingContext) { isVisibleDescriptor(it) } else null protected val lookupElementFactory: LookupElementFactory = run { - val receiverTypes = if (jetReference != null) { - val expression = jetReference.expression + val receiverTypes = if (reference != null) { + val expression = reference.expression val (receivers, callType) = referenceVariantsHelper!!.getReferenceVariantsReceivers(expression) val dataFlowInfo = bindingContext!!.getDataFlowInfo(expression) var receiverTypes = receivers.flatMap { @@ -113,7 +147,7 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess if (configuration.completeNonAccessibleDeclarations) return true if (descriptor is DeclarationDescriptorWithVisibility && inDescriptor != null) { - return descriptor.isVisible(inDescriptor, bindingContext, jetReference?.expression) + return descriptor.isVisible(inDescriptor, bindingContext, reference?.expression) } return true @@ -135,7 +169,7 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess private var alreadyAddedDescriptors: Collection by Delegates.notNull() fun getReferenceVariants(kindFilter: DescriptorKindFilter, shouldCastToRuntimeType: Boolean): Collection { - val descriptors = referenceVariantsHelper!!.getReferenceVariants(jetReference!!.expression, kindFilter, shouldCastToRuntimeType, prefixMatcher.asNameFilter()) + val descriptors = referenceVariantsHelper!!.getReferenceVariants(reference!!.expression, kindFilter, shouldCastToRuntimeType, prefixMatcher.asNameFilter()) if (!shouldCastToRuntimeType) { if (position.getContainingFile() is JetCodeFragment) { alreadyAddedDescriptors = descriptors @@ -160,13 +194,13 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess } protected fun shouldRunExtensionsCompletion(): Boolean - = configuration.completeNonImportedDeclarations || prefixMatcher.getPrefix().length >= 3 + = configuration.completeNonImportedDeclarations || prefixMatcher.getPrefix().length() >= 3 protected fun getKotlinTopLevelCallables(): Collection - = indicesHelper.getTopLevelCallables({ prefixMatcher.prefixMatches(it) }, jetReference!!.expression) + = indicesHelper.getTopLevelCallables({ prefixMatcher.prefixMatches(it) }, reference!!.expression) protected fun getKotlinExtensions(): Collection - = indicesHelper.getCallableExtensions({ prefixMatcher.prefixMatches(it) }, jetReference!!.expression) + = indicesHelper.getCallableExtensions({ prefixMatcher.prefixMatches(it) }, reference!!.expression) protected fun addAllClasses(kindFilter: (ClassKind) -> Boolean) { AllClassesCompletion( @@ -185,7 +219,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, assert(parameters.getCompletionType() == CompletionType.BASIC) if (!NamedParametersCompletion.isOnlyNamedParameterExpected(position)) { - val completeReference = jetReference != null && !isOnlyKeywordCompletion() + val completeReference = reference != null && !isOnlyKeywordCompletion() val onlyTypes = completeReference && shouldRunOnlyTypeCompletion() val kindFilter = if (onlyTypes) @@ -197,7 +231,20 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, addReferenceVariants(kindFilter, shouldCastToRuntimeType = false) } - KeywordCompletion.complete(parameters, prefixMatcher.getPrefix(), collector) + val ampIndex = prefix.indexOf("@") + val keywordsPrefix = if (ampIndex < 0) prefix else prefix.substring(0, ampIndex) // if there is '@' in the prefix - use shorter prefix to not loose 'this' etc + KeywordCompletion.complete(expression ?: parameters.getPosition(), keywordsPrefix) { lookupElement -> + when (lookupElement.getLookupString()) { + // if "this" is parsed correctly in the current context - insert it and all this@xxx items + "this" -> { + if (expression != null) { + collector.addElements(thisExpressionItems(bindingContext!!, expression).map { it.factory() }) + } + } + + else -> collector.addElement(lookupElement) + } + } if (completeReference) { if (!configuration.completeNonImportedDeclarations && isNoQualifierContext()) { @@ -240,7 +287,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, val typeReference = position.getStrictParentOfType() if (typeReference != null) { val firstPartReference = PsiTreeUtil.findChildOfType(typeReference, javaClass()) - return firstPartReference == jetReference!!.expression + return firstPartReference == reference!!.expression } return false @@ -261,33 +308,36 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para private val DESCRIPTOR_KIND_MASK = DescriptorKindFilter.VALUES exclude SamConstructorDescriptorKindExclude override fun doComplete() { - if (jetReference != null) { + if (expression != null) { val mapper = ToFromOriginalFileMapper(parameters.getOriginalFile() as JetFile, position.getContainingFile() as JetFile, parameters.getOffset()) - val completion = SmartCompletion(jetReference.expression, resolutionFacade, moduleDescriptor, - bindingContext!!, { isVisibleDescriptor(it) }, originalSearchScope, + val completion = SmartCompletion(expression, resolutionFacade, moduleDescriptor, + bindingContext!!, { isVisibleDescriptor(it) }, prefixMatcher, originalSearchScope, mapper, lookupElementFactory) val result = completion.execute() if (result != null) { collector.addElements(result.additionalItems) - val filter = result.declarationFilter - if (filter != null) { - getReferenceVariants(DESCRIPTOR_KIND_MASK, false).forEach { collector.addElements(filter(it)) } - flushToResultSet() + if (reference != null) { + val filter = result.declarationFilter + if (filter != null) { + getReferenceVariants(DESCRIPTOR_KIND_MASK, false).forEach { collector.addElements(filter(it)) } + flushToResultSet() - processNonImported { collector.addElements(filter(it)) } - flushToResultSet() + processNonImported { collector.addElements(filter(it)) } + flushToResultSet() - if (position.getContainingFile() is JetCodeFragment) { - getReferenceVariants(DESCRIPTOR_KIND_MASK, true).forEach { collector.addElementsWithReceiverCast(filter(it)) } + if (position.getContainingFile() is JetCodeFragment) { + getReferenceVariants(DESCRIPTOR_KIND_MASK, true).forEach { collector.addElementsWithReceiverCast(filter(it)) } + flushToResultSet() + } + } + + // it makes no sense to search inheritors if there is no reference because it means that we have prefix like "this@" + result.inheritanceSearcher?.search({ prefixMatcher.prefixMatches(it) }) { + collector.addElement(it) flushToResultSet() } } - - result.inheritanceSearcher?.search({ prefixMatcher.prefixMatches(it) }) { - collector.addElement(it) - flushToResultSet() - } } } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt b/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt index 1a81c5dc3ec..2eea6dcd3be 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt @@ -44,7 +44,19 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue import org.jetbrains.jet.lang.psi.psiUtil.getReceiverExpression import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils -import com.intellij.openapi.editor.Document +import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor +import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils +import org.jetbrains.jet.lang.psi.JetFunctionLiteral +import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression +import org.jetbrains.jet.lang.psi.JetValueArgument +import org.jetbrains.jet.lang.psi.JetValueArgumentList +import org.jetbrains.jet.lang.psi.JetCallExpression +import org.jetbrains.jet.lang.psi.JetExpression +import java.util.ArrayList +import org.jetbrains.jet.plugin.util.getImplicitReceiversWithInstance +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.jet.renderer.DescriptorRenderer +import org.jetbrains.jet.plugin.util.FuzzyType enum class ItemPriority { MULTIPLE_ARGUMENTS_ITEM @@ -185,3 +197,55 @@ fun InsertionContext.isAfterDot(): Boolean { return false } +// do not complete this items by prefix like "is" +fun shouldCompleteThisItems(prefixMatcher: PrefixMatcher): Boolean { + val prefix = prefixMatcher.getPrefix() + val s = "this@" + return if (prefix.length() > s.length()) + prefix.startsWith(s) + else + s.startsWith(prefix) +} + +data class ThisItemInfo(val factory: () -> LookupElement, val type: FuzzyType) + +fun thisExpressionItems(bindingContext: BindingContext, context: JetExpression): Collection { + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return listOf() + + val result = ArrayList() + for ((i, receiver) in scope.getImplicitReceiversWithInstance().withIndex()) { + val thisType = receiver.getType() + val fuzzyType = FuzzyType(thisType, listOf()) + val qualifier = if (i == 0) null else (thisQualifierName(receiver) ?: continue) + + fun createLookupElement(): LookupElement { + var element = LookupElementBuilder.create(KeywordLookupObject, if (qualifier == null) "this" else "this@" + qualifier) + element = element.withPresentableText("this") + element = element.withBoldness(true) + if (qualifier != null) { + element = element.withTailText("@" + qualifier, false) + } + element = element.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType)) + return element + } + + result.add(ThisItemInfo({ createLookupElement() }, fuzzyType)) + } + return result +} + +private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? { + val descriptor = receiver.getContainingDeclaration() + val name = descriptor.getName() + if (!name.isSpecial()) { + return name.asString() + } + + val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? JetFunctionLiteral + val valueArgument = (functionLiteral?.getParent() as? JetFunctionLiteralExpression) + ?.getParent() as? JetValueArgument ?: return null + val parent = valueArgument.getParent() + val callExpression = (if (parent is JetValueArgumentList) parent else valueArgument) + .getParent() as? JetCallExpression + return (callExpression?.getCalleeExpression() as? JetSimpleNameExpression)?.getReferencedName() +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt index ad5f4d75f2a..46bc3a69191 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt @@ -34,8 +34,9 @@ import org.jetbrains.jet.lexer.JetTokens.* import org.jetbrains.jet.lang.psi.psiUtil.prevLeafSkipWhitespacesAndComments import org.jetbrains.jet.lang.psi.psiUtil.getNonStrictParentOfType import com.intellij.psi.tree.IElementType +import com.intellij.codeInsight.lookup.LookupElement -class KeywordLookupObject(val keyword: String) +object KeywordLookupObject object KeywordCompletion { private val NON_ACTUAL_KEYWORDS = setOf(REIFIED_KEYWORD, @@ -47,22 +48,20 @@ object KeywordCompletion { private val KEYWORD_TO_DUMMY_POSTFIX = mapOf(OUT_KEYWORD to " X") - public fun complete(parameters: CompletionParameters, prefix: String, collector: LookupElementsCollector) { - val position = parameters.getPosition() - + public fun complete(position: PsiElement, prefix: String, consumer: (LookupElement) -> Unit) { if (!GENERAL_FILTER.isAcceptable(position, position)) return val parserFilter = buildFilter(position) for (keywordToken in ALL_KEYWORDS) { val keyword = keywordToken.getValue() if (keyword.startsWith(prefix)/* use simple matching by prefix, not prefix matcher from completion*/ && parserFilter(keywordToken)) { - val element = LookupElementBuilder.create(KeywordLookupObject(keyword), keyword) + val element = LookupElementBuilder.create(KeywordLookupObject, keyword) .bold() .withInsertHandler(if (keywordToken !in FUNCTION_KEYWORDS) KotlinKeywordInsertHandler else KotlinFunctionInsertHandler.NO_PARAMETERS_HANDLER) - collector.addElement(element) + consumer(element) } } } @@ -223,10 +222,7 @@ object KeywordCompletion { } private fun PsiElement.getStartOffsetInAncestor(ancestor: PsiElement): Int { - val parent = getParent()!! - return if (parent == ancestor) - getStartOffsetInParent() - else - parent.getStartOffsetInAncestor(ancestor) + getStartOffsetInParent() + if (ancestor == this) return 0 + return getParent()!!.getStartOffsetInAncestor(ancestor) + getStartOffsetInParent() } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionCharFilter.kt b/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionCharFilter.kt index 4d44dde37f5..48ee0a66995 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionCharFilter.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionCharFilter.kt @@ -34,7 +34,7 @@ public class KotlinCompletionCharFilter() : CharFilter() { if (lookup.getPsiFile() !is JetFile) return null if (!lookup.isCompletion()) return null - if (Character.isJavaIdentifierPart(c) || c == ':' /* used in '::xxx'*/) { + if (Character.isJavaIdentifierPart(c) || c == ':' /* used in '::xxx'*/ || c == '@') { return CharFilter.Result.ADD_TO_PREFIX } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt index b486e7d97cf..83114c45cd9 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt @@ -29,24 +29,25 @@ import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.plugin.util.makeNotNullable import org.jetbrains.jet.plugin.util.makeNullable import org.jetbrains.jet.renderer.DescriptorRenderer -import org.jetbrains.jet.lang.psi.psiUtil.getReceiverExpression import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade -import org.jetbrains.jet.plugin.caches.resolve.resolveToDescriptor import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.jet.lang.types.typeUtil.isSubtypeOf import org.jetbrains.jet.plugin.util.FuzzyType import org.jetbrains.jet.plugin.util.fuzzyReturnType +import org.jetbrains.jet.plugin.caches.resolve.resolveToDescriptor +import org.jetbrains.jet.lang.psi.psiUtil.getReceiverExpression trait InheritanceItemsSearcher { fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) } -class SmartCompletion(val expression: JetSimpleNameExpression, +class SmartCompletion(val expression: JetExpression, val resolutionFacade: ResolutionFacade, val moduleDescriptor: ModuleDescriptor, val bindingContext: BindingContext, val visibilityFilter: (DeclarationDescriptor) -> Boolean, + val prefixMatcher: PrefixMatcher, val inheritorSearchScope: GlobalSearchScope, val toFromOriginalFileMapper: ToFromOriginalFileMapper, val lookupElementFactory: LookupElementFactory) { @@ -99,7 +100,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, val asTypePositionResult = buildForAsTypePosition() if (asTypePositionResult != null) return asTypePositionResult - val receiver = expression.getReceiverExpression() + val receiver = if (expression is JetSimpleNameExpression) expression.getReceiverExpression() else null val expressionWithType = if (receiver != null) { expression.getParent() as? JetExpression ?: return null } @@ -146,13 +147,15 @@ class SmartCompletion(val expression: JetSimpleNameExpression, TypeInstantiationItems(resolutionFacade, moduleDescriptor, bindingContext, visibilityFilter, toFromOriginalFileMapper, inheritorSearchScope, lookupElementFactory) .addTo(additionalItems, inheritanceSearchers, expectedInfos) - StaticMembers(bindingContext, resolutionFacade, lookupElementFactory).addToCollection(additionalItems, expectedInfos, expression, itemsToSkip) + if (expression is JetSimpleNameExpression) { + StaticMembers(bindingContext, resolutionFacade, lookupElementFactory).addToCollection(additionalItems, expectedInfos, expression, itemsToSkip) + } - ThisItems(bindingContext).addToCollection(additionalItems, expressionWithType, expectedInfos) + additionalItems.addThisItems(expression, expectedInfos) LambdaItems.addToCollection(additionalItems, functionExpectedInfos) - KeywordValues.addToCollection(additionalItems, filteredExpectedInfos/* use filteredExpectedInfos to not include null after == */, expressionWithType) + KeywordValues.addToCollection(additionalItems, filteredExpectedInfos/* use filteredExpectedInfos to not include null after == */, expression) MultipleArgumentsItemProvider(bindingContext, smartCastTypes).addToCollection(additionalItems, expectedInfos, expression) } @@ -168,6 +171,18 @@ class SmartCompletion(val expression: JetSimpleNameExpression, return Result(::filterDeclaration, additionalItems, inheritanceSearcher) } + private fun MutableCollection.addThisItems(place: JetExpression, expectedInfos: Collection) { + if (shouldCompleteThisItems(prefixMatcher)) { + val items = thisExpressionItems(bindingContext, place) + for ((factory, type) in items) { + val classifier = { (expectedInfo: ExpectedInfo) -> type.classifyExpectedInfo(expectedInfo) } + addLookupElements(null, expectedInfos, classifier) { + factory().assignSmartCompletionPriority(SmartCompletionItemPriority.THIS) + } + } + } + } + private fun DeclarationDescriptor.fuzzyTypes(smartCastTypes: (VariableDescriptor) -> Collection): Collection { if (this is CallableDescriptor) { var returnType = fuzzyReturnType() ?: return listOf() diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt deleted file mode 100644 index 80c82cee753..00000000000 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2010-2014 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.jet.plugin.completion.smart - -import com.intellij.codeInsight.lookup.LookupElement -import org.jetbrains.jet.lang.psi.JetExpression -import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor -import com.intellij.codeInsight.lookup.LookupElementBuilder -import org.jetbrains.jet.renderer.DescriptorRenderer -import org.jetbrains.jet.lang.psi.JetFunctionLiteral -import org.jetbrains.jet.lang.psi.JetValueArgument -import org.jetbrains.jet.lang.psi.JetValueArgumentList -import org.jetbrains.jet.lang.psi.JetCallExpression -import org.jetbrains.jet.lang.psi.JetSimpleNameExpression -import org.jetbrains.jet.lang.resolve.BindingContext -import org.jetbrains.jet.plugin.completion.ExpectedInfo -import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils -import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression -import org.jetbrains.jet.plugin.util.FuzzyType -import org.jetbrains.jet.plugin.util.getImplicitReceiversWithInstance - -class ThisItems(val bindingContext: BindingContext) { - public fun addToCollection(collection: MutableCollection, context: JetExpression, expectedInfos: Collection) { - val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return - - for ((i, receiver) in scope.getImplicitReceiversWithInstance().withIndex()) { - val thisType = receiver.getType() - val fuzzyType = FuzzyType(thisType, listOf()) - val classifier = { (expectedInfo: ExpectedInfo) -> fuzzyType.classifyExpectedInfo(expectedInfo) } - fun createLookupElement(): LookupElement? { - //TODO: use this code when KT-4258 fixed - //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: return null) - val qualifier = if (i == 0) null else (thisQualifierName(receiver) ?: return null) - val expressionText = if (qualifier == null) "this" else "this@" + qualifier - return LookupElementBuilder.create(expressionText) - .withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType)) - .assignSmartCompletionPriority(SmartCompletionItemPriority.THIS) - } - collection.addLookupElements(null, expectedInfos, classifier) { createLookupElement() } - } - } - - private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? { - val descriptor = receiver.getContainingDeclaration() - val name = descriptor.getName() - if (!name.isSpecial()) { - return name.asString() - } - - val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? JetFunctionLiteral - return (((((functionLiteral?.getParent() as? JetFunctionLiteralExpression) - ?.getParent() as? JetValueArgument) - ?.getParent() as? JetValueArgumentList) - ?.getParent() as? JetCallExpression) - ?.getCalleeExpression() as? JetSimpleNameExpression) - ?.getReferencedName() - } - -} \ No newline at end of file diff --git a/idea/testData/completion/handlers/charFilter/QualifiedThis.kt b/idea/testData/completion/handlers/charFilter/QualifiedThis.kt new file mode 100644 index 00000000000..5e90323bfd0 --- /dev/null +++ b/idea/testData/completion/handlers/charFilter/QualifiedThis.kt @@ -0,0 +1,11 @@ +class Outer { + inner class Inner { + fun String.foo() { + t + } + } +} + +// COMPLETION_TYPE: BASIC +// ELEMENT: * +// CHAR: @ diff --git a/idea/testData/completion/handlers/charFilter/QualifiedThis.kt.after b/idea/testData/completion/handlers/charFilter/QualifiedThis.kt.after new file mode 100644 index 00000000000..ff3d0911a5d --- /dev/null +++ b/idea/testData/completion/handlers/charFilter/QualifiedThis.kt.after @@ -0,0 +1,11 @@ +class Outer { + inner class Inner { + fun String.foo() { + t@ + } + } +} + +// COMPLETION_TYPE: BASIC +// ELEMENT: * +// CHAR: @ diff --git a/idea/testData/completion/keywords/InArgumentList.kt b/idea/testData/completion/keywords/InArgumentList.kt index def1fc00587..fc78f8d63b3 100644 --- a/idea/testData/completion/keywords/InArgumentList.kt +++ b/idea/testData/completion/keywords/InArgumentList.kt @@ -16,10 +16,9 @@ fun foo(p: Int) { // EXIST: package // EXIST: return // EXIST: super -// EXIST: this // EXIST: throw // EXIST: true // EXIST: try // EXIST: when // EXIST: while -// NUMBER: 17 +// NUMBER: 16 diff --git a/idea/testData/completion/keywords/InCodeBlock.kt b/idea/testData/completion/keywords/InCodeBlock.kt index 7c6a38f2e82..d3491428a7e 100644 --- a/idea/testData/completion/keywords/InCodeBlock.kt +++ b/idea/testData/completion/keywords/InCodeBlock.kt @@ -15,7 +15,6 @@ fun foo() { // EXIST: package // EXIST: return // EXIST: super -// EXIST: this // EXIST: throw // EXIST: trait // EXIST: true @@ -24,4 +23,4 @@ fun foo() { // EXIST: var // EXIST: when // EXIST: while -// NUMBER: 22 +// NUMBER: 21 diff --git a/idea/testData/completion/keywords/InFunctionExpressionBody.kt b/idea/testData/completion/keywords/InFunctionExpressionBody.kt index 7e282538e4f..2dcddd44fd0 100644 --- a/idea/testData/completion/keywords/InFunctionExpressionBody.kt +++ b/idea/testData/completion/keywords/InFunctionExpressionBody.kt @@ -11,10 +11,9 @@ fun foo() = // EXIST: package // EXIST: return // EXIST: super -// EXIST: this // EXIST: throw // EXIST: true // EXIST: try // EXIST: when // EXIST: while -// NUMBER: 17 +// NUMBER: 16 diff --git a/idea/testData/completion/keywords/InGetterExpressionBody.kt b/idea/testData/completion/keywords/InGetterExpressionBody.kt index adfdbd6b9bc..685ad04d5b0 100644 --- a/idea/testData/completion/keywords/InGetterExpressionBody.kt +++ b/idea/testData/completion/keywords/InGetterExpressionBody.kt @@ -12,10 +12,9 @@ val prop: Int // EXIST: package // EXIST: return // EXIST: super -// EXIST: this // EXIST: throw // EXIST: true // EXIST: try // EXIST: when // EXIST: while -// NUMBER: 17 +// NUMBER: 16 diff --git a/idea/testData/completion/keywords/InParameterDefaultValue.kt b/idea/testData/completion/keywords/InParameterDefaultValue.kt index 13602f98c0b..a2e2b9b1f9e 100644 --- a/idea/testData/completion/keywords/InParameterDefaultValue.kt +++ b/idea/testData/completion/keywords/InParameterDefaultValue.kt @@ -11,10 +11,9 @@ fun foo(p: Int = ) // EXIST: package // EXIST: return // EXIST: super -// EXIST: this // EXIST: throw // EXIST: true // EXIST: try // EXIST: when // EXIST: while -// NUMBER: 17 +// NUMBER: 16 diff --git a/idea/testData/completion/keywords/InPropertyInitializer.kt b/idea/testData/completion/keywords/InPropertyInitializer.kt index 03563d947ff..03642b9f8f3 100644 --- a/idea/testData/completion/keywords/InPropertyInitializer.kt +++ b/idea/testData/completion/keywords/InPropertyInitializer.kt @@ -11,10 +11,9 @@ var a : Int = // EXIST: package // EXIST: return // EXIST: super -// EXIST: this // EXIST: throw // EXIST: true // EXIST: try // EXIST: when // EXIST: while -// NUMBER: 17 +// NUMBER: 16 diff --git a/idea/testData/completion/keywords/QualifiedThis.kt b/idea/testData/completion/keywords/QualifiedThis.kt new file mode 100644 index 00000000000..d79a40139d3 --- /dev/null +++ b/idea/testData/completion/keywords/QualifiedThis.kt @@ -0,0 +1,13 @@ +class Outer { + inner class Inner { + fun String.foo() { + this@ + } + } +} + +// ABSENT: this +// ABSENT: "this@foo" +// EXIST: { lookupString: "this@Inner", itemText: "this", tailText: "@Inner", typeText: "Outer.Inner", attributes: "bold" } +// EXIST: { lookupString: "this@Outer", itemText: "this", tailText: "@Outer", typeText: "Outer", attributes: "bold" } +// NUMBER: 2 diff --git a/idea/testData/completion/keywords/This.kt b/idea/testData/completion/keywords/This.kt new file mode 100644 index 00000000000..dc49e9b310e --- /dev/null +++ b/idea/testData/completion/keywords/This.kt @@ -0,0 +1,28 @@ +class Outer { + class Nested { + inner class Inner { + fun String.foo() { + takeHandler1 { + takeHandler2({ + takeHandler3 { this } + }) + } + } + } + } +} + +fun takeHandler1(handler: Int.() -> Unit){} +fun takeHandler2(handler: Char.() -> Unit){} +fun takeHandler3(handler: Char.() -> Unit){} + +// INVOCATION_COUNT: 1 +// EXIST: { lookupString: "this", itemText: "this", tailText: null, typeText: "Char", attributes: "bold" } +// ABSENT: "this@takeHandler3" +// EXIST: { lookupString: "this@takeHandler2", itemText: "this", tailText: "@takeHandler2", typeText: "Char", attributes: "bold" } +// EXIST: { lookupString: "this@takeHandler1", itemText: "this", tailText: "@takeHandler1", typeText: "Int", attributes: "bold" } +// EXIST: { lookupString: "this@foo", itemText: "this", tailText: "@foo", typeText: "String", attributes: "bold" } +// EXIST: { lookupString: "this@Inner", itemText: "this", tailText: "@Inner", typeText: "Outer.Nested.Inner", attributes: "bold" } +// EXIST: { lookupString: "this@Nested", itemText: "this", tailText: "@Nested", typeText: "Outer.Nested", attributes: "bold" } +// ABSENT: "this@Outer" +// NUMBER: 6 diff --git a/idea/testData/completion/keywords/ThisPrefixMatching.kt b/idea/testData/completion/keywords/ThisPrefixMatching.kt new file mode 100644 index 00000000000..b81c13a0cc7 --- /dev/null +++ b/idea/testData/completion/keywords/ThisPrefixMatching.kt @@ -0,0 +1,12 @@ +fun is1(): Boolean{} +fun is2(): Boolean{} + +class Outer { + inner class Inner { + fun String.foo() { + is + } + } +} + +// NUMBER: 0 diff --git a/idea/testData/completion/smart/QualifiedThis.kt b/idea/testData/completion/smart/QualifiedThis.kt index 08ebd0ce4a5..ce51d8065ac 100644 --- a/idea/testData/completion/smart/QualifiedThis.kt +++ b/idea/testData/completion/smart/QualifiedThis.kt @@ -1,7 +1,13 @@ -class Foo{ - fun String.foo(){ - val foo : Foo = +class Outer { + inner class Inner { + fun String.foo() { + val v: Any = this@ + } } } -// EXIST: { lookupString:"this@Foo", typeText:"Foo" } +// ABSENT: this +// ABSENT: "this@foo" +// EXIST: { lookupString: "this@Inner", itemText: "this", tailText: "@Inner", typeText: "Outer.Inner", attributes: "bold" } +// EXIST: { lookupString: "this@Outer", itemText: "this", tailText: "@Outer", typeText: "Outer", attributes: "bold" } +// NUMBER: 2 diff --git a/idea/testData/completion/smart/This.kt b/idea/testData/completion/smart/This.kt index feb635d5c87..6d0e5e771e3 100644 --- a/idea/testData/completion/smart/This.kt +++ b/idea/testData/completion/smart/This.kt @@ -1,5 +1,30 @@ -fun String.foo(){ - val s : String = +class Outer { + class Nested { + inner class Inner { + fun String.foo() { + takeHandler1 { + takeHandler2({ + takeHandler3 { + takeHandler4 { val v: Any = } + } + }) + } + } + } + } } -// EXIST: { lookupString:"this", typeText:"String" } +fun takeHandler1(handler: Int.() -> Unit){} +fun takeHandler2(handler: Char.() -> Unit){} +fun takeHandler3(handler: Any?.() -> Unit){} +fun takeHandler4(handler: Any.() -> Unit){} + +// EXIST: { lookupString: "this", itemText: "this", tailText: null, typeText: "Any", attributes: "bold" } +// ABSENT: "this@takeHandler4" +// ABSENT: "this@takeHandler3" +// EXIST: { lookupString: "this@takeHandler2", itemText: "this", tailText: "@takeHandler2", typeText: "Char", attributes: "bold" } +// EXIST: { lookupString: "this@takeHandler1", itemText: "this", tailText: "@takeHandler1", typeText: "Int", attributes: "bold" } +// EXIST: { lookupString: "this@foo", itemText: "this", tailText: "@foo", typeText: "String", attributes: "bold" } +// EXIST: { lookupString: "this@Inner", itemText: "this", tailText: "@Inner", typeText: "Outer.Nested.Inner", attributes: "bold" } +// EXIST: { lookupString: "this@Nested", itemText: "this", tailText: "@Nested", typeText: "Outer.Nested", attributes: "bold" } +// ABSENT: "this@Outer" diff --git a/idea/tests/org/jetbrains/jet/completion/KeywordCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/KeywordCompletionTestGenerated.java index 4337a7a8a4b..57a60fa1b97 100644 --- a/idea/tests/org/jetbrains/jet/completion/KeywordCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/KeywordCompletionTestGenerated.java @@ -276,6 +276,24 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes doTest(fileName); } + @TestMetadata("QualifiedThis.kt") + public void testQualifiedThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/QualifiedThis.kt"); + doTest(fileName); + } + + @TestMetadata("This.kt") + public void testThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/This.kt"); + doTest(fileName); + } + + @TestMetadata("ThisPrefixMatching.kt") + public void testThisPrefixMatching() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/ThisPrefixMatching.kt"); + doTest(fileName); + } + @TestMetadata("TopScope.kt") public void testTopScope() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/TopScope.kt"); diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/CompletionCharFilterTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/handlers/CompletionCharFilterTestGenerated.java index f01f13cd712..76065cdd80b 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/CompletionCharFilterTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/handlers/CompletionCharFilterTestGenerated.java @@ -19,7 +19,6 @@ package org.jetbrains.jet.completion.handlers; import com.intellij.testFramework.TestDataPath; import org.jetbrains.jet.JUnit3RunnerWithInners; import org.jetbrains.jet.JetTestUtils; -import org.jetbrains.jet.test.InnerTestClasses; import org.jetbrains.jet.test.TestMetadata; import org.junit.runner.RunWith; @@ -168,6 +167,12 @@ public class CompletionCharFilterTestGenerated extends AbstractCompletionCharFil doTest(fileName); } + @TestMetadata("QualifiedThis.kt") + public void testQualifiedThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/charFilter/QualifiedThis.kt"); + doTest(fileName); + } + @TestMetadata("RangeTyping.kt") public void testRangeTyping() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/charFilter/RangeTyping.kt");