Skip to content

Commit 7bc5911

Browse files
dimonchik0036Space Team
authored and
Space Team
committed
[LL FIR] provide fallback symbol provider for callables from multifile class part
Regular `functionCache`/`propertyCache` doesn't work because for such cases `declarationProvider` doesn't return the requested declaration as it is not indexed. This change is already covered by tests from b7e9906, but the fix for `KotlinStandaloneDeclarationProviderFactory` is required to simulate how real indices work as they work on top of stubs for compiled classes ^KT-68484 Fixed
1 parent e33f4f8 commit 7bc5911

File tree

5 files changed

+244
-71
lines changed

5 files changed

+244
-71
lines changed

analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/declarations/KotlinStandaloneDeclarationProvider.kt

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
33
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
44
*/
55

@@ -26,20 +26,16 @@ import com.intellij.util.io.StringRef
2626
import com.intellij.util.io.UnsyncByteArrayOutputStream
2727
import org.jetbrains.kotlin.analysis.api.KaImplementationDetail
2828
import org.jetbrains.kotlin.analysis.api.impl.base.symbols.pointers.SmartPointerIncompatiblePsiFile
29+
import org.jetbrains.kotlin.analysis.api.platform.declarations.*
30+
import org.jetbrains.kotlin.analysis.api.platform.mergeSpecificProviders
31+
import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
2932
import org.jetbrains.kotlin.analysis.decompiler.konan.K2KotlinNativeMetadataDecompiler
3033
import org.jetbrains.kotlin.analysis.decompiler.konan.KlibMetaFileType
3134
import org.jetbrains.kotlin.analysis.decompiler.psi.BuiltinsVirtualFileProvider
3235
import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinBuiltInDecompiler
3336
import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinBuiltInFileType
3437
import org.jetbrains.kotlin.analysis.decompiler.stub.file.ClsKotlinBinaryClassCache
3538
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinClsStubBuilder
36-
import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
37-
import org.jetbrains.kotlin.analysis.api.platform.declarations.KotlinDeclarationProvider
38-
import org.jetbrains.kotlin.analysis.api.platform.declarations.KotlinDeclarationProviderFactory
39-
import org.jetbrains.kotlin.analysis.api.platform.declarations.KotlinDeclarationProviderMerger
40-
import org.jetbrains.kotlin.analysis.api.platform.declarations.createDeclarationProvider
41-
import org.jetbrains.kotlin.analysis.api.platform.declarations.KotlinCompositeDeclarationProvider
42-
import org.jetbrains.kotlin.analysis.api.platform.mergeSpecificProviders
4339
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
4440
import org.jetbrains.kotlin.idea.KotlinLanguage
4541
import org.jetbrains.kotlin.name.*
@@ -394,10 +390,29 @@ class KotlinStandaloneDeclarationProviderFactory(
394390
binaryRoots
395391
.map { collectStubsFromBinaryRoot(it, binaryClassCache) }
396392
.forEach { processCollectedBinaryStubs(it, isSharedStubs = false) }
393+
394+
for (file in sourceKtFiles) {
395+
if (!file.isCompiled) continue
396+
397+
// Special handling for builtins is required as normally they are indexed by [loadBuiltIns],
398+
// so [buildStubByVirtualFile] skips them explicitly, but actually stubs for them exist
399+
val stub = buildStubByVirtualFile(file.virtualFile, binaryClassCache, preserveBuiltins = skipBuiltins)
400+
401+
// Only files for which stub exists should be indexed, so some synthetic classes should be ignored.
402+
// This behavior is closer to real indices.
403+
if (stub != null) {
404+
stub.psi = file
405+
processMultifileClassStub(stub)
406+
407+
file.accept(recorder)
408+
}
409+
}
397410
}
398411

399412
sourceKtFiles.forEach { file ->
400-
file.accept(recorder)
413+
if (!shouldBuildStubsForBinaryLibraries || !file.isCompiled) {
414+
file.accept(recorder)
415+
}
401416
}
402417
}
403418

@@ -424,18 +439,20 @@ class KotlinStandaloneDeclarationProviderFactory(
424439
}
425440
}
426441

