From d98924723e769d81ce90f3ae8b7704e4538fcc82 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 21 May 2026 16:23:26 +0530 Subject: [PATCH 1/6] fix: avoid sharing visited set in fast-path and full-scan Signed-off-by: Akash Yadav --- .../kotlin/compiler/services/ProjectStructureProvider.kt | 9 ++------- .../lsp/kotlin/completion/KotlinCompletions.kt | 3 +-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt index e660d09078..2092a26035 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt @@ -85,8 +85,6 @@ internal class ProjectStructureProvider : KtLspService, KotlinProjectStructurePr // Fast path: in-memory file registered by onFileContentChanged. inMemoryVfToModule[virtualFile]?.let { return it } - val visited = mutableSetOf() - val backingFilePath = (element.containingFile as? KtFile)?.let { it.backingFilePath ?: it.originalKtFile?.backingFilePath } @@ -99,15 +97,12 @@ internal class ProjectStructureProvider : KtLspService, KotlinProjectStructurePr // This covers the common case (element is in the same module or one of its direct // library dependencies) without scanning every top-level module. if (useSiteModule != null) { - searchVirtualFileInModule(virtualFile, useSiteModule, visited)?.let { return it } + searchVirtualFileInModule(virtualFile, useSiteModule, mutableSetOf())?.let { return it } } // Full scan: search every top-level module and their transitive dependencies. - // The shared `visited` set avoids re-visiting what we already searched above, - // but still reaches modules that are NOT in useSiteModule's dependency tree - // (e.g. a library module that is a sibling of useSiteModule, not a child of it). modules.forEach { module -> - searchVirtualFileInModule(virtualFile, module, visited)?.let { return it } + searchVirtualFileInModule(virtualFile, module, mutableSetOf())?.let { return it } } // Path-based fallback for in-memory LightVirtualFiles created by onFileContentChanged. diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/completion/KotlinCompletions.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/completion/KotlinCompletions.kt index 4de4c88c7d..a558208e94 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/completion/KotlinCompletions.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/completion/KotlinCompletions.kt @@ -301,9 +301,8 @@ private fun KaSession.collectScopeCompletions( val scopeContext = ctx.scopeContext logger.info( - "Complete scope members of {}: [{}] matching '{}'", + "Complete scope members of {}: matching '{}'", ktElement, - ktElement.text, ctx.partial ) From 370cb03975a842cd68f5ca073ea9be68b1621317 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 21 May 2026 16:24:33 +0530 Subject: [PATCH 2/6] fix: guard CompilationEnv.refreshSources with lock to avoid race Signed-off-by: Akash Yadav --- .../lsp/kotlin/compiler/CompilationEnvironment.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt index 22566b59a1..3f0e8750b0 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt @@ -408,12 +408,14 @@ internal class CompilationEnvironment( } fun refreshSources() { - ResolutionScopeProvider.getInstance(project) - .invalidateAll() + project.write { + ResolutionScopeProvider.getInstance(project) + .invalidateAll() - modules.asFlatSequence() - .filterIsInstance() - .forEach { it.invalidateSearchScope() } + modules.asFlatSequence() + .filterIsInstance() + .forEach { it.invalidateSearchScope() } + } ktSymbolIndex.refreshSources() // TODO: Should also update/notify Java file services about possibly changed Java files From f30fc1501ea99e4ef90be67d8c75824f8e8a17ae Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 26 May 2026 01:09:17 +0530 Subject: [PATCH 3/6] fix: capture failure details when SourceFileIndexer fails Signed-off-by: Akash Yadav --- lsp/kotlin/build.gradle.kts | 1 + .../lsp/kotlin/KotlinLanguageServer.kt | 2 + .../kotlin/compiler/CompilationEnvironment.kt | 5 ++- .../kotlin/compiler/index/KtSymbolIndex.kt | 2 + .../compiler/index/SourceFileIndexer.kt | 40 +++++++++++++++---- .../compiler/modules/AbstractKtModule.kt | 8 ++++ 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/lsp/kotlin/build.gradle.kts b/lsp/kotlin/build.gradle.kts index 2fb68ceecd..9b16f87796 100644 --- a/lsp/kotlin/build.gradle.kts +++ b/lsp/kotlin/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { implementation(libs.common.kotlin) implementation(libs.common.kotlin.coroutines.core) implementation(libs.common.kotlin.coroutines.android) + implementation(libs.sentry.android.core) compileOnly(projects.common) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt index 0b829abb76..a10d66a52e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt @@ -55,6 +55,7 @@ import com.itsaky.androidide.tasks.createJobCancelChecker import com.itsaky.androidide.utils.DocumentUtils import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.ifNotEmpty +import io.sentry.Sentry import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -321,6 +322,7 @@ class KotlinLanguageServer : ILanguageServer { @Subscribe @Suppress("unused") fun onBuildCompleted(event: BuildCompletedEvent) { + Sentry.addBreadcrumb("onBuildCompleted: result=${event.result}") compiler?.refreshSources() } diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt index 3f0e8750b0..51f41fe93a 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt @@ -21,6 +21,7 @@ import com.itsaky.androidide.lsp.kotlin.utils.toVirtualFileOrNull import com.itsaky.androidide.projects.FileManager import com.itsaky.androidide.projects.api.Workspace import com.itsaky.androidide.utils.KeyedDebouncingAction +import io.sentry.Sentry import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -118,7 +119,7 @@ import kotlin.time.Duration.Companion.milliseconds @Suppress("UnstableApiUsage") @OptIn(K1Deprecation::class) internal class CompilationEnvironment( - name: String, + val name: String, val kind: CompilationKind, workspace: Workspace, val ktProject: KotlinProjectModel, @@ -408,7 +409,9 @@ internal class CompilationEnvironment( } fun refreshSources() { + Sentry.addBreadcrumb("refreshSources (env=${name}, modules=${modules.size})") project.write { + Sentry.addBreadcrumb("refreshSources(env=${name}): in-progress") ResolutionScopeProvider.getInstance(project) .invalidateAll() diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt index 5449efcd76..7f10cc308e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt @@ -6,6 +6,7 @@ import com.itsaky.androidide.lsp.kotlin.compiler.modules.KtModule import com.itsaky.androidide.lsp.kotlin.compiler.read import com.itsaky.androidide.lsp.kotlin.utils.toVirtualFileOrNull import com.itsaky.androidide.utils.DocumentUtils +import io.sentry.Sentry import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -117,6 +118,7 @@ internal class KtSymbolIndex( } fun refreshSources() { + Sentry.addBreadcrumb("KtSymbolIndex.refreshSources()") indexingJob ?: startIndexing() scanningJob?.cancel() diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt index 3affd2e1fb..46377d8637 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt @@ -6,6 +6,7 @@ import com.itsaky.androidide.lsp.kotlin.compiler.read import com.itsaky.androidide.lsp.kotlin.utils.toNioPathOrNull import com.itsaky.androidide.progress.ICancelChecker import com.itsaky.androidide.projects.FileManager +import io.sentry.Sentry import org.appdevforall.codeonthego.indexing.jvm.JvmClassInfo import org.appdevforall.codeonthego.indexing.jvm.JvmFieldInfo import org.appdevforall.codeonthego.indexing.jvm.JvmFunctionInfo @@ -22,6 +23,7 @@ import org.appdevforall.codeonthego.indexing.jvm.KotlinPropertyInfo import org.appdevforall.codeonthego.indexing.jvm.KtFileMetadata import org.appdevforall.codeonthego.indexing.jvm.KtFileMetadataIndex import org.jetbrains.kotlin.analysis.api.KaExperimentalApi +import org.jetbrains.kotlin.analysis.api.KaImplementationDetail import org.jetbrains.kotlin.analysis.api.KaSession import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol @@ -35,6 +37,7 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtNamedFunction @@ -43,6 +46,8 @@ import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtTreeVisitorVoid import org.jetbrains.kotlin.psi.KtTypeAlias +import org.jetbrains.kotlin.psi.debugText.getDebugText +import org.jetbrains.kotlin.utils.exceptions.KotlinExceptionWithAttachments import java.time.Instant import kotlin.io.path.pathString @@ -102,16 +107,35 @@ internal suspend fun indexSourceFile( fileIndex.upsert(newFile.copy(symbolKeys = symbols.map { it.key })) } +@OptIn(KaImplementationDetail::class) private fun KaSession.analyzeDeclaration(filePath: String, dcl: KtDeclaration): JvmSymbol? { dcl.name ?: return null - return when (dcl) { - is KtNamedFunction -> analyzeFunction(filePath, dcl) - is KtClassOrObject -> analyzeClassOrObject(filePath, dcl) - is KtParameter -> analyzeParameter(filePath, dcl) - is KtProperty -> analyzeProperty(filePath, dcl) - is KtTypeAlias -> analyzeTypeAlias(filePath, dcl) - else -> null - } + return runCatching { + when (dcl) { + is KtNamedFunction -> analyzeFunction(filePath, dcl) + is KtClassOrObject -> analyzeClassOrObject(filePath, dcl) + is KtParameter -> analyzeParameter(filePath, dcl) + is KtProperty -> analyzeProperty(filePath, dcl) + is KtTypeAlias -> analyzeTypeAlias(filePath, dcl) + else -> null + } + }.onFailure { err -> + Sentry.captureException(err) { scope -> + scope.apply { + setExtra("fpth", filePath) + setExtra("dcl", dcl.name) + setExtra("dcl.dbg", dcl.getDebugText()) + setExtra("par.dbg", (dcl.parent as? KtElement)?.getDebugText() ?: dcl.parent?.toString() ?: "none") + if (err is KotlinExceptionWithAttachments) { + err.attachments.forEachIndexed { index, attachment -> + val extraPrefix = "ktex.atc${index}" + setExtra("${extraPrefix}.pth", attachment.path) + setExtra("${extraPrefix}.encB", attachment.encodedBytes) + } + } + } + } + }.getOrNull() } /** diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt index 4f55672127..03b5fb86a7 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt @@ -1,5 +1,7 @@ package com.itsaky.androidide.lsp.kotlin.compiler.modules +import io.sentry.Sentry +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.analysis.api.KaPlatformInterface import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KaContentScopeProvider import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KaModuleBase @@ -25,10 +27,12 @@ internal abstract class AbstractKtModule( val files = computeFiles(extended = true).toList() _baseSearchScope = GlobalSearchScope.filesScope(project, files) _contentScope = KaContentScopeProvider.getInstance(project).getRefinedContentScope(this) + Sentry.addBreadcrumb("createSearchScopes(mod=$this, base=${_baseSearchScope?.hashCode()}, content=${_contentScope?.hashCode()})") } fun invalidateSearchScope() { synchronized(searchScopeLock) { + Sentry.addBreadcrumb("invalidateSearchScope(mod=$this)") _baseSearchScope = null _contentScope = null } @@ -52,6 +56,10 @@ internal abstract class AbstractKtModule( override val directFriendDependencies: List get() = emptyList() + @OptIn(KaExperimentalApi::class) + override val moduleDescription: String + get() = "module '$id' (ref=${hashCode()}, baseScope=${_baseSearchScope?.hashCode()}, contentScope=${_contentScope?.hashCode()}, deps=${directRegularDependencies.joinToString { it.id }})" + override fun toString(): String { return id } From 3bc525565bfe9f8a61eda854a6ba2de1c1c70928 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 26 May 2026 19:37:26 +0530 Subject: [PATCH 4/6] fix: use attachments to include kt psi exp attachments Signed-off-by: Akash Yadav --- .../compiler/index/SourceFileIndexer.kt | 209 +++++++++--------- 1 file changed, 106 insertions(+), 103 deletions(-) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt index 46377d8637..eb5f4e859e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt @@ -6,6 +6,7 @@ import com.itsaky.androidide.lsp.kotlin.compiler.read import com.itsaky.androidide.lsp.kotlin.utils.toNioPathOrNull import com.itsaky.androidide.progress.ICancelChecker import com.itsaky.androidide.projects.FileManager +import io.sentry.Attachment import io.sentry.Sentry import org.appdevforall.codeonthego.indexing.jvm.JvmClassInfo import org.appdevforall.codeonthego.indexing.jvm.JvmFieldInfo @@ -58,7 +59,8 @@ internal fun KtFile.toMetadata(project: Project, isIndexed: Boolean = false): Kt "KtFile '$name' has neither a resolvable virtualFile path nor a backingFilePath" }.pathString, packageFqName = packageFqName.asString(), - lastModified = (backingFilePath?.let { FileManager.getLastModified(it) }) ?: Instant.ofEpochMilli(virtualFile?.timeStamp ?: System.currentTimeMillis()), + lastModified = (backingFilePath?.let { FileManager.getLastModified(it) }) + ?: Instant.ofEpochMilli(virtualFile?.timeStamp ?: System.currentTimeMillis()), modificationStamp = modificationStamp, isIndexed = isIndexed, symbolKeys = emptyList() @@ -125,12 +127,13 @@ private fun KaSession.analyzeDeclaration(filePath: String, dcl: KtDeclaration): setExtra("fpth", filePath) setExtra("dcl", dcl.name) setExtra("dcl.dbg", dcl.getDebugText()) - setExtra("par.dbg", (dcl.parent as? KtElement)?.getDebugText() ?: dcl.parent?.toString() ?: "none") + setExtra( + "par.dbg", + (dcl.parent as? KtElement)?.getDebugText() ?: dcl.parent?.toString() ?: "none" + ) if (err is KotlinExceptionWithAttachments) { - err.attachments.forEachIndexed { index, attachment -> - val extraPrefix = "ktex.atc${index}" - setExtra("${extraPrefix}.pth", attachment.path) - setExtra("${extraPrefix}.encB", attachment.encodedBytes) + err.attachments.forEach { attachment -> + scope.addAttachment(Attachment(attachment.bytes, attachment.path)) } } } @@ -164,10 +167,10 @@ private fun KtDeclaration.containingClassInternalName(): String? { } private fun KtModifierListOwner.jvmVisibility(): JvmVisibility = when { - hasModifier(KtTokens.PRIVATE_KEYWORD) -> JvmVisibility.PRIVATE + hasModifier(KtTokens.PRIVATE_KEYWORD) -> JvmVisibility.PRIVATE hasModifier(KtTokens.PROTECTED_KEYWORD) -> JvmVisibility.PROTECTED - hasModifier(KtTokens.INTERNAL_KEYWORD) -> JvmVisibility.INTERNAL - else -> JvmVisibility.PUBLIC + hasModifier(KtTokens.INTERNAL_KEYWORD) -> JvmVisibility.INTERNAL + else -> JvmVisibility.PUBLIC } /** @@ -212,16 +215,16 @@ private fun KaSession.analyzeFunction(filePath: String, dcl: KtNamedFunction): J val parameters = fnSymbol.valueParameters.map { param -> JvmParameterInfo( - name = param.name.asString(), - typeName = kaTypeInternalName(param.returnType), + name = param.name.asString(), + typeName = kaTypeInternalName(param.returnType), typeDisplayName = kaTypeDisplayName(param.returnType), hasDefaultValue = param.hasDefaultValue, - isVararg = param.isVararg, + isVararg = param.isVararg, ) } val receiverType = fnSymbol.receiverParameter?.returnType - val returnType = fnSymbol.returnType + val returnType = fnSymbol.returnType // Mirrors KotlinMetadataScanner.extractFunction key / name conventions. val qualifiedName = if (containingClass != null) "$containingClass#$fnName" @@ -236,33 +239,33 @@ private fun KaSession.analyzeFunction(filePath: String, dcl: KtNamedFunction): J } return JvmSymbol( - key = key, - sourceId = filePath, - name = qualifiedName, - shortName = fnName, + key = key, + sourceId = filePath, + name = qualifiedName, + shortName = fnName, packageName = pkg, - kind = if (receiverType != null) JvmSymbolKind.EXTENSION_FUNCTION else JvmSymbolKind.FUNCTION, - language = JvmSourceLanguage.KOTLIN, - visibility = visibility, + kind = if (receiverType != null) JvmSymbolKind.EXTENSION_FUNCTION else JvmSymbolKind.FUNCTION, + language = JvmSourceLanguage.KOTLIN, + visibility = visibility, data = JvmFunctionInfo( - containingClassName = containingClass ?: "", - returnTypeName = kaTypeInternalName(returnType), + containingClassName = containingClass ?: "", + returnTypeName = kaTypeInternalName(returnType), returnTypeDisplayName = kaTypeDisplayName(returnType), - parameterCount = parameters.size, - parameters = parameters, - signatureDisplay = signatureDisplay, - typeParameters = fnSymbol.typeParameters.map { it.name.asString() }, + parameterCount = parameters.size, + parameters = parameters, + signatureDisplay = signatureDisplay, + typeParameters = fnSymbol.typeParameters.map { it.name.asString() }, kotlin = KotlinFunctionInfo( - receiverTypeName = receiverType?.let { kaTypeInternalName(it) } ?: "", + receiverTypeName = receiverType?.let { kaTypeInternalName(it) } ?: "", receiverTypeDisplayName = receiverType?.let { kaTypeDisplayName(it) } ?: "", - isSuspend = fnSymbol.isSuspend, - isInline = fnSymbol.isInline, - isInfix = fnSymbol.isInfix, - isOperator = fnSymbol.isOperator, - isTailrec = fnSymbol.isTailRec, - isExternal = fnSymbol.isExternal, - isExpect = fnSymbol.isExpect, - isReturnTypeNullable = returnType.isMarkedNullable, + isSuspend = fnSymbol.isSuspend, + isInline = fnSymbol.isInline, + isInfix = fnSymbol.isInfix, + isOperator = fnSymbol.isOperator, + isTailrec = fnSymbol.isTailRec, + isExternal = fnSymbol.isExternal, + isExpect = fnSymbol.isExpect, + isReturnTypeNullable = returnType.isMarkedNullable, ), ), ) @@ -275,8 +278,8 @@ private fun KaSession.analyzeClassOrObject(filePath: String, dcl: KtClassOrObjec if (visibility == JvmVisibility.PRIVATE) return null val internalName = dcl.internalName() ?: return null - val pkg = dcl.containingKtFile.packageFqName.asString() - val shortName = internalName.substringAfterLast('/').substringAfterLast('$') + val pkg = dcl.containingKtFile.packageFqName.asString() + val shortName = internalName.substringAfterLast('/').substringAfterLast('$') val containingClass = dcl.containingClassInternalName() val clsSymbol = dcl.symbol as? KaClassSymbol ?: return null @@ -295,7 +298,7 @@ private fun KaSession.analyzeClassOrObject(filePath: String, dcl: KtClassOrObjec val supertypes = clsSymbol.superTypes.mapNotNull { st -> if (st !is KaClassType) return@mapNotNull null - val sId = st.classId + val sId = st.classId val sPkg = sId.packageFqName.asString() val sRel = sId.relativeClassName.asString() val sInternal = if (sPkg.isEmpty()) sRel.replace('.', '$') @@ -304,76 +307,76 @@ private fun KaSession.analyzeClassOrObject(filePath: String, dcl: KtClassOrObjec } return JvmSymbol( - key = internalName, - sourceId = filePath, - name = internalName, - shortName = shortName, + key = internalName, + sourceId = filePath, + name = internalName, + shortName = shortName, packageName = pkg, - kind = kind, - language = JvmSourceLanguage.KOTLIN, - visibility = visibility, + kind = kind, + language = JvmSourceLanguage.KOTLIN, + visibility = visibility, data = JvmClassInfo( - internalName = internalName, + internalName = internalName, containingClassName = containingClass ?: "", - supertypeNames = supertypes, - typeParameters = clsSymbol.typeParameters.map { it.name.asString() }, - isAbstract = dcl.hasModifier(KtTokens.ABSTRACT_KEYWORD), - isFinal = dcl.hasModifier(KtTokens.FINAL_KEYWORD), - isInner = dcl is KtClass && dcl.isInner(), - isStatic = containingClass != null && !(dcl is KtClass && dcl.isInner()), + supertypeNames = supertypes, + typeParameters = clsSymbol.typeParameters.map { it.name.asString() }, + isAbstract = dcl.hasModifier(KtTokens.ABSTRACT_KEYWORD), + isFinal = dcl.hasModifier(KtTokens.FINAL_KEYWORD), + isInner = dcl is KtClass && dcl.isInner(), + isStatic = containingClass != null && !(dcl is KtClass && dcl.isInner()), kotlin = KotlinClassInfo( - isData = dcl is KtClass && dcl.isData(), - isValue = dcl is KtClass && dcl.hasModifier(KtTokens.VALUE_KEYWORD), - isSealed = dcl is KtClass && dcl.hasModifier(KtTokens.SEALED_KEYWORD), + isData = dcl is KtClass && dcl.isData(), + isValue = dcl is KtClass && dcl.hasModifier(KtTokens.VALUE_KEYWORD), + isSealed = dcl is KtClass && dcl.hasModifier(KtTokens.SEALED_KEYWORD), isFunInterface = dcl is KtClass && dcl.hasModifier(KtTokens.FUN_KEYWORD), - isExpect = dcl.hasModifier(KtTokens.EXPECT_KEYWORD), - isActual = dcl.hasModifier(KtTokens.ACTUAL_KEYWORD), - isExternal = dcl.hasModifier(KtTokens.EXTERNAL_KEYWORD), + isExpect = dcl.hasModifier(KtTokens.EXPECT_KEYWORD), + isActual = dcl.hasModifier(KtTokens.ACTUAL_KEYWORD), + isExternal = dcl.hasModifier(KtTokens.EXTERNAL_KEYWORD), ), ), ) } private fun KaSession.analyzeProperty(filePath: String, dcl: KtProperty): JvmSymbol? { - val propName = dcl.name ?: return null + val propName = dcl.name ?: return null val visibility = dcl.jvmVisibility() if (visibility == JvmVisibility.PRIVATE) return null - val pkg = dcl.containingKtFile.packageFqName.asString() + val pkg = dcl.containingKtFile.packageFqName.asString() val containingClass = dcl.containingClassInternalName() - val propSymbol = dcl.symbol as? KaPropertySymbol ?: return null - val returnType = propSymbol.returnType + val propSymbol = dcl.symbol as? KaPropertySymbol ?: return null + val returnType = propSymbol.returnType val receiverType = propSymbol.receiverParameter?.returnType val qualifiedName = if (containingClass != null) "$containingClass#$propName" else "$pkg#$propName" return JvmSymbol( - key = qualifiedName, - sourceId = filePath, - name = qualifiedName, - shortName = propName, + key = qualifiedName, + sourceId = filePath, + name = qualifiedName, + shortName = propName, packageName = pkg, - kind = if (receiverType != null) JvmSymbolKind.EXTENSION_PROPERTY else JvmSymbolKind.PROPERTY, - language = JvmSourceLanguage.KOTLIN, - visibility = visibility, + kind = if (receiverType != null) JvmSymbolKind.EXTENSION_PROPERTY else JvmSymbolKind.PROPERTY, + language = JvmSourceLanguage.KOTLIN, + visibility = visibility, data = JvmFieldInfo( containingClassName = containingClass ?: "", - typeName = kaTypeInternalName(returnType), - typeDisplayName = kaTypeDisplayName(returnType), + typeName = kaTypeInternalName(returnType), + typeDisplayName = kaTypeDisplayName(returnType), kotlin = KotlinPropertyInfo( - receiverTypeName = receiverType?.let { kaTypeInternalName(it) } ?: "", + receiverTypeName = receiverType?.let { kaTypeInternalName(it) } ?: "", receiverTypeDisplayName = receiverType?.let { kaTypeDisplayName(it) } ?: "", - isConst = dcl.hasModifier(KtTokens.CONST_KEYWORD), - isLateinit = dcl.hasModifier(KtTokens.LATEINIT_KEYWORD), - hasGetter = dcl.getter != null, - hasSetter = dcl.setter != null, - isDelegated = dcl.delegateExpression != null, + isConst = dcl.hasModifier(KtTokens.CONST_KEYWORD), + isLateinit = dcl.hasModifier(KtTokens.LATEINIT_KEYWORD), + hasGetter = dcl.getter != null, + hasSetter = dcl.setter != null, + isDelegated = dcl.delegateExpression != null, isTypeNullable = returnType.isMarkedNullable, - isExpect = dcl.hasModifier(KtTokens.EXPECT_KEYWORD), - isActual = dcl.hasModifier(KtTokens.ACTUAL_KEYWORD), - isExternal = dcl.hasModifier(KtTokens.EXTERNAL_KEYWORD), + isExpect = dcl.hasModifier(KtTokens.EXPECT_KEYWORD), + isActual = dcl.hasModifier(KtTokens.ACTUAL_KEYWORD), + isExternal = dcl.hasModifier(KtTokens.EXTERNAL_KEYWORD), ), ), ) @@ -387,32 +390,32 @@ private fun KaSession.analyzeProperty(filePath: String, dcl: KtProperty): JvmSym private fun KaSession.analyzeParameter(filePath: String, dcl: KtParameter): JvmSymbol? { if (!dcl.hasValOrVar()) return null - val propName = dcl.name ?: return null + val propName = dcl.name ?: return null val visibility = dcl.jvmVisibility() if (visibility == JvmVisibility.PRIVATE) return null - val pkg = dcl.containingKtFile.packageFqName.asString() + val pkg = dcl.containingKtFile.packageFqName.asString() val containingClass = dcl.containingClassInternalName() val paramSymbol = dcl.symbol as? KaValueParameterSymbol ?: return null - val returnType = paramSymbol.returnType + val returnType = paramSymbol.returnType val qualifiedName = if (containingClass != null) "$containingClass#$propName" else "$pkg#$propName" return JvmSymbol( - key = qualifiedName, - sourceId = filePath, - name = qualifiedName, - shortName = propName, + key = qualifiedName, + sourceId = filePath, + name = qualifiedName, + shortName = propName, packageName = pkg, - kind = JvmSymbolKind.PROPERTY, - language = JvmSourceLanguage.KOTLIN, - visibility = visibility, + kind = JvmSymbolKind.PROPERTY, + language = JvmSourceLanguage.KOTLIN, + visibility = visibility, data = JvmFieldInfo( containingClassName = containingClass ?: "", - typeName = kaTypeInternalName(returnType), - typeDisplayName = kaTypeDisplayName(returnType), + typeName = kaTypeInternalName(returnType), + typeDisplayName = kaTypeDisplayName(returnType), kotlin = KotlinPropertyInfo( isTypeNullable = returnType.isMarkedNullable, ), @@ -421,31 +424,31 @@ private fun KaSession.analyzeParameter(filePath: String, dcl: KtParameter): JvmS } private fun KaSession.analyzeTypeAlias(filePath: String, dcl: KtTypeAlias): JvmSymbol? { - val aliasName = dcl.name ?: return null + val aliasName = dcl.name ?: return null val visibility = dcl.jvmVisibility() if (visibility == JvmVisibility.PRIVATE) return null val pkg = dcl.containingKtFile.packageFqName.asString() - val aliasSymbol = dcl.symbol + val aliasSymbol = dcl.symbol val expandedType = aliasSymbol.expandedType // Key convention mirrors KotlinMetadataScanner: dot-notation FQ name. val fqName = if (pkg.isEmpty()) aliasName else "$pkg.$aliasName" return JvmSymbol( - key = fqName, - sourceId = filePath, - name = fqName, - shortName = aliasName, + key = fqName, + sourceId = filePath, + name = fqName, + shortName = aliasName, packageName = pkg, - kind = JvmSymbolKind.TYPE_ALIAS, - language = JvmSourceLanguage.KOTLIN, - visibility = visibility, + kind = JvmSymbolKind.TYPE_ALIAS, + language = JvmSourceLanguage.KOTLIN, + visibility = visibility, data = JvmTypeAliasInfo( - expandedTypeName = kaTypeInternalName(expandedType), + expandedTypeName = kaTypeInternalName(expandedType), expandedTypeDisplayName = kaTypeDisplayName(expandedType), - typeParameters = aliasSymbol.typeParameters.map { it.name.asString() }, + typeParameters = aliasSymbol.typeParameters.map { it.name.asString() }, ), ) } From 63a633ac73242387e0924b8bda5b03bcb3d5c2d8 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 26 May 2026 19:39:57 +0530 Subject: [PATCH 5/6] fix: specify super for Kt*Module impls explicitly Signed-off-by: Akash Yadav --- .../lsp/kotlin/compiler/modules/KtLibraryModule.kt | 4 ++++ .../androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt index 85b7de142c..ce6821e717 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt @@ -114,6 +114,10 @@ internal class KtLibraryModule( override val libraryName: String get() = id + @OptIn(KaExperimentalApi::class) + override val moduleDescription: String + get() = super.moduleDescription + override val binaryRoots: Collection get() = contentRoots diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt index 31f28c8b17..1de6077dc6 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt @@ -4,6 +4,7 @@ import com.itsaky.androidide.lsp.kotlin.compiler.DEFAULT_JVM_TARGET import com.itsaky.androidide.lsp.kotlin.compiler.DEFAULT_LANGUAGE_VERSION import com.itsaky.androidide.lsp.kotlin.compiler.read import com.itsaky.androidide.projects.api.ModuleProject +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.analysis.api.KaPlatformInterface import org.jetbrains.kotlin.analysis.api.projectStructure.KaSourceModule import org.jetbrains.kotlin.com.intellij.openapi.project.Project @@ -79,6 +80,10 @@ internal class KtSourceModule( override val name: String get() = module.name + @OptIn(KaExperimentalApi::class) + override val moduleDescription: String + get() = super.moduleDescription + override val languageVersionSettings: LanguageVersionSettings get() = LanguageVersionSettingsImpl( languageVersion = versions.first, From cfa9a16d13c0d9fe4613d98cf52115fa6b774e66 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 26 May 2026 19:50:58 +0530 Subject: [PATCH 6/6] fix: incorrect module description Signed-off-by: Akash Yadav --- .../androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt | 2 +- .../androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt index ce6821e717..aaca94149e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt @@ -116,7 +116,7 @@ internal class KtLibraryModule( @OptIn(KaExperimentalApi::class) override val moduleDescription: String - get() = super.moduleDescription + get() = super.moduleDescription override val binaryRoots: Collection get() = contentRoots diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt index 1de6077dc6..254d863574 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt @@ -82,7 +82,7 @@ internal class KtSourceModule( @OptIn(KaExperimentalApi::class) override val moduleDescription: String - get() = super.moduleDescription + get() = super.moduleDescription override val languageVersionSettings: LanguageVersionSettings get() = LanguageVersionSettingsImpl(