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 fb07be3eacb..36ac327f139 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 @@ -108,40 +108,61 @@ internal class LLFirProvider( @NoMutableState private inner class SymbolProvider : FirSymbolProvider(session) { - override fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List> = - providerHelper.getTopLevelCallableSymbols(packageFqName, name) + override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? { + val classifierNamesInPackage = providerHelper.getTopLevelClassifierNamesInPackage(classId.packageFqName) + if (!mayHaveTopLevelClassifier(classId, classifierNamesInPackage.names, classifierNamesInPackage.mayHaveFunctionClass)) { + return null + } + + return getFirClassifierByFqName(classId)?.symbol + } + + override fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List> { + if (!mayHaveTopLevelCallable(packageFqName, name)) return emptyList() + return providerHelper.getTopLevelCallableSymbols(packageFqName, name) + } @FirSymbolProviderInternals override fun getTopLevelCallableSymbolsTo(destination: MutableList>, packageFqName: FqName, name: Name) { - destination += getTopLevelCallableSymbols(packageFqName, name) + if (!mayHaveTopLevelCallable(packageFqName, name)) return + destination += providerHelper.getTopLevelCallableSymbols(packageFqName, name) } - override fun getTopLevelFunctionSymbols(packageFqName: FqName, name: Name): List = - providerHelper.getTopLevelFunctionSymbols(packageFqName, name) + override fun getTopLevelFunctionSymbols(packageFqName: FqName, name: Name): List { + if (!mayHaveTopLevelCallable(packageFqName, name)) return emptyList() + return providerHelper.getTopLevelFunctionSymbols(packageFqName, name) + } @FirSymbolProviderInternals override fun getTopLevelFunctionSymbolsTo(destination: MutableList, packageFqName: FqName, name: Name) { - destination += getTopLevelFunctionSymbols(packageFqName, name) + if (!mayHaveTopLevelCallable(packageFqName, name)) return + destination += providerHelper.getTopLevelFunctionSymbols(packageFqName, name) } - override fun getTopLevelPropertySymbols(packageFqName: FqName, name: Name): List = - providerHelper.getTopLevelPropertySymbols(packageFqName, name) + override fun getTopLevelPropertySymbols(packageFqName: FqName, name: Name): List { + if (!mayHaveTopLevelCallable(packageFqName, name)) return emptyList() + return providerHelper.getTopLevelPropertySymbols(packageFqName, name) + } @FirSymbolProviderInternals override fun getTopLevelPropertySymbolsTo(destination: MutableList, packageFqName: FqName, name: Name) { - destination += getTopLevelPropertySymbols(packageFqName, name) + if (!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) - // TODO: Consider having proper implementations for sake of optimizations + // 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? = null - override fun computeCallableNamesInPackage(packageFqName: FqName): Set? = null - override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? { - return getFirClassifierByFqName(classId)?.symbol - } + override fun knownTopLevelClassifiersInPackage(packageFqName: FqName): Set = + providerHelper.getTopLevelClassifierNamesInPackage(packageFqName).names + + override fun computeCallableNamesInPackage(packageFqName: FqName): Set = + providerHelper.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 beec21ef792..2d43d72ef9f 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 @@ -22,6 +22,7 @@ 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 @@ -48,6 +49,25 @@ 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() } @@ -59,6 +79,11 @@ internal class LLFirProviderHelper( } } + private val topLevelCallableNamesByPackage = + firSession.firCachesFactory.createCache> { packageFqName -> + declarationProvider.getTopLevelCallableNamesInPackage(packageFqName) + } + fun getFirClassifierByFqNameAndDeclaration( classId: ClassId, classLikeDeclaration: KtClassLikeDeclaration?, @@ -68,6 +93,9 @@ 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) @@ -82,6 +110,9 @@ 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) {