Correct substitutions for smart completion after "by" and "in"

This commit is contained in:
Valentin Kipyatkov
2016-03-24 22:02:52 +03:00
parent 7d3229538e
commit ba6accd5f9
14 changed files with 187 additions and 54 deletions
@@ -19,6 +19,7 @@
package org.jetbrains.kotlin.idea.util package org.jetbrains.kotlin.idea.util
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.resolve.calls.inference.CallHandle import org.jetbrains.kotlin.resolve.calls.inference.CallHandle
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl
@@ -56,15 +57,26 @@ class FuzzyType(
init { init {
if (freeParameters.isNotEmpty()) { if (freeParameters.isNotEmpty()) {
val usedTypeParameters = HashSet<TypeParameterDescriptor>() // we allow to pass type parameters from another function with the same original in freeParameters
usedTypeParameters.addUsedTypeParameters(type) val usedTypeParameters = HashSet<TypeParameterDescriptor>().apply { addUsedTypeParameters(type) }
this.freeParameters = freeParameters.filter { it in usedTypeParameters }.toSet() if (usedTypeParameters.isNotEmpty()) {
val originalFreeParameters = freeParameters.map { it.toOriginal() }.toSet()
this.freeParameters = usedTypeParameters.filter { it.toOriginal() in originalFreeParameters }.toSet()
}
else {
this.freeParameters = emptySet()
}
} }
else { else {
this.freeParameters = emptySet() 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 equals(other: Any?) = other is FuzzyType && other.type == type && other.freeParameters == freeParameters
override fun hashCode() = type.hashCode() override fun hashCode() = type.hashCode()
@@ -137,9 +149,9 @@ class FuzzyType(
// that's why we have to check subtyping manually // that's why we have to check subtyping manually
val substitutor = constraintSystem.resultingSubstitutor val substitutor = constraintSystem.resultingSubstitutor
val substitutedType = substitutor.substitute(type, Variance.INVARIANT) ?: return null 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 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 if (!substitutedType.checkInheritance(otherSubstitutedType)) return null
val substitution = constraintSystem.typeVariables.map { it.originalTypeParameter }.associateBy({ it.typeConstructor }) { val substitution = constraintSystem.typeVariables.map { it.originalTypeParameter }.associateBy({ it.typeConstructor }) {
@@ -11,7 +11,7 @@ fun foo(a: A, cA: Collection<A>, cB: Collection<B>, cC: Collection<C>, cAny: Co
} }
// EXIST: cA // EXIST: cA
// ABSENT: cB // EXIST: cB
// ABSENT: cC // ABSENT: cC
// EXIST: cAny // EXIST: cAny
// EXIST: lA // EXIST: lA
@@ -19,6 +19,6 @@ fun foo(a: A, cA: Collection<A>, cB: Collection<B>, cC: Collection<C>, cAny: Co
// ABSENT: lC // ABSENT: lC
// EXIST: lAny // EXIST: lAny
// EXIST: aA // EXIST: aA
// ABSENT: aB // EXIST: aB
// ABSENT: aC // ABSENT: aC
// EXIST: aAny // EXIST: aAny
@@ -2,4 +2,4 @@ fun foo(s: String) {
if (s in <caret>) if (s in <caret>)
} }
// EXIST: { lookupString:"listOf", itemText: "listOf", tailText: "(vararg elements: T) (kotlin.collections)", typeText:"List<T>" } // EXIST: { lookupString:"listOf", itemText: "listOf", tailText: "(vararg elements: String) (kotlin.collections)", typeText:"List<String>" }
@@ -10,4 +10,4 @@ interface A {
} }
} }
// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: T)", typeText:"X<T>" } // EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: String)", typeText:"X<String>" }
@@ -10,4 +10,4 @@ interface A {
} }
} }
// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: T)", typeText:"X<T>" } // EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: String)", typeText:"X<String>" }
@@ -4,7 +4,7 @@ class C {
val v by Delegates.<caret> val v by Delegates.<caret>
} }
// EXIST: notNull // EXIST: { itemText: "notNull", typeText: "ReadWriteProperty<Any?, T>" }
// EXIST: observable // EXIST: { itemText: "observable", typeText: "ReadWriteProperty<Any?, T>" }
// EXIST: vetoable // EXIST: { itemText: "vetoable", typeText: "ReadWriteProperty<Any?, T>" }
// NOTHING_ELSE // NOTHING_ELSE
@@ -0,0 +1,10 @@
import kotlin.properties.Delegates
class C {
val v: String by Delegates.<caret>
}
// EXIST: { itemText: "notNull", typeText: "ReadWriteProperty<Any?, String>" }
// EXIST: { itemText: "observable", typeText: "ReadWriteProperty<Any?, String>" }
// EXIST: { itemText: "vetoable", typeText: "ReadWriteProperty<Any?, String>" }
// NOTHING_ELSE
@@ -0,0 +1,15 @@
import kotlin.reflect.KProperty
class Property<TOwner, TValue>
operator fun <TValue, TOwner> Property<TOwner, TValue>.getValue(thisRef: TOwner, property: KProperty<*>): TValue {
throw Exception()
}
fun<TOwner, TValue> createProperty(): Property<TOwner, TValue> = Property()
class C {
val v by create<caret>
}
// EXIST: { itemText: "createProperty", typeText: "Property<C, TValue>" }
@@ -0,0 +1,15 @@
import kotlin.reflect.KProperty
class Property<TOwner, TValue>
operator fun <TValue, TOwner> Property<TOwner, TValue>.getValue(thisRef: TOwner, property: KProperty<*>): TValue {
throw Exception()
}
fun<TOwner, TValue> createProperty(): Property<TOwner, TValue> = Property()
class C {
val v: Int by create<caret>
}
// EXIST: { itemText: "createProperty", typeText: "Property<C, Int>" }
@@ -0,0 +1,19 @@
import kotlin.reflect.KProperty
class Property<TOwner1, TValue1>
operator fun <TValue2, TOwner2> Property<TOwner2, TValue2>.getValue(thisRef: TOwner2, property: KProperty<*>): TValue2 {
throw Exception()
}
operator fun <TValue3, TOwner3> Property<TOwner3, TValue3>.setValue(thisRef: TOwner3, property: KProperty<*>, value: TValue3) {
throw Exception()
}
fun<TOwner4, TValue4> createProperty(): Property<TOwner4, TValue4> = Property()
class C {
var v by create<caret>
}
// EXIST: { itemText: "createProperty", typeText: "Property<C, TValue4>" }
@@ -0,0 +1,19 @@
import kotlin.reflect.KProperty
class Property<TOwner1, TValue1>
operator fun <TValue2, TOwner2> Property<TOwner2, TValue2>.getValue(thisRef: TOwner2, property: KProperty<*>): TValue2 {
throw Exception()
}
operator fun <TValue3, TOwner3> Property<TOwner3, TValue3>.setValue(thisRef: TOwner3, property: KProperty<*>, value: TValue3) {
throw Exception()
}
fun<TOwner4, TValue4> createProperty(): Property<TOwner4, TValue4> = Property()
class C {
var v: Int by create<caret>
}
// EXIST: { itemText: "createProperty", typeText: "Property<C, Int>" }
@@ -1450,6 +1450,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName); 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") @TestMetadata("ExplicitValType.kt")
public void testExplicitValType() throws Exception { public void testExplicitValType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt");
@@ -1462,6 +1468,30 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName); 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") @TestMetadata("ExtensionVal.kt")
public void testExtensionVal() throws Exception { public void testExtensionVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt");
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.* import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.utils.addToStdlib.check import org.jetbrains.kotlin.utils.addToStdlib.check
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
@@ -592,7 +593,7 @@ class ExpectedInfos(
val byTypeFilter = object : ByTypeFilter { val byTypeFilter = object : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? { 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)) return listOf(ExpectedInfo(byTypeFilter, null, null))
@@ -614,16 +615,22 @@ class ExpectedInfos(
val byTypeFilter = object : ByTypeFilter { val byTypeFilter = object : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? { override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? {
val getValueOperator = typesWithGetDetector.findOperator(descriptorType) ?: return null val (getValueOperator, getOperatorSubstitutor) = typesWithGetDetector.findOperator(descriptorType) ?: return null
if (typesWithSetDetector != null) { if (typesWithSetDetector == null) return getOperatorSubstitutor
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
}
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)) return listOf(ExpectedInfo(byTypeFilter, null, null))
@@ -19,12 +19,14 @@ package org.jetbrains.kotlin.idea.core
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.idea.util.FuzzyType 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.idea.util.nullability
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.typeUtil.TypeNullability import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.isValidOperator import org.jetbrains.kotlin.util.isValidOperator
@@ -35,26 +37,26 @@ abstract class TypesWithOperatorDetector(
private val scope: LexicalScope, private val scope: LexicalScope,
private val indicesHelper: KotlinIndicesHelper? private val indicesHelper: KotlinIndicesHelper?
) { ) {
protected abstract fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean protected abstract fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): TypeSubstitutor?
private val cache = HashMap<FuzzyType, FunctionDescriptor?>() private val cache = HashMap<FuzzyType, Pair<FunctionDescriptor, TypeSubstitutor>?>()
private val typesWithExtensionFromScope: Map<KotlinType, FunctionDescriptor> by lazy { private val extensionOperators: Collection<FunctionDescriptor> by lazy {
scope.collectFunctions(name, NoLookupLocation.FROM_IDE) val result = ArrayList<FunctionDescriptor>()
.filter { it.extensionReceiverParameter != null && it.isValidOperator() && isSuitableByType(it, it.typeParameters) } collectExtensionOperators(scope.collectFunctions(name, NoLookupLocation.FROM_IDE), result)
.map { it.extensionReceiverParameter!!.type to it } indicesHelper?.getTopLevelExtensionOperatorsByName(name.asString())?.let { collectExtensionOperators(it, result) }
.toMap() result.distinctBy { it.original }
} }
private val typesWithExtensionFromIndices: Map<KotlinType, FunctionDescriptor> by lazy { private fun collectExtensionOperators(functions: Collection<FunctionDescriptor>, result: MutableCollection<FunctionDescriptor>) {
indicesHelper?.getTopLevelExtensionOperatorsByName(name.asString()) for (function in functions) {
?.filter { it.extensionReceiverParameter != null && it.isValidOperator() && isSuitableByType(it, it.typeParameters) } if (function.extensionReceiverParameter == null || !function.isValidOperator()) continue
?.map { it.extensionReceiverParameter!!.type to it } val substitutor = checkIsSuitableByType(function, function.typeParameters) ?: continue
?.filter { it.first !in typesWithExtensionFromScope.keys } result.add(function.substitute(substitutor))
?.toMap() ?: emptyMap() }
} }
fun findOperator(type: FuzzyType): FunctionDescriptor? { fun findOperator(type: FuzzyType): Pair<FunctionDescriptor, TypeSubstitutor>? {
if (cache.containsKey(type)) { if (cache.containsKey(type)) {
return cache[type] return cache[type]
} }
@@ -65,16 +67,21 @@ abstract class TypesWithOperatorDetector(
} }
} }
private fun findOperatorNoCache(type: FuzzyType): FunctionDescriptor? { private fun findOperatorNoCache(type: FuzzyType): Pair<FunctionDescriptor, TypeSubstitutor>? {
if (type.nullability() != TypeNullability.NULLABLE) { if (type.nullability() != TypeNullability.NULLABLE) {
val memberFunction = type.type.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) for (memberFunction in type.type.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE)) {
.firstOrNull { it.isValidOperator() && isSuitableByType(it, type.freeParameters) } if (memberFunction.isValidOperator()) {
if (memberFunction != null) return memberFunction checkIsSuitableByType(memberFunction, type.freeParameters)?.let { substitutor ->
return Pair(memberFunction.substitute(substitutor), substitutor)
}
}
}
} }
for ((typeWithExtension, operator) in typesWithExtensionFromScope + typesWithExtensionFromIndices) { for (operator in extensionOperators) {
if (type.checkIsSubtypeOf(typeWithExtension) != null) { val substitutor = type.checkIsSubtypeOf(operator.fuzzyExtensionReceiverType()!!)
return operator //TODO: substitution if (substitutor != null) {
return Pair(operator.substitute(substitutor), substitutor)
} }
} }
@@ -88,10 +95,10 @@ class TypesWithContainsDetector(
private val argumentType: KotlinType private val argumentType: KotlinType
) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope, indicesHelper) { ) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope, indicesHelper) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean { override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): TypeSubstitutor? {
val parameter = function.valueParameters.single() val parameter = function.valueParameters.single()
val fuzzyParameterType = FuzzyType(parameter.type, function.typeParameters + freeTypeParams) 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? private val propertyType: KotlinType?
) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope, indicesHelper) { ) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope, indicesHelper) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean { override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): TypeSubstitutor? {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) 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) { if (propertyType == null) return substitutor
val returnType = FuzzyType(function.returnType ?: return false, freeTypeParams)
return returnType.checkIsSubtypeOf(propertyType) != null
}
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 private val propertyOwnerType: KotlinType
) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope, indicesHelper) { ) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope, indicesHelper) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean { override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): TypeSubstitutor? {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams)
return paramType.checkIsSuperTypeOf(propertyOwnerType) != null return paramType.checkIsSuperTypeOf(propertyOwnerType)
} }
} }