Misc: Support light-classless classes in inheritors search
In particular, support line markers for expect-classes and navigation to expect-classes from their base class #KT-20329 Fixed
This commit is contained in:
+3
-3
@@ -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 {
|
||||
|
||||
+6
-4
@@ -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)) {
|
||||
|
||||
+61
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<PsiMethod>): Set<PsiClass> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-1
@@ -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<PsiElement, DefinitionsScopedSea
|
||||
}
|
||||
|
||||
private fun processClassImplementations(klass: KtClass, consumer: Processor<PsiElement>): Boolean {
|
||||
val psiClass = runReadAction { klass.toLightClass() } ?: return true
|
||||
val psiClass = runReadAction { klass.toLightClass() ?: KtFakeLightClass(klass) }
|
||||
|
||||
val searchScope = runReadAction { psiClass.useScope }
|
||||
if (searchScope is LocalSearchScope) {
|
||||
|
||||
+6
-5
@@ -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<PsiClass, DirectCl
|
||||
|
||||
val name = baseClass.name ?: return
|
||||
|
||||
val originalScope = queryParameters.scope
|
||||
val scope = originalScope as? GlobalSearchScope ?: baseClass.containingFile?.fileScope() ?: return
|
||||
val file = if (baseClass is KtFakeLightClass) baseClass.kotlinOrigin.containingFile else baseClass.containingFile
|
||||
|
||||
val file = baseClass.containingFile
|
||||
val originalScope = queryParameters.scope
|
||||
val scope = originalScope as? GlobalSearchScope ?: file.fileScope() ?: return
|
||||
|
||||
val names = mutableSetOf(name)
|
||||
val project = file.project
|
||||
@@ -63,7 +64,7 @@ open class KotlinDirectInheritorsSearcher : QueryExecutorBase<PsiClass, DirectCl
|
||||
names.forEach { name ->
|
||||
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) }
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package test
|
||||
|
||||
open class <lineMarker>SimpleParent</lineMarker>
|
||||
|
||||
expect open class <lineMarker><lineMarker>ExpectedChild</lineMarker></lineMarker> : SimpleParent
|
||||
|
||||
class ExpectedChildChild : ExpectedChild()
|
||||
|
||||
class SimpleChild : SimpleParent()
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// !CHECK_HIGHLIGHTING
|
||||
|
||||
package test
|
||||
|
||||
actual open class ExpectedChild : SimpleParent()
|
||||
|
||||
class ExpectedChildChildJvm : ExpectedChild()
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// !CHECK_HIGHLIGHTING
|
||||
|
||||
package test
|
||||
|
||||
open class <caret>SimpleParent
|
||||
|
||||
expect open class ExpectedChild : SimpleParent
|
||||
|
||||
class ExpectedChildChild : ExpectedChild()
|
||||
|
||||
class SimpleChild : SimpleParent()
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
actual open class <lineMarker><lineMarker>ExpectedChild</lineMarker></lineMarker> : SimpleParent()
|
||||
|
||||
class ExpectedChildChildJvm : ExpectedChild()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package test
|
||||
|
||||
open class SimpleParent
|
||||
|
||||
expect open class <caret>ExpectedChild : SimpleParent
|
||||
|
||||
class ExpectedChildChild : ExpectedChild()
|
||||
|
||||
class SimpleChild : SimpleParent()
|
||||
|
||||
// REF: [common] (test).ExpectedChildChild
|
||||
// REF: [jvm] (test).ExpectedChildChildJvm
|
||||
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
actual open class ExpectedChild : SimpleParent()
|
||||
|
||||
class ExpectedChildChildJvm : ExpectedChild()
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
package test
|
||||
|
||||
open class <caret>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
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
actual open class ExpectedChild : SimpleParent()
|
||||
|
||||
class ExpectedChildChildJvm : ExpectedChild()
|
||||
@@ -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, _ ->
|
||||
|
||||
+8
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user