[AA Standalone] Consider type aliases in direct inheritors search
A type alias may still be inherited from. For example: ``` sealed class MyClass typealias T = MyClass class Inheritor : T() // `Inheritor` is a direct inheritor of `MyClass`. ``` The index is a simplified version of the IDE's `KotlinTypeAliasByExpansionShortNameIndex`, but it should be sufficient for virtually all cases. ^KT-66013
This commit is contained in:
committed by
Space Team
parent
54f2655b4d
commit
a9d7b0c595
+24
@@ -22,4 +22,28 @@ internal class KotlinStaticDeclarationIndex {
|
|||||||
* Allows quickly finding [KtClassOrObject]s which have a given simple name as a supertype. The map may contain local classes as well.
|
* Allows quickly finding [KtClassOrObject]s which have a given simple name as a supertype. The map may contain local classes as well.
|
||||||
*/
|
*/
|
||||||
internal val classesBySupertypeName: MutableMap<Name, MutableSet<KtClassOrObject>> = mutableMapOf()
|
internal val classesBySupertypeName: MutableMap<Name, MutableSet<KtClassOrObject>> = mutableMapOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a simple name `N` to type aliases `A` in whose definition `N` occurs as the topmost user type, which is a prerequisite for other
|
||||||
|
* classes inheriting from `N` by referring to `A`. Does not support function types (e.g. `Function1`).
|
||||||
|
*
|
||||||
|
* There is no guarantee that the type alias can be inherited from. For example, if its expanded type is final, the type alias is not
|
||||||
|
* inheritable. The resulting type alias `A` may also occur in the expanded type of another type alias (which may also be inheritable),
|
||||||
|
* so the index may need to be followed transitively.
|
||||||
|
*
|
||||||
|
* The index is used to find direct class inheritors.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* abstract class C
|
||||||
|
*
|
||||||
|
* typealias A = C
|
||||||
|
*
|
||||||
|
* class X : A()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The index contains the following entry: `"C" -> A`.
|
||||||
|
*/
|
||||||
|
internal val inheritableTypeAliasesByAliasedName: MutableMap<Name, MutableSet<KtTypeAlias>> = mutableMapOf()
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-2
@@ -35,11 +35,13 @@ import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
|
|||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.name.*
|
import org.jetbrains.kotlin.name.*
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getImportedSimpleNameByImportAlias
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getSuperNames
|
import org.jetbrains.kotlin.psi.psiUtil.getSuperNames
|
||||||
import org.jetbrains.kotlin.psi.stubs.KotlinClassOrObjectStub
|
import org.jetbrains.kotlin.psi.stubs.KotlinClassOrObjectStub
|
||||||
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
|
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
|
||||||
import org.jetbrains.kotlin.psi.stubs.impl.*
|
import org.jetbrains.kotlin.psi.stubs.impl.*
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
|
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
|
||||||
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
|
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@@ -209,7 +211,7 @@ public class KotlinStaticDeclarationProviderFactory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitTypeAlias(typeAlias: KtTypeAlias) {
|
override fun visitTypeAlias(typeAlias: KtTypeAlias) {
|
||||||
addToTypeAliasMap(typeAlias)
|
indexTypeAlias(typeAlias)
|
||||||
super.visitTypeAlias(typeAlias)
|
super.visitTypeAlias(typeAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +260,11 @@ public class KotlinStaticDeclarationProviderFactory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun indexTypeAlias(typeAlias: KtTypeAlias) {
|
||||||
|
addToTypeAliasMap(typeAlias)
|
||||||
|
indexTypeAliasDefinition(typeAlias)
|
||||||
|
}
|
||||||
|
|
||||||
private fun addToTypeAliasMap(typeAlias: KtTypeAlias) {
|
private fun addToTypeAliasMap(typeAlias: KtTypeAlias) {
|
||||||
typeAlias.getClassId()?.let { classId ->
|
typeAlias.getClassId()?.let { classId ->
|
||||||
index.typeAliasMap.computeIfAbsent(classId.packageFqName) {
|
index.typeAliasMap.computeIfAbsent(classId.packageFqName) {
|
||||||
@@ -266,6 +273,44 @@ public class KotlinStaticDeclarationProviderFactory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun indexTypeAliasDefinition(typeAlias: KtTypeAlias) {
|
||||||
|
val typeElement = typeAlias.getTypeReference()?.typeElement ?: return
|
||||||
|
|
||||||
|
findInheritableSimpleNames(typeElement).forEach { expandedName ->
|
||||||
|
index.inheritableTypeAliasesByAliasedName
|
||||||
|
.computeIfAbsent(Name.identifier(expandedName)) { mutableSetOf() }
|
||||||
|
.add(typeAlias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simplified version of `KtTypeElement.index()` from the IDE. If we need to move more indexing code to Standalone, we should
|
||||||
|
* consider moving more code from the IDE to the Analysis API.
|
||||||
|
*
|
||||||
|
* @see KotlinStaticDeclarationIndex.inheritableTypeAliasesByAliasedName
|
||||||
|
*/
|
||||||
|
private fun findInheritableSimpleNames(typeElement: KtTypeElement): List<String> {
|
||||||
|
return when (typeElement) {
|
||||||
|
is KtUserType -> {
|
||||||
|
val referenceName = typeElement.referencedName ?: return emptyList()
|
||||||
|
|
||||||
|
buildList {
|
||||||
|
add(referenceName)
|
||||||
|
|
||||||
|
val ktFile = typeElement.containingKtFile
|
||||||
|
if (!ktFile.isCompiled) {
|
||||||
|
addIfNotNull(getImportedSimpleNameByImportAlias(typeElement.containingKtFile, referenceName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `typealias T = A?` is inheritable.
|
||||||
|
is KtNullableType -> typeElement.innerType?.let(::findInheritableSimpleNames) ?: emptyList()
|
||||||
|
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addToFunctionMap(function: KtNamedFunction) {
|
private fun addToFunctionMap(function: KtNamedFunction) {
|
||||||
if (!function.isTopLevel) return
|
if (!function.isTopLevel) return
|
||||||
val packageFqName = (function.parent as KtFile).packageFqName
|
val packageFqName = (function.parent as KtFile).packageFqName
|
||||||
@@ -330,7 +375,7 @@ public class KotlinStaticDeclarationProviderFactory(
|
|||||||
// member functions and properties
|
// member functions and properties
|
||||||
stub.childrenStubs.forEach(::indexStub)
|
stub.childrenStubs.forEach(::indexStub)
|
||||||
}
|
}
|
||||||
is KotlinTypeAliasStubImpl -> addToTypeAliasMap(stub.psi)
|
is KotlinTypeAliasStubImpl -> indexTypeAlias(stub.psi)
|
||||||
is KotlinFunctionStubImpl -> addToFunctionMap(stub.psi)
|
is KotlinFunctionStubImpl -> addToFunctionMap(stub.psi)
|
||||||
is KotlinPropertyStubImpl -> addToPropertyMap(stub.psi)
|
is KotlinPropertyStubImpl -> addToPropertyMap(stub.psi)
|
||||||
is KotlinPlaceHolderStubImpl -> {
|
is KotlinPlaceHolderStubImpl -> {
|
||||||
@@ -416,6 +461,9 @@ public class KotlinStaticDeclarationProviderFactory(
|
|||||||
|
|
||||||
public fun getDirectInheritorCandidates(baseClassName: Name): Set<KtClassOrObject> =
|
public fun getDirectInheritorCandidates(baseClassName: Name): Set<KtClassOrObject> =
|
||||||
index.classesBySupertypeName[baseClassName].orEmpty()
|
index.classesBySupertypeName[baseClassName].orEmpty()
|
||||||
|
|
||||||
|
public fun getInheritableTypeAliases(aliasedName: Name): Set<KtTypeAlias> =
|
||||||
|
index.inheritableTypeAliasesByAliasedName[aliasedName].orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+16
-1
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
|||||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.contains
|
import org.jetbrains.kotlin.psi.psiUtil.contains
|
||||||
@@ -42,7 +43,11 @@ class KotlinStandaloneDirectInheritorsProvider(private val project: Project) : K
|
|||||||
includeLocalInheritors: Boolean,
|
includeLocalInheritors: Boolean,
|
||||||
): Iterable<KtClassOrObject> {
|
): Iterable<KtClassOrObject> {
|
||||||
val classId = ktClass.getClassId() ?: return emptyList()
|
val classId = ktClass.getClassId() ?: return emptyList()
|
||||||
val possibleInheritors = staticDeclarationProviderFactory.getDirectInheritorCandidates(classId.shortClassName)
|
|
||||||
|
val aliases = mutableSetOf(classId.shortClassName)
|
||||||
|
calculateAliases(classId.shortClassName, aliases)
|
||||||
|
|
||||||
|
val possibleInheritors = aliases.flatMap { staticDeclarationProviderFactory.getDirectInheritorCandidates(it) }
|
||||||
|
|
||||||
if (possibleInheritors.isEmpty()) {
|
if (possibleInheritors.isEmpty()) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
@@ -65,6 +70,16 @@ class KotlinStandaloneDirectInheritorsProvider(private val project: Project) : K
|
|||||||
return possibleInheritors.filter { isValidInheritor(it, baseFirClass, scope, includeLocalInheritors) }
|
return possibleInheritors.filter { isValidInheritor(it, baseFirClass, scope, includeLocalInheritors) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun calculateAliases(aliasedName: Name, aliases: MutableSet<Name>) {
|
||||||
|
staticDeclarationProviderFactory.getInheritableTypeAliases(aliasedName).forEach { alias ->
|
||||||
|
val aliasName = alias.nameAsSafeName
|
||||||
|
val isNewAliasName = aliases.add(aliasName)
|
||||||
|
if (isNewAliasName) {
|
||||||
|
calculateAliases(aliasName, aliases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun isValidInheritor(
|
private fun isValidInheritor(
|
||||||
candidate: KtClassOrObject,
|
candidate: KtClassOrObject,
|
||||||
baseFirClass: FirClass,
|
baseFirClass: FirClass,
|
||||||
|
|||||||
+6
@@ -1,2 +1,8 @@
|
|||||||
/OneSealedChild
|
/OneSealedChild
|
||||||
class OneSealedChild : MySealedClass()
|
class OneSealedChild : MySealedClass()
|
||||||
|
|
||||||
|
/ThreeSealedChild
|
||||||
|
class ThreeSealedChild : T2()
|
||||||
|
|
||||||
|
/TwoSealedChild
|
||||||
|
class TwoSealedChild : T1()
|
||||||
|
|||||||
+6
@@ -1,2 +1,8 @@
|
|||||||
/OneSealedChild
|
/OneSealedChild
|
||||||
class OneSealedChild : MySealedClass()
|
class OneSealedChild : MySealedClass()
|
||||||
|
|
||||||
|
/ThreeSealedChild
|
||||||
|
class ThreeSealedChild : T2()
|
||||||
|
|
||||||
|
/TwoSealedChild
|
||||||
|
class TwoSealedChild : T1()
|
||||||
|
|||||||
+6
@@ -1,2 +1,8 @@
|
|||||||
/OneSealedChild
|
/OneSealedChild
|
||||||
class OneSealedChild : MySealedInterface
|
class OneSealedChild : MySealedInterface
|
||||||
|
|
||||||
|
/ThreeSealedChild
|
||||||
|
class ThreeSealedChild : T2
|
||||||
|
|
||||||
|
/TwoSealedChild
|
||||||
|
class TwoSealedChild : T1
|
||||||
|
|||||||
+6
@@ -1,2 +1,8 @@
|
|||||||
/OneSealedChild
|
/OneSealedChild
|
||||||
class OneSealedChild : MySealedInterface
|
class OneSealedChild : MySealedInterface
|
||||||
|
|
||||||
|
/ThreeSealedChild
|
||||||
|
class ThreeSealedChild : T2
|
||||||
|
|
||||||
|
/TwoSealedChild
|
||||||
|
class TwoSealedChild : T1
|
||||||
|
|||||||
@@ -183,16 +183,7 @@ fun StubBasedPsiElementBase<out KotlinClassOrObjectStub<out KtClassOrObject>>.ge
|
|||||||
|
|
||||||
val file = containingFile
|
val file = containingFile
|
||||||
if (file is KtFile) {
|
if (file is KtFile) {
|
||||||
val directive = file.findImportByAlias(referencedName)
|
getImportedSimpleNameByImportAlias(file, referencedName)?.let(result::add)
|
||||||
if (directive != null) {
|
|
||||||
var reference = directive.importedReference
|
|
||||||
while (reference is KtDotQualifiedExpression) {
|
|
||||||
reference = reference.selectorExpression
|
|
||||||
}
|
|
||||||
if (reference is KtSimpleNameExpression) {
|
|
||||||
result.add(reference.getReferencedName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,4 +712,18 @@ internal fun isKtFile(parent: PsiElement?): Boolean {
|
|||||||
//avoid loading KtFile which depends on java psi, which is not available in some setup
|
//avoid loading KtFile which depends on java psi, which is not available in some setup
|
||||||
//e.g. remote dev https://youtrack.jetbrains.com/issue/GTW-7554
|
//e.g. remote dev https://youtrack.jetbrains.com/issue/GTW-7554
|
||||||
return parent is PsiFile && parent.language == KotlinLanguage.INSTANCE
|
return parent is PsiFile && parent.language == KotlinLanguage.INSTANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getImportedSimpleNameByImportAlias(file: KtFile, aliasName: String): String? {
|
||||||
|
val directive = file.findImportByAlias(aliasName) ?: return null
|
||||||
|
|
||||||
|
var reference = directive.importedReference
|
||||||
|
while (reference is KtDotQualifiedExpression) {
|
||||||
|
reference = reference.selectorExpression
|
||||||
|
}
|
||||||
|
if (reference is KtSimpleNameExpression) {
|
||||||
|
return reference.getReferencedName()
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user