From 4dc7081dc4b407c4b78f4a4a19ad2fb17b994c82 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 6 Jul 2015 22:16:13 +0300 Subject: [PATCH] Completion: fixed filtering shadowed declarations for generic functions --- .../idea/util/ShadowedDeclarationsFilter.kt | 9 ++- .../jetbrains/kotlin/util/descriptorUtils.kt | 58 +++++++++++++++++++ .../kotlin/idea/completion/CompletionUtils.kt | 36 ------------ .../completion/DeclarationLookupObjectImpl.kt | 1 + .../kotlin/idea/completion/smart/Utils.kt | 1 + .../shadowing/PreferCloserReceiverGeneric.kt | 14 +++++ .../PreferMoreSpecificExtensionGeneric.kt | 9 +++ ...erMoreSpecificExtensionGenericWithParam.kt | 9 +++ ...MoreSpecificExtensionGeneric.dependency.kt | 7 +++ .../MoreSpecificExtensionGeneric.kt | 6 ++ .../test/JSBasicCompletionTestGenerated.java | 18 ++++++ .../test/JvmBasicCompletionTestGenerated.java | 18 ++++++ ...tiFileJvmBasicCompletionTestGenerated.java | 6 ++ 13 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt create mode 100644 idea/idea-completion/testData/basic/common/shadowing/PreferCloserReceiverGeneric.kt create mode 100644 idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGeneric.kt create mode 100644 idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGenericWithParam.kt create mode 100644 idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.dependency.kt create mode 100644 idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ShadowedDeclarationsFilter.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ShadowedDeclarationsFilter.kt index fc6551a2fc9..577e5bc0566 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ShadowedDeclarationsFilter.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ShadowedDeclarationsFilter.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.resolve.scopes.ChainedScope import org.jetbrains.kotlin.resolve.scopes.ExplicitImportsScope import org.jetbrains.kotlin.resolve.validation.SymbolUsageValidator import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.util.descriptorsEqualWithSubstitution import java.util.ArrayList import java.util.HashSet @@ -168,8 +169,12 @@ public class ShadowedDeclarationsFilter( CompositeChecker(listOf()), SymbolUsageValidator.Empty, AdditionalTypeChecker.Composite(listOf()), false) val callResolver = createContainerForMacros(project, moduleDescriptor).callResolver val results = if (isFunction) callResolver.resolveFunctionCall(context) else callResolver.resolveSimpleProperty(context) - val resultingDescriptors = results.getResultingCalls().map { it.getResultingDescriptor() }.toSet() - val filtered = descriptors.filter { it in resultingDescriptors } + val resultingDescriptors = results.getResultingCalls().map { it.getResultingDescriptor() } + val resultingOriginals = resultingDescriptors.mapTo(HashSet()) { it.getOriginal() } + val filtered = descriptors.filter { candidateDescriptor -> + candidateDescriptor.getOriginal() in resultingOriginals /* optimization */ + && resultingDescriptors.any { descriptorsEqualWithSubstitution(it, candidateDescriptor) } + } return if (filtered.isNotEmpty()) filtered else descriptors /* something went wrong, none of our declarations among resolve candidates, let's not filter anything */ } diff --git a/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt new file mode 100644 index 00000000000..61598d00aad --- /dev/null +++ b/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt @@ -0,0 +1,58 @@ +/* + * 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.util + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.types.TypeConstructor +import org.jetbrains.kotlin.types.checker.JetTypeChecker +import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls + +public fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean { + if (descriptor1 == descriptor2) return true + if (descriptor1 == null || descriptor2 == null) return false + if (descriptor1.getOriginal() != descriptor2.getOriginal()) return false + if (descriptor1 !is CallableDescriptor) return true + descriptor2 as CallableDescriptor + + val typeChecker = JetTypeChecker.withAxioms(object: JetTypeChecker.TypeConstructorEquality { + override fun equals(a: TypeConstructor, b: TypeConstructor): Boolean { + val typeParam1 = a.getDeclarationDescriptor() as? TypeParameterDescriptor + val typeParam2 = b.getDeclarationDescriptor() as? TypeParameterDescriptor + if (typeParam1 != null + && typeParam2 != null + && typeParam1.getContainingDeclaration() == descriptor1 + && typeParam2.getContainingDeclaration() == descriptor2) { + return typeParam1.getIndex() == typeParam2.getIndex() + } + + return a == b + } + }) + + if (!typeChecker.equalTypesOrNulls(descriptor1.getReturnType(), descriptor2.getReturnType())) return false + + val parameters1 = descriptor1.getValueParameters() + val parameters2 = descriptor2.getValueParameters() + if (parameters1.size() != parameters2.size()) return false + for ((param1, param2) in parameters1.zip(parameters2)) { + if (!typeChecker.equalTypes(param1.getType(), param2.getType())) return false + } + return true +} + diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt index e4b0d7583fe..ec998138fdc 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt @@ -140,42 +140,6 @@ enum class CallableWeight { val CALLABLE_WEIGHT_KEY = Key("CALLABLE_WEIGHT_KEY") -fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean { - if (descriptor1 == descriptor2) return true - if (descriptor1 == null || descriptor2 == null) return false - if (descriptor1.getOriginal() != descriptor2.getOriginal()) return false - if (descriptor1 !is CallableDescriptor) return true - descriptor2 as CallableDescriptor - - // optimization: - if (descriptor1 == descriptor1.getOriginal() && descriptor2 == descriptor2.getOriginal()) return true - - val typeChecker = JetTypeChecker.withAxioms(object: JetTypeChecker.TypeConstructorEquality { - override fun equals(a: TypeConstructor, b: TypeConstructor): Boolean { - val typeParam1 = a.getDeclarationDescriptor() as? TypeParameterDescriptor - val typeParam2 = b.getDeclarationDescriptor() as? TypeParameterDescriptor - if (typeParam1 != null - && typeParam2 != null - && typeParam1.getContainingDeclaration() == descriptor1 - && typeParam2.getContainingDeclaration() == descriptor2) { - return typeParam1.getIndex() == typeParam2.getIndex() - } - - return a == b - } - }) - - if (!typeChecker.equalTypesOrNulls(descriptor1.getReturnType(), descriptor2.getReturnType())) return false - - val parameters1 = descriptor1.getValueParameters() - val parameters2 = descriptor2.getValueParameters() - if (parameters1.size() != parameters2.size()) return false - for ((param1, param2) in parameters1.zip(parameters2)) { - if (!typeChecker.equalTypes(param1.getType(), param2.getType())) return false - } - return true -} - fun InsertionContext.isAfterDot(): Boolean { var offset = getStartOffset() val chars = getDocument().getCharsSequence() diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DeclarationLookupObjectImpl.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DeclarationLookupObjectImpl.kt index 6a17db302ff..82fc10112bc 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DeclarationLookupObjectImpl.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DeclarationLookupObjectImpl.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject +import org.jetbrains.kotlin.util.descriptorsEqualWithSubstitution import javax.swing.Icon /** diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt index 7248aacd28b..cb1c7208726 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.util.descriptorsEqualWithSubstitution import java.util.ArrayList import java.util.HashMap import java.util.HashSet diff --git a/idea/idea-completion/testData/basic/common/shadowing/PreferCloserReceiverGeneric.kt b/idea/idea-completion/testData/basic/common/shadowing/PreferCloserReceiverGeneric.kt new file mode 100644 index 00000000000..ee7ce332369 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/shadowing/PreferCloserReceiverGeneric.kt @@ -0,0 +1,14 @@ +interface I { + fun xxx(): T +} + +fun foo(i1: I, i2: I) { + with (i1) { + with (i2) { + xx + } + } +} + +// EXIST: { lookupString: "xxx", itemText: "xxx", tailText: "()", typeText: "String" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGeneric.kt b/idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGeneric.kt new file mode 100644 index 00000000000..6227f2ec54f --- /dev/null +++ b/idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGeneric.kt @@ -0,0 +1,9 @@ +fun List.xxx(){} +fun Iterable.xxx(){} + +fun foo() { + listOf(1).xx +} + +// EXIST: { lookupString: "xxx", itemText: "xxx", tailText: "() for List in ", typeText: "Unit" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGenericWithParam.kt b/idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGenericWithParam.kt new file mode 100644 index 00000000000..780455b69ff --- /dev/null +++ b/idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGenericWithParam.kt @@ -0,0 +1,9 @@ +fun List.xxx(t: T){} +fun Iterable.xxx(t: T){} + +fun foo() { + listOf(1).xx +} + +// EXIST: { lookupString: "xxx", itemText: "xxx", tailText: "(t: Int) for List in ", typeText: "Unit" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.dependency.kt b/idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.dependency.kt new file mode 100644 index 00000000000..32d8a983113 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.dependency.kt @@ -0,0 +1,7 @@ +package dependency + +interface Iterable +interface List : Iterable + +fun List.xxx(t: T){} +fun Iterable.xxx(t: T){} diff --git a/idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.kt b/idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.kt new file mode 100644 index 00000000000..54651e1bb41 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/MoreSpecificExtensionGeneric.kt @@ -0,0 +1,6 @@ +fun foo(list: dependency.List) { + list.xx +} + +// EXIST: { lookupString: "xxx", itemText: "xxx", tailText: "(t: Int) for List in dependency", typeText: "Unit" } +// NOTHING_ELSE diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index c2a1f942200..df3dca167dc 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -1650,6 +1650,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } + @TestMetadata("PreferCloserReceiverGeneric.kt") + public void testPreferCloserReceiverGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferCloserReceiverGeneric.kt"); + doTest(fileName); + } + @TestMetadata("PreferMemberExtension.kt") public void testPreferMemberExtension() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMemberExtension.kt"); @@ -1673,6 +1679,18 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtension.kt"); doTest(fileName); } + + @TestMetadata("PreferMoreSpecificExtensionGeneric.kt") + public void testPreferMoreSpecificExtensionGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGeneric.kt"); + doTest(fileName); + } + + @TestMetadata("PreferMoreSpecificExtensionGenericWithParam.kt") + public void testPreferMoreSpecificExtensionGenericWithParam() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGenericWithParam.kt"); + doTest(fileName); + } } @TestMetadata("idea/idea-completion/testData/basic/common/typeArgsOrNot") diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index f8475833bc8..4a7e00f9a02 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -1650,6 +1650,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("PreferCloserReceiverGeneric.kt") + public void testPreferCloserReceiverGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferCloserReceiverGeneric.kt"); + doTest(fileName); + } + @TestMetadata("PreferMemberExtension.kt") public void testPreferMemberExtension() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMemberExtension.kt"); @@ -1673,6 +1679,18 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtension.kt"); doTest(fileName); } + + @TestMetadata("PreferMoreSpecificExtensionGeneric.kt") + public void testPreferMoreSpecificExtensionGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGeneric.kt"); + doTest(fileName); + } + + @TestMetadata("PreferMoreSpecificExtensionGenericWithParam.kt") + public void testPreferMoreSpecificExtensionGenericWithParam() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/shadowing/PreferMoreSpecificExtensionGenericWithParam.kt"); + doTest(fileName); + } } @TestMetadata("idea/idea-completion/testData/basic/common/typeArgsOrNot") diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java index 5712c7300b2..a8116ef2271 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java @@ -137,6 +137,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ doTest(fileName); } + @TestMetadata("MoreSpecificExtensionGeneric") + public void testMoreSpecificExtensionGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionGeneric/"); + doTest(fileName); + } + @TestMetadata("MoreSpecificExtensionInDifferentPackage") public void testMoreSpecificExtensionInDifferentPackage() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/MoreSpecificExtensionInDifferentPackage/");