diff --git a/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt b/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt index c05288dc615..dcc582cebd2 100644 --- a/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt +++ b/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt @@ -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 { @@ -131,45 +133,60 @@ public class KotlinIndicesHelper(private val project: Project) { expression: JetSimpleNameExpression, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection { - val context = resolveSession.resolveToElement(expression) - val receiverExpression = expression.getReceiverExpression() ?: return listOf() - val isInfixCall = expression.getParent() is JetBinaryExpression - val expressionType = context.get(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(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() + 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 { - val importDirective = JetPsiFactory(receiverExpression).createImportDirective(callableFQN.asString()) + receiverValue: ReceiverValue, + dataFlowInfo: DataFlowInfo, + isInfixCall: Boolean, + scope: JetScope, + module: ModuleDescriptor, + bindingContext: BindingContext): List { + 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()) - .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 { diff --git a/idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/NotImportedExtensionForImplicitReceiver.dependency.kt b/idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/NotImportedExtensionForImplicitReceiver.dependency.kt new file mode 100644 index 00000000000..95071fcb7d5 --- /dev/null +++ b/idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/NotImportedExtensionForImplicitReceiver.dependency.kt @@ -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() { } diff --git a/idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/NotImportedExtensionForImplicitReceiver.kt b/idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/NotImportedExtensionForImplicitReceiver.kt new file mode 100644 index 00000000000..598eb03db2a --- /dev/null +++ b/idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/NotImportedExtensionForImplicitReceiver.kt @@ -0,0 +1,14 @@ +package first + +class C { + fun String.firstFun() { + hello + } +} + +// EXIST: helloFun +// EXIST: helloWithParams +// EXIST: helloProp +// EXIST: helloForC +// ABSENT: helloFake +// NUMBER: 4 diff --git a/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.after.kt b/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.after.kt new file mode 100644 index 00000000000..8fe754e4fcb --- /dev/null +++ b/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.after.kt @@ -0,0 +1,9 @@ +// "Import" "true" +// ERROR: Unresolved reference: someFun +package testingExtensionFunctionsImport + +import testingExtensionFunctionsImport.data.someFun + +fun String.some() { + someFun() +} diff --git a/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.Main.kt b/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.Main.kt new file mode 100644 index 00000000000..387aa6524f6 --- /dev/null +++ b/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.Main.kt @@ -0,0 +1,7 @@ +// "Import" "true" +// ERROR: Unresolved reference: someFun +package testingExtensionFunctionsImport + +fun String.some() { + someFun() +} diff --git a/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.data.Sample.kt b/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.data.Sample.kt new file mode 100644 index 00000000000..67c1800b093 --- /dev/null +++ b/idea/testData/quickfix/autoImports/extensionFunctionImportImplicitReceiver.before.data.Sample.kt @@ -0,0 +1,4 @@ +package testingExtensionFunctionsImport.data + +fun String.someFun() { +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/completion/MultiFileJvmBasicCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/MultiFileJvmBasicCompletionTestGenerated.java index b75482dfee8..1ab8ed5c817 100644 --- a/idea/tests/org/jetbrains/jet/completion/MultiFileJvmBasicCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/MultiFileJvmBasicCompletionTestGenerated.java @@ -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/"); diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java index a300360db86..4968413fb0e 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java @@ -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");