[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:
committed by
Space Team
parent
b82a589e56
commit
accc9b0eb3
+36
-34
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+19
-10
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user