From 8af228dbf0547b01138239a498a28cb5aa711c82 Mon Sep 17 00:00:00 2001 From: Ofir Blaus <143079327+ofir-blaus@users.noreply.github.com> Date: Thu, 15 Oct 2026 23:12:30 +0300 Subject: [PATCH] Skip files the Kotlin analysis cannot handle, and always exit A top-level destructuring declaration in a .kt file (`val (a, b) = ...`) is not valid Kotlin, so the Analysis API has no FIR for it, and asking for its type threw "No fir element was found for KtDestructuringDeclaration". That exception aborted the whole run. Afterwards a non-daemon pooled thread kept the JVM alive, so the process hung instead of exiting. - Skip a top-level destructuring declaration instead of resolving it. - If indexing a file throws, skip that file (no shard, one line on stderr) and keep indexing the rest. - Exit with status 1 when the run fails, so the process never hangs. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../kotlin_analysis/KotlinAnalysisIndexer.kt | 12 ++++- .../scip_java/kotlin_analysis/Main.kt | 21 +++++---- .../KotlinAnalysisIndexerTest.kt | 44 +++++++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt index c33ab80dc..bea734f4e 100644 --- a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt @@ -126,7 +126,16 @@ class KotlinAnalysisIndexer( val documents = mutableListOf() val ktFiles = session.modulesWithFiles.values.flatten().filterIsInstance() for (ktFile in ktFiles) { - val document = indexFile(ktFile) + val document = + try { + indexFile(ktFile) + } catch (e: Exception) { + val reason = e.message?.lineSequence()?.firstOrNull() + System.err.println( + "scip-kotlin-analysis: skipped ${ktFile.virtualFilePath}: ${e.javaClass.name}: $reason" + ) + continue + } writeShard(ktFile, document) callback(document) documents.add(document) @@ -583,6 +592,7 @@ class KotlinAnalysisIndexer( override fun visitDestructuringDeclaration( destructuringDeclaration: KtDestructuringDeclaration ) { + if (destructuringDeclaration.parent is KtFile) return val isParameter = destructuringDeclaration.parent is KtParameter val type = destructuringDeclaration.initializer?.let { renderedExpressionType(it) } diff --git a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt index 5e6f41170..e9e140792 100644 --- a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt @@ -37,15 +37,20 @@ fun main(args: Array) { require(sourceroot != null) { "missing required flag: --sourceroot" } require(targetroot != null) { "missing required flag: --targetroot" } require(sourceRoots.isNotEmpty()) { "missing source files or directories" } - KotlinAnalysisIndexer( - sourceroot = sourceroot, - targetroot = targetroot, - sourceRoots = sourceRoots, - classpath = classpath, - jdkHome = jdkHome, - ) - .run() // The Analysis API environment leaves non-daemon threads behind; exit // explicitly so callers are not left waiting on them. + try { + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = targetroot, + sourceRoots = sourceRoots, + classpath = classpath, + jdkHome = jdkHome, + ) + .run() + } catch (e: Throwable) { + e.printStackTrace() + exitProcess(1) + } exitProcess(0) } diff --git a/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt b/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt index 6391232ae..73b4b5786 100644 --- a/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt +++ b/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt @@ -93,6 +93,50 @@ class KotlinAnalysisIndexerTest { ) } + @Test + fun `a top-level destructuring in a kt file is skipped, not the file or the run`() { + val sourceroot = Files.createTempDirectory("scip-kotlin-analysis").toRealPath() + val src = Files.createDirectories(sourceroot.resolve("src")) + Files.writeString( + src.resolve("Broken.kt"), + """ + |package snapshots + | + |fun before(): Int = 1 + | + |val (first, second) = Pair(1, 2) + | + |fun after(): Int = before() + """ + .trimMargin(), + ) + Files.writeString( + src.resolve("Other.kt"), + """ + |package snapshots + | + |fun other(): Int = after() + """ + .trimMargin(), + ) + val documents = + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = sourceroot.resolve("scip-targetroot"), + sourceRoots = listOf(src), + classpath = listOf(kotlinStdlibJar()), + ) + .run() + .sortedBy { it.relativePath } + + assertEquals(listOf("src/Broken.kt", "src/Other.kt"), documents.map { it.relativePath }) + val broken = definitions(documents[0]) + assertContains(broken, "snapshots/before().") + assertContains(broken, "snapshots/after().") + assertContains(references(documents[0]), "snapshots/before().") + assertContains(references(documents[1]), "snapshots/after().") + } + private fun definitions(document: Document): List = document.occurrencesList .filter { it.symbolRoles and SymbolRole.Definition.number != 0 }