427-
private fun processStub(ktFileStub: KotlinFileStubImpl) {
442+
private fun processMultifileClassStub(ktFileStub: KotlinFileStubImpl) {
428443
val ktFile: KtFile = ktFileStub.psi
429-
addToFacadeFileMap(ktFile)
430-
431-
val partNames = ktFileStub.facadePartSimpleNames
432-
if (partNames != null) {
433-
val packageFqName = ktFileStub.getPackageFqName()
434-
for (partName in partNames) {
435-
val multiFileClassPartFqName: FqName = packageFqName.child(Name.identifier(partName))
436-
index.multiFileClassPartMap.computeIfAbsent(multiFileClassPartFqName) { mutableSetOf() }.add(ktFile)
437-
}
444+
445+
val partNames = ktFileStub.facadePartSimpleNames ?: return
446+
val packageFqName = ktFileStub.getPackageFqName()
447+
for (partName in partNames) {
448+
val multiFileClassPartFqName: FqName = packageFqName.child(Name.identifier(partName))
449+
index.multiFileClassPartMap.computeIfAbsent(multiFileClassPartFqName) { mutableSetOf() }.add(ktFile)
438450
}
451+
}
452+
453+
private fun processStub(ktFileStub: KotlinFileStubImpl) {
454+
addToFacadeFileMap(ktFileStub.psi)
455+
processMultifileClassStub(ktFileStub)
439456

440457
// top-level functions and properties, built-in classes
441458
ktFileStub.childrenStubs.forEach(::indexStub)
@@ -449,23 +466,27 @@ class KotlinStandaloneDeclarationProviderFactory(
449466
VfsUtilCore.visitChildrenRecursively(binaryRoot, object : VirtualFileVisitor<Void>() {
450467
override fun visitFile(file: VirtualFile): Boolean {
451468
if (!file.isDirectory) {
452-
val stub = buildStubByVirtualFile(file, binaryClassCache) ?: return true
469+
val stub = buildStubByVirtualFile(file, binaryClassCache, preserveBuiltins = false) ?: return true
453470
put(file, stub)
454471
}
455472
return true
456473
}
457474
})
458475
}
459476

460-
private fun buildStubByVirtualFile(file: VirtualFile, binaryClassCache: ClsKotlinBinaryClassCache): KotlinFileStubImpl? {
477+
private fun buildStubByVirtualFile(
478+
file: VirtualFile,
479+
binaryClassCache: ClsKotlinBinaryClassCache,
480+
preserveBuiltins: Boolean,
481+
): KotlinFileStubImpl? {
461482
val fileContent = FileContentImpl.createByFile(file)
462483
val fileType = fileContent.fileType
463484
val stubBuilder = when (fileType) {
464485
JavaClassFileType.INSTANCE if binaryClassCache.isKotlinJvmCompiledFile(file, fileContent.content) -> {
465486
KotlinClsStubBuilder()
466487
}
467488

468-
KotlinBuiltInFileType if file.extension != BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION -> {
489+
KotlinBuiltInFileType if preserveBuiltins || file.extension != BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION -> {
469490
builtInDecompiler.stubBuilder
470491
}
471492

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.analysis.low.level.api.fir.symbolProviders
7+
8+
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.JvmAndBuiltinsDeserializedContainerSourceProvider
9+
import org.jetbrains.kotlin.fir.FirSession
10+
import org.jetbrains.kotlin.fir.caches.firCachesFactory
11+
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
12+
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
13+
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
14+
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
15+
import org.jetbrains.kotlin.name.CallableId
16+
import org.jetbrains.kotlin.name.FqName
17+
import org.jetbrains.kotlin.name.JvmStandardClassIds
18+
import org.jetbrains.kotlin.name.Name
19+
import org.jetbrains.kotlin.psi.KtCallableDeclaration
20+
import org.jetbrains.kotlin.psi.KtNamedFunction
21+
import org.jetbrains.kotlin.psi.KtProperty
22+
23+
/**
24+
* Issue: [KT-68484](https://youtrack.jetbrains.com/issue/KT-68484).
25+
*
26+
* This class provides fallback symbols for top-level callables from synthetic multifile class part
27+
* ([MULTIFILE_CLASS_PART][org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.MULTIFILE_CLASS_PART]).
28+
*
29+
* @see org.jetbrains.kotlin.analysis.decompiler.stub.file.ClsClassFinder.isKotlinInternalCompiledFile
30+
* @see addCallableIfNeeded
31+
**/
32+
internal class LLKotlinStubBasedLibraryMultifileClassPartCallableSymbolProvider(val session: FirSession) {
33+
private val fallbackFunctionCache = session.firCachesFactory.createCache(::loadFunction)
34+
private val fallbackPropertyCache = session.firCachesFactory.createCache(::loadProperty)
35+
36+
/**
37+
* This fallback is required for multifile part classes which are not present in indices
38+
* but might be requested as in some cases we still build stubs for them.
39+
*/
40+
fun addCallableIfNeeded(
41+
callableCandidates: MutableList<FirCallableSymbol<*>>,
42+
packageFqName: FqName,
43+
shortName: Name,
44+
callableDeclaration: KtCallableDeclaration,
45+
) {
46+
val fileName = callableDeclaration.containingKtFile.virtualFile?.nameWithoutExtension ?: return
47+
if (!fileName.endsWith("Kt") || JvmStandardClassIds.MULTIFILE_PART_NAME_DELIMITER !in fileName) {
48+
return
49+
}
50+
51+
val callableId = CallableId(packageFqName, shortName)
52+
val symbol = when (callableDeclaration) {
53+
is KtNamedFunction -> fallbackFunctionCache.getValue(callableDeclaration, callableId)
54+
is KtProperty -> fallbackPropertyCache.getValue(callableDeclaration, callableId)
55+
else -> null
56+
}
57+
58+
symbol?.let(callableCandidates::add)
59+
}
60+
61+
private fun loadFunction(function: KtNamedFunction, callableId: CallableId): FirNamedFunctionSymbol? {
62+
return LLKotlinStubBasedLibrarySymbolProvider.loadFunction(
63+
function = function,
64+
callableId = callableId,
65+
functionOrigin = FirDeclarationOrigin.Library,
66+
deserializedContainerSourceProvider = JvmAndBuiltinsDeserializedContainerSourceProvider,
67+
session = session,
68+
)
69+
}
70+
71+
private fun loadProperty(property: KtProperty, callableId: CallableId): FirPropertySymbol? {
72+
return LLKotlinStubBasedLibrarySymbolProvider.loadProperty(
73+
property = property,
74+
callableId = callableId,
75+
propertyOrigin = FirDeclarationOrigin.Library,
76+
deserializedContainerSourceProvider = JvmAndBuiltinsDeserializedContainerSourceProvider,
77+
session = session,
78+
)
79+
}
80+
}

analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/symbolProviders/LLKotlinStubBasedLibrarySymbolProvider.kt

Lines changed: 99 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,15 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.caches.getNotNullValueFor
1212
import org.jetbrains.kotlin.analysis.low.level.api.fir.projectStructure.LLFirModuleData
1313
import org.jetbrains.kotlin.analysis.low.level.api.fir.projectStructure.llFirModuleData
1414
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
15-
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.DeserializedContainerSourceProvider
16-
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.StubBasedAnnotationDeserializer
17-
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.StubBasedFirDeserializationContext
18-
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.StubBasedFirTypeDeserializer
19-
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.deserializeClassToSymbol
20-
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.loadStubByElement
15+
import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.*
16+
import org.jetbrains.kotlin.fir.FirSession
2117
import org.jetbrains.kotlin.fir.caches.FirCache
2218
import org.jetbrains.kotlin.fir.caches.firCachesFactory
2319
import org.jetbrains.kotlin.fir.caches.getValue
2420
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
2521
import org.jetbrains.kotlin.fir.isNewPlaceForBodyGeneration
2622
import org.jetbrains.kotlin.fir.java.deserialization.KotlinBuiltins
23+
import org.jetbrains.kotlin.fir.moduleData
2724
import org.jetbrains.kotlin.fir.realPsi
2825
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
2926
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals
@@ -36,7 +33,8 @@ import org.jetbrains.kotlin.psi.*
3633
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
3734
import org.jetbrains.kotlin.psi.stubs.KotlinClassOrObjectStub
3835
import org.jetbrains.kotlin.psi.stubs.KotlinClassStub
39-
import org.jetbrains.kotlin.psi.stubs.impl.*
36+
import org.jetbrains.kotlin.psi.stubs.impl.KotlinFunctionStubImpl
37+
import org.jetbrains.kotlin.psi.stubs.impl.KotlinPropertyStubImpl
4038
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
4139
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
4240

@@ -183,47 +181,31 @@ internal open class LLKotlinStubBasedLibrarySymbolProvider(
183181

184182
return ArrayList<FirNamedFunctionSymbol>(topLevelFunctions.size).apply {
185183
for (function in topLevelFunctions) {
186-
val functionStub = function.stub as? KotlinFunctionStubImpl ?: loadStubByElement(function)
187-
val functionFile = function.containingKtFile
188-
val functionOrigin = getDeclarationOriginFor(functionFile)
189-
val containerSource =
190-
deserializedContainerSourceProvider.getFacadeContainerSource(functionFile, functionStub?.origin, functionOrigin)
191-
192-
if (!functionOrigin.isBuiltIns &&
193-
containerSource is FacadeClassSource &&
194-
containerSource.className.internalName in KotlinBuiltins
195-
) {
196-
continue
197-
}
198-
199-
val symbol = FirNamedFunctionSymbol(callableId)
200-
val rootContext = StubBasedFirDeserializationContext
201-
.createRootContext(session, moduleData, callableId, function, symbol, functionOrigin, containerSource)
202-
203-
add(rootContext.memberDeserializer.loadFunction(function, null, session, symbol).symbol)
184+
val symbol = loadFunction(
185+
function = function,
186+
callableId = callableId,
187+
functionOrigin = getDeclarationOriginFor(function.containingKtFile),
188+
deserializedContainerSourceProvider = deserializedContainerSourceProvider,
189+
session = session,
190+
) ?: continue
191+
add(symbol)
204192
}
205193
}
206194
}
207195

208196
private fun loadPropertiesByCallableId(callableId: CallableId, foundProperties: Collection<KtProperty>?): List<FirPropertySymbol> {
209197
val topLevelProperties = foundProperties ?: declarationProvider.getTopLevelProperties(callableId)
210198

211-
return buildList {
199+
return ArrayList<FirPropertySymbol>(topLevelProperties.size).apply {
212200
for (property in topLevelProperties) {
213-
val propertyStub = property.stub as? KotlinPropertyStubImpl ?: loadStubByElement(property)
214-
val propertyFile = property.containingKtFile
215-
val propertyOrigin = getDeclarationOriginFor(propertyFile)
216-
val containerSource = deserializedContainerSourceProvider.getFacadeContainerSource(
217-
propertyFile,
218-
propertyStub?.origin,
219-
propertyOrigin,
220-
)
221-
222-
val symbol = FirPropertySymbol(callableId)
223-
val rootContext = StubBasedFirDeserializationContext
224-
.createRootContext(session, moduleData, callableId, property, symbol, propertyOrigin, containerSource)
225-
226-
add(rootContext.memberDeserializer.loadProperty(property, null, symbol).symbol)
201+
val symbol = loadProperty(
202+
property = property,
203+
callableId = callableId,
204+
propertyOrigin = getDeclarationOriginFor(property.containingKtFile),
205+
deserializedContainerSourceProvider = deserializedContainerSourceProvider,
206+
session = session,
207+
) ?: continue
208+
add(symbol)
227209
}
228210
}
229211
}
@@ -376,6 +358,83 @@ internal open class LLKotlinStubBasedLibrarySymbolProvider(
376358
is KtProperty -> propertyCache.getValue(callableId)
377359
else -> null
378360
}
361+
379362
return callableSymbols?.singleOrNull { it.fir.realPsi == callableDeclaration }
380363
}
364+
365+
companion object {
366+
fun loadProperty(
367+
property: KtProperty,
368+
callableId: CallableId,
369+
propertyOrigin: FirDeclarationOrigin,
370+
deserializedContainerSourceProvider: DeserializedContainerSourceProvider,
371+
session: FirSession,
372+
): FirPropertySymbol? {
373+
val propertyStub = property.stub as? KotlinPropertyStubImpl ?: loadStubByElement(property)
374+
val propertyFile = property.containingKtFile
375+
val containerSource = deserializedContainerSourceProvider.getFacadeContainerSource(
376+
file = propertyFile,
377+
stubOrigin = propertyStub?.origin,
378+
declarationOrigin = propertyOrigin,
379+
)
380+
381+
val symbol = FirPropertySymbol(callableId)
382+
val rootContext = StubBasedFirDeserializationContext.createRootContext(
383+
session = session,
384+
moduleData = session.moduleData,
385+
callableId = callableId,
386+
parameterListOwner = property,
387+
symbol = symbol,
388+
initialOrigin = propertyOrigin,
389+
containerSource = containerSource,
390+
)
391+
392+
return rootContext.memberDeserializer.loadProperty(
393+
property = property,
394+
classSymbol = null,
395+
existingSymbol = symbol,
396+
).symbol
397+
}
398+
399+
fun loadFunction(
400+
function: KtNamedFunction,
401+
callableId: CallableId,
402+
functionOrigin: FirDeclarationOrigin,
403+
deserializedContainerSourceProvider: DeserializedContainerSourceProvider,
404+
session: FirSession,
405+
): FirNamedFunctionSymbol? {
406+
val functionStub = function.stub as? KotlinFunctionStubImpl ?: loadStubByElement(function)
407+
val functionFile = function.containingKtFile
408+
val containerSource = deserializedContainerSourceProvider.getFacadeContainerSource(
409+
file = functionFile,
410+
stubOrigin = functionStub?.origin,
411+
declarationOrigin = functionOrigin,
412+
)
413+
414+
if (!functionOrigin.isBuiltIns &&
415+
containerSource is FacadeClassSource &&
416+
containerSource.className.internalName in KotlinBuiltins
417+
) {
418+
return null
419+
}
420+
421+
val symbol = FirNamedFunctionSymbol(callableId)
422+
val rootContext = StubBasedFirDeserializationContext.createRootContext(
423+
session = session,
424+
moduleData = session.moduleData,
425+
callableId = callableId,
426+
parameterListOwner = function,
427+
symbol = symbol,
428+
initialOrigin = functionOrigin,
429+
containerSource = containerSource,
430+
)
431+
432+
return rootContext.memberDeserializer.loadFunction(
433+
function = function,
434+
classSymbol = null,
435+
session = session,
436+
existingSymbol = symbol,
437+
).symbol
438+
}
439+
}
381440
}

0 commit comments

Comments
 (0)