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
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<TypeParameterDescriptor>()
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<TypeParameterDescriptor>().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 }) {
@@ -11,7 +11,7 @@ fun foo(a: A, cA: Collection<A>, cB: Collection<B>, cC: Collection<C>, cAny: Co
}
// EXIST: cA
// ABSENT: cB
// EXIST: cB
// ABSENT: cC
// EXIST: cAny
// EXIST: lA
@@ -19,6 +19,6 @@ fun foo(a: A, cA: Collection<A>, cB: Collection<B>, cC: Collection<C>, cAny: Co
// ABSENT: lC
// EXIST: lAny
// EXIST: aA
// ABSENT: aB
// EXIST: aB
// ABSENT: aC
// EXIST: aAny
@@ -2,4 +2,4 @@ fun foo(s: String) {
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>
}
// EXIST: notNull
// EXIST: observable
// EXIST: vetoable
// EXIST: { itemText: "notNull", typeText: "ReadWriteProperty<Any?, T>" }
// EXIST: { itemText: "observable", typeText: "ReadWriteProperty<Any?, T>" }
// EXIST: { itemText: "vetoable", typeText: "ReadWriteProperty<Any?, T>" }
// 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);
}
@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");
@@ -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))
@@ -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<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 {
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<FunctionDescriptor> by lazy {
val result = ArrayList<FunctionDescriptor>()
collectExtensionOperators(scope.collectFunctions(name, NoLookupLocation.FROM_IDE), result)
indicesHelper?.getTopLevelExtensionOperatorsByName(name.asString())?.let { collectExtensionOperators(it, result) }
result.distinctBy { it.original }
}
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()
private fun collectExtensionOperators(functions: Collection<FunctionDescriptor>, result: MutableCollection<FunctionDescriptor>) {
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<FunctionDescriptor, TypeSubstitutor>? {
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<FunctionDescriptor, TypeSubstitutor>? {
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<TypeParameterDescriptor>): Boolean {
override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): 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<TypeParameterDescriptor>): Boolean {
override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): 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<TypeParameterDescriptor>): Boolean {
override fun checkIsSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): TypeSubstitutor? {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams)
return paramType.checkIsSuperTypeOf(propertyOwnerType) != null
return paramType.checkIsSuperTypeOf(propertyOwnerType)
}
}