Smart completion after "by" and "in": non-imported operators supported
This commit is contained in:
+1
-1
@@ -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
|
||||
)
|
||||
|
||||
+1
-1
@@ -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 }
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
+2
-2
@@ -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))
|
||||
}
|
||||
|
||||
+8
@@ -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
|
||||
+11
@@ -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
|
||||
+22
@@ -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 = ""
|
||||
+26
@@ -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
|
||||
+12
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user