[kotlin] KTIJ-25208, KTIJ-31165 type safe processing util methods

GitOrigin-RevId: 4c9f1209f7e4fbd7d7b0d91fdb868adb33a673b5
This commit is contained in:
Andrew Kozlov
2024-10-10 15:41:38 +02:00
committed by intellij-monorepo-bot
parent 1b2256358c
commit a082ee2095
6 changed files with 147 additions and 143 deletions

View File

@@ -99,10 +99,8 @@ private class IdeKotlinDeclarationProvider(
classId.asStringForIndexes(),
project,
scope
) {
ProgressManager.checkCanceled()
it.getClassId() == classId
}
) { it.getClassId() == classId }
.toList()
override fun getAllTypeAliasesByClassId(classId: ClassId): Collection<KtTypeAlias> {
return listOfNotNull(getTypeAliasByClassId(classId)) //todo

View File

@@ -9,26 +9,19 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.util.SmartList
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.KaClassType
import org.jetbrains.kotlin.analysis.api.types.KaFlexibleType
import org.jetbrains.kotlin.analysis.api.types.KaIntersectionType
import org.jetbrains.kotlin.analysis.api.types.KaType
import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
import org.jetbrains.kotlin.analysis.api.types.KaTypeParameterType
import org.jetbrains.kotlin.analysis.api.types.*
import org.jetbrains.kotlin.base.analysis.isExcludedFromAutoImport
import org.jetbrains.kotlin.psi.psiUtil.isExpectDeclaration
import org.jetbrains.kotlin.idea.base.psi.kotlinFqName
import org.jetbrains.kotlin.idea.stubindex.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isExpectDeclaration
import org.jetbrains.kotlin.serialization.deserialization.METADATA_FILE_EXTENSION
import org.jetbrains.kotlin.utils.yieldIfNotNull
@OptIn(KaExperimentalApi::class)
class KtSymbolFromIndexProvider private constructor(
@@ -39,11 +32,14 @@ class KtSymbolFromIndexProvider private constructor(
get() = useSiteFile.project
context(KaSession)
private fun useSiteFilter(element: PsiElement): Boolean {
if (element.kotlinFqName?.isExcludedFromAutoImport(project, useSiteFile) == true) return false
private fun <T : PsiElement> T.isAcceptable(psiFilter: (T) -> Boolean): Boolean {
if (!psiFilter(this)) return false
val isCommon = useSiteModule.targetPlatform.isCommon()
return isCommon || (element as? KtDeclaration)?.isExpectDeclaration() != true
if (kotlinFqName?.isExcludedFromAutoImport(project, useSiteFile) == true) return false
return this !is KtDeclaration
|| !isExpectDeclaration()
|| useSiteModule.targetPlatform.isCommon()
}
context(KaSession)
@@ -51,12 +47,15 @@ class KtSymbolFromIndexProvider private constructor(
name: Name,
psiFilter: (KtClassLikeDeclaration) -> Boolean = { true },
): Sequence<KaClassLikeSymbol> {
val valueFilter: (KtClassLikeDeclaration) -> Boolean = { psiFilter(it) && useSiteFilter(it) }
val resolveExtensionScope = resolveExtensionScopeWithTopLevelDeclarations
return getClassLikeSymbols(
classDeclarations = KotlinClassShortNameIndex.getAllElements(name.asString(), project, scope, valueFilter),
typeAliasDeclarations = KotlinTypeAliasShortNameIndex.getAllElements(name.asString(), project, scope, valueFilter),
classDeclarations = KotlinClassShortNameIndex.getAllElements(name.asString(), project, scope) {
it.isAcceptable(psiFilter)
},
typeAliasDeclarations = KotlinTypeAliasShortNameIndex.getAllElements(name.asString(), project, scope) {
it.isAcceptable(psiFilter)
},
declarationsFromExtension = resolveExtensionScope.classifiers(name).filterIsInstance<KaClassLikeSymbol>(),
)
}
@@ -67,30 +66,28 @@ class KtSymbolFromIndexProvider private constructor(
psiFilter: (KtClassLikeDeclaration) -> Boolean = { true },
): Sequence<KaClassLikeSymbol> {
val keyFilter: (String) -> Boolean = { nameFilter(getShortName(it)) }
val valueFilter: (KtClassLikeDeclaration) -> Boolean = { psiFilter(it) && useSiteFilter(it) }
val resolveExtensionScope = resolveExtensionScopeWithTopLevelDeclarations
return getClassLikeSymbols(
classDeclarations = KotlinFullClassNameIndex.getAllElements(project, scope, keyFilter, valueFilter),
typeAliasDeclarations = KotlinTypeAliasShortNameIndex.getAllElements(project, scope, keyFilter, valueFilter),
classDeclarations = KotlinFullClassNameIndex.getAllElements(project, scope, keyFilter) {
it.isAcceptable(psiFilter)
},
typeAliasDeclarations = KotlinTypeAliasShortNameIndex.getAllElements(project, scope, keyFilter) {
it.isAcceptable(psiFilter)
},
declarationsFromExtension = resolveExtensionScope.classifiers(nameFilter).filterIsInstance<KaClassLikeSymbol>(),
)
}
context(KaSession)
private fun getClassLikeSymbols(
classDeclarations: List<KtClassOrObject>,
typeAliasDeclarations: List<KtTypeAlias>,
classDeclarations: Sequence<KtClassOrObject>,
typeAliasDeclarations: Sequence<KtTypeAlias>,
declarationsFromExtension: Sequence<KaClassLikeSymbol>
): Sequence<KaClassLikeSymbol> = sequence {
for (ktClassOrObject in classDeclarations) {
yieldIfNotNull(ktClassOrObject.namedClassSymbol)
}
for (typeAlias in typeAliasDeclarations) {
yield(typeAlias.symbol)
}
yieldAll(declarationsFromExtension)
}
): Sequence<KaClassLikeSymbol> =
classDeclarations.mapNotNull { it.namedClassSymbol } +
typeAliasDeclarations.map { it.symbol } +
declarationsFromExtension
context(KaSession)
fun getJavaClassesByNameFilter(
@@ -127,7 +124,7 @@ class KtSymbolFromIndexProvider private constructor(
return nonKotlinNamesCaches.flatMap { cache ->
cache.getClassesByName(nameString, scope).asSequence()
}.filter { psiFilter(it) && useSiteFilter(it) }
}.filter { it.isAcceptable(psiFilter) }
.mapNotNull { it.namedClassSymbol }
}
@@ -135,25 +132,27 @@ class KtSymbolFromIndexProvider private constructor(
fun getKotlinCallableSymbolsByName(
name: Name,
psiFilter: (KtCallableDeclaration) -> Boolean = { true },
): Sequence<KaCallableSymbol> {
val nameString = name.asString()
val values = SmartList<KtNamedDeclaration>()
val processor = CancelableCollectFilterProcessor(values) {
it is KtCallableDeclaration && psiFilter(it) && useSiteFilter(it) && !it.isKotlinBuiltins()
): Sequence<KaCallableSymbol> = sequenceOf(
KotlinFunctionShortNameIndex,
KotlinPropertyShortNameIndex,
).flatMap { helper ->
val processor = CancelableCollectFilterProcessor { declaration: KtNamedDeclaration ->
declaration is KtCallableDeclaration
&& declaration.isAcceptable(psiFilter)
&& !declaration.isKotlinBuiltins()
}
KotlinFunctionShortNameIndex.processElements(nameString, project, scope, processor)
KotlinPropertyShortNameIndex.processElements(nameString, project, scope, processor)
return sequence {
for (callableDeclaration in values) {
yieldIfNotNull(callableDeclaration.symbol as? KaCallableSymbol)
}
yieldAll(
resolveExtensionScopeWithTopLevelDeclarations.callables(name)
)
}
}
helper.processElements(
key = name.asString(),
project = project,
scope = scope,
processor = processor,
)
processor.results
}.map { it.symbol }
.filterIsInstance<KaCallableSymbol>() +
resolveExtensionScopeWithTopLevelDeclarations.callables(name)
context(KaSession)
fun getJavaCallableSymbolsByName(
@@ -165,7 +164,7 @@ class KtSymbolFromIndexProvider private constructor(
return nonKotlinNamesCaches.flatMap { cache ->
cache.getMethodsByName(nameString, scope).asSequence() +
cache.getFieldsByName(nameString, scope)
}.filter { psiFilter(it) && useSiteFilter(it) }
}.filter { it.isAcceptable(psiFilter) }
.mapNotNull { it.callableSymbol }
}
@@ -176,25 +175,28 @@ class KtSymbolFromIndexProvider private constructor(
fun getTopLevelCallableSymbolsByNameFilter(
nameFilter: (Name) -> Boolean,
psiFilter: (KtCallableDeclaration) -> Boolean = { true }
): Sequence<KaCallableSymbol> {
val values = SmartList<KtCallableDeclaration>()
val processor = CancelableCollectFilterProcessor(values) {
psiFilter(it) && useSiteFilter(it) && !it.isKotlinBuiltins() && it.receiverTypeReference == null
): Sequence<KaCallableSymbol> = sequenceOf(
KotlinTopLevelFunctionFqnNameIndex,
KotlinTopLevelPropertyFqnNameIndex,
).flatMap { helper ->
val processor = CancelableCollectFilterProcessor { declaration: KtCallableDeclaration ->
declaration.isAcceptable(psiFilter)
&& !declaration.isKotlinBuiltins()
&& declaration.receiverTypeReference == null
}
val keyFilter: (String) -> Boolean = { nameFilter(getShortName(it)) }
KotlinTopLevelFunctionFqnNameIndex.processAllElements(project, scope, keyFilter, processor)
KotlinTopLevelPropertyFqnNameIndex.processAllElements(project, scope, keyFilter, processor)
helper.processAllElements(
project = project,
scope = scope,
filter = { nameFilter(getShortName(it)) },
processor = processor,
)
return sequence {
for (callableDeclaration in values) {
yieldIfNotNull(callableDeclaration.symbol as? KaCallableSymbol)
}
yieldAll(
resolveExtensionScopeWithTopLevelDeclarations.callables(nameFilter).filter { !it.isExtension }
)
}
}
processor.results
}.map { it.symbol }
.filterIsInstance<KaCallableSymbol>() +
resolveExtensionScopeWithTopLevelDeclarations.callables(nameFilter)
.filterNot { it.isExtension }
context(KaSession)
fun getExtensionCallableSymbolsByName(
@@ -213,10 +215,9 @@ class KtSymbolFromIndexProvider private constructor(
).flatMap { indexHelper ->
val key = KotlinExtensionsByReceiverTypeStubIndexHelper.Companion.Key(receiverTypeName, name)
indexHelper.getAllElements(key.key, project, scope) {
psiFilter(it)
&& useSiteFilter(it)
&& !it.isKotlinBuiltins()
indexHelper.getAllElements(key.key, project, scope) { declaration ->
declaration.isAcceptable(psiFilter)
&& !declaration.isKotlinBuiltins()
}
}
}.map { it.symbol }
@@ -247,10 +248,9 @@ class KtSymbolFromIndexProvider private constructor(
KotlinTopLevelExtensionsByReceiverTypeIndex,
KotlinExtensionsInObjectsByReceiverTypeIndex,
).flatMap { index ->
index.getAllElements(project, scope, keyFilter) {
psiFilter(it)
&& useSiteFilter(it)
&& !it.isKotlinBuiltins()
index.getAllElements(project, scope, keyFilter) { declaration: KtCallableDeclaration ->
declaration.isAcceptable(psiFilter)
&& !declaration.isKotlinBuiltins()
}
}.map { it.symbol }
.filterIsInstance<KaCallableSymbol>()
@@ -340,8 +340,12 @@ class KtSymbolFromIndexProvider private constructor(
}
}
private val KotlinBuiltins = setOf("kotlin/ArrayIntrinsicsKt", "kotlin/internal/ProgressionUtilKt")
fun KtCallableDeclaration.isKotlinBuiltins(): Boolean {
private val KotlinBuiltins = setOf(
"kotlin/ArrayIntrinsicsKt",
"kotlin/internal/ProgressionUtilKt",
)
private fun KtCallableDeclaration.isKotlinBuiltins(): Boolean {
val file = containingKtFile
val virtualFile = file.virtualFile
if (virtualFile.extension == METADATA_FILE_EXTENSION) return true

View File

@@ -6,7 +6,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
import org.jetbrains.kotlin.psi.psiUtil.isExpectDeclaration
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelExpectFunctionFqNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelExpectPropertyFqNameIndex
@@ -15,6 +14,7 @@ import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.psi.psiUtil.isExpectDeclaration
object ExpectActualUtils {
fun KtDeclaration.expectedDeclarationIfAny(): KtDeclaration? =
@@ -50,20 +50,12 @@ object ExpectActualUtils {
} ?: unwrappedElement
fun collectTopLevelExpectDeclarations(project: Project, modules: List<KaModule>): List<KtNamedDeclaration> {
val searchScope = GlobalSearchScope.union(modules.map { module -> module.contentScope })
val searchScope = GlobalSearchScope.union(modules.map { it.contentScope })
val indexes = listOf(
return sequenceOf(
KotlinTopLevelExpectFunctionFqNameIndex,
KotlinTopLevelExpectPropertyFqNameIndex,
)
return indexes.flatMap { index ->
index.getAllElements(
project,
searchScope,
keyFilter = { true },
valueFilter = { true }
)
}
).flatMap { it.getAllElements<KtNamedDeclaration>(project, searchScope) }
.toList()
}
}

View File

@@ -106,25 +106,23 @@ class KotlinIndicesHelper(
fun getTopLevelExtensionOperatorsByName(name: String): Collection<FunctionDescriptor> {
return KotlinFunctionShortNameIndex.getAllElements(name, project, scope) {
it.parent is KtFile && it.receiverTypeReference != null && it.hasModifier(KtTokens.OPERATOR_KEYWORD)
}
.flatMap {
ProgressManager.checkCanceled()
it.resolveToDescriptors<FunctionDescriptor>()
}
.filter { descriptorFilter(it) && it.extensionReceiverParameter != null }
.distinct()
}.flatMap {
ProgressManager.checkCanceled()
it.resolveToDescriptors<FunctionDescriptor>()
}.filter { descriptorFilter(it) }
.filter { it.extensionReceiverParameter != null }
.toSet()
}
fun getMemberOperatorsByName(name: String): Collection<FunctionDescriptor> {
return KotlinFunctionShortNameIndex.getAllElements(name, project, scope) {
it.parent is KtClassBody && it.receiverTypeReference == null && it.hasModifier(KtTokens.OPERATOR_KEYWORD)
}
.flatMap {
ProgressManager.checkCanceled()
it.resolveToDescriptors<FunctionDescriptor>()
}
.filter { descriptorFilter(it) && it.extensionReceiverParameter == null }
.distinct()
}.flatMap {
ProgressManager.checkCanceled()
it.resolveToDescriptors<FunctionDescriptor>()
}.filter { descriptorFilter(it) }
.filter { it.extensionReceiverParameter == null }
.toSet()
}
fun processTopLevelCallables(nameFilter: (String) -> Boolean, processor: (CallableDescriptor) -> Unit) {
@@ -317,7 +315,8 @@ class KotlinIndicesHelper(
fun getKotlinEnumsByName(name: String): Collection<DeclarationDescriptor> {
val enumEntries = KotlinClassShortNameIndex.getAllElements(name, project, scope) {
it is KtEnumEntry
}
}.toList()
val result = HashSet<DeclarationDescriptor>(enumEntries.size)
for (enumEntry in enumEntries) {
ProgressManager.checkCanceled()

View File

@@ -12,9 +12,9 @@ import com.intellij.psi.stubs.StubIndexKey
import com.intellij.util.CommonProcessors
import com.intellij.util.Processor
import com.intellij.util.Processors
import com.intellij.util.SmartList
import com.intellij.util.indexing.IdFilter
import org.jetbrains.kotlin.idea.base.indices.*
import org.jetbrains.kotlin.idea.base.indices.getAllKeysAndMeasure
import org.jetbrains.kotlin.idea.base.indices.getByKeyAndMeasure
import org.jetbrains.kotlin.idea.base.indices.processAllKeysAndMeasure
import org.jetbrains.kotlin.idea.base.indices.processElementsAndMeasure
@@ -32,10 +32,15 @@ abstract class KotlinStringStubIndexHelper<Key : NavigatablePsiElement>(private
return getAllKeysAndMeasure(indexKey, logger) { StubIndex.getInstance().getAllKeys(indexKey, project) }
}
fun getAllElements(s: String, project: Project, scope: GlobalSearchScope, filter: (Key) -> Boolean): List<Key> {
val values = SmartList<Key>()
processElements(s, project, scope, null, CancelableCollectFilterProcessor(values, filter))
return values
fun getAllElements(
key: String,
project: Project,
scope: GlobalSearchScope,
filter: (Key) -> Boolean = { true },
): Sequence<Key> {
val processor = CancelableCollectFilterProcessor<Key>(filter = filter)
processElements(key, project, scope, null, processor)
return processor.results.asSequence() // todo move valueFilter out
}
/**
* Note: [processor] should not invoke any indices as it could lead to deadlock. Nested index access is forbidden.
@@ -47,21 +52,30 @@ abstract class KotlinStringStubIndexHelper<Key : NavigatablePsiElement>(private
/**
* Note: [processor] should not invoke any indices as it could lead to deadlock. Nested index access is forbidden.
*/
fun processElements(s: String, project: Project, scope: GlobalSearchScope, idFilter: IdFilter? = null, processor: Processor<in Key>): Boolean {
return processElementsAndMeasure(indexKey, logger) {
StubIndex.getInstance().processElements(indexKey, s, project, scope, idFilter, valueClass, processor)
}
}
fun getAllElements(
fun processElements(
key: String,
project: Project,
scope: GlobalSearchScope,
keyFilter: (String) -> Boolean = { true },
valueFilter: (Key) -> Boolean
): List<Key> {
val values = SmartList<Key>()
processAllElements(project, scope, keyFilter, CancelableCollectFilterProcessor(values, valueFilter))
return values
idFilter: IdFilter? = null,
processor: Processor<in Key>,
): Boolean = processElementsAndMeasure(indexKey, logger) {
StubIndex.getInstance().processElements(indexKey, key, project, scope, idFilter, valueClass, processor)
}
inline fun <reified SubKey> getAllElements(
project: Project,
scope: GlobalSearchScope,
noinline keyFilter: (String) -> Boolean = { true },
noinline valueFilter: (SubKey) -> Boolean = { true },
): Sequence<SubKey> {
val processor = CancelableCollectFilterProcessor<SubKey>(filter = valueFilter)
processAllElements(project, scope, keyFilter) { key ->
if (key is SubKey)
processor.process(key)
else
true
}
return processor.results.asSequence() // todo move valueFilter out
}
fun processAllElements(
@@ -132,9 +146,10 @@ class CancelableDelegateFilterProcessor<T>(
}
class CancelableCollectFilterProcessor<T>(
collection: Collection<T>,
private val filter: (T) -> Boolean
collection: Collection<T> = mutableListOf(),
private val filter: (T) -> Boolean,
) : CommonProcessors.CollectProcessor<T>(collection) {
override fun process(t: T): Boolean {
ProgressManager.checkCanceled()
return super.process(t)

View File

@@ -27,13 +27,13 @@ fun KtDeclaration.findAllActualForExpect(searchScope: SearchScope = runReadActio
// covers cases like classes, class functions and class properties
containingClassOrObjectOrSelf?.fqName?.let { fqName ->
val fqNameAsString = fqName.asString()
val targetDeclarations: List<KtDeclaration> = KotlinFullClassNameIndex.getAllElements(fqNameAsString, project, scope, filter = {
val targetDeclarations = KotlinFullClassNameIndex.getAllElements(fqNameAsString, project, scope) {
it.matchesWithExpect(containingClassOrObjectOrSelf)
}) + KotlinTopLevelTypeAliasFqNameIndex.getAllElements(fqNameAsString, project, scope, filter = {
} + KotlinTopLevelTypeAliasFqNameIndex.getAllElements(fqNameAsString, project, scope) {
it.matchesWithExpect(containingClassOrObjectOrSelf)
})
}
return targetDeclarations.asSequence().mapNotNull { targetDeclaration ->
return targetDeclarations.mapNotNull { targetDeclaration ->
when (declaration) {
is KtClassOrObject -> targetDeclaration
is KtConstructor<*> -> {
@@ -56,28 +56,24 @@ fun KtDeclaration.findAllActualForExpect(searchScope: SearchScope = runReadActio
}
else -> null
}?.createSmartPointer()
}
}
}.map { it.createSmartPointer() }
}
// top level functions
val packageFqName = declaration.containingKtFile.packageFqName
val name = declaration.name ?: return emptySequence()
val topLevelFqName = packageFqName.child(Name.identifier(name)).asString()
return when (declaration) {
is KtNamedFunction -> {
KotlinTopLevelFunctionFqnNameIndex.getAllElements(topLevelFqName, project, scope) {
it.matchesWithExpect(declaration)
}.asSequence().map(KtNamedFunction::createSmartPointer)
is KtNamedFunction -> KotlinTopLevelFunctionFqnNameIndex.getAllElements(topLevelFqName, project, scope) {
it.matchesWithExpect(declaration)
}
is KtProperty -> {
KotlinTopLevelPropertyFqnNameIndex.getAllElements(topLevelFqName, project, scope) {
it.matchesWithExpect(declaration)
}.asSequence().map(KtProperty::createSmartPointer)
is KtProperty -> KotlinTopLevelPropertyFqnNameIndex.getAllElements(topLevelFqName, project, scope) {
it.matchesWithExpect(declaration)
}
else -> emptySequence()
}
}.map { it.createSmartPointer() }
}
@OptIn(KaExperimentalApi::class)