[FIR/LL FIR] Introduce FirSymbolNamesProvider

- In LL FIR, we have increasingly formalized symbol name caches as
  palpable objects. The main reasons for this formalization were the
  need to share implementations of caching between different (LL FIR)
  symbol providers, the need to build composite name caches from
  individual name caches, and the introduction of resolve extensions
  which may provide additional declarations and thus complicate the name
  set construction for Kotlin symbol providers in LL FIR.
- `LLFirSymbolProviderNameCache` also shared a lot of similarities with
  cache handling in FIR providers like
  `FirCachingCompositeSymbolProvider` and
  `AbstractFirDeserializedSymbolProvider`.
- This commit introduces a `FirSymbolNamesProvider` as a component of
  `FirSymbolProvider`. This symbol names provider's task is to provide
  the sets of names which `FirSymbolProvider` previously provided. It
  also allows sharing implementations of `mayHaveTopLevel*` once and for
  all, which is an improvement over the previously scattered
  implementations (the same ideas replicated many times throughout
  different symbol providers).
- `FirSymbolNamesProvider` by design doesn't cache, as many symbol
  providers may not need such a cache. `FirCachedSymbolNamesProvider`
  can be used to cache symbol names if needed. The symbol name provider
  architecture also makes it easier to switch between caching and
  non-caching, without the need to reimplement caches every time.
- Synthetic function types complicate the picture, but this complication
  is now exposed with the rest of the API, instead of being hidden in a
  few implementations here and there. This allows symbol providers to
  more explicitly state whether they can provide generated function
  types, which is an advantage for the correctness of composite symbol
  providers.

Some specific notes:

- In `FirSyntheticFunctionInterfaceProviderBase`, the class ID check has
  been replaced with a full `mayHaveTopLevelClassifier` check so that
  the cache doesn't get filled with `null` entries.
- `LLFirKotlinSymbolProviderNameCache` is turned into a non-caching
  `LLFirKotlinSymbolNamesProvider` so that this symbol names provider
  and those of resolve extensions can be composed into one caching
  symbol provider in `LLFirProviderHelper` without creating layers of
  caches. If the Kotlin symbol names provider was caching out of the
  box, `LLFirProviderHelper.symbolNameCache` would cache the
  names (1) in the combined symbol names cache and (2) in the Kotlin
  symbol names cache.
  - A caching Kotlin symbol names cache can still be created easily with
    the `LLFirKotlinSymbolNamesProvider.cached` constructor function.
