From cda539d769e6c6ece3f2f474ef9e69182ec3506b Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 1 Feb 2016 19:02:06 -0700 Subject: [PATCH] Initial implementation of smart completion after "by" #KT-11787 Fixed --- .../smart/propertyDelegate/ExplicitValType.kt | 35 ++++++ .../smart/propertyDelegate/ExplicitVarType.kt | 29 +++++ .../smart/propertyDelegate/ExtensionVal.kt | 39 ++++++ .../smart/propertyDelegate/ExtensionVar.kt | 42 +++++++ .../smart/propertyDelegate/ValInClass.kt | 39 ++++++ .../smart/propertyDelegate/VarInClass.kt | 42 +++++++ .../test/JvmSmartCompletionTestGenerated.java | 45 +++++++ .../kotlin/idea/core/ExpectedInfos.kt | 35 +++++- .../idea/core/TypesWithContainsDetector.kt | 59 --------- .../idea/core/TypesWithOperatorDetector.kt | 116 ++++++++++++++++++ 10 files changed, 421 insertions(+), 60 deletions(-) create mode 100644 idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt create mode 100644 idea/idea-completion/testData/smart/propertyDelegate/ExplicitVarType.kt create mode 100644 idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt create mode 100644 idea/idea-completion/testData/smart/propertyDelegate/ExtensionVar.kt create mode 100644 idea/idea-completion/testData/smart/propertyDelegate/ValInClass.kt create mode 100644 idea/idea-completion/testData/smart/propertyDelegate/VarInClass.kt delete mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithContainsDetector.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt new file mode 100644 index 00000000000..8553fb2f8d8 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExplicitValType.kt @@ -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 +} + +// EXIST: lazy +// EXIST: createX1 +// EXIST: createX2 +// ABSENT: createX3 +// EXIST: createY1 +// ABSENT: createY2 +/*TODO: add constructors*/ diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExplicitVarType.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExplicitVarType.kt new file mode 100644 index 00000000000..5754ea56102 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExplicitVarType.kt @@ -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 +} + +// ABSENT: lazy +// ABSENT: createX1 +// EXIST: createX2 +// ABSENT: createX3 diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt new file mode 100644 index 00000000000..9336efe9a47 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionVal.kt @@ -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 + +// EXIST: lazy +// EXIST: createX1 +// ABSENT: createX2 +// EXIST: createX3 +// EXIST: createY1 +// ABSENT: createY2 +// EXIST: createY3 +/*TODO: add constructors*/ diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ExtensionVar.kt b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionVar.kt new file mode 100644 index 00000000000..816ebb3950b --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ExtensionVar.kt @@ -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 + +// ABSENT: lazy +// EXIST: createX1 +// ABSENT: createX2 +// ABSENT: createX3 +// EXIST: createX4 +// ABSENT: createX5 diff --git a/idea/idea-completion/testData/smart/propertyDelegate/ValInClass.kt b/idea/idea-completion/testData/smart/propertyDelegate/ValInClass.kt new file mode 100644 index 00000000000..81cb5a3d521 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/ValInClass.kt @@ -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 +} + +// EXIST: lazy +// EXIST: createX1 +// ABSENT: createX2 +// EXIST: createX3 +// EXIST: createY1 +// ABSENT: createY2 +// EXIST: createY3 +/*TODO: add constructors*/ diff --git a/idea/idea-completion/testData/smart/propertyDelegate/VarInClass.kt b/idea/idea-completion/testData/smart/propertyDelegate/VarInClass.kt new file mode 100644 index 00000000000..a6f6c1513f5 --- /dev/null +++ b/idea/idea-completion/testData/smart/propertyDelegate/VarInClass.kt @@ -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 +} + +// ABSENT: lazy +// EXIST: createX1 +// ABSENT: createX2 +// ABSENT: createX3 +// EXIST: createX4 +// ABSENT: createX5 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 c73e6aba160..0850d39c4ee 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 @@ -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) 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 6cd4e8f722f..4ded54c4050 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 @@ -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? { + 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? { val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null return listOf(ExpectedInfo(expectedType, null, null)) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithContainsDetector.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithContainsDetector.kt deleted file mode 100644 index c9ccdd478ba..00000000000 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithContainsDetector.kt +++ /dev/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() - - private val typesWithExtensionContains: Collection = 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): Boolean { - if (!function.isValidOperator()) return false - val parameter = function.valueParameters.single() - val fuzzyParameterType = FuzzyType(parameter.type, function.typeParameters + freeTypeParams) - return fuzzyParameterType.checkIsSuperTypeOf(argumentType) != 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 new file mode 100644 index 00000000000..d1bbe2aba71 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt @@ -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): Boolean + + private val cache = HashMap() + + private val typesWithExtension: 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() + } + + 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): 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): 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): Boolean { + val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) + return paramType.checkIsSuperTypeOf(propertyOwnerType) != null + } +} \ No newline at end of file