diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/overridersSearch.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/overridersSearch.kt index 1e9c77d3fee..dabe297e59d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/overridersSearch.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/overridersSearch.kt @@ -20,6 +20,7 @@ import com.intellij.psi.* import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.AllOverridingMethodsSearch import com.intellij.psi.search.searches.DirectClassInheritorsSearch +import com.intellij.psi.search.searches.FunctionalExpressionSearch import com.intellij.psi.search.searches.OverridingMethodsSearch import com.intellij.psi.util.MethodSignatureUtil import com.intellij.psi.util.PsiUtil @@ -126,10 +127,10 @@ private fun forEachKotlinOverride( members: List, scope: SearchScope, processor: (superMember: PsiElement, overridingMember: PsiElement) -> Boolean -) { +): Boolean { val baseClassDescriptor = runReadAction { ktClass.unsafeResolveToDescriptor() as ClassDescriptor } val baseDescriptors = runReadAction { members.mapNotNull { it.unsafeResolveToDescriptor() as? CallableMemberDescriptor }.filter { it.isOverridable } } - if (baseDescriptors.isEmpty()) return + if (baseDescriptors.isEmpty()) return true HierarchySearchRequest(ktClass, scope.restrictToKotlinSources(), true).searchInheritors().forEach { val inheritor = (it as? KtLightClass)?.kotlinOrigin ?: return@forEach @@ -140,25 +141,36 @@ private fun forEachKotlinOverride( val overridingDescriptor = inheritorDescriptor.findCallableMemberBySignature(it.substitute(substitutor) as CallableMemberDescriptor) val overridingMember = overridingDescriptor?.source?.getPsi() if (overridingMember != null) { - if (!processor(superMember, overridingMember)) return + if (!processor(superMember, overridingMember)) return false } } } + + return true } -fun PsiMethod.forEachOverridingMethod(processor: (PsiMethod) -> Boolean) { - val scope = runReadAction { useScope } +fun PsiMethod.forEachOverridingMethod( + scope: SearchScope = runReadAction { useScope }, + processor: (PsiMethod) -> Boolean +): Boolean { + if (!OverridingMethodsSearch.search(this, scope.excludeKotlinSources(), true).forEach(processor)) return false - OverridingMethodsSearch.search(this, scope.excludeKotlinSources(), true).forEach(processor) - - val ktMember = (this as? KtLightMethod)?.kotlinOrigin as? KtNamedDeclaration ?: return - val ktClass = runReadAction { ktMember.containingClassOrObject as? KtClass } ?: return - forEachKotlinOverride(ktClass, listOf(ktMember), scope) { _, overrider -> + val ktMember = (this as? KtLightMethod)?.kotlinOrigin as? KtNamedDeclaration ?: return true + val ktClass = runReadAction { ktMember.containingClassOrObject as? KtClass } ?: return true + return forEachKotlinOverride(ktClass, listOf(ktMember), scope) { _, overrider -> val lightMethods = runReadAction { overrider.toLightMethods() } lightMethods.all { processor(it) } } } +fun PsiMethod.forEachImplementation( + scope: SearchScope = runReadAction { useScope }, + processor: (PsiElement) -> Boolean +): Boolean { + return forEachOverridingMethod(scope, processor) + && FunctionalExpressionSearch.search(this, scope.excludeKotlinSources()).forEach(processor) +} + fun PsiClass.forEachDeclaredMemberOverride(processor: (superMember: PsiElement, overridingMember: PsiElement) -> Boolean) { val scope = runReadAction { useScope } diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt index c6eb26eb73a..dfb3d7c82b9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachImplementation import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.contains @@ -116,8 +117,7 @@ class KotlinDefinitionsSearcher : QueryExecutor): Boolean { val psiMethod = runReadAction { LightClassUtil.getLightClassMethod(function) } - - return psiMethod?.let { MethodImplementationsSearch.processImplementations(it, consumer, scope) } ?: true + return psiMethod?.forEachImplementation(scope, consumer::process) ?: true } private fun processPropertyImplementations(parameter: KtParameter, scope: SearchScope, consumer: Processor): Boolean { diff --git a/idea/testData/navigation/implementations/multiModule/suspendFunImpl/common/common.kt b/idea/testData/navigation/implementations/multiModule/suspendFunImpl/common/common.kt new file mode 100644 index 00000000000..5386c8a8bc4 --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/suspendFunImpl/common/common.kt @@ -0,0 +1,8 @@ +package test + +interface I { + suspend fun foo(s: String) +} + +// REF: [js] (in test.C).foo(String) +// REF: [jvm] (in test.C).foo(String) \ No newline at end of file diff --git a/idea/testData/navigation/implementations/multiModule/suspendFunImpl/js/js.kt b/idea/testData/navigation/implementations/multiModule/suspendFunImpl/js/js.kt new file mode 100644 index 00000000000..34ba36b7993 --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/suspendFunImpl/js/js.kt @@ -0,0 +1,5 @@ +package test + +class C : I { + override suspend fun foo(s: String) { } +} \ No newline at end of file diff --git a/idea/testData/navigation/implementations/multiModule/suspendFunImpl/jvm/jvm.kt b/idea/testData/navigation/implementations/multiModule/suspendFunImpl/jvm/jvm.kt new file mode 100644 index 00000000000..34ba36b7993 --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/suspendFunImpl/jvm/jvm.kt @@ -0,0 +1,5 @@ +package test + +class C : I { + override suspend fun foo(s: String) { } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt new file mode 100644 index 00000000000..a4cfa2e00ad --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2017 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.idea.navigation + +import com.intellij.openapi.editor.EditorFactory +import com.intellij.psi.PsiDocumentManager +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider +import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest +import org.jetbrains.kotlin.idea.stubs.createFacet +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.idea.test.extractMarkerOffset +import java.io.File + +class KotlinGotoImplementationMultiModuleTest : AbstractMultiModuleTest() { + override fun getTestDataPath(): String { + return File(PluginTestCaseBase.getTestDataPathBase(), "/navigation/implementations/multiModule").path + File.separator + } + + private fun doMultiPlatformTest( + testFileName: String, + commonModuleName: String = "common", + vararg actuals: Pair> = arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) + ) { + val commonModule = module(commonModuleName) + commonModule.createFacet(TargetPlatformKind.Common, false) + + actuals.forEach { (actualName, actualKind) -> + val implModule = module(actualName) + implModule.createFacet(actualKind, implementedModuleName = commonModuleName) + implModule.enableMultiPlatform() + implModule.addDependency(commonModule) + } + + val file = PluginJetFilesProvider.allFilesInProject(myProject).single { it.name == testFileName } + val doc = PsiDocumentManager.getInstance(myProject).getDocument(file)!! + val offset = doc.extractMarkerOffset(project, "") + val editor = EditorFactory.getInstance().createEditor(doc, myProject) + editor.caretModel.moveToOffset(offset) + try { + val gotoData = NavigationTestUtils.invokeGotoImplementations(editor, file) + NavigationTestUtils.assertGotoDataMatching(editor, gotoData, true) + } + finally { + EditorFactory.getInstance().releaseEditor(editor) + } + } + + fun testSuspendFunImpl() { + doMultiPlatformTest( + "common.kt", + actuals = *arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], "js" to TargetPlatformKind.JavaScript) + ) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java b/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java index 5c2031c8bfe..59612ca58e7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java +++ b/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java @@ -50,6 +50,10 @@ public final class NavigationTestUtils { } public static void assertGotoDataMatching(Editor editor, GotoTargetHandler.GotoData gotoData) { + assertGotoDataMatching(editor, gotoData, false); + } + + public static void assertGotoDataMatching(Editor editor, GotoTargetHandler.GotoData gotoData, boolean renderModule) { // Get expected references from the tested document List expectedReferences = InTextDirectivesUtils.findListWithPrefixes(editor.getDocument().getText(), "// REF:"); for (int i = 0; i < expectedReferences.size(); i++) { @@ -69,7 +73,7 @@ public final class NavigationTestUtils { @Override public String apply(@Nullable PsiElement element) { Assert.assertNotNull(element); - return ReferenceUtils.renderAsGotoImplementation(element); + return ReferenceUtils.renderAsGotoImplementation(element, renderModule); } }); diff --git a/idea/tests/org/jetbrains/kotlin/test/util/ReferenceUtils.kt b/idea/tests/org/jetbrains/kotlin/test/util/ReferenceUtils.kt index ebf86eae50b..8890c82bcbc 100644 --- a/idea/tests/org/jetbrains/kotlin/test/util/ReferenceUtils.kt +++ b/idea/tests/org/jetbrains/kotlin/test/util/ReferenceUtils.kt @@ -23,13 +23,15 @@ import com.intellij.psi.PsiAnonymousClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiPackage import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.psiUtil.plainContent import org.junit.Assert -fun PsiElement.renderAsGotoImplementation(): String { +@JvmOverloads +fun PsiElement.renderAsGotoImplementation(renderModule: Boolean = false): String { val navigationElement = navigationElement if (navigationElement is KtObjectDeclaration && navigationElement.isCompanion()) { @@ -55,6 +57,9 @@ fun PsiElement.renderAsGotoImplementation(): String { if (locationString == null && parent is PsiAnonymousClass) { locationString = "" } + if (renderModule) { + locationString = "[${navigationElement.module?.name ?: ""}] $locationString" + } return if (locationString == null || navigationElement is PsiPackage) presentableText!! else