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:
Alexey Sedunov
2017-09-26 16:34:01 +03:00
parent 87f3d24f9b
commit 17abce2811
17 changed files with 170 additions and 17 deletions
@@ -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 {
@@ -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)) {
@@ -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
@@ -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) {
@@ -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) }
}
@@ -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()
@@ -0,0 +1,7 @@
// !CHECK_HIGHLIGHTING
package test
actual open class ExpectedChild : SimpleParent()
class ExpectedChildChildJvm : ExpectedChild()
@@ -0,0 +1,11 @@
// !CHECK_HIGHLIGHTING
package test
open class <caret>SimpleParent
expect open class ExpectedChild : SimpleParent
class ExpectedChildChild : ExpectedChild()
class SimpleChild : SimpleParent()
@@ -0,0 +1,5 @@
package test
actual open class <lineMarker><lineMarker>ExpectedChild</lineMarker></lineMarker> : SimpleParent()
class ExpectedChildChildJvm : ExpectedChild()
@@ -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()
@@ -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
@@ -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, _ ->
@@ -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")
}
}