Fixed KT-5611 Completion doesn't include not imported extensions for implicit receiver

#KT-5611 Fixed
This commit is contained in:
Valentin Kipyatkov
2014-10-08 12:17:20 +04:00
parent 22c80d2694
commit a124827c4e
8 changed files with 104 additions and 35 deletions
@@ -35,6 +35,8 @@ import java.util.HashSet
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.jet.lang.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.jet.lang.resolve.QualifiedExpressionResolver.LookupMode
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo
public class KotlinIndicesHelper(private val project: Project) {
public fun getTopLevelObjects(nameFilter: (String) -> Boolean, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection<ClassDescriptor> {
@@ -131,45 +133,60 @@ public class KotlinIndicesHelper(private val project: Project) {
expression: JetSimpleNameExpression,
resolveSession: ResolveSessionForBodies,
scope: GlobalSearchScope): Collection<CallableDescriptor> {
val context = resolveSession.resolveToElement(expression)
val receiverExpression = expression.getReceiverExpression() ?: return listOf()
val isInfixCall = expression.getParent() is JetBinaryExpression
val expressionType = context.get<JetExpression, JetType>(BindingContext.EXPRESSION_TYPE, receiverExpression)
val jetScope = context.get(BindingContext.RESOLUTION_SCOPE, receiverExpression)
val bindingContext = resolveSession.resolveToElement(expression)
val dataFlowInfo = bindingContext.getDataFlowInfo(expression)
if (expressionType == null || jetScope == null || expressionType.isError()) {
return listOf()
}
val sourceNames = JetTopLevelFunctionsFqnNameIndex.getInstance().getAllKeys(project).stream() + JetTopLevelPropertiesFqnNameIndex.getInstance().getAllKeys(project).stream()
val sourceNames = JetTopLevelFunctionsFqnNameIndex.getInstance().getAllKeys(project).stream() +
JetTopLevelPropertiesFqnNameIndex.getInstance().getAllKeys(project).stream()
val allFqNames = sourceNames.map { FqName(it) } + JetFromJavaDescriptorHelper.getTopLevelCallableFqNames(project, scope, true).stream()
val matchingFqNames = allFqNames.filter { nameFilter(it.shortName().asString()) }.toSet()
// Iterate through the function with attempt to resolve found functions
return allFqNames
.filter { nameFilter(it.shortName().asString()) }
.toSet()
.flatMap { findSuitableExtensions(it, receiverExpression, expressionType, isInfixCall, jetScope, resolveSession.getModuleDescriptor(), context) }
val receiverExpression = expression.getReceiverExpression()
if (receiverExpression != null) {
val isInfixCall = expression.getParent() is JetBinaryExpression
val expressionType = bindingContext.get<JetExpression, JetType>(BindingContext.EXPRESSION_TYPE, receiverExpression)
if (expressionType == null || expressionType.isError()) return listOf()
val resolutionScope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, receiverExpression) ?: return listOf()
val receiverValue = ExpressionReceiver(receiverExpression, expressionType)
return matchingFqNames.flatMap {
findSuitableExtensions(it, receiverValue, dataFlowInfo, isInfixCall, resolutionScope, resolveSession.getModuleDescriptor(), bindingContext)
}
}
else {
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expression] ?: return listOf()
val result = HashSet<CallableDescriptor>()
for (receiver in resolutionScope.getImplicitReceiversHierarchy()) {
matchingFqNames.flatMapTo(result) {
findSuitableExtensions(it, receiver.getValue(), dataFlowInfo, false, resolutionScope, resolveSession.getModuleDescriptor(), bindingContext)
}
}
return result
}
}
/**
* Check that function or property with the given qualified name can be resolved in given scope and called on given receiver
*/
private fun findSuitableExtensions(callableFQN: FqName,
receiverExpression: JetExpression,
receiverType: JetType,
isInfixCall: Boolean,
scope: JetScope,
module: ModuleDescriptor,
bindingContext: BindingContext): List<CallableDescriptor> {
val importDirective = JetPsiFactory(receiverExpression).createImportDirective(callableFQN.asString())
receiverValue: ReceiverValue,
dataFlowInfo: DataFlowInfo,
isInfixCall: Boolean,
scope: JetScope,
module: ModuleDescriptor,
bindingContext: BindingContext): List<CallableDescriptor> {
val importDirective = JetPsiFactory(project).createImportDirective(callableFQN.asString())
val declarationDescriptors = analyzeImportReference(importDirective, scope, BindingTraceContext(), module)
val receiverValue = ExpressionReceiver(receiverExpression, receiverType)
val dataFlowInfo = bindingContext.getDataFlowInfo(receiverExpression)
return declarationDescriptors
.filterIsInstance(javaClass<CallableDescriptor>())
.filter { it.getExtensionReceiverParameter() != null && ExpressionTypingUtils.checkIsExtensionCallable(receiverValue, it, isInfixCall, bindingContext, dataFlowInfo) }
.filter { it.getExtensionReceiverParameter() != null &&
ExpressionTypingUtils.checkIsExtensionCallable(receiverValue, it, isInfixCall, bindingContext, dataFlowInfo) }
}
public fun getClassDescriptors(nameFilter: (String) -> Boolean, analyzer: KotlinCodeAnalyzer, scope: GlobalSearchScope): Collection<ClassDescriptor> {
@@ -0,0 +1,13 @@
package second
import first.C
fun String.helloFun() { }
fun String.helloWithParams(i : Int): String = ""
val String.helloProp: Int get() = 1
val C.helloForC: Int get() = 1
fun Int.helloFake() { }
@@ -0,0 +1,14 @@
package first
class C {
fun String.firstFun() {
hello<caret>
}
}
// EXIST: helloFun
// EXIST: helloWithParams
// EXIST: helloProp
// EXIST: helloForC
// ABSENT: helloFake
// NUMBER: 4
@@ -0,0 +1,9 @@
// "Import" "true"
// ERROR: Unresolved reference: someFun
package testingExtensionFunctionsImport
import testingExtensionFunctionsImport.data.someFun
fun String.some() {
<caret>someFun()
}
@@ -0,0 +1,7 @@
// "Import" "true"
// ERROR: Unresolved reference: someFun
package testingExtensionFunctionsImport
fun String.some() {
<caret>someFun()
}
@@ -0,0 +1,4 @@
package testingExtensionFunctionsImport.data
fun String.someFun() {
}
@@ -17,13 +17,9 @@
package org.jetbrains.jet.completion;
import com.intellij.testFramework.TestDataPath;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
@@ -116,6 +112,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
doTest(fileName);
}
@TestMetadata("NotImportedExtensionForImplicitReceiver")
public void testNotImportedExtensionForImplicitReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/");
doTest(fileName);
}
@TestMetadata("NotImportedExtensionFunction")
public void testNotImportedExtensionFunction() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedExtensionFunction/");
@@ -17,13 +17,10 @@
package org.jetbrains.jet.plugin.quickfix;
import com.intellij.testFramework.TestDataPath;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
@@ -76,6 +73,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
doTestWithExtraFile(fileName);
}
@TestMetadata("extensionFunctionImportImplicitReceiver.before.Main.kt")
public void testExtensionFunctionImportImplicitReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("extensionPropertyImport.before.Main.kt")
public void testExtensionPropertyImport() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/extensionPropertyImport.before.Main.kt");