diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt index afac851eea0..cbffbe0e15f 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt @@ -91,7 +91,7 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC private fun getJavaFileStub(): PsiJavaFileStub = getLightClassDataHolder().javaFileStub - protected fun getDescriptor(): ClassDescriptor? { + fun getDescriptor(): ClassDescriptor? { return LightClassGenerationSupport.getInstance(project).resolveToDescriptor(classOrObject) as? ClassDescriptor } @@ -481,13 +481,13 @@ fun getOutermostClassOrObject(classOrObject: KtClassOrObject): KtClassOrObject { interface LightClassInheritanceHelper { fun isInheritor( - lightClass: KtLightClassForSourceDeclaration, + lightClass: KtLightClass, baseClass: PsiClass, checkDeep: Boolean ): ImpreciseResolveResult object NoHelp : LightClassInheritanceHelper { - override fun isInheritor(lightClass: KtLightClassForSourceDeclaration, baseClass: PsiClass, checkDeep: Boolean) = UNSURE + override fun isInheritor(lightClass: KtLightClass, baseClass: PsiClass, checkDeep: Boolean) = UNSURE } companion object { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/IdeLightClassInheritanceHelper.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/IdeLightClassInheritanceHelper.kt index 4ebb94afec8..5ac1c13e94e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/IdeLightClassInheritanceHelper.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/IdeLightClassInheritanceHelper.kt @@ -20,19 +20,21 @@ import com.intellij.psi.CommonClassNames import com.intellij.psi.PsiClass import org.jetbrains.kotlin.asJava.ImpreciseResolveResult import org.jetbrains.kotlin.asJava.ImpreciseResolveResult.* -import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration +import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.classes.LightClassInheritanceHelper import org.jetbrains.kotlin.asJava.classes.defaultJavaAncestorQualifiedName import org.jetbrains.kotlin.idea.search.PsiBasedClassResolver -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry +import org.jetbrains.kotlin.psi.KtSuperTypeListEntry class IdeLightClassInheritanceHelper : LightClassInheritanceHelper { override fun isInheritor( - lightClass: KtLightClassForSourceDeclaration, + lightClass: KtLightClass, baseClass: PsiClass, checkDeep: Boolean ): ImpreciseResolveResult { - val classOrObject = lightClass.kotlinOrigin + val classOrObject = lightClass.kotlinOrigin ?: return UNSURE val entries = classOrObject.superTypeListEntries val hasSuperClass = entries.any { it is KtSuperTypeCallEntry } if (baseClass.qualifiedName == classOrObject.defaultJavaAncestorQualifiedName() && (!hasSuperClass || checkDeep)) { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/KtFakeLightClass.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/KtFakeLightClass.kt new file mode 100644 index 00000000000..b5a75df1ffb --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/lightClasses/KtFakeLightClass.kt @@ -0,0 +1,61 @@ +/* + * 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.caches.resolve.lightClasses + +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElementFactory +import com.intellij.psi.impl.light.AbstractLightClass +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.classes.LightClassInheritanceHelper +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.load.java.structure.LightClassOriginKind +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.resolve.DescriptorUtils + +// Used as a placeholder when actual light class does not exist (expect-classes, for example) +// The main purpose is to allow search of inheritors within hierarchies containing such classes +class KtFakeLightClass(override val kotlinOrigin: KtClassOrObject) : + AbstractLightClass(kotlinOrigin.manager, KotlinLanguage.INSTANCE), + KtLightClass { + private val _delegate by lazy { PsiElementFactory.SERVICE.getInstance(kotlinOrigin.project).createClass(kotlinOrigin.name ?: "") } + private val _containingClass by lazy { kotlinOrigin.containingClassOrObject?.let { KtFakeLightClass(it) } } + + override val clsDelegate get() = _delegate + override val originKind get() = LightClassOriginKind.SOURCE + + override fun getDelegate() = _delegate + override fun copy() = KtFakeLightClass(kotlinOrigin) + + override fun getQualifiedName() = kotlinOrigin.fqName?.asString() + override fun getContainingClass() = _containingClass + override fun getNavigationElement() = kotlinOrigin + override fun getIcon(flags: Int) = kotlinOrigin.getIcon(flags) + override fun getContainingFile() = kotlinOrigin.containingFile + override fun getUseScope() = kotlinOrigin.useScope + + override fun isInheritor(baseClass: PsiClass, checkDeep: Boolean): Boolean { + LightClassInheritanceHelper.getService(project).isInheritor(this, baseClass, checkDeep).ifSure { return it } + + val baseKtClass = (baseClass as? KtLightClass)?.kotlinOrigin ?: return false + val baseDescriptor = baseKtClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: return false + val thisDescriptor = kotlinOrigin.resolveToDescriptorIfAny() as? ClassDescriptor ?: return false + return if (checkDeep) DescriptorUtils.isSubclass(thisDescriptor, baseDescriptor) else DescriptorUtils.isDirectSubclass(thisDescriptor, baseDescriptor) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/JavaPsiUtils.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/JavaPsiUtils.kt index c35edb8b912..ba4f76a52d3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/JavaPsiUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/JavaPsiUtils.kt @@ -23,6 +23,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.idea.caches.resolve.lightClasses.KtFakeLightClass import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedFunction @@ -42,12 +43,12 @@ fun collectContainingClasses(methods: Collection): Set { return classes } -internal fun getPsiClass(element: PsiElement?): PsiClass? { +internal tailrec fun getPsiClass(element: PsiElement?): PsiClass? { return when { element == null -> null element is PsiClass -> element - element is KtClass -> element.toLightClass() - element.parent is KtClass -> (element.parent as KtClass).toLightClass() + element is KtClass -> element.toLightClass() ?: KtFakeLightClass(element) + element.parent is KtClass -> getPsiClass(element.parent) else -> null } } diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt index b21a78d16c8..27739366102 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.descriptors.MemberDescriptor import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.lightClasses.KtFakeLightClass import org.jetbrains.kotlin.idea.core.isInheritable import org.jetbrains.kotlin.idea.core.isOverridable import org.jetbrains.kotlin.idea.core.toDescriptor @@ -248,7 +249,7 @@ private fun collectInheritedClassMarker(element: KtClass, result: MutableCollect return } - val lightClass = element.toLightClass() ?: return + val lightClass = element.toLightClass() ?: KtFakeLightClass(element) if (ClassInheritorsSearch.search(lightClass, false).findFirst() == null) return 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 f502971eabf..7673577d28c 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.caches.resolve.lightClasses.KtFakeLightClass import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachImplementation import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* @@ -88,7 +89,7 @@ class KotlinDefinitionsSearcher : QueryExecutor): Boolean { - val psiClass = runReadAction { klass.toLightClass() } ?: return true + val psiClass = runReadAction { klass.toLightClass() ?: KtFakeLightClass(klass) } val searchScope = runReadAction { psiClass.useScope } if (searchScope is LocalSearchScope) { diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDirectInheritorsSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDirectInheritorsSearcher.kt index ad4372f1e29..e7eaa7b8cfa 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDirectInheritorsSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDirectInheritorsSearcher.kt @@ -22,7 +22,8 @@ import com.intellij.psi.PsiClass import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.DirectClassInheritorsSearch import com.intellij.util.Processor -import org.jetbrains.kotlin.idea.decompiler.navigation.SourceNavigationHelper +import org.jetbrains.kotlin.asJava.toLightClassWithBuiltinMapping +import org.jetbrains.kotlin.idea.caches.resolve.lightClasses.KtFakeLightClass import org.jetbrains.kotlin.idea.search.fileScope import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.stubindex.KotlinSuperClassIndex @@ -35,10 +36,10 @@ open class KotlinDirectInheritorsSearcher : QueryExecutorBase KotlinSuperClassIndex.getInstance() .get(name, baseClass.project, noLibrarySourceScope).asSequence() - .mapNotNull { candidate -> SourceNavigationHelper.getOriginalPsiClassOrCreateLightClass(candidate) } + .mapNotNull { candidate -> candidate.toLightClassWithBuiltinMapping() ?: KtFakeLightClass(candidate) } .filter { candidate -> candidate.isInheritor(baseClass, false) } .forEach { candidate -> consumer.process(candidate) } } diff --git a/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassCommonSide/common/common.kt b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassCommonSide/common/common.kt new file mode 100644 index 00000000000..b82e20396a9 --- /dev/null +++ b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassCommonSide/common/common.kt @@ -0,0 +1,9 @@ +package test + +open class SimpleParent + +expect open class ExpectedChild : SimpleParent + +class ExpectedChildChild : ExpectedChild() + +class SimpleChild : SimpleParent() \ No newline at end of file diff --git a/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassCommonSide/jvm/jvm.kt b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassCommonSide/jvm/jvm.kt new file mode 100644 index 00000000000..e9fc8d3a1bc --- /dev/null +++ b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassCommonSide/jvm/jvm.kt @@ -0,0 +1,7 @@ +// !CHECK_HIGHLIGHTING + +package test + +actual open class ExpectedChild : SimpleParent() + +class ExpectedChildChildJvm : ExpectedChild() \ No newline at end of file diff --git a/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassPlatformSide/common/common.kt b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassPlatformSide/common/common.kt new file mode 100644 index 00000000000..7a9af35ec48 --- /dev/null +++ b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassPlatformSide/common/common.kt @@ -0,0 +1,11 @@ +// !CHECK_HIGHLIGHTING + +package test + +open class SimpleParent + +expect open class ExpectedChild : SimpleParent + +class ExpectedChildChild : ExpectedChild() + +class SimpleChild : SimpleParent() \ No newline at end of file diff --git a/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassPlatformSide/jvm/jvm.kt b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassPlatformSide/jvm/jvm.kt new file mode 100644 index 00000000000..a6b9eefab4f --- /dev/null +++ b/idea/testData/multiModuleLineMarker/hierarchyWithExpectClassPlatformSide/jvm/jvm.kt @@ -0,0 +1,5 @@ +package test + +actual open class ExpectedChild : SimpleParent() + +class ExpectedChildChildJvm : ExpectedChild() \ No newline at end of file diff --git a/idea/testData/navigation/implementations/multiModule/expectClass/common/common.kt b/idea/testData/navigation/implementations/multiModule/expectClass/common/common.kt new file mode 100644 index 00000000000..92fad937dd6 --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/expectClass/common/common.kt @@ -0,0 +1,12 @@ +package test + +open class SimpleParent + +expect open class ExpectedChild : SimpleParent + +class ExpectedChildChild : ExpectedChild() + +class SimpleChild : SimpleParent() + +// REF: [common] (test).ExpectedChildChild +// REF: [jvm] (test).ExpectedChildChildJvm \ No newline at end of file diff --git a/idea/testData/navigation/implementations/multiModule/expectClass/jvm/jvm.kt b/idea/testData/navigation/implementations/multiModule/expectClass/jvm/jvm.kt new file mode 100644 index 00000000000..96377ab2dfa --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/expectClass/jvm/jvm.kt @@ -0,0 +1,5 @@ +package test + +actual open class ExpectedChild : SimpleParent() + +class ExpectedChildChildJvm : ExpectedChild() \ No newline at end of file diff --git a/idea/testData/navigation/implementations/multiModule/expectClassSuperclass/common/common.kt b/idea/testData/navigation/implementations/multiModule/expectClassSuperclass/common/common.kt new file mode 100644 index 00000000000..dc8b3ebe6bf --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/expectClassSuperclass/common/common.kt @@ -0,0 +1,16 @@ +package test + +open class SimpleParent + +expect open class ExpectedChild : SimpleParent + +class ExpectedChildChild : ExpectedChild() + +class SimpleChild : SimpleParent() + +// REF: [common] (test).ExpectedChild +// REF: [common] (test).ExpectedChild +// REF: [common] (test).ExpectedChildChild +// REF: [common] (test).SimpleChild +// REF: [jvm] (test).ExpectedChild +// REF: [jvm] (test).ExpectedChildChildJvm \ No newline at end of file diff --git a/idea/testData/navigation/implementations/multiModule/expectClassSuperclass/jvm/jvm.kt b/idea/testData/navigation/implementations/multiModule/expectClassSuperclass/jvm/jvm.kt new file mode 100644 index 00000000000..96377ab2dfa --- /dev/null +++ b/idea/testData/navigation/implementations/multiModule/expectClassSuperclass/jvm/jvm.kt @@ -0,0 +1,5 @@ +package test + +actual open class ExpectedChild : SimpleParent() + +class ExpectedChildChildJvm : ExpectedChild() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleLineMarkerTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleLineMarkerTest.kt index 527f9c4f4e8..0914a4ce019 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleLineMarkerTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleLineMarkerTest.kt @@ -86,6 +86,14 @@ class MultiModuleLineMarkerTest : AbstractMultiModuleHighlightingTest() { doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) } + fun testHierarchyWithExpectClassCommonSide() { + doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) + } + + fun testHierarchyWithExpectClassPlatformSide() { + doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) + } + fun testKotlinTestAnnotations() { doMultiPlatformTest(TargetPlatformKind.JavaScript, configureModule = { module, _ -> diff --git a/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt index a4cfa2e00ad..47315cc2996 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/navigation/KotlinGotoImplementationMultiModuleTest.kt @@ -67,4 +67,12 @@ class KotlinGotoImplementationMultiModuleTest : AbstractMultiModuleTest() { actuals = *arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], "js" to TargetPlatformKind.JavaScript) ) } + + fun testExpectClassSuperclass() { + doMultiPlatformTest("common.kt") + } + + fun testExpectClass() { + doMultiPlatformTest("common.kt") + } } \ No newline at end of file