Initial implementation of smart completion after "by"

#KT-11787 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-02-01 19:02:06 -07:00
parent c2065c1856
commit cda539d769
10 changed files with 421 additions and 60 deletions
@@ -0,0 +1,35 @@
import kotlin.reflect.KProperty
class X1 {
operator fun getValue(thisRef: C, property: KProperty<*>): CharSequence = ""
}
class X2 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
}
class X3 {
operator fun getValue(thisRef: C, property: KProperty<*>): Any = ""
}
class Y1
class Y2
operator fun Y1.getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun Y2.getValue(thisRef: C, property: KProperty<*>): Int = 1
fun createX1() = X1()
fun createX2() = X2()
fun createX3() = X3()
fun createY1() = Y1()
fun createY2() = Y2()
class C {
val property: CharSequence by <caret>
}
// EXIST: lazy
// EXIST: createX1
// EXIST: createX2
// ABSENT: createX3
// EXIST: createY1
// ABSENT: createY2
/*TODO: add constructors*/
@@ -0,0 +1,29 @@
import kotlin.reflect.KProperty
class X1 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: String) {}
}
class X2 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: CharSequence) {}
}
class X3 {
operator fun getValue(thisRef: C, property: KProperty<*>): CharSequence = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: String) {}
}
fun createX1() = X1()
fun createX2() = X2()
fun createX3() = X3()
class C {
var property: CharSequence by <caret>
}
// ABSENT: lazy
// ABSENT: createX1
// EXIST: createX2
// ABSENT: createX3
@@ -0,0 +1,39 @@
import kotlin.reflect.KProperty
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 = ""
fun createX1() = X1()
fun createX2() = X2()
fun createX3() = X3()
fun createY1() = Y1()
fun createY2() = Y2()
fun createY3() = Y3()
class C
val C.property by <caret>
// EXIST: lazy
// EXIST: createX1
// ABSENT: createX2
// EXIST: createX3
// EXIST: createY1
// ABSENT: createY2
// EXIST: createY3
/*TODO: add constructors*/
@@ -0,0 +1,42 @@
import kotlin.reflect.KProperty
class X1 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: String) {}
}
class X2 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
}
class X3 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: String, property: KProperty<*>, value: String) {}
}
class X4 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: CharSequence) {}
}
class X5 {
operator fun getValue(thisRef: C, property: KProperty<*>): CharSequence = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: String) {}
}
fun createX1() = X1()
fun createX2() = X2()
fun createX3() = X3()
fun createX4() = X4()
fun createX5() = X5()
class C
var C.property by <caret>
// ABSENT: lazy
// EXIST: createX1
// ABSENT: createX2
// ABSENT: createX3
// EXIST: createX4
// ABSENT: createX5
@@ -0,0 +1,39 @@
import kotlin.reflect.KProperty
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 = ""
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: lazy
// EXIST: createX1
// ABSENT: createX2
// EXIST: createX3
// EXIST: createY1
// ABSENT: createY2
// EXIST: createY3
/*TODO: add constructors*/
@@ -0,0 +1,42 @@
import kotlin.reflect.KProperty
class X1 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: String) {}
}
class X2 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
}
class X3 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: String, property: KProperty<*>, value: String) {}
}
class X4 {
operator fun getValue(thisRef: C, property: KProperty<*>): String = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: CharSequence) {}
}
class X5 {
operator fun getValue(thisRef: C, property: KProperty<*>): CharSequence = ""
operator fun setValue(thisRef: C, property: KProperty<*>, value: String) {}
}
fun createX1() = X1()
fun createX2() = X2()
fun createX3() = X3()
fun createX4() = X4()
fun createX5() = X5()
class C {
var property by <caret>
}
// ABSENT: lazy
// EXIST: createX1
// ABSENT: createX2
// ABSENT: createX3
// EXIST: createX4
// ABSENT: createX5
@@ -1436,6 +1436,51 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
}
}
@TestMetadata("idea/idea-completion/testData/smart/propertyDelegate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PropertyDelegate extends AbstractJvmSmartCompletionTest {
public void testAllFilesPresentInPropertyDelegate() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/smart/propertyDelegate"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ExplicitValType.kt")
public void testExplicitValType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt");
doTest(fileName);
}
@TestMetadata("ExplicitVarType.kt")
public void testExplicitVarType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExplicitVarType.kt");
doTest(fileName);
}
@TestMetadata("ExtensionVal.kt")
public void testExtensionVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt");
doTest(fileName);
}
@TestMetadata("ExtensionVar.kt")
public void testExtensionVar() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ExtensionVar.kt");
doTest(fileName);
}
@TestMetadata("ValInClass.kt")
public void testValInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/ValInClass.kt");
doTest(fileName);
}
@TestMetadata("VarInClass.kt")
public void testVarInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/propertyDelegate/VarInClass.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/smart/smartCasts")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -158,6 +158,7 @@ class ExpectedInfos(
?: calculateForReturn(expressionWithType)
?: calculateForLoopRange(expressionWithType)
?: calculateForInOperatorArgument(expressionWithType)
?: calculateForPropertyDelegate(expressionWithType)
?: getFromBindingContext(expressionWithType)
?: return emptyList()
return expectedInfos.filterNot { it.fuzzyType?.type?.isError ?: false }
@@ -590,12 +591,44 @@ class ExpectedInfos(
val byTypeFilter = object : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? {
return if (detector.hasContains(descriptorType)) TypeSubstitutor.EMPTY else null
return if (detector.findOperator(descriptorType) != null) TypeSubstitutor.EMPTY else null
}
}
return listOf(ExpectedInfo(byTypeFilter, null, null))
}
private fun calculateForPropertyDelegate(expressionWithType: KtExpression): Collection<ExpectedInfo>? {
val delegate = expressionWithType.parent as? KtPropertyDelegate ?: return null
val propertyDeclaration = delegate.parent as? KtProperty ?: return null
val property = resolutionFacade.resolveToDescriptor(propertyDeclaration) as? PropertyDescriptor ?: return null
val scope = expressionWithType.getResolutionScope(bindingContext, resolutionFacade)
val propertyOwnerType = property.extensionReceiverParameter?.type
?: property.dispatchReceiverParameter?.type
?: 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 byTypeFilter = object : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? {
val getValueOperator = 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
}
return TypeSubstitutor.EMPTY //TODO: substitutor from property type
}
}
return listOf(ExpectedInfo(byTypeFilter, null, null))
//TODO: special items for "Delegates...."
}
private fun getFromBindingContext(expressionWithType: KtExpression): Collection<ExpectedInfo>? {
val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null
return listOf(ExpectedInfo(expectedType, null, null))
@@ -1,59 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.nullability
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
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.typeUtil.TypeNullability
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.isValidOperator
import java.util.*
internal class TypesWithContainsDetector(
private val scope: LexicalScope,
private val argumentType: KotlinType
) {
private val cache = HashMap<FuzzyType, Boolean>()
private val typesWithExtensionContains: Collection<KotlinType> = scope
.collectFunctions(OperatorNameConventions.CONTAINS, NoLookupLocation.FROM_IDE)
.filter { it.extensionReceiverParameter != null && isGoodContainsFunction(it, listOf()) }
.map { it.extensionReceiverParameter!!.type }
fun hasContains(type: FuzzyType): Boolean {
return cache.getOrPut(type, { hasContainsNoCache(type) })
}
private fun hasContainsNoCache(type: FuzzyType): Boolean {
return type.nullability() != TypeNullability.NULLABLE &&
type.type.memberScope.getContributedFunctions(OperatorNameConventions.CONTAINS, NoLookupLocation.FROM_IDE).any { isGoodContainsFunction(it, type.freeParameters) }
|| typesWithExtensionContains.any { type.checkIsSubtypeOf(it) != null }
}
private fun isGoodContainsFunction(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
if (!function.isValidOperator()) return false
val parameter = function.valueParameters.single()
val fuzzyParameterType = FuzzyType(parameter.type, function.typeParameters + freeTypeParams)
return fuzzyParameterType.checkIsSuperTypeOf(argumentType) != null
}
}
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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.typeUtil.TypeNullability
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
) {
protected abstract fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean
private val cache = HashMap<FuzzyType, FunctionDescriptor?>()
private val typesWithExtension: 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()
}
fun findOperator(type: FuzzyType): FunctionDescriptor? {
if (cache.containsKey(type)) {
return cache[type]
}
else {
val result = findOperatorNoCache(type)
cache[type] = result
return result
}
}
private fun findOperatorNoCache(type: FuzzyType): FunctionDescriptor? {
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 ((typeWithExtension, operator) in typesWithExtension) {
if (type.checkIsSubtypeOf(typeWithExtension) != null) {
return operator //TODO: substitution
}
}
return null
}
}
class TypesWithContainsDetector(
scope: LexicalScope,
private val argumentType: KotlinType
) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
val parameter = function.valueParameters.single()
val fuzzyParameterType = FuzzyType(parameter.type, function.typeParameters + freeTypeParams)
return fuzzyParameterType.checkIsSuperTypeOf(argumentType) != null
}
}
class TypesWithGetValueDetector(
scope: LexicalScope,
private val propertyOwnerType: KotlinType,
private val propertyType: KotlinType?
) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams)
if (paramType.checkIsSuperTypeOf(propertyOwnerType) == null) return false
if (propertyType != null) {
val returnType = FuzzyType(function.returnType ?: return false, freeTypeParams)
return returnType.checkIsSubtypeOf(propertyType) != null
}
return true
}
}
class TypesWithSetValueDetector(
scope: LexicalScope,
private val propertyOwnerType: KotlinType
) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope) {
override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams)
return paramType.checkIsSuperTypeOf(propertyOwnerType) != null
}
}