Smart completion after "by" and "in": non-imported operators supported

This commit is contained in:
Valentin Kipyatkov
2016-03-24 16:42:05 +03:00
parent cda539d769
commit e8c35e16e0
13 changed files with 122 additions and 24 deletions
@@ -78,7 +78,7 @@ class BasicCompletionSession(
private val smartCompletion = expression?.let {
SmartCompletion(
it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, prefixMatcher,
it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, indicesHelper(false), prefixMatcher,
GlobalSearchScope.EMPTY_SCOPE, toFromOriginalFileMapper, callTypeAndReceiver,
isJvmModule, forBasicCompletion = true
)
@@ -82,7 +82,7 @@ private fun needExplicitParameterTypes(context: InsertionContext, placeholderRan
val resolutionFacade = file.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(expression, BodyResolveMode.PARTIAL)
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, useHeuristicSignatures = false).calculate(expression)
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper = null, useHeuristicSignatures = false).calculate(expression)
val functionTypes = expectedInfos
.mapNotNull { it.fuzzyType?.type }
@@ -55,6 +55,7 @@ class SmartCompletion(
private val bindingContext: BindingContext,
private val moduleDescriptor: ModuleDescriptor,
private val visibilityFilter: (DeclarationDescriptor) -> Boolean,
private val indicesHelper: KotlinIndicesHelper,
private val prefixMatcher: PrefixMatcher,
private val inheritorSearchScope: GlobalSearchScope,
private val toFromOriginalFileMapper: ToFromOriginalFileMapper,
@@ -302,7 +303,7 @@ class SmartCompletion(
// if expected types are too general, try to use expected type from outer calls
var count = 0
while (true) {
val infos = ExpectedInfos(bindingContext, resolutionFacade, useOuterCallsExpectedTypeCount = count)
val infos = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper, useOuterCallsExpectedTypeCount = count)
.calculate(expression)
if (count == 2 /* use two outer calls maximum */ || infos.none { it.fuzzyType?.isAlmostEverything() ?: false }) {
return if (forBasicCompletion)
@@ -59,7 +59,7 @@ class SmartCompletionSession(
private val smartCompletion by lazy(LazyThreadSafetyMode.NONE) {
expression?.let {
SmartCompletion(it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter,
SmartCompletion(it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, indicesHelper(false),
prefixMatcher, originalSearchScope, toFromOriginalFileMapper,
callTypeAndReceiver, isJvmModule)
}
@@ -192,7 +192,7 @@ class SmartCompletionSession(
override fun getValueArgumentList() = throw UnsupportedOperationException()
}
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade)
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper(false))
.calculateForArgument(dummyCall, dummyArgument)
collector.addElements(LambdaItems.collect(expectedInfos))
}
@@ -0,0 +1,8 @@
package dependency
interface X
interface Y : X
interface Z
operator fun X.contains(s: String): Boolean = true
fun Z.contains(s: String): Boolean = true
@@ -0,0 +1,11 @@
import dependency.X
import dependency.Y
import dependency.Z
fun foo(s: String, x: X, y: Y, z: Z) {
if (s in <caret>)
}
// EXIST: x
// EXIST: y
// ABSENT: z
@@ -0,0 +1,22 @@
package dependency
import kotlin.reflect.KProperty
import main.C
class X1 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
}
class X2 {
operator fun getValue(thisRef: String, property: KProperty<*>): String = ""
}
class X3 {
operator fun getValue(thisRef: Any, property: KProperty<*>): String = ""
}
class Y1
class Y2
class Y3
operator fun Y1.getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun Y2.getValue(thisRef: String, property: KProperty<*>): String = ""
operator fun Y3.getValue(thisRef: Any, property: KProperty<*>): String = ""
@@ -0,0 +1,26 @@
package main
import dependency.X1
import dependency.X2
import dependency.X3
import dependency.Y1
import dependency.Y2
import dependency.Y3
fun createX1() = X1()
fun createX2() = X2()
fun createX3() = X3()
fun createY1() = Y1()
fun createY2() = Y2()
fun createY3() = Y3()
class C {
val property by <caret>
}
// EXIST: createX1
// ABSENT: createX2
// EXIST: createX3
// EXIST: createY1
// ABSENT: createY2
// EXIST: createY3
@@ -137,6 +137,18 @@ public class MultiFileSmartCompletionTestGenerated extends AbstractMultiFileSmar
doTest(fileName);
}
@TestMetadata("NotImportedContains")
public void testNotImportedContains() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/NotImportedContains/");
doTest(fileName);
}
@TestMetadata("NotImportedGetValue")
public void testNotImportedGetValue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/");
doTest(fileName);
}
@TestMetadata("StaticMembers1")
public void testStaticMembers1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/StaticMembers1/");
@@ -140,6 +140,7 @@ object IfConditionAdditionalData : ExpectedInfo.AdditionalData
class ExpectedInfos(
private val bindingContext: BindingContext,
private val resolutionFacade: ResolutionFacade,
private val indicesHelper: KotlinIndicesHelper?,
private val useHeuristicSignatures: Boolean = true,
private val useOuterCallsExpectedTypeCount: Int = 0
) {
@@ -207,7 +208,7 @@ class ExpectedInfos(
if (useOuterCallsExpectedTypeCount > 0 && results.any(::makesSenseToUseOuterCallExpectedType)) {
val callExpression = (call.callElement as? KtExpression)?.getQualifiedExpressionForSelectorOrThis() ?: return results
val expectedFuzzyTypes = ExpectedInfos(bindingContext, resolutionFacade, useHeuristicSignatures, useOuterCallsExpectedTypeCount - 1)
val expectedFuzzyTypes = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper, useHeuristicSignatures, useOuterCallsExpectedTypeCount - 1)
.calculate(callExpression)
.mapNotNull { it.fuzzyType }
if (expectedFuzzyTypes.isEmpty() || expectedFuzzyTypes.any { it.freeParameters.isNotEmpty() }) return results
@@ -587,7 +588,7 @@ class ExpectedInfos(
val leftOperandType = binaryExpression.left?.let { bindingContext.getType(it) } ?: return null
val scope = expressionWithType.getResolutionScope(bindingContext, resolutionFacade)
val detector = TypesWithContainsDetector(scope, leftOperandType)
val detector = TypesWithContainsDetector(scope, indicesHelper, leftOperandType)
val byTypeFilter = object : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? {
@@ -608,8 +609,8 @@ class ExpectedInfos(
?: return null
val explicitPropertyType = property.returnType?.check { propertyDeclaration.typeReference != null }
val typesWithGetDetector = TypesWithGetValueDetector(scope, propertyOwnerType, explicitPropertyType)
val typesWithSetDetector = if (property.isVar) TypesWithSetValueDetector(scope, propertyOwnerType) else null
val typesWithGetDetector = TypesWithGetValueDetector(scope, indicesHelper, propertyOwnerType, explicitPropertyType)
val typesWithSetDetector = if (property.isVar) TypesWithSetValueDetector(scope, indicesHelper, propertyOwnerType) else null
val byTypeFilter = object : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? {
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.core
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.JavaProjectCodeInsightSettings
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiFile
@@ -26,10 +25,7 @@ import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.psi.stubs.StringStubIndexExtension
import com.intellij.util.Processor
import com.intellij.util.indexing.IdFilter
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.getJavaFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
@@ -78,12 +74,12 @@ class KotlinIndicesHelper(
}
fun getTopLevelCallablesByName(name: String): Collection<CallableDescriptor> {
val declarations = HashSet<KtCallableDeclaration>()
val declarations = LinkedHashSet<KtCallableDeclaration>()
declarations.addTopLevelNonExtensionCallablesByName(KotlinFunctionShortNameIndex.getInstance(), name)
declarations.addTopLevelNonExtensionCallablesByName(KotlinPropertyShortNameIndex.getInstance(), name)
return declarations
.flatMap { it.resolveToDescriptorsWithHack() }
.filter { it.extensionReceiverParameter == null && descriptorFilter(it) }
.filter { descriptorFilter(it) }
}
private fun MutableSet<KtCallableDeclaration>.addTopLevelNonExtensionCallablesByName(
@@ -93,6 +89,15 @@ class KotlinIndicesHelper(
index.get(name, project, scope).filterTo(this) { it.parent is KtFile && it.receiverTypeReference == null }
}
fun getTopLevelExtensionOperatorsByName(name: String): Collection<FunctionDescriptor> {
return KotlinFunctionShortNameIndex.getInstance().get(name, project, scope)
.filter { it.parent is KtFile && it.receiverTypeReference != null && it.hasModifier(KtTokens.OPERATOR_KEYWORD) }
.flatMap { it.resolveToDescriptorsWithHack() }
.filterIsInstance<FunctionDescriptor>()
.filter { descriptorFilter(it) }
.distinct()
}
fun processTopLevelCallables(nameFilter: (String) -> Boolean, processor: (CallableDescriptor) -> Unit) {
fun processIndex(index: StringStubIndexExtension<out KtCallableDeclaration>) {
for (key in index.getAllKeys(project)) {
@@ -30,22 +30,30 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.isValidOperator
import java.util.*
//TODO: support not imported extensions
abstract class TypesWithOperatorDetector(
private val name: Name,
private val scope: LexicalScope
private val scope: LexicalScope,
private val indicesHelper: KotlinIndicesHelper?
) {
protected abstract fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean
private val cache = HashMap<FuzzyType, FunctionDescriptor?>()
private val typesWithExtension: Map<KotlinType, FunctionDescriptor> by lazy {
private val typesWithExtensionFromScope: Map<KotlinType, FunctionDescriptor> by lazy {
scope.collectFunctions(name, NoLookupLocation.FROM_IDE)
.filter { it.extensionReceiverParameter != null && it.isValidOperator() && isSuitableByType(it, it.typeParameters) }
.map { it.extensionReceiverParameter!!.type to it }
.toMap()
}
private val typesWithExtensionFromIndices: Map<KotlinType, FunctionDescriptor> by lazy {
indicesHelper?.getTopLevelExtensionOperatorsByName(name.asString())
?.filter { it.extensionReceiverParameter != null && it.isValidOperator() && isSuitableByType(it, it.typeParameters) }
?.map { it.extensionReceiverParameter!!.type to it }
?.filter { it.first !in typesWithExtensionFromScope.keys }
?.toMap() ?: emptyMap()
}
fun findOperator(type: FuzzyType): FunctionDescriptor? {
if (cache.containsKey(type)) {
return cache[type]
@@ -64,19 +72,21 @@ abstract class TypesWithOperatorDetector(
if (memberFunction != null) return memberFunction
}
for ((typeWithExtension, operator) in typesWithExtension) {
for ((typeWithExtension, operator) in typesWithExtensionFromScope + typesWithExtensionFromIndices) {
if (type.checkIsSubtypeOf(typeWithExtension) != null) {
return operator //TODO: substitution
}
}
return null
}
}
class TypesWithContainsDetector(
scope: LexicalScope,
indicesHelper: KotlinIndicesHelper?,
private val argumentType: KotlinType
) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope) {
) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope, indicesHelper) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
val parameter = function.valueParameters.single()
@@ -87,9 +97,10 @@ class TypesWithContainsDetector(
class TypesWithGetValueDetector(
scope: LexicalScope,
indicesHelper: KotlinIndicesHelper?,
private val propertyOwnerType: KotlinType,
private val propertyType: KotlinType?
) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope) {
) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope, indicesHelper) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams)
@@ -106,8 +117,9 @@ class TypesWithGetValueDetector(
class TypesWithSetValueDetector(
scope: LexicalScope,
indicesHelper: KotlinIndicesHelper?,
private val propertyOwnerType: KotlinType
) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope) {
) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope, indicesHelper) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams)
@@ -40,7 +40,7 @@ class SuitableVariableMacro : BaseKotlinVariableMacro<SuitableVariableMacro.Stat
if (contextElement is KtNameReferenceExpression) {
val callTypeAndReceiver = CallTypeAndReceiver.detect(contextElement)
if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) {
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade).calculate(contextElement)
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, null).calculate(contextElement)
if (expectedInfos.isNotEmpty()) {
val scope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
val smartCastCalculator = SmartCastCalculator(bindingContext, scope.ownerDescriptor, contextElement, null, resolutionFacade)