This commit is contained in:
Marco Pennekamp
2023-05-09 00:29:58 +02:00
committed by Space Team
parent b82a589e56
commit accc9b0eb3
26 changed files with 550 additions and 498 deletions
@@ -13,8 +13,10 @@ import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.isNewPlaceForBodyGeneration
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals
import org.jetbrains.kotlin.fir.resolve.providers.FirCachedSymbolNamesProvider
import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.metadata.ProtoBuf
@@ -83,23 +85,46 @@ abstract class AbstractFirDeserializedSymbolProvider(
) : FirSymbolProvider(session) {
// ------------------------ Caches ------------------------
/**
* [packageNamesForNonClassDeclarations] might contain names of packages containing type aliases, on top of packages containing
* callables, so it's not the same as `symbolNamesProvider.getPackageNamesWithTopLevelCallables` and cannot be replaced by it.
*/
private val packageNamesForNonClassDeclarations: Set<String> by lazy(LazyThreadSafetyMode.PUBLICATION) {
computePackageSetWithNonClassDeclarations()
}
override val symbolNamesProvider: FirSymbolNamesProvider = object : FirCachedSymbolNamesProvider(session) {
override fun computeTopLevelClassifierNames(packageFqName: FqName): Set<String>? {
val classesInPackage = knownTopLevelClassesInPackage(packageFqName) ?: return null
if (packageFqName.asString() !in packageNamesForNonClassDeclarations) return classesInPackage
val typeAliasNames = typeAliasesNamesByPackage.getValue(packageFqName)
if (typeAliasNames.isEmpty()) return classesInPackage
return buildSet {
addAll(classesInPackage)
typeAliasNames.mapTo(this) { it.asString() }
}
}
override fun getPackageNamesWithTopLevelCallables(): Set<String> = packageNamesForNonClassDeclarations
override fun computePackageNamesWithTopLevelCallables(): Set<String> = packageNamesForNonClassDeclarations
override fun computeTopLevelCallableNames(packageFqName: FqName): Set<Name> =
getPackageParts(packageFqName).flatMapTo(mutableSetOf()) {
it.topLevelFunctionNameIndex.keys + it.topLevelPropertyNameIndex.keys
}
}
private val typeAliasesNamesByPackage: FirCache<FqName, Set<Name>, Nothing?> =
session.firCachesFactory.createCache { fqName: FqName ->
getPackageParts(fqName).flatMapTo(mutableSetOf()) { it.typeAliasNameIndex.keys }
}
private val allNamesByPackage: FirCache<FqName, Set<Name>, Nothing?> =
session.firCachesFactory.createCache { fqName: FqName ->
getPackageParts(fqName).flatMapTo(mutableSetOf()) {
it.topLevelFunctionNameIndex.keys + it.topLevelPropertyNameIndex.keys
}
}
private val packagePartsCache = session.firCachesFactory.createCache(::tryComputePackagePartInfos)
private val typeAliasCache: FirCache<ClassId, FirTypeAliasSymbol?, FirDeserializationContext?> =
session.firCachesFactory.createCacheWithPostCompute(
createValue = { classId, _ -> findAndDeserializeTypeAlias(classId) },
@@ -109,6 +134,7 @@ abstract class AbstractFirDeserializedSymbolProvider(
}
}
)
private val classCache: FirCache<ClassId, FirRegularClassSymbol?, FirDeserializationContext?> =
session.firCachesFactory.createCacheWithPostCompute(
createValue = { classId, context -> findAndDeserializeClass(classId, context) },
@@ -132,24 +158,6 @@ abstract class AbstractFirDeserializedSymbolProvider(
// This method should only be used for sake of optimization to avoid having too many empty-list/null values in our caches
protected abstract fun computePackageSetWithNonClassDeclarations(): Set<String>
override fun computePackageSetWithTopLevelCallables(): Set<String> = computePackageSetWithNonClassDeclarations()
override fun computeCallableNamesInPackage(packageFqName: FqName): Set<Name> = allNamesByPackage.getValue(packageFqName)
override fun knownTopLevelClassifiersInPackage(packageFqName: FqName): Set<String>? {
val classesInPackage = knownTopLevelClassesInPackage(packageFqName) ?: return null
if (packageFqName.asString() !in packageNamesForNonClassDeclarations) return classesInPackage
val typeAliasNames = typeAliasesNamesByPackage.getValue(packageFqName)
if (typeAliasNames.isEmpty()) return classesInPackage
return buildSet {
addAll(classesInPackage)
typeAliasNames.mapTo(this) { it.asString() }
}
}
protected abstract fun knownTopLevelClassesInPackage(packageFqName: FqName): Set<String>?
protected abstract fun extractClassMetadata(
@@ -253,8 +261,8 @@ abstract class AbstractFirDeserializedSymbolProvider(
val parentClassId = classId.outerClassId
// Actually, the second "if" should be enough but the first one might work faster
if (parentClassId == null && !mayHaveTopLevelClass(classId)) return null
if (parentClassId != null && !mayHaveTopLevelClass(classId.outermostClassId)) return null
if (parentClassId == null && !symbolNamesProvider.mayHaveTopLevelClassifier(classId)) return null
if (parentClassId != null && !symbolNamesProvider.mayHaveTopLevelClassifier(classId.outermostClassId)) return null
if (parentContext == null && parentClassId != null) {
val alreadyLoaded = classCache.getValueIfComputed(classId)
@@ -266,11 +274,6 @@ abstract class AbstractFirDeserializedSymbolProvider(
return classCache.getValue(classId, parentContext)
}
private fun mayHaveTopLevelClass(topLevelClassId: ClassId): Boolean {
val knownClassNames = knownTopLevelClassifiersInPackage(topLevelClassId.packageFqName) ?: return true
return topLevelClassId.shortClassName.asString() in knownClassNames
}
private fun getTypeAlias(classId: ClassId): FirTypeAliasSymbol? {
if (!classId.relativeClassName.isOneSegmentFQN()) return null
@@ -295,8 +298,7 @@ abstract class AbstractFirDeserializedSymbolProvider(
private fun <C : FirCallableSymbol<*>> FirCache<CallableId, List<C>, Nothing?>.getCallables(id: CallableId): List<C> {
// Don't actually query FirCache when we're sure there are no relevant value
// It helps to decrease the size of a cache thus leading to better query time
if (id.packageName.asString() !in packageNamesForNonClassDeclarations) return emptyList()
if (id.callableName !in allNamesByPackage.getValue(id.packageName)) return emptyList()
if (!symbolNamesProvider.mayHaveTopLevelCallable(id.packageName, id.callableName)) return emptyList()
return getValue(id)
}
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.deserialization.FirBuiltinAnnotationDeserializer
import org.jetbrains.kotlin.fir.deserialization.FirConstDeserializer
import org.jetbrains.kotlin.fir.deserialization.FirDeserializationContext
import org.jetbrains.kotlin.fir.deserialization.deserializeClassToSymbol
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals
import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
@@ -71,18 +72,26 @@ open class FirBuiltinSymbolProvider(
} ?: syntheticFunctionInterfaceProvider.getClassLikeSymbolByClassId(classId)
}
override fun computePackageSetWithTopLevelCallables(): Set<String> =
allPackageFragments.keys.mapTo(mutableSetOf()) { it.asString() }
override val symbolNamesProvider: FirSymbolNamesProvider = object : FirSymbolNamesProvider() {
override fun getPackageNamesWithTopLevelCallables(): Set<String> =
allPackageFragments.keys.mapTo(mutableSetOf()) { it.asString() }
override fun knownTopLevelClassifiersInPackage(packageFqName: FqName): Set<String> =
allPackageFragments[packageFqName]?.flatMapTo(mutableSetOf()) { fragment ->
fragment.classDataFinder.allClassIds.map { it.shortClassName.asString() }
}.orEmpty()
override fun getTopLevelClassifierNamesInPackage(packageFqName: FqName): Set<String> =
allPackageFragments[packageFqName]?.flatMapTo(mutableSetOf()) { fragment ->
fragment.classDataFinder.allClassIds.map { it.shortClassName.asString() }
}.orEmpty()
override fun computeCallableNamesInPackage(packageFqName: FqName): Set<Name> =
allPackageFragments[packageFqName]?.flatMapTo(mutableSetOf()) {
it.getTopLevelCallableNames()
}.orEmpty()
override fun getTopLevelCallableNamesInPackage(packageFqName: FqName): Set<Name> =
allPackageFragments[packageFqName]?.flatMapTo(mutableSetOf()) {
it.getTopLevelCallableNames()
}.orEmpty()
// This symbol provider delegates to `FirBuiltinSyntheticFunctionInterfaceProvider`, so synthetic function types can be provided.
override val mayHaveSyntheticFunctionTypes: Boolean get() = true
override fun mayHaveSyntheticFunctionType(classId: ClassId): Boolean =
syntheticFunctionInterfaceProvider.symbolNamesProvider.mayHaveSyntheticFunctionType(classId)
}
@FirSymbolProviderInternals
override fun getTopLevelCallableSymbolsTo(destination: MutableList<FirCallableSymbol<*>>, packageFqName: FqName, name: Name) {