diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt index 0642b44d889..946111584cf 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.util import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.resolve.calls.inference.CallHandle import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl @@ -56,15 +57,26 @@ class FuzzyType( init { if (freeParameters.isNotEmpty()) { - val usedTypeParameters = HashSet() - usedTypeParameters.addUsedTypeParameters(type) - this.freeParameters = freeParameters.filter { it in usedTypeParameters }.toSet() + // we allow to pass type parameters from another function with the same original in freeParameters + val usedTypeParameters = HashSet().apply { addUsedTypeParameters(type) } + if (usedTypeParameters.isNotEmpty()) { + val originalFreeParameters = freeParameters.map { it.toOriginal() }.toSet() + this.freeParameters = usedTypeParameters.filter { it.toOriginal() in originalFreeParameters }.toSet() + } + else { + this.freeParameters = emptySet() + } } else { this.freeParameters = emptySet() } } + private fun TypeParameterDescriptor.toOriginal(): TypeParameterDescriptor { + val functionDescriptor = containingDeclaration as? FunctionDescriptor ?: return this + return functionDescriptor.original.typeParameters[index] + } + override fun equals(other: Any?) = other is FuzzyType && other.type == type && other.freeParameters == freeParameters override fun hashCode() = type.hashCode() @@ -137,9 +149,9 @@ class FuzzyType( // that's why we have to check subtyping manually val substitutor = constraintSystem.resultingSubstitutor val substitutedType = substitutor.substitute(type, Variance.INVARIANT) ?: return null - if (substitutedType.isError) return null + if (substitutedType.isError) return TypeSubstitutor.EMPTY val otherSubstitutedType = substitutor.substitute(otherType.type, Variance.INVARIANT) ?: return null - if (otherSubstitutedType.isError) return null + if (otherSubstitutedType.isError) return TypeSubstitutor.EMPTY if (!substitutedType.checkInheritance(otherSubstitutedType)) return null val substitution = constraintSystem.typeVariables.map { it.originalTypeParameter }.associateBy({ it.typeConstructor }) { diff --git a/idea/idea-completion/testData/smart/heuristicSignatures/InOperator.kt b/idea/idea-completion/testData/smart/heuristicSignatures/InOperator.kt index dcd9575af4e..aba00810998 100644 --- a/idea/idea-completion/testData/smart/heuristicSignatures/InOperator.kt +++ b/idea/idea-completion/testData/smart/heuristicSignatures/InOperator.kt @@ -11,7 +11,7 @@ fun foo(a: A, cA: Collection, cB: Collection, cC: Collection, cAny: Co } // EXIST: cA -// ABSENT: cB +// EXIST: cB // ABSENT: cC // EXIST: cAny // EXIST: lA @@ -19,6 +19,6 @@ fun foo(a: A, cA: Collection, cB: Collection, cC: Collection, cAny: Co // ABSENT: lC // EXIST: lAny // EXIST: aA -// ABSENT: aB +// EXIST: aB // ABSENT: aC // EXIST: aAny diff --git a/idea/idea-completion/testData/smart/inOperator/GenericMethod.kt b/idea/idea-completion/testData/smart/inOperator/GenericMethod.kt index eb8ef49d6f5..b55958fa38f 100644 --- a/idea/idea-completion/testData/smart/inOperator/GenericMethod.kt +++ b/idea/idea-completion/testData/smart/inOperator/GenericMethod.kt @@ -2,4 +2,4 @@ fun foo(s: String) { if (s in ) } -// EXIST: { lookupString:"listOf", itemText: "listOf", tailText: "(vararg elements: T) (kotlin.collections)", typeText:"List" } +// EXIST: { lookupString:"listOf", itemText: "listOf", tailText: "(vararg elements: String) (kotlin.collections)", typeText:"List" } diff --git a/idea/idea-completion/testData/smart/inOperator/GenericMethod2.kt b/idea/idea-completion/testData/smart/inOperator/GenericMethod2.kt index 3aac6350a8f..c2e8fac643c 100644 --- a/idea/idea-completion/testData/smart/inOperator/GenericMethod2.kt +++ b/idea/idea-completion/testData/smart/inOperator/GenericMethod2.kt @@ -10,4 +10,4 @@ interface A { } } -// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: T)", typeText:"X" } +// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: String)", typeText:"X" } diff --git a/idea/idea-completion/testData/smart/inOperator/GenericMethod3.kt b/idea/idea-completion/testData/smart/inOperator/GenericMethod3.kt index 38880de94f1..83c929e4a28 100644 --- a/idea/idea-completion/testData/smart/inOperator/GenericMethod3.kt +++ b/idea/idea-completion/testData/smart/inOperator/GenericMethod3.kt @@ -10,4 +10,4 @@ interface A { } } -// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: T)", typeText:"X" } +// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: String)", typeText:"X" } diff --git a/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDot.kt b/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDot.kt index bb4fba923a3..a8c1a118c44 100644 --- a/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDot.kt +++ b/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDot.kt @@ -4,7 +4,7 @@ class C { val v by Delegates. } -// EXIST: notNull -// EXIST: observable -// EXIST: vetoable +// EXIST: { itemText: "notNull", typeText: "ReadWriteProperty" } +// EXIST: { itemText: "observable", typeText: "ReadWriteProperty" } +// EXIST: { itemText: "vetoable", typeText: "ReadWriteProperty" } // NOTHING_ELSE diff --git a/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDotExplicitPropertyType.kt b/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDotExplicitPropertyType.kt new file mode 100644 index 00000000000..b1105def681 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/DelegatesDotExplicitPropertyType.kt @@ -0,0 +1,10 @@ +import kotlin.properties.Delegates + +class C { + val v: String by Delegates. +} + +// EXIST: { itemText: "notNull", typeText: "ReadWriteProperty" } +// EXIST: { itemText: "observable", typeText: "ReadWriteProperty" } +// EXIST: { itemText: "vetoable", typeText: "ReadWriteProperty" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution1.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution1.kt new file mode 100644 index 00000000000..b6bf6e1ca89 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution1.kt @@ -0,0 +1,15 @@ +import kotlin.reflect.KProperty + +class Property + +operator fun Property.getValue(thisRef: TOwner, property: KProperty<*>): TValue { + throw Exception() +} + +fun createProperty(): Property = Property() + +class C { + val v by create +} + +// EXIST: { itemText: "createProperty", typeText: "Property" } diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution2.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution2.kt new file mode 100644 index 00000000000..bafa3808357 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution2.kt @@ -0,0 +1,15 @@ +import kotlin.reflect.KProperty + +class Property + +operator fun Property.getValue(thisRef: TOwner, property: KProperty<*>): TValue { + throw Exception() +} + +fun createProperty(): Property = Property() + +class C { + val v: Int by create +} + +// EXIST: { itemText: "createProperty", typeText: "Property" } diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution3.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution3.kt new file mode 100644 index 00000000000..b267d638fa3 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution3.kt @@ -0,0 +1,19 @@ +import kotlin.reflect.KProperty + +class Property + +operator fun Property.getValue(thisRef: TOwner2, property: KProperty<*>): TValue2 { + throw Exception() +} + +operator fun Property.setValue(thisRef: TOwner3, property: KProperty<*>, value: TValue3) { + throw Exception() +} + +fun createProperty(): Property = Property() + +class C { + var v by create +} + +// EXIST: { itemText: "createProperty", typeText: "Property" } diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution4.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution4.kt new file mode 100644 index 00000000000..d0623b9d00d --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution4.kt @@ -0,0 +1,19 @@ +import kotlin.reflect.KProperty + +class Property + +operator fun Property.getValue(thisRef: TOwner2, property: KProperty<*>): TValue2 { + throw Exception() +} + +operator fun Property.setValue(thisRef: TOwner3, property: KProperty<*>, value: TValue3) { + throw Exception() +} + +fun createProperty(): Property = Property() + +class C { + var v: Int by create +} + +// EXIST: { itemText: "createProperty", typeText: "Property" } 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 fac57ff3091..f769792b64b 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 @@ -1450,6 +1450,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("DelegatesDotExplicitPropertyType.kt") + public void testDelegatesDotExplicitPropertyType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/DelegatesDotExplicitPropertyType.kt"); + doTest(fileName); + } + @TestMetadata("ExplicitValType.kt") public void testExplicitValType() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt"); @@ -1462,6 +1468,30 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("ExtensionSubstitution1.kt") + public void testExtensionSubstitution1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution1.kt"); + doTest(fileName); + } + + @TestMetadata("ExtensionSubstitution2.kt") + public void testExtensionSubstitution2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution2.kt"); + doTest(fileName); + } + + @TestMetadata("ExtensionSubstitution3.kt") + public void testExtensionSubstitution3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution3.kt"); + doTest(fileName); + } + + @TestMetadata("ExtensionSubstitution4.kt") + public void testExtensionSubstitution4() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionSubstitution4.kt"); + doTest(fileName); + } + @TestMetadata("ExtensionVal.kt") public void testExtensionVal() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt index 6450cbd4c31..face8bc8fed 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt @@ -39,6 +39,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.typeUtil.* import org.jetbrains.kotlin.utils.addToStdlib.check import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList @@ -592,7 +593,7 @@ class ExpectedInfos( val byTypeFilter = object : ByTypeFilter { override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? { - return if (detector.findOperator(descriptorType) != null) TypeSubstitutor.EMPTY else null + return detector.findOperator(descriptorType)?.second } } return listOf(ExpectedInfo(byTypeFilter, null, null)) @@ -614,16 +615,22 @@ class ExpectedInfos( val byTypeFilter = object : ByTypeFilter { override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? { - val getValueOperator = typesWithGetDetector.findOperator(descriptorType) ?: return null + val (getValueOperator, getOperatorSubstitutor) = typesWithGetDetector.findOperator(descriptorType) ?: return null - if (typesWithSetDetector != null) { - val setValueOperator = typesWithSetDetector.findOperator(descriptorType) ?: return null - val propertyType = explicitPropertyType ?: getValueOperator.returnType!! - val setParamType = FuzzyType(setValueOperator.valueParameters.last().type, setValueOperator.typeParameters) - if (setParamType.checkIsSuperTypeOf(propertyType) == null) return null - } + if (typesWithSetDetector == null) return getOperatorSubstitutor - return TypeSubstitutor.EMPTY //TODO: substitutor from property type + val substitutedType = FuzzyType(getOperatorSubstitutor.substitute(descriptorType.type, Variance.INVARIANT)!!, descriptorType.freeParameters) + + val (setValueOperator, setOperatorSubstitutor) = typesWithSetDetector.findOperator(substitutedType) ?: return null + val propertyType = if (explicitPropertyType != null) + FuzzyType(explicitPropertyType, emptyList()) + else + getValueOperator.fuzzyReturnType()!! + val setParamType = FuzzyType(setValueOperator.valueParameters.last().type, setValueOperator.typeParameters) + val setParamTypeSubstitutor = setParamType.checkIsSuperTypeOf(propertyType) ?: return null + return TypeSubstitutor.createChainedSubstitutor(getOperatorSubstitutor.substitution, + TypeSubstitutor.createChainedSubstitutor(setOperatorSubstitutor.substitution, + setParamTypeSubstitutor.substitution).substitution) } } return listOf(ExpectedInfo(byTypeFilter, null, null)) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt index a0f49a6ca31..d66d91d1f17 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt @@ -19,12 +19,14 @@ package org.jetbrains.kotlin.idea.core import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.idea.util.FuzzyType +import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType import org.jetbrains.kotlin.idea.util.nullability import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.typeUtil.TypeNullability import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.isValidOperator @@ -35,26 +37,26 @@ abstract class TypesWithOperatorDetector( private val scope: LexicalScope, private val indicesHelper: KotlinIndicesHelper? ) { - protected abstract fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean + protected abstract fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): TypeSubstitutor? - private val cache = HashMap() + private val cache = HashMap?>() - private val typesWithExtensionFromScope: Map 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 extensionOperators: Collection by lazy { + val result = ArrayList() + collectExtensionOperators(scope.collectFunctions(name, NoLookupLocation.FROM_IDE), result) + indicesHelper?.getTopLevelExtensionOperatorsByName(name.asString())?.let { collectExtensionOperators(it, result) } + result.distinctBy { it.original } } - private val typesWithExtensionFromIndices: Map 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() + private fun collectExtensionOperators(functions: Collection, result: MutableCollection) { + for (function in functions) { + if (function.extensionReceiverParameter == null || !function.isValidOperator()) continue + val substitutor = checkIsSuitableByType(function, function.typeParameters) ?: continue + result.add(function.substitute(substitutor)) + } } - fun findOperator(type: FuzzyType): FunctionDescriptor? { + fun findOperator(type: FuzzyType): Pair? { if (cache.containsKey(type)) { return cache[type] } @@ -65,16 +67,21 @@ abstract class TypesWithOperatorDetector( } } - private fun findOperatorNoCache(type: FuzzyType): FunctionDescriptor? { + private fun findOperatorNoCache(type: FuzzyType): Pair? { if (type.nullability() != TypeNullability.NULLABLE) { - val memberFunction = type.type.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) - .firstOrNull { it.isValidOperator() && isSuitableByType(it, type.freeParameters) } - if (memberFunction != null) return memberFunction + for (memberFunction in type.type.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE)) { + if (memberFunction.isValidOperator()) { + checkIsSuitableByType(memberFunction, type.freeParameters)?.let { substitutor -> + return Pair(memberFunction.substitute(substitutor), substitutor) + } + } + } } - for ((typeWithExtension, operator) in typesWithExtensionFromScope + typesWithExtensionFromIndices) { - if (type.checkIsSubtypeOf(typeWithExtension) != null) { - return operator //TODO: substitution + for (operator in extensionOperators) { + val substitutor = type.checkIsSubtypeOf(operator.fuzzyExtensionReceiverType()!!) + if (substitutor != null) { + return Pair(operator.substitute(substitutor), substitutor) } } @@ -88,10 +95,10 @@ class TypesWithContainsDetector( private val argumentType: KotlinType ) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope, indicesHelper) { - override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { + override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): TypeSubstitutor? { val parameter = function.valueParameters.single() val fuzzyParameterType = FuzzyType(parameter.type, function.typeParameters + freeTypeParams) - return fuzzyParameterType.checkIsSuperTypeOf(argumentType) != null + return fuzzyParameterType.checkIsSuperTypeOf(argumentType) } } @@ -102,16 +109,15 @@ class TypesWithGetValueDetector( private val propertyType: KotlinType? ) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope, indicesHelper) { - override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { + override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): TypeSubstitutor? { val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) - if (paramType.checkIsSuperTypeOf(propertyOwnerType) == null) return false + val substitutor = paramType.checkIsSuperTypeOf(propertyOwnerType) ?: return null - if (propertyType != null) { - val returnType = FuzzyType(function.returnType ?: return false, freeTypeParams) - return returnType.checkIsSubtypeOf(propertyType) != null - } + if (propertyType == null) return substitutor - return true + val fuzzyReturnType = FuzzyType(function.returnType ?: return null, freeTypeParams) + val substitutorFromPropertyType = fuzzyReturnType.checkIsSubtypeOf(propertyType) ?: return null + return TypeSubstitutor.createChainedSubstitutor(substitutor.substitution, substitutorFromPropertyType.substitution) } } @@ -121,8 +127,8 @@ class TypesWithSetValueDetector( private val propertyOwnerType: KotlinType ) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope, indicesHelper) { - override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { + override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): TypeSubstitutor? { val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) - return paramType.checkIsSuperTypeOf(propertyOwnerType) != null + return paramType.checkIsSuperTypeOf(propertyOwnerType) } } \ No newline at end of file