diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProvider.kt index 36ac327f139..005e1e1d816 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProvider.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProvider.kt @@ -109,60 +109,53 @@ internal class LLFirProvider( @NoMutableState private inner class SymbolProvider : FirSymbolProvider(session) { override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? { - val classifierNamesInPackage = providerHelper.getTopLevelClassifierNamesInPackage(classId.packageFqName) - if (!mayHaveTopLevelClassifier(classId, classifierNamesInPackage.names, classifierNamesInPackage.mayHaveFunctionClass)) { - return null - } - + if (!providerHelper.symbolNameCache.mayHaveTopLevelClassifier(classId)) return null return getFirClassifierByFqName(classId)?.symbol } override fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List> { - if (!mayHaveTopLevelCallable(packageFqName, name)) return emptyList() + if (!providerHelper.symbolNameCache.mayHaveTopLevelCallable(packageFqName, name)) return emptyList() return providerHelper.getTopLevelCallableSymbols(packageFqName, name) } @FirSymbolProviderInternals override fun getTopLevelCallableSymbolsTo(destination: MutableList>, packageFqName: FqName, name: Name) { - if (!mayHaveTopLevelCallable(packageFqName, name)) return + if (!providerHelper.symbolNameCache.mayHaveTopLevelCallable(packageFqName, name)) return destination += providerHelper.getTopLevelCallableSymbols(packageFqName, name) } override fun getTopLevelFunctionSymbols(packageFqName: FqName, name: Name): List { - if (!mayHaveTopLevelCallable(packageFqName, name)) return emptyList() + if (!providerHelper.symbolNameCache.mayHaveTopLevelCallable(packageFqName, name)) return emptyList() return providerHelper.getTopLevelFunctionSymbols(packageFqName, name) } @FirSymbolProviderInternals override fun getTopLevelFunctionSymbolsTo(destination: MutableList, packageFqName: FqName, name: Name) { - if (!mayHaveTopLevelCallable(packageFqName, name)) return + if (!providerHelper.symbolNameCache.mayHaveTopLevelCallable(packageFqName, name)) return destination += providerHelper.getTopLevelFunctionSymbols(packageFqName, name) } override fun getTopLevelPropertySymbols(packageFqName: FqName, name: Name): List { - if (!mayHaveTopLevelCallable(packageFqName, name)) return emptyList() + if (!providerHelper.symbolNameCache.mayHaveTopLevelCallable(packageFqName, name)) return emptyList() return providerHelper.getTopLevelPropertySymbols(packageFqName, name) } @FirSymbolProviderInternals override fun getTopLevelPropertySymbolsTo(destination: MutableList, packageFqName: FqName, name: Name) { - if (!mayHaveTopLevelCallable(packageFqName, name)) return + if (!providerHelper.symbolNameCache.mayHaveTopLevelCallable(packageFqName, name)) return destination += providerHelper.getTopLevelPropertySymbols(packageFqName, name) } - private fun mayHaveTopLevelCallable(packageFqName: FqName, name: Name): Boolean = - name in computeCallableNamesInPackage(packageFqName) - override fun getPackage(fqName: FqName): FqName? = providerHelper.getPackage(fqName) // Computing the set of such package names is expensive and would require a new index. For now, it is not worth the marginal gains. override fun computePackageSetWithTopLevelCallables(): Set? = null - override fun knownTopLevelClassifiersInPackage(packageFqName: FqName): Set = - providerHelper.getTopLevelClassifierNamesInPackage(packageFqName).names + override fun knownTopLevelClassifiersInPackage(packageFqName: FqName): Set? = + providerHelper.symbolNameCache.getTopLevelClassifierNamesInPackage(packageFqName)?.names - override fun computeCallableNamesInPackage(packageFqName: FqName): Set = - providerHelper.getTopLevelCallableNamesInPackage(packageFqName) + override fun computeCallableNamesInPackage(packageFqName: FqName): Set? = + providerHelper.symbolNameCache.getTopLevelCallableNamesInPackage(packageFqName) } } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt index 2d43d72ef9f..f5d753ccf9d 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt @@ -7,6 +7,7 @@ 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.low.level.api.fir.util.FirElementFinder +import org.jetbrains.kotlin.analysis.low.level.api.fir.util.LLFirSymbolProviderNameCache import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider import org.jetbrains.kotlin.analysis.providers.KotlinPackageProvider import org.jetbrains.kotlin.builtins.StandardNames @@ -22,7 +23,6 @@ import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol -import org.jetbrains.kotlin.fir.types.functionTypeService import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -49,26 +49,6 @@ internal class LLFirProviderHelper( ?: error("Classifier $classId was found in file ${ktClass.containingKtFile.virtualFilePath} but was not found in FirFile") } - internal class ClassifierNamesInPackage( - val names: Set, - - /** - * Some packages (such as `kotlin` or `kotlin.coroutines`) may contain function classes generated by the compiler. To avoid a hash - * map access when checking `mayHaveTopLevelClassifier`, [mayHaveFunctionClass] is precomputed for each package name. Also see - * [org.jetbrains.kotlin.builtins.functions.FunctionTypeKind]. - */ - val mayHaveFunctionClass: Boolean, - ) - - private val topLevelClassifierNamesByPackage = - firSession.firCachesFactory.createCache { packageFqName -> - val names = declarationProvider - .getTopLevelKotlinClassLikeDeclarationNamesInPackage(packageFqName) - .mapTo(mutableSetOf()) { it.asString() } - val mayHaveFunctionClass = firSession.functionTypeService.hasKindWithSpecificPackage(packageFqName) - ClassifierNamesInPackage(names, mayHaveFunctionClass) - } - private val callablesByCallableId = firSession.firCachesFactory.createCache>> { callableId -> val files = declarationProvider.getTopLevelCallableFiles(callableId).ifEmpty { return@createCache emptyList() } buildList { @@ -79,10 +59,15 @@ internal class LLFirProviderHelper( } } - private val topLevelCallableNamesByPackage = - firSession.firCachesFactory.createCache> { packageFqName -> + val symbolNameCache = object : LLFirSymbolProviderNameCache(firSession) { + override fun computeClassifierNames(packageFqName: FqName): Set? = + declarationProvider + .getTopLevelKotlinClassLikeDeclarationNamesInPackage(packageFqName) + .mapTo(mutableSetOf()) { it.asString() } + + override fun computeCallableNames(packageFqName: FqName): Set? = declarationProvider.getTopLevelCallableNamesInPackage(packageFqName) - } + } fun getFirClassifierByFqNameAndDeclaration( classId: ClassId, @@ -93,9 +78,6 @@ internal class LLFirProviderHelper( return classifierByClassId.getValue(classId, classLikeDeclaration) } - fun getTopLevelClassifierNamesInPackage(packageFqName: FqName): ClassifierNamesInPackage = - topLevelClassifierNamesByPackage.getValue(packageFqName) - fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List> { if (!allowKotlinPackage && packageFqName.isKotlinPackage()) return emptyList() val callableId = CallableId(packageFqName, name) @@ -110,9 +92,6 @@ internal class LLFirProviderHelper( return getTopLevelCallableSymbols(packageFqName, name).filterIsInstance() } - fun getTopLevelCallableNamesInPackage(packageFqName: FqName): Set = - topLevelCallableNamesByPackage.getValue(packageFqName) - private fun FirFile.collectCallableDeclarationsTo(list: MutableList>, name: Name) { declarations.mapNotNullTo(list) { declaration -> if (declaration is FirCallableDeclaration && declaration.symbol.callableId.callableName == name) { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/LLFirSymbolProviderNameCache.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/LLFirSymbolProviderNameCache.kt new file mode 100644 index 00000000000..6804b6448f9 --- /dev/null +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/LLFirSymbolProviderNameCache.kt @@ -0,0 +1,62 @@ +/* + * 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.low.level.api.fir.util + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.caches.createCache +import org.jetbrains.kotlin.fir.caches.firCachesFactory +import org.jetbrains.kotlin.fir.caches.getValue +import org.jetbrains.kotlin.fir.resolve.providers.mayHaveTopLevelClassifier +import org.jetbrains.kotlin.fir.types.functionTypeService +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +/** + * Caches the names of classifiers and callables contained in a package. [LLFirSymbolProviderNameCache] is used by symbol providers to abort + * symbol finding early if the symbol name isn't contained in the symbol provider's domain. + */ +internal abstract class LLFirSymbolProviderNameCache(private val firSession: FirSession) { + internal data class ClassifierNamesInPackage( + val names: Set, + + /** + * Some packages (such as `kotlin` or `kotlin.coroutines`) may contain function classes generated by the compiler. To avoid a hash + * map access when checking `mayHaveTopLevelClassifier`, [mayHaveFunctionClass] is precomputed for each package name. Also see + * [org.jetbrains.kotlin.builtins.functions.FunctionTypeKind]. + */ + val mayHaveFunctionClass: Boolean, + ) + + abstract fun computeClassifierNames(packageFqName: FqName): Set? + abstract fun computeCallableNames(packageFqName: FqName): Set? + + private val topLevelClassifierNamesByPackage = + firSession.firCachesFactory.createCache { packageFqName -> + val names = computeClassifierNames(packageFqName) ?: return@createCache null + val mayHaveFunctionClass = firSession.functionTypeService.hasKindWithSpecificPackage(packageFqName) + ClassifierNamesInPackage(names, mayHaveFunctionClass) + } + + private val topLevelCallableNamesByPackage = + firSession.firCachesFactory.createCache?>(::computeCallableNames) + + fun getTopLevelClassifierNamesInPackage(packageFqName: FqName): ClassifierNamesInPackage? = + topLevelClassifierNamesByPackage.getValue(packageFqName) + + fun getTopLevelCallableNamesInPackage(packageFqName: FqName): Set? = + topLevelCallableNamesByPackage.getValue(packageFqName) + + fun mayHaveTopLevelClassifier(classId: ClassId): Boolean { + val (names, mayHaveFunctionClass) = getTopLevelClassifierNamesInPackage(classId.packageFqName) ?: return true + return names.mayHaveTopLevelClassifier(classId, firSession, mayHaveFunctionClass) + } + + fun mayHaveTopLevelCallable(packageFqName: FqName, name: Name): Boolean { + val names = getTopLevelCallableNamesInPackage(packageFqName) ?: return true + return name in names + } +} diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/FirSymbolProvider.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/FirSymbolProvider.kt index af6694ba6a0..641f8303dd9 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/FirSymbolProvider.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/FirSymbolProvider.kt @@ -79,31 +79,6 @@ abstract class FirSymbolProvider(val session: FirSession) : FirSessionComponent * @returns top-level callable names that belong to `packageFqName` or null if it's complicated to compute the set */ abstract fun computeCallableNamesInPackage(packageFqName: FqName): Set? - - /** - * Whether [classId] may be contained in the [knownClassifierNames]. - * - * If it's certain that [classId] cannot be a function class (for example when [classId] is known to come from a package without - * function classes), [mayBeFunctionClass] can be set to `false`. This avoids a hash map access in - * [org.jetbrains.kotlin.builtins.functions.FunctionTypeKindExtractor.getFunctionalClassKindWithArity]. - */ - protected fun mayHaveTopLevelClassifier( - classId: ClassId, - knownClassifierNames: Set, - mayBeFunctionClass: Boolean = true, - ): Boolean { - if (mayBeFunctionClass && isNameForFunctionClass(classId)) return true - - val outerClassId = classId.outerClassId - if (outerClassId == null && classId.shortClassName.asString() !in knownClassifierNames) return false - if (outerClassId != null && classId.outermostClassId.shortClassName.asString() !in knownClassifierNames) return false - - return true - } - - private fun isNameForFunctionClass(classId: ClassId): Boolean { - return session.functionTypeService.getKindByClassNamePrefix(classId.packageFqName, classId.shortClassName.asString()) != null - } } /** @@ -142,6 +117,31 @@ fun FirSymbolProvider.getRegularClassSymbolByClassId(classId: ClassId): FirRegul return getClassLikeSymbolByClassId(classId) as? FirRegularClassSymbol } +/** + * Whether [classId] may be contained in the set of known classifier names. + * + * If it's certain that [classId] cannot be a function class (for example when [classId] is known to come from a package without + * function classes), [mayBeFunctionClass] can be set to `false`. This avoids a hash map access in + * [org.jetbrains.kotlin.builtins.functions.FunctionTypeKindExtractor.getFunctionalClassKindWithArity]. + */ +fun Set.mayHaveTopLevelClassifier( + classId: ClassId, + session: FirSession, + mayBeFunctionClass: Boolean = true, +): Boolean { + if (mayBeFunctionClass && isNameForFunctionClass(classId, session)) return true + + val outerClassId = classId.outerClassId + if (outerClassId == null && classId.shortClassName.asString() !in this) return false + if (outerClassId != null && classId.outermostClassId.shortClassName.asString() !in this) return false + + return true +} + +private fun isNameForFunctionClass(classId: ClassId, session: FirSession): Boolean { + return session.functionTypeService.getKindByClassNamePrefix(classId.packageFqName, classId.shortClassName.asString()) != null +} + fun ClassId.toSymbol(session: FirSession): FirClassifierSymbol<*>? { return session.symbolProvider.getClassLikeSymbolByClassId(this) } diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirCachingCompositeSymbolProvider.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirCachingCompositeSymbolProvider.kt index 2bee23af8d2..2e5825ece25 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirCachingCompositeSymbolProvider.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirCachingCompositeSymbolProvider.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.caches.getValue import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals import org.jetbrains.kotlin.fir.resolve.providers.flatMapToNullableSet +import org.jetbrains.kotlin.fir.resolve.providers.mayHaveTopLevelClassifier import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol @@ -106,7 +107,7 @@ class FirCachingCompositeSymbolProvider( override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? { val knownClassifierNames = knownTopLevelClassifierNamesInPackage.getValue(classId.packageFqName) - if (knownClassifierNames != null && !mayHaveTopLevelClassifier(classId, knownClassifierNames)) return null + if (knownClassifierNames != null && !knownClassifierNames.mayHaveTopLevelClassifier(classId, session)) return null return classLikeCache.getValue(classId) }