diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt index ab695d50b94..3a19494316c 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt @@ -152,8 +152,7 @@ class ReferenceVariantsHelper( } is CallTypeAndReceiver.CALLABLE_REFERENCE -> { - val resolutionScope = contextElement.getResolutionScope(bindingContext, resolutionFacade) - return getVariantsForCallableReference(callTypeAndReceiver.receiver, resolutionScope, kindFilter, nameFilter) + return getVariantsForCallableReference(callTypeAndReceiver, contextElement, useReceiverType, kindFilter, nameFilter) } is CallTypeAndReceiver.DEFAULT -> receiverExpression = null @@ -227,24 +226,37 @@ class ReferenceVariantsHelper( } private fun getVariantsForCallableReference( - receiverExpression: KtExpression?, - resolutionScope: LexicalScope, + callTypeAndReceiver: CallTypeAndReceiver.CALLABLE_REFERENCE, + contextElement: PsiElement, + useReceiverType: KotlinType?, kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean ): Collection { val descriptors = LinkedHashSet() - if (receiverExpression != null) { - val type = - (bindingContext.get(BindingContext.DOUBLE_COLON_LHS, receiverExpression) as? DoubleColonLHS.Type)?.type - ?: return emptyList() - descriptors.addNonExtensionMembers(listOf(type), kindFilter, nameFilter, constructorFilter = { true }) + val resolutionScope = contextElement.getResolutionScope(bindingContext, resolutionFacade) - descriptors.addScopeAndSyntheticExtensions(resolutionScope, listOf(type), CallType.CALLABLE_REFERENCE, kindFilter, nameFilter) + val receiver = callTypeAndReceiver.receiver + if (receiver != null) { + val isStatic = bindingContext[BindingContext.DOUBLE_COLON_LHS, receiver] is DoubleColonLHS.Type - val staticScope = (type.constructor.declarationDescriptor as? ClassDescriptor)?.staticScope - if (staticScope != null) { - descriptors.addAll(staticScope.getDescriptorsFiltered(kindFilter, nameFilter)) + val explicitReceiverTypes = if (useReceiverType != null) { + listOf(useReceiverType) + } + else { + callTypeAndReceiver.receiverTypes(bindingContext, contextElement, moduleDescriptor, resolutionFacade, predictableSmartCastsOnly = false)!! + } + + val constructorFilter = { descriptor: ClassDescriptor -> if (isStatic) true else descriptor.isInner } + descriptors.addNonExtensionMembers(explicitReceiverTypes, kindFilter, nameFilter, constructorFilter) + + descriptors.addScopeAndSyntheticExtensions(resolutionScope, explicitReceiverTypes, CallType.CALLABLE_REFERENCE, kindFilter, nameFilter) + + if (isStatic) { + explicitReceiverTypes + .map { (it.constructor.declarationDescriptor as? ClassDescriptor)?.staticScope } + .filterNotNull() + .flatMapTo(descriptors) { scope -> scope.getDescriptorsFiltered(kindFilter, nameFilter) } } } else { diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt index d2d51980adc..2331e7cef35 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression import org.jetbrains.kotlin.psi.psiUtil.isPackageDirectiveExpression import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastManager import org.jetbrains.kotlin.resolve.descriptorUtil.classValueType @@ -34,6 +35,7 @@ import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.expressions.DoubleColonLHS import org.jetbrains.kotlin.util.supertypesWithAny @@ -216,9 +218,20 @@ fun CallTypeAndReceiver<*, *>.receiverTypes( val receiverExpression: KtExpression? when (this) { is CallTypeAndReceiver.CALLABLE_REFERENCE -> { - return receiver?.let { - (bindingContext[BindingContext.DOUBLE_COLON_LHS, it] as? DoubleColonLHS.Type)?.type - }.singletonOrEmptyList() + if (receiver != null) { + val lhs = bindingContext[BindingContext.DOUBLE_COLON_LHS, receiver] ?: return emptyList() + when (lhs) { + is DoubleColonLHS.Type -> return listOf(lhs.type) + + is DoubleColonLHS.Expression -> { + val receiverValue = ExpressionReceiver.create(receiver, lhs.type, bindingContext) + return receiverValueTypes(receiverValue, lhs.dataFlowInfo, bindingContext, moduleDescriptor, predictableSmartCastsOnly) + } + } + } + else { + return emptyList() + } } is CallTypeAndReceiver.DEFAULT -> receiverExpression = null @@ -267,13 +280,23 @@ fun CallTypeAndReceiver<*, *>.receiverTypes( val dataFlowInfo = bindingContext.getDataFlowInfo(contextElement) - return receiverValues.flatMap { receiverValue -> - val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverValue, bindingContext, moduleDescriptor) - if (dataFlowValue.isPredictable || !predictableSmartCastsOnly) { // we don't include smart cast receiver types for "unpredictable" receiver value to mark members grayed - SmartCastManager().getSmartCastVariantsWithLessSpecificExcluded(receiverValue, bindingContext, moduleDescriptor, dataFlowInfo) - } - else { - listOf(receiverValue.type) - } + return receiverValues.flatMap { + receiverValueTypes(it, dataFlowInfo, bindingContext, moduleDescriptor, predictableSmartCastsOnly) + } +} + +private fun receiverValueTypes( + receiverValue: ReceiverValue, + dataFlowInfo: DataFlowInfo, + bindingContext: BindingContext, + moduleDescriptor: ModuleDescriptor, + predictableSmartCastsOnly: Boolean +): List { + val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverValue, bindingContext, moduleDescriptor) + return if (dataFlowValue.isPredictable || !predictableSmartCastsOnly) { // we don't include smart cast receiver types for "unpredictable" receiver value to mark members grayed + SmartCastManager().getSmartCastVariantsWithLessSpecificExcluded(receiverValue, bindingContext, moduleDescriptor, dataFlowInfo) + } + else { + listOf(receiverValue.type) } } \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/callableReference/ExpressionQualifier.kt b/idea/idea-completion/testData/basic/common/callableReference/ExpressionQualifier.kt new file mode 100644 index 00000000000..4dc20aec532 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/callableReference/ExpressionQualifier.kt @@ -0,0 +1,78 @@ +abstract class A { + abstract fun memberFunInA() + abstract val memberValInA: Int + + inner class InnerInA + class NestedInA +} + +abstract class B : A() { + abstract fun memberFunInB() + abstract val memberValInB: Int + + inner class InnerInB + class NestedInB +} + +fun A.extensionFun(){} + +val A.extensionVal: Int + get() = 1 + +fun B.extensionFunForB(){} + +fun Any.anyExtensionFun(){} +fun String.wrongExtensionFun(){} + +fun globalFun(p: Int) {} +val globalVal = 1 + +class C { + fun memberFun(){} + + val memberVal = 1 + + fun A.memberExtensionFun(){} + + fun foo(a: A) { + fun localFun(){} + + if (a is B) { + val v = a:: + } + } + + companion object { + fun companionObjectFun(){} + + fun A.companionExtension(){} + } +} + +// ABSENT: class +// ABSENT: class.java +// EXIST: { itemText: "memberFunInA", attributes: "" } +// EXIST: { itemText: "memberValInA", attributes: "" } + +/*TODO!*/ +// ABSENT: { itemText: "InnerInA", attributes: "" } + +// ABSENT: NestedInA +// EXIST: { itemText: "extensionFun", attributes: "" } +// EXIST: { itemText: "extensionVal", attributes: "" } +// EXIST: { itemText: "anyExtensionFun", attributes: "" } +// ABSENT: wrongExtensionFun +// ABSENT: globalFun +// ABSENT: globalVal +// ABSENT: memberFun +// ABSENT: memberVal +// ABSENT: memberExtensionFun +// ABSENT: localFun +// ABSENT: companionObjectFun +// ABSENT: companionExtension + +// EXIST: { itemText: "memberFunInB", attributes: "bold" } +// EXIST: { itemText: "memberValInB", attributes: "bold" } +// EXIST: { itemText: "InnerInB", attributes: "bold" } +// ABSENT: NestedInB +// EXIST: { itemText: "extensionFunForB", attributes: "bold" } diff --git a/idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions2.kt b/idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions2.kt new file mode 100644 index 00000000000..2a1eac1902b --- /dev/null +++ b/idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions2.kt @@ -0,0 +1,10 @@ +import java.io.File + +fun f(file: File) { + val v = file:: +} + +// EXIST_JAVA_ONLY: { itemText: "getFreeSpace", tailText: "()", attributes: "bold" } +// ABSENT: freeSpace +// EXIST_JAVA_ONLY: { itemText: "isFile", tailText: "()", attributes: "bold" } +// ABSENT: { itemText: "isFile", tailText: " (from isFile())" } diff --git a/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/CallableReferenceNotImportedExtension2.dependency.kt b/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/CallableReferenceNotImportedExtension2.dependency.kt new file mode 100644 index 00000000000..9fdc5264363 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/CallableReferenceNotImportedExtension2.dependency.kt @@ -0,0 +1,7 @@ +package dependency + +fun CharSequence.extFun(){} + +val String.extVal: Int get() = 1 + +fun Int.wrongExtFun(){} diff --git a/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/CallableReferenceNotImportedExtension2.kt b/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/CallableReferenceNotImportedExtension2.kt new file mode 100644 index 00000000000..1c82bd561b0 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/CallableReferenceNotImportedExtension2.kt @@ -0,0 +1,5 @@ +val v = "a":: + +// EXIST: extFun +// EXIST: extVal +// ABSENT: wrongExtFun 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 fff41374671..8cb7aa54d48 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 @@ -1200,6 +1200,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } + @TestMetadata("ExpressionQualifier.kt") + public void testExpressionQualifier() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/ExpressionQualifier.kt"); + doTest(fileName); + } + @TestMetadata("HigherOrderFunction.kt") public void testHigherOrderFunction() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/HigherOrderFunction.kt"); @@ -1217,6 +1223,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions.kt"); doTest(fileName); } + + @TestMetadata("SyntheticExtensions2.kt") + public void testSyntheticExtensions2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions2.kt"); + doTest(fileName); + } } @TestMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues") 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 af8577c37a9..a2f07dfb79d 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 @@ -1200,6 +1200,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("ExpressionQualifier.kt") + public void testExpressionQualifier() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/ExpressionQualifier.kt"); + doTest(fileName); + } + @TestMetadata("HigherOrderFunction.kt") public void testHigherOrderFunction() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/HigherOrderFunction.kt"); @@ -1217,6 +1223,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions.kt"); doTest(fileName); } + + @TestMetadata("SyntheticExtensions2.kt") + public void testSyntheticExtensions2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/callableReference/SyntheticExtensions2.kt"); + doTest(fileName); + } } @TestMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues") 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 4977aa62e06..d2bec073cb0 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 @@ -47,6 +47,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ doTest(fileName); } + @TestMetadata("CallableReferenceNotImportedExtension2") + public void testCallableReferenceNotImportedExtension2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/CallableReferenceNotImportedExtension2/"); + doTest(fileName); + } + @TestMetadata("CallablesInExcludedPackage") public void testCallablesInExcludedPackage() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/CallablesInExcludedPackage/"); diff --git a/idea/testData/quickfix/autoImports/callableReferenceExtension2.after.kt b/idea/testData/quickfix/autoImports/callableReferenceExtension2.after.kt new file mode 100644 index 00000000000..f0118de9cb9 --- /dev/null +++ b/idea/testData/quickfix/autoImports/callableReferenceExtension2.after.kt @@ -0,0 +1,5 @@ +import dependency.extensionFun + +// "Import" "true" +// ERROR: Unresolved reference: extensionFun +val v = "a"::extensionFun diff --git a/idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Dependency.kt b/idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Dependency.kt new file mode 100644 index 00000000000..b7e1cc74a49 --- /dev/null +++ b/idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Dependency.kt @@ -0,0 +1,3 @@ +package dependency + +fun String.extensionFun(){} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Main.kt b/idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Main.kt new file mode 100644 index 00000000000..c1d29334b5f --- /dev/null +++ b/idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Main.kt @@ -0,0 +1,3 @@ +// "Import" "true" +// ERROR: Unresolved reference: extensionFun +val v = "a"::extensionFun diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index fe89a6c6d2d..6fed634c53b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -71,6 +71,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes doTestWithExtraFile(fileName); } + @TestMetadata("callableReferenceExtension2.before.Main.kt") + public void testCallableReferenceExtension2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/callableReferenceExtension2.before.Main.kt"); + doTestWithExtraFile(fileName); + } + @TestMetadata("callableReferenceTopLevel.before.Main.kt") public void testCallableReferenceTopLevel() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/callableReferenceTopLevel.before.Main.kt");