Completion and import popup for bound callable references

This commit is contained in:
Valentin Kipyatkov
2016-06-20 19:36:30 +03:00
parent 079d857aa2
commit e05aa41bbc
13 changed files with 206 additions and 24 deletions
@@ -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<DeclarationDescriptor> {
val descriptors = LinkedHashSet<DeclarationDescriptor>()
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 {
@@ -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<KotlinType> {
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)
}
}
@@ -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::<caret>
}
}
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" }
@@ -0,0 +1,10 @@
import java.io.File
fun f(file: File) {
val v = file::<caret>
}
// EXIST_JAVA_ONLY: { itemText: "getFreeSpace", tailText: "()", attributes: "bold" }
// ABSENT: freeSpace
// EXIST_JAVA_ONLY: { itemText: "isFile", tailText: "()", attributes: "bold" }
// ABSENT: { itemText: "isFile", tailText: " (from isFile())" }
@@ -0,0 +1,7 @@
package dependency
fun CharSequence.extFun(){}
val String.extVal: Int get() = 1
fun Int.wrongExtFun(){}
@@ -0,0 +1,5 @@
val v = "a"::<caret>
// EXIST: extFun
// EXIST: extVal
// ABSENT: wrongExtFun
@@ -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")
@@ -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")
@@ -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/");
@@ -0,0 +1,5 @@
import dependency.extensionFun
// "Import" "true"
// ERROR: Unresolved reference: extensionFun
val v = "a"::extensionFun<caret>
@@ -0,0 +1,3 @@
package dependency
fun String.extensionFun(){}
@@ -0,0 +1,3 @@
// "Import" "true"
// ERROR: Unresolved reference: extensionFun
val v = "a"::extensionFun<caret>
@@ -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");