[AA] KT-58580 Add KotlinDeclarationProviderMerger

- A proper merging strategy for declaration providers is required for
  cases where the main declaration provider created by
  `createDeclarationProvider` can't provide all declarations that the
  original declaration providers can provide. Then, only a sublist of
  the declaration providers should be merged, while keeping the
  unmergeable declaration providers intact.

^KT-58580 fixed
This commit is contained in:
Marco Pennekamp
2023-05-17 15:37:27 +02:00
committed by Space Team
parent fba0648005
commit 915c412929
12 changed files with 105 additions and 21 deletions
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementDefault
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.decompiler.psi.BuiltInDefinitionFile
import org.jetbrains.kotlin.analysis.project.structure.KtBuiltinsModule
import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProvider
import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProviderImpl
import org.jetbrains.kotlin.analysis.providers.*
@@ -69,6 +68,7 @@ object AnalysisApiBaseTestServiceRegistrar: AnalysisApiTestServiceRegistrar() {
} finally {
BuiltInDefinitionFile.FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES = filter
}
registerService(KotlinDeclarationProviderMerger::class.java, KotlinStaticDeclarationProviderMerger(project))
registerService(KotlinPackageProviderFactory::class.java, KotlinStaticPackageProviderFactory(project, allKtFiles))
registerService(KotlinReferenceProvidersService::class.java, HLApiReferenceProviderService::class.java)
registerService(KotlinResolutionScopeProvider::class.java, KotlinByModulesResolutionScopeProvider::class.java)
@@ -49,9 +49,40 @@ public abstract class KotlinDeclarationProvider {
public abstract class KotlinDeclarationProviderFactory {
public abstract fun createDeclarationProvider(scope: GlobalSearchScope, contextualModule: KtModule?): KotlinDeclarationProvider
public companion object {
public fun getInstance(project: Project): KotlinDeclarationProviderFactory =
project.getService(KotlinDeclarationProviderFactory::class.java)
}
}
public fun Project.createDeclarationProvider(scope: GlobalSearchScope, module: KtModule?): KotlinDeclarationProvider {
return getService(KotlinDeclarationProviderFactory::class.java)
.createDeclarationProvider(scope, module)
}
/**
* While the main declaration provider created via [createDeclarationProvider] is scope-based, there are other declaration providers which
* are not, such as file-based declaration providers. Not all declarations provided by such declaration providers can be provided by the
* main declaration provider, even if the correct scope is provided (such as a file-based scope). For example, the main declaration provider
* may be based on an index which doesn't contain the declarations provided by file-based declaration providers.
*
* Hence, [KotlinDeclarationProvider]s cannot just be combined by combining the scopes of all declaration providers and calling
* [createDeclarationProvider]. [KotlinDeclarationProviderMerger] should implement proper merging logic that takes these concerns into
* account.
*/
public abstract class KotlinDeclarationProviderMerger {
/**
* Merges [declarationProviders] if possible, creating a combined declaration provider that should be more efficient compared to calling
* separate declaration providers. Not all given declaration providers might be mergeable, or there might be multiple separate sets of
* declaration providers which can be merged individually, so the resulting declaration provider may be a composite Kotlin declaration
* provider.
*/
public abstract fun mergeDeclarationProviders(declarationProviders: List<KotlinDeclarationProvider>): KotlinDeclarationProvider
public companion object {
public fun getInstance(project: Project): KotlinDeclarationProviderMerger =
project.getService(KotlinDeclarationProviderMerger::class.java)
}
}
public fun Project.createDeclarationProvider(scope: GlobalSearchScope, module: KtModule?): KotlinDeclarationProvider =
KotlinDeclarationProviderFactory.getInstance(this).createDeclarationProvider(scope, module)
public fun Project.mergeDeclarationProviders(declarationProviders: List<KotlinDeclarationProvider>): KotlinDeclarationProvider =
KotlinDeclarationProviderMerger.getInstance(this).mergeDeclarationProviders(declarationProviders)
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.providers.impl
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderMerger
import org.jetbrains.kotlin.analysis.providers.impl.declarationProviders.CompositeKotlinDeclarationProvider
public abstract class KotlinDeclarationProviderMergerBase : KotlinDeclarationProviderMerger() {
override fun mergeDeclarationProviders(declarationProviders: List<KotlinDeclarationProvider>): KotlinDeclarationProvider {
// We should flatten declaration providers before merging so that the merger has access to all declaration providers, and also after
// merging because composite declaration providers may be created by the merger.
return CompositeKotlinDeclarationProvider.createFlattened(
mergeToList(
CompositeKotlinDeclarationProvider.flatten(declarationProviders)
)
)
}
protected abstract fun mergeToList(declarationProviders: List<KotlinDeclarationProvider>): List<KotlinDeclarationProvider>
}
@@ -21,13 +21,14 @@ import com.intellij.util.containers.CollectionFactory
import com.intellij.util.indexing.FileContent
import com.intellij.util.indexing.FileContentImpl
import com.intellij.util.io.URLUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinBuiltInDecompiler
import org.jetbrains.kotlin.analysis.decompiler.stub.file.ClsKotlinBinaryClassCache
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinClsStubBuilder
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderFactory
import org.jetbrains.kotlin.analysis.providers.createDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.impl.util.mergeOnly
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.name.*
@@ -39,7 +40,7 @@ import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerial
public class KotlinStaticDeclarationProvider internal constructor(
private val index: KotlinStaticDeclarationIndex,
private val scope: GlobalSearchScope,
public val scope: GlobalSearchScope,
) : KotlinDeclarationProvider() {
private val KtElement.inScope: Boolean
@@ -118,7 +119,6 @@ public class KotlinStaticDeclarationProvider internal constructor(
getTopLevelProperties(callableId).mapTo(this) { it.containingKtFile }
getTopLevelFunctions(callableId).mapTo(this) { it.containingKtFile }
}
}
public class KotlinStaticDeclarationProviderFactory(
@@ -368,3 +368,15 @@ public class KotlinFakeClsStubsCache {
}
}
}
public class KotlinStaticDeclarationProviderMerger(private val project: Project) : KotlinDeclarationProviderMergerBase() {
override fun mergeToList(declarationProviders: List<KotlinDeclarationProvider>): List<KotlinDeclarationProvider> =
declarationProviders.mergeOnly<_, KotlinStaticDeclarationProvider> { providers ->
val combinedScope = GlobalSearchScope.union(providers.map { it.scope })
project.createDeclarationProvider(combinedScope, module = null).apply {
check(this is KotlinStaticDeclarationProvider) {
"`KotlinStaticDeclarationProvider` can only be merged into a combined declaration provider of the same type."
}
}
}
}
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
public class CompositeKotlinDeclarationProvider private constructor(
private val providers: List<KotlinDeclarationProvider>
public val providers: List<KotlinDeclarationProvider>
) : KotlinDeclarationProvider() {
override fun getClassLikeDeclarationByClassId(classId: ClassId): KtClassLikeDeclaration? {
return providers.firstNotNullOfOrNull { it.getClassLikeDeclarationByClassId(classId) }
@@ -75,5 +75,16 @@ public class CompositeKotlinDeclarationProvider private constructor(
else -> CompositeKotlinDeclarationProvider(providers)
}
}
public fun createFlattened(providers: List<KotlinDeclarationProvider>): KotlinDeclarationProvider =
create(if (providers.size > 1) flatten(providers) else providers)
public fun flatten(providers: List<KotlinDeclarationProvider>): List<KotlinDeclarationProvider> =
providers.flatMap { provider ->
when (provider) {
is CompositeKotlinDeclarationProvider -> provider.providers
else -> listOf(provider)
}
}
}
}
@@ -141,6 +141,7 @@ public class StandaloneAnalysisAPISessionBuilder(
kotlinCoreProjectEnvironment.environment.jarFileSystem as CoreJarFileSystem
)
)
registerService(KotlinDeclarationProviderMerger::class.java, KotlinStaticDeclarationProviderMerger(this))
registerService(KotlinPackageProviderFactory::class.java, KotlinStaticPackageProviderFactory(project, ktFiles))
registerService(KtAnalysisSessionProvider::class.java, KtFirAnalysisSessionProvider(this))
@@ -159,6 +159,10 @@ internal fun configureProjectEnvironment(
KotlinDeclarationProviderFactory::class.java,
KotlinStaticDeclarationProviderFactory(project, ktFiles)
)
project.registerService(
KotlinDeclarationProviderMerger::class.java,
KotlinStaticDeclarationProviderMerger(project)
)
project.registerService(
KotlinPackageProviderFactory::class.java,
KotlinStaticPackageProviderFactory(project, ktFiles)
@@ -6,13 +6,11 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.providers
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.low.level.api.fir.caches.NullableCaffeineCache
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.createDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.mergeDeclarationProviders
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.resolve.providers.FirCompositeCachedSymbolNamesProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
@@ -118,8 +116,7 @@ internal class LLFirCombinedKotlinSymbolProvider private constructor(
companion object {
fun merge(session: LLFirSession, project: Project, providers: List<LLFirKotlinSymbolProvider>): FirSymbolProvider? =
if (providers.size > 1) {
val combinedScope = GlobalSearchScope.union(providers.map { it.session.llFirModuleData.ktModule.contentScope })
val declarationProvider = project.createDeclarationProvider(combinedScope, session.ktModule)
val declarationProvider = project.mergeDeclarationProviders(providers.map { it.declarationProvider })
LLFirCombinedKotlinSymbolProvider(session, project, providers, declarationProvider)
} else providers.singleOrNull()
}
@@ -26,6 +26,8 @@ import org.jetbrains.kotlin.psi.KtProperty
* @see LLFirCombinedKotlinSymbolProvider
*/
internal abstract class LLFirKotlinSymbolProvider(session: FirSession) : FirSymbolProvider(session) {
abstract val declarationProvider: KotlinDeclarationProvider
/**
* This function is optimized for a known [classLikeDeclaration].
*/
@@ -109,6 +109,8 @@ internal class LLFirProvider(
@NoMutableState
internal inner class SymbolProvider : LLFirKotlinSymbolProvider(session) {
override val declarationProvider: KotlinDeclarationProvider get() = providerHelper.declarationProvider
override val symbolNamesProvider: FirSymbolNamesProvider get() = providerHelper.symbolNameCache
override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? {
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.providers
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirFileBuilder
import org.jetbrains.kotlin.analysis.providers.impl.declarationProviders.CompositeKotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.CompositeKotlinPackageProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.LLFirResolveExtensionTool
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.llResolveExtensionTool
@@ -14,6 +13,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.util.FirElementFinder
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.LLFirKotlinSymbolNamesProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.KotlinPackageProvider
import org.jetbrains.kotlin.analysis.providers.impl.declarationProviders.CompositeKotlinDeclarationProvider
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.fir.FirSession
@@ -43,7 +43,7 @@ internal class LLFirProviderHelper(
) {
private val extensionTool: LLFirResolveExtensionTool? = firSession.llResolveExtensionTool
private val declarationProvider = CompositeKotlinDeclarationProvider.create(
val declarationProvider = CompositeKotlinDeclarationProvider.create(
listOfNotNull(
mainDeclarationProvider,
extensionTool?.declarationProvider,
@@ -5,10 +5,10 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirKotlinSymbolProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.LLFirKotlinSymbolNamesProvider
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirKotlinSymbolProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.LLFirKotlinSymbolNamesProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.createDeclarationProvider
import org.jetbrains.kotlin.fir.FirSession
@@ -19,8 +19,8 @@ import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.deserialization.SingleModuleDataProvider
import org.jetbrains.kotlin.fir.java.deserialization.JvmClassFileBasedSymbolProvider
import org.jetbrains.kotlin.fir.java.deserialization.KotlinBuiltins
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
import org.jetbrains.kotlin.fir.realPsi
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals
import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
import org.jetbrains.kotlin.fir.symbols.impl.*
@@ -52,10 +52,10 @@ internal open class JvmStubBasedFirDeserializedSymbolProvider(
scope: GlobalSearchScope,
private val initialOrigin: FirDeclarationOrigin
) : LLFirKotlinSymbolProvider(session) {
private val declarationProvider = project.createDeclarationProvider(scope, module = null)
private val moduleData = moduleDataProvider.getModuleData(null)
final override val declarationProvider = project.createDeclarationProvider(scope, module = null)
override val symbolNamesProvider: FirSymbolNamesProvider = LLFirKotlinSymbolNamesProvider.cached(session, declarationProvider)
private val typeAliasCache: FirCache<ClassId, FirTypeAliasSymbol?, StubBasedFirDeserializationContext?